How to handle configuration in Go [closed] - go

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
The community reviewed whether to reopen this question 12 months ago and left it closed:
Opinion-based Update the question so it can be answered with facts and citations by editing this post.
Improve this question
What is the preferred way to handle configuration parameters for a Go program (the kind of stuff one might use properties files or ini files for, in other contexts)?

The JSON format worked for me quite well. The
standard library offers methods to write the data structure indented, so it is quite
readable.
See also this golang-nuts thread.
The benefits of JSON are that it is fairly simple to parse and human readable/editable
while offering semantics for lists and mappings (which can become quite handy), which
is not the case with many ini-type config parsers.
Example usage:
conf.json:
{
"Users": ["UserA","UserB"],
"Groups": ["GroupA"]
}
Program to read the configuration
import (
"encoding/json"
"os"
"fmt"
)
type Configuration struct {
Users []string
Groups []string
}
file, _ := os.Open("conf.json")
defer file.Close()
decoder := json.NewDecoder(file)
configuration := Configuration{}
err := decoder.Decode(&configuration)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(configuration.Users) // output: [UserA, UserB]

Another option is to use TOML, which is an INI-like format created by Tom Preston-Werner. I built a Go parser for it that is extensively tested. You can use it like other options proposed here. For example, if you have this TOML data in something.toml
Age = 198
Cats = [ "Cauchy", "Plato" ]
Pi = 3.14
Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z
Then you can load it into your Go program with something like
type Config struct {
Age int
Cats []string
Pi float64
Perfection []int
DOB time.Time
}
var conf Config
if _, err := toml.DecodeFile("something.toml", &conf); err != nil {
// handle error
}

Viper is a golang configuration management system that works with JSON, YAML, and TOML. It looks pretty interesting.

I usually use JSON for more complicated data structures. The downside is that you easily end up with a bunch of code to tell the user where the error was, various edge cases and what not.
For base configuration (api keys, port numbers, ...) I've had very good luck with the gcfg package. It is based on the git config format.
From the documentation:
Sample config:
; Comment line
[section]
name = value # Another comment
flag # implicit value for bool is true
Go struct:
type Config struct {
Section struct {
Name string
Flag bool
}
}
And the code needed to read it:
var cfg Config
err := gcfg.ReadFileInto(&cfg, "myconfig.gcfg")
It also supports slice values, so you can allow specifying a key multiple times and other nice features like that.

Just use standard go flags with iniflags.
go flags
import "flag"
var nFlag = flag.Int("n", 1234, "help message for flag n")
iniflags
package main
import (
"flag"
...
"github.com/vharitonsky/iniflags"
...
)
var (
flag1 = flag.String("flag1", "default1", "Description1")
...
flagN = flag.Int("flagN", 123, "DescriptionN")
)
func main() {
iniflags.Parse() // use instead of flag.Parse()
}
Standard go flags have the following benefits:
Idiomatic.
Easy to use. Flags can be easily added and scattered across arbitrary packages your project uses.
Flags have out-of-the-box support for default values and description.
Flags provide standard 'help' output with default values and description.
The only drawback standard go flags have - is management problems when the number of flags used in your app becomes too large.
Iniflags elegantly solves this problem: just modify two lines in your main package and it magically gains support for reading flag values from ini file. Flags from ini files can be overriden by passing new values in command-line.
See also https://groups.google.com/forum/#!topic/golang-nuts/TByzyPgoAQE for details.

I have started using Gcfg which uses Ini-like files. It's simple - if you want something simple, this is a good choice.
Here's the loading code I am using currently, which has default settings and allows command line flags (not shown) that override some of my config:
package util
import (
"code.google.com/p/gcfg"
)
type Config struct {
Port int
Verbose bool
AccessLog string
ErrorLog string
DbDriver string
DbConnection string
DbTblPrefix string
}
type configFile struct {
Server Config
}
const defaultConfig = `
[server]
port = 8000
verbose = false
accessLog = -
errorLog = -
dbDriver = mysql
dbConnection = testuser:TestPasswd9#/test
dbTblPrefix =
`
func LoadConfiguration(cfgFile string, port int, verbose bool) Config {
var err error
var cfg configFile
if cfgFile != "" {
err = gcfg.ReadFileInto(&cfg, cfgFile)
} else {
err = gcfg.ReadStringInto(&cfg, defaultConfig)
}
PanicOnError(err)
if port != 0 {
cfg.Server.Port = port
}
if verbose {
cfg.Server.Verbose = true
}
return cfg.Server
}

