diff --git a/cmd/config.go b/cmd/config.go index 8bd915ff..74708284 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,9 +1,12 @@ package cmd import ( + "encoding/json" "fmt" "io" "io/ioutil" + "net/http" + "net/url" "os" "strconv" "strings" @@ -27,12 +30,37 @@ type ConfigRelatedOptions struct { func (c ConfigRelatedOptions) Parse(out io.Writer, component string, config interface{}) error { var rawConfig map[string]interface{} if cfgFile := c.Path; cfgFile != "" { - input, err := ioutil.ReadFile(cfgFile) - if err != nil { - return fmt.Errorf("unable to read configuration file: %w", err) - } - if err := yaml.Unmarshal(input, &rawConfig); err != nil { - return fmt.Errorf("unable to parse configuration file: %w", err) + if strings.HasPrefix(cfgFile, "http://") || strings.HasPrefix(cfgFile, "https://") { + u, err := url.Parse(cfgFile) + if err != nil { + return fmt.Errorf("cannot parse configuration URL: %w", err) + } + if u.Path == "" { + u.Path = fmt.Sprintf("/api/v0/orchestrator/configuration/%s", component) + } + resp, err := http.Get(u.String()) + if err != nil { + return fmt.Errorf("unable to fetch configuration file: %w", err) + } + defer resp.Body.Close() + if contentType := resp.Header.Get("Content-Type"); contentType != "application/json" { + return fmt.Errorf("received configuration file is not JSON (%s)", contentType) + } + input, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unable to read configuration file: %w", err) + } + if err := json.Unmarshal(input, &rawConfig); err != nil { + return fmt.Errorf("unable to parse JSON configuration file: %w", err) + } + } else { + input, err := ioutil.ReadFile(cfgFile) + if err != nil { + return fmt.Errorf("unable to read configuration file: %w", err) + } + if err := yaml.Unmarshal(input, &rawConfig); err != nil { + return fmt.Errorf("unable to parse configuration file: %w", err) + } } } diff --git a/cmd/config_test.go b/cmd/config_test.go index 107126ee..8af46205 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -2,9 +2,13 @@ package cmd_test import ( "bytes" + "fmt" "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" + "strings" "testing" "time" @@ -40,21 +44,23 @@ type dummyModule2DetailsConfiguration struct { IntervalValue time.Duration } -var dummyDefaultConfiguration = dummyConfiguration{ - Module1: dummyModule1Configuration{ - Listen: "127.0.0.1:8080", - Topic: "nothingness", - Workers: 100, - }, - Module2: dummyModule2Configuration{ - MoreDetails: MoreDetails{ - Stuff: "hello", +func dummyDefaultConfiguration() dummyConfiguration { + return dummyConfiguration{ + Module1: dummyModule1Configuration{ + Listen: "127.0.0.1:8080", + Topic: "nothingness", + Workers: 100, }, - Details: dummyModule2DetailsConfiguration{ - Workers: 1, - IntervalValue: time.Minute, + Module2: dummyModule2Configuration{ + MoreDetails: MoreDetails{ + Stuff: "hello", + }, + Details: dummyModule2DetailsConfiguration{ + Workers: 1, + IntervalValue: time.Minute, + }, }, - }, + } } func TestDump(t *testing.T) { @@ -80,7 +86,7 @@ module2: Dump: true, } - parsed := dummyDefaultConfiguration + parsed := dummyDefaultConfiguration() out := bytes.NewBuffer([]byte{}) if err := c.Parse(out, "dummy", &parsed); err != nil { t.Fatalf("Parse() error:\n%+v", err) @@ -157,6 +163,15 @@ module2: ioutil.WriteFile(configFile, []byte(config), 0644) // Environment + clean := func() { + for _, env := range os.Environ() { + if strings.HasPrefix(env, "AKVORADO_DUMMY_") { + os.Unsetenv(strings.Split(env, "=")[0]) + } + } + } + clean() + defer clean() os.Setenv("AKVORADO_DUMMY_MODULE1_LISTEN", "127.0.0.1:9000") os.Setenv("AKVORADO_DUMMY_MODULE1_TOPIC", "something") os.Setenv("AKVORADO_DUMMY_MODULE2_DETAILS_INTERVALVALUE", "10m") @@ -171,7 +186,7 @@ module2: Dump: true, } - parsed := dummyDefaultConfiguration + parsed := dummyDefaultConfiguration() out := bytes.NewBuffer([]byte{}) if err := c.Parse(out, "dummy", &parsed); err != nil { t.Fatalf("Parse() error:\n%+v", err) @@ -201,3 +216,63 @@ module2: t.Errorf("Parse() (-got, +want):\n%s", diff) } } + +func TestHTTPConfiguration(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, ` +{ + "module1": { + "topic": "flows" + }, + "module2": { + "details": { + "workers": 5, + "interval-value": "20m" + }, + "stuff": "bye", + "elements": [ + {"name": "first", "gauge": 67}, + {"name": "second"} + ] + } +} +`) + })) + defer ts.Close() + + c := cmd.ConfigRelatedOptions{ + Path: ts.URL, + Dump: true, + } + + parsed := dummyDefaultConfiguration() + out := bytes.NewBuffer([]byte{}) + if err := c.Parse(out, "dummy", &parsed); err != nil { + t.Fatalf("Parse() error:\n%+v", err) + } + // Expected configuration + expected := dummyConfiguration{ + Module1: dummyModule1Configuration{ + Listen: "127.0.0.1:8080", + Topic: "flows", + Workers: 100, + }, + Module2: dummyModule2Configuration{ + MoreDetails: MoreDetails{ + Stuff: "bye", + }, + Details: dummyModule2DetailsConfiguration{ + Workers: 5, + IntervalValue: 20 * time.Minute, + }, + Elements: []dummyModule2ElementsConfiguration{ + {"first", 67}, + {"second", 0}, + }, + }, + } + if diff := helpers.Diff(parsed, expected); diff != "" { + t.Errorf("Parse() (-got, +want):\n%s", diff) + } +}