have a look at gonfig
// load
config, _ := gonfig.FromJson(myJsonFile)
// read with defaults
host, _ := config.GetString("service/host", "localhost")
port, _ := config.GetInt("service/port", 80)
test, _ := config.GetBool("service/testing", false)
rate, _ := config.GetFloat("service/rate", 0.0)
// parse section into target structure
config.GetAs("service/template", &template)

https://github.com/spf13/viper and https://github.com/zpatrick/go-config are a pretty good libraries for configuration files.

Use toml like this article Reading config files the Go way

I wrote a simple ini config library in golang.
https://github.com/c4pt0r/cfg
goroutine-safe, easy to use
package cfg
import (
"testing"
)
func TestCfg(t *testing.T) {
c := NewCfg("test.ini")
if err := c.Load() ; err != nil {
t.Error(err)
}
c.WriteInt("hello", 42)
c.WriteString("hello1", "World")
v, err := c.ReadInt("hello", 0)
if err != nil || v != 42 {
t.Error(err)
}
v1, err := c.ReadString("hello1", "")
if err != nil || v1 != "World" {
t.Error(err)
}
if err := c.Save(); err != nil {
t.Error(err)
}
}
===================Update=======================
Recently I need an INI parser with section support, and I write a simple package:
github.com/c4pt0r/cfg
u can parse INI like using "flag" package:
package main
import (
"log"
"github.com/c4pt0r/ini"
)
var conf = ini.NewConf("test.ini")
var (
v1 = conf.String("section1", "field1", "v1")
v2 = conf.Int("section1", "field2", 0)
)
func main() {
conf.Parse()
log.Println(*v1, *v2)
}

You might also be interested in go-libucl, a set of Go bindings for UCL, the Universal Configuration Language. UCL is a bit like JSON, but with better support for humans: it supports comments and human-readable constructs like SI multipliers (10k, 40M, etc.) and has a little bit less boilerplate (e.g., quotes around keys). It's actually pretty close to the nginx configuration file format, if you're already familiar with that.

I agree with nemo and I wrote a little tool to make it all real easy.
bitbucket.org/gotamer/cfg is a json configuration package
You define your config items in your application as a struct.
A json config file template from your struct is saved on the first run
You can save runtime modifications to the config
See doc.go for an example

I tried JSON. It worked. But I hate having to create the struct of the exact fields and types I might be setting. To me that was a pain. I noticed it was the method used by all the configuration options I could find. Maybe my background in dynamic languages makes me blind to the benefits of such verboseness. I made a new simple config file format, and a more dynamic-ish lib for reading it out.
https://github.com/chrisftw/ezconf
I am pretty new to the Go world, so it might not be the Go way. But it works, it is pretty quick, and super simple to use.
Pros
Super simple
Less code
Cons
No Arrays or Map types
Very flat file format
Non-standard conf files
Does have a little convention built-in, which I now if frowned upon in general in Go community. (Looks for config file in the config directory)

Related

How can I compare read(1.proto) = read(2.proto) in Go(assuming there's just one message definition)?

Context: I'm trying to resolve this issue.
In other words, there's a NormalizeJsonString() for JSON strings (see this for more context:
// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func NormalizeJsonString(jsonString interface{}) (string, error) {
that allows to have the following code:
return structure.NormalizeJsonString(old) == structure.NormalizeJsonString(new)
but it doesn't work for strings that are proto files (all proto files are guaranteed to have just one message definition). For example, I could see:
syntax = "proto3";
- package bar.proto;
+ package bar.proto;
option java_outer_classname = "FooProto";
message Foo {
...
- int64 xyz = 3;
+ int64 xyz = 3;
Is there NormalizeProtoString available in some Go SDKs? I found MessageDifferencer but it's in C++ only. Another option I considered was to replace all new lines / group of whitespaces with a single whitespace but it's a little bit hacky.
To do this in a semantic fashion, the proto definitions should really be parsed. Naively stripping and/or replacing whitespace may get you somewhere, but likely will have gotchas.
As far as I'm aware the latest official Go protobuf package don't have anything to handle parsing protobuf definitions - the protoc compiler handles that side of affairs, and this is written in C++
There would be options to execute the protoc compiler to get hold of the descriptor set output (e.g. protoc --descriptor_set_out=...), however I'm guessing this would also be slightly haphazard considering it requires one to have protoc available - and version differences could potentially cause problems too.
Assuming that is no go, one further option is to use a 3rd party parser written in Go - github.com/yoheimuta/go-protoparser seems to handle things quite well. One slight issue when making comparisons is that the parser records meta information about source line + column positions for each type; however it is relatively easy to make a comparison and ignore these, by using github.com/google/go-cmp
For example:
package main
import (
"fmt"
"log"
"os"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/yoheimuta/go-protoparser/v4"
"github.com/yoheimuta/go-protoparser/v4/parser"
"github.com/yoheimuta/go-protoparser/v4/parser/meta"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
proto1, err := parseFile("example1.proto")
if err != nil {
return err
}
proto2, err := parseFile("example2.proto")
if err != nil {
return err
}
equal := cmp.Equal(proto1, proto2, cmpopts.IgnoreTypes(meta.Meta{}))
fmt.Printf("equal: %t", equal)
return nil
}
func parseFile(path string) (*parser.Proto, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return protoparser.Parse(f)
}
outputs:
equal: true
for the example you provided.

unserialize php in goland

I have a file with serialized array in PHP.
The content of the file locks like this
a:2:{i:250;s:7:"my_catz";s:7:"abcd.jp";a:2:{s:11:"category_id";i:250;s:13:"category_name";s:7:"my_catz";}}
The array unserialized is this
(
[250] => my_catz
[abcd.jp] => Array
(
[category_id] => 250
[category_name] => my_catz
)
)
Now, i want to get the content of the file in GO, unserialize it convert it to an array.
In GO i can get the content of the file using
dat, err := os.ReadFile("/etc/squid3/compiled-categories.db")
if err != nil {
if e.Debug {
log.Printf("error reading /etc/squid3/compiled-categories.db: ", err)
}
}
And unserialized it using github.com/techoner/gophp library
package categorization
import (
"fmt"
"os"
"github.com/techoner/gophp"
"log"
"errors"
)
type Data struct {
Website string
Debug bool
}
func (e Data) CheckPersonalCategories() (int,string) {
if e.Debug {
log.Printf("Checking Personal Categories")
}
if _, err := os.Stat("/etc/squid3/compiled-categories.db"); errors.Is(err, os.ErrNotExist) {
if e.Debug {
log.Printf("/etc/squid3/compiled-categories.db not exit: ", err)
}
return 0,""
}
dat, err := os.ReadFile("/etc/squid3/compiled-categories.db")
if err != nil {
if e.Debug {
log.Printf("error reading /etc/squid3/compiled-categories.db: ", err)
}
}
out, _ := gophp.Unserialize(dat)
fmt.Println(out["abcd.jp"])
return 0,""
}
But I can't access to the array, for example, when I try access to array key using out["abcd.jp"] i get this error message
invalid operation: out["abcd.jp"] (type interface {} does not support indexing)
The result of out is
map[250:my_catz abcd.jp:map[category_id:250 category_name:my_catz]]
Seams that is unserializing
Don't make assumptions about what is and isn't succeeding in your code. Error responses are the only reliable way to know whether a function succeeded. In this case the assumption may hold, but ignoring errors is always a mistake. Invest time in catching errors and at least panic them - don't instead waste your time ignoring errors and then trying to debug unreliable code.
invalid operation: out["abcd.jp"] (type interface {} does not support indexing)
The package you're using unfortunately doesn't provide any documentation so you have to read the source to understand that gophp.Unserialize returns (interface{}, error). This makes sense; php can serialize any value, so Unserialize must be able to return any value.
out is therefore an interface{} whose underlying value depends on the data. To turn an interface{} into a particular value requires a type assertion. In this case, we think the underlying data should be map[string]interface{}. So we need to do a type assertion:
mout, ok := out.(map[string]interface{})
Before we get to the working code, one more point I'd like you to think about. Look at the code below: I started it from your code, but the resemblance is very slight. I took out almost all the code because it was completely irrelevant to your question. I added the input data to the code to make a minimal reproduction of your code (as I asked you to do and you declined to do). This is a very good use of your time for 2 reasons: first, it makes it a lot easier to get answers (both because it shows sufficient effort on your part and because it simplifies the description of the problem), and second, because it's excellent practice for debugging. I make minimal reproductions of code flows all the time to better understand how to do things.
You'll notice you can run this code now without any additional effort. That's the right way to provide a minimal reproducible example - not with a chunk of mostly irrelevant code which still can't be executed by anybody.
The Go Plaground is a great way to demonstrate go-specific code that others can execute and investigate. You can also see the code below at https://go.dev/play/p/QfCl08Gx53e
package main
import (
"fmt"
"github.com/techoner/gophp"
)
type Data struct {
Website string
Debug bool
}
func main() {
var dat = []byte(`a:2:{i:250;s:7:"my_catz";s:7:"abcd.jp";a:2:{s:11:"category_id";i:250;s:13:"category_name";s:7:"my_catz";}}`)
out, err := gophp.Unserialize(dat)
if err != nil {
panic(err)
}
if mout, ok := out.(map[string]interface{}); ok {
fmt.Println(mout["abcd.jp"])
}
}

How to make an object of a class and have getters for struct?

I have recently started working with golang so having difficulties understanding on how to achieve same thing which I am able to do it easily in Java or C#. I am trying to make an object of configmanager class and when the first time configmanager class is called, it should initialize all my configs and store it in memory in some struct object. And then I should have access to configmanager object and should be able to access all those configs from my main function using some getters maybe?
Below is my configmanager go class. It is very simple for now to make it easier to understand.
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/david/internal/utilities"
)
type ClientMetrics struct {
ClientMetrics []ClientMetric `json:"clientMetrics"`
}
type CustomerData struct {
Process []string `json:"Process"`
Mat []string `json:"Mat"`
}
type ClientMetric struct {
ClientID int `json:"clientId"`
CustomerData CustomerData `json:"customerData,omitempty"`
LegCustomer []int `json:"legCustomer"`
OtherIds []int `json:"otherIds,omitempty"`
CatID int `json:"catId,omitempty"`
}
func Init(root string) (err error) {
files, err := utilities.FindFiles(root, "process-data.json")
if err != nil {
return fmt.Errorf("cannot find process-data.json file: %v", err)
}
for _, file := range files {
body, err := ioutil.ReadFile(file)
if err != nil {
return fmt.Errorf("unable to read file: %v", err)
}
var auto ClientMetrics
json.Unmarshal(body, &auto)
}
return nil
}
And here is I use it in my main function - This is just basic code just to demonstrate what I am doing but this isn't production ready code.
package main
import (
"github.com/david/internal/config"
)
func main() {
root := "/home/Configurations"
config.Init(root)
//
}
In my above Init function, I find process-data.json file if it is there on the disk and then load it in memory by deserializing it into ClientMetrics object. Everything works fine as shown above.
Problem Statement
Since I am coming from Java and C# background, so my confusion is how can I make an object of configmanager class and how should I initialize all my configs during the first time when I call configmanager and also have access to ClientMetrics struct using some getters. In Java and C#, I used to have constructor where I initialize all these things and then have some getters to access the config. How should I do the same thing in golang.
My main confusion is do we have constuctors in go and how should I make getters to access struct object in my main function? I am just looking for better design in go and how should I do my above code in golang?
Update
I think I wasn't able to explain properly. I have X (let's suppose 5 for now) different json files in a folder and each of those json files needs to have their own struct because they are totally different json files. My configmanager file will be responsible to load all those 5 json files into their own struct and then I should be able to access all those 5 structs and their fields from the object of configmanager. All this should happen during the initialization of configmanager class when it is called for the first time.
Here is just an example where I have bunch of json files in their own corresponding folder (folderX). I have three categories of files (clientMap-*.json, dataMap-*.json, process-data.json) as shown below.
Products
├── folder1
│ ├── clientMap-123.json
│ ├── clientMap-987.json
│ ├── clientMap-7161.json
├── folder2
│ ├── dataMap-18271.json
│ ├── dataMap-12921.json
│ ├── dataMap-98121.json
└── folder3
├── process-data.json
Now I need to read all these files (clientMap-*.json, dataMap-*.json, process-data.json) in their own struct. And I should able to use configmanager class to get corresponding struct and their fields too after unmarshalling.
For example: Read clientMap-*.json files.
files, err := utilities.FindFiles(root, "clientMap-*.json")
// load all clientMap-*.json files in its own struct after unmarshalling
Similarly for dataMap-*.json files
files, err := utilities.FindFiles(root, "dataMap-*.json")
// load all dataMap-*.json files in its own struct after unmarshalling
Also for process-data.json files
files, err := utilities.FindFiles(root, "process-data.json")
// load all process-data.json files in its own struct after unmarshalling
My FindFiles method will find all the files even with regex like above. files variable is an array containing list of all files matching particular pattern. Now I can create ConfigManager struct type holding all other structs for my config but I am trying to find a solution which is easily extensible so that in future if I add any other json file category it should be able to extend easily. What is the correct way to solve this?
This question is hard to answer because you kind of forgot to ask one in a definitive way, so I will start my answer by extracting a question based on what you wrote. I believe we can do so from this:
Problem Statement
[...] my confusion is how can I make an object of configmanager class and how should I initialize all my configs during the first time when I call configmanager and also have access to ClientMetrics struct using some getters
I believe the real question here is "How do I separate the concern of reading and unmarshalling the files from the concern of storing the results for my program to use?".
It's common to separate concerns by breaking things up into multiple functions/methods, which you have already done to some extent. Storage however is strictly a matter of types, so we need a type capable of holding the results. I'll use this opportunity to omit the word Manager from the type's name, because all it does is to provide useless abstraction. This type does not manage the config. It is the config, in that it contains all of the config.
type Config struct {
ClientMapConfigs []ClientMapConfig
DataMapConfigs []DataMapConfig
ProcessDataConfigs []ProcessDataConfig
}
Notice the fields start with an upper-case letter, making them public. This communicates that there could be nonsense in there, as nothing is protected from writes, which aligns with the fact that we read this data from files. A correct program must validate this data before using it. You could then communicate the validity of the validated data in the variable name.
func main() {
validConfig := getValidConfig("path/to/dir")
// ...
}
func getValidConfig(configDirectoryPath string) *Config {
config, err := NewConfigFromConfigDirectory(configDirectoryPath)
if err != nil {
log.Printf("Failed to read config from dir '%s': %v\n", configDirectoryPath, err)
os.Exit(1)
}
if err = ValidateConfig(config); err != nil {
log.Printf("Config from dir '%s' failed to validate: %v\n", configDirectoryPath, err)
os.Exit(1)
}
}
func NewConfigFromConfigDirectory(configDirectoryPath string) *Config {
// <- read individual configs and add to slices here
return &Config{ // This is the closest to a "constructor" that Go has.
ClientMapConfigs: clientMapConfigs,
DataMapConfigs: dataMapConfigs,
ProcessDataConfigs: processDataConfigs,
}
}
Notice that there is no imminent need for the functions validating and reading the config to have a receiver, i.e. be a method of a struct. They're fine as standalone functions until your requirements change in ways which demand the introduction of statefulness for either logic.
Also I use exit code 1 for the error-cases here, because Golang uses code 2 when the program terminates due to a panic. The former can be thought of as a problem with the environment, while the later indicates a problem within the program itself. It's a useful distinction to make, and aligns with the semantics of Exception versus RuntimeException which you may know from Java.
I can have let's say 10 different configs (files) and each of those configs can have their own structs since it's a different configs so I need to have separate struct for them
That looks like dynamic JSON struct unmarshalling, which was presented in 2015 by John Asmuth in decoding with mixed structures
You can run the following example here.
type Dog struct {
Name string
Frisbees int
}
type Cat struct {
Name string
Strings int
}
type RawAnimal struct {
Type string
Cat
Dog
}
type Animal RawAnimal
func (a *Animal) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, (*RawAnimal)(a)); err != nil {
return err
}
switch a.Type {
case "cat":
return json.Unmarshal(data, &a.Cat)
case "dog":
return json.Unmarshal(data, &a.Dog)
}
return fmt.Errorf("unknown type %q", a.Type)
}
From there, your ConfigManager will instantiate the right Config structure, depending on the raw JSON read.
I think the trouble is that you're looking at Go from a Java/C# perspective and thus struggling to make sense of the features. If you have the time, then I would suggest that you go thru some Go tutorials or books before you start coding (this one is pretty good: https://www.amazon.com/Programming-Language-Addison-Wesley-Professional-Computing/dp/0134190440)
To answer your question directly, what you need to do is to create a function which returns a pointer to an object of the struct (see here for a simple example: https://gobyexample.com/structs)
Taking ClientMetric as an example:
func NewClientMetric(ClientID int, CustomerData CustomerData, LegCustomer []int, OtherIds []int, CatID int) (*ClientMetric, error) {
//validate your inputs
//in case of errors, create and error message in the variable err and then: return nil, err
//other code which would go into a typical constructor would go here
return &ClientMetric{ClientID,CustomerData, LegCustomer, OtherIds, CatID}, nil
}
In this case, the function NewClientMetric is the constructor and it returns a pointer/reference to the newly created object. It also returns an error object, which is the same as saying that the constructor throws exceptions. Just as you would need to use try/catch in Java, you would need to check to handle the err variable returned by this function.
You would need to make similar functions for each of your types.
As for getters & setters, generally speaking, that should be avoided in Go. You can access the attributes of a struct directly. A function (like a getter) is only useful if you're going to do something to the attribute before returning it. Something like this:
type Customer struct {
FirstName string
LastName string
}
func (this *Customer) GetFullName() string {
return this.FirstName + " " + this.LastName
}
and these can then be accessed like this:
var customer *Customer
customer = &Customer{"John","Smith")
fmt.Println(customer.FirstName)
fmt.Println(customer.LastName)
fmt.Println(customer.GetFullName())
Please note that attributes, functions and methods which begin with a capital letter are public, the others are private. If FirstName was written as firstName, it would not be accessible outside the package in which it was declared.
Please do note that I'm not checking for errors if the pointer is null/nil, but that is always recommended.

Unmarshal YAML file - How to read YAML parameters with leading zeros as a string?

How to read YAML properties with leading zeros as string without any conversion?
YAML example file:
provisioning:
devices:
000000001515:
properties:
example: 30s
options:
dummy: true
Golang Code - Minimal reproducible example:
package main
import (
"fmt"
"github.com/ghodss/yaml"
)
//DeviceSpec device
type DeviceSpec struct {
Properties map[string]string `yaml:"properties,omitempty"`
Options map[string]string `yaml:"options,omitempty"`
}
//Spec provisi
type Spec struct {
Provisioning struct {
Devices map[string]DeviceSpec `yaml:"devices,omitempty"`
}
}
var input = []byte(`
provisioning:
devices:
000000001515:
properties:
example: 30s
options:
dummy: true
`)
func main() {
config := Spec{}
if err := yaml.Unmarshal(input, &config); err != nil {
panic(err)
}
for uuid, _ := range config.Provisioning.Devices {
fmt.Println(uuid)
}
}
Output
845
Expected Output
000000001515
Solution
The issue was related to the YAML package I was using "github.com/ghodss/yaml" v1.0.0. Replacing it with the "gopkg.in/yaml.v3" package fixed the issue and it works just fine - see the comment section bellow - thank you all.
import (
"fmt"
"gopkg.in/yaml.v3"
)
Note: This question is similar to this but this question is using Python: How to read/load yaml parameters with leading zeros as a string?
Now that we found the error, let's have a post mortem about what happened:
The library you used, github.com/ghodss/yaml, says that
this library first converts YAML to JSON using go-yaml and then uses json.Marshal and json.Unmarshal to convert to or from the struct.
It doesn't particularly help that the linked blog post about why this would be a good idea vanished. Anyway, let's see what the code there actually does:
var yamlObj interface{}
err := yamlUnmarshal(y, &yamlObj)
So it takes your YAML input and loads it into an interface. This as a bad idea when you actually supply the target type. What happens here is that go-yaml is free to choose the target types, and if it is, it apparently reads in numbers with leading zeros as octal numbers. This is in line with the outdated definition of the !!int tag for YAML 1.1:
[-+]?0[0-7_]+ # (base 8)
However, that has long been superseded by YAML 1.2:
0o [0-7]+ tag:yaml.org,2002:int (Base 8)
go-yaml seems to implement the YAML 1.1 rule. This seems a bad decision and may have been done accidentally, possibly warranting an issue report.
So anyway, when the YAML gets loaded, the value gets interpreted as octal value. github.com/ghodss/yaml proceeds to mangle the value into JSON and then unmarshal it again, but we've already lost.
The takeaway is that I would consider github.com/ghodss/yaml harmful and advise not to use it.

Constants in Go established during init

In my Go program, there are configuration values that I want to be constant for the duration of program execution, but that I want to be able to change at the deployment site. As far as I can tell, there's no way to achieve this with the const keyword, since (again, as far as I can tell) its value must be a constant specified at compile time. This means that the only way to achieve what I want would be to declare normal variables and initialize them during the package's init function. It's not that that won't work, but rather that there will now be nothing to prevent these pseudo-constant's values from changing.
My two questions are:
Am I missing something about how const works?
Assuming I'm not, what's the preferred way to handle this? A public function that returns a private variable that I never expose, never changing it? Just hoping people don't alter the variables, since they're really configuration settings?
Create a file "config.go" and create the vars you want to expose.
Don't export them (make them all lower case). Instead create public (upper case) funcs that give access to each item.
package config
var x = 10
func X() int {
return x
}
When you want those variables you simply import ./config and use them in code as follows:
if config.X()
Obviously, you can set the variables in the package init.
The following code is almost the same as the second method of #Christopher, except that it is not a module, it located in the main package.
package main
import (
"os"
)
type Config struct {
debug bool
key string
proxyNumber int
}
func (c *Config) Debug() bool {
return c.debug
}
func (c *Config) Key() string {
return c.key
}
func (c *Config) ProxyNumber() int {
return c.proxyNumber
}
const (
CONFIG_NAME = "config.ini"
)
var config *Config
func init() {
DefaultConfig()
if Exists(CONFIG_NAME) {
//try to save the config file
}else {
//try to load from the config file
}
}
func DefaultConfig() {
config = &Config{debug:true, key:"abcde",
proxyNumber:5,
}
}
//Exist: check the file exist
func Exists(path string) bool {
_, err := os.Stat(path)
if err == nil { return true }
if os.IsNotExist(err) { return false }
return false
}
you can use config to load and save the config file.
This is a very good question because it delves into what I suspect may be an omission from Go - immutable state.
From the language reference, "constant expressions may contain only constant operands and are evaluated at compile-time."
You cannot make vars constant, which is a shame. Joe's answer proposes encapsulation as a solution, which will work well - but it's verbose, tedious and might introduce errors.
By comparison, many impure functional languages combine mutable variables with single-assignment immutable values. For example, Scala has the keywords 'val' and 'var'; the meaning of Scala's 'var' is quite similar to Go's 'var'. Immutability is a useful tool in the toolbox because referentially-transparent side-effect-free functions can be written, alongside stateful mutable procedural code. Both have their place. Immutability is also an valuable tool for concurrency because there is no worry about possible race conditions if immutable values are shared between goroutines.
So in my opinion, amongst its many strengths, this is one of Go's shortcomings. It would presumably not be hard to support vals as well as vars, the difference being that the compiler checks that each val is assigned exactly once.
Until that feature is added, you have encapsulation as your only option.
You can do something like this:
package main
import (
"fmt"
"strconv"
)
var a string
func main() {
myvar, err := strconv.Atoi(a)
if err != nil {
fmt.Println(err)
}
fmt.Println(myvar)
}
and compile the program with
go build -ldflags '-X main.a 10' test.go
That way you can define a constant during compile time.
Just use standard go flags with iniflags. Standard go flags allow setting arbitrary config variables at program start via passing command-line flags, while iniflags "magically" add support for reading config variables from ini files.
You can wrap the variable in a function that returns its value:
func genConst(x int) func() int {
return func() int {
return x
}
}
var Constx = genConst(5)
var x1 = Constx()
This way the value cannot be changed by accident, even in the file where it's defined

Resources