Skip to content

Commit 308ea63

Browse files
authored
Add postgres experience (#95)
* Initial implementation of flavors template registry. Signed-off-by: Christoph Pakulski <[email protected]> * Moved logic producing config from individual flavor to generic flavor package. Signed-off-by: Christoph Pakulski <[email protected]> * Added InPort listening port to template parameters. Signed-off-by: Christoph Pakulski <[email protected]> * Added comments and corrected formatting. Signed-off-by: Christoph Pakulski <[email protected]> * Format and style changes for linter and go format checker. Signed-off-by: Christoph Pakulski <[email protected]> * Code corrections for linter. Signed-off-by: Christoph Pakulski <[email protected]> * Reduced gocycle complexity in NewRunCmd. Signed-off-by: Christoph Pakulski <[email protected]> * Changes after code review. Signed-off-by: Christoph Pakulski <[email protected]> * Fixed crash when template args are specified and getnevoy args point directly to Envoy image. Signed-off-by: Christoph Pakulski <[email protected]> * Added logic to create static or strict_dns type of cluster based on comma separated list of endpoints. Signed-off-by: Christoph Pakulski <[email protected]> * Added error checking when template.Execute functions are run when postgres specific config is created out of templates. Signed-off-by: Christoph Pakulski <[email protected]> * Renamed template argument from endpoint to endpoints. Signed-off-by: Christoph Pakulski <[email protected]> * Corrected methods' comments. Small refactoring of unit tests. Signed-off-by: Christoph Pakulski <[email protected]> * Removed checking for user specified config params. Signed-off-by: Christoph Pakulski <[email protected]>
1 parent 0312e74 commit 308ea63

File tree

10 files changed

+693
-10
lines changed

10 files changed

+693
-10
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.12
55
require (
66
bitbucket.org/creachadair/shell v0.0.6
77
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
8+
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
89
github.com/go-ole/go-ole v1.2.4 // indirect
910
github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48
1011
github.com/golang/protobuf v1.3.2

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
5050
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
5151
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
5252
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
53+
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
5354
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
5455
github.com/aws/aws-sdk-go v1.13.24/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
5556
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=

pkg/binary/envoy/config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
package envoy
1616

1717
import (
18+
"fmt"
19+
"io/ioutil"
20+
"os"
21+
"path/filepath"
1822
"time"
1923

2024
"github.com/gogo/protobuf/types"
@@ -70,3 +74,18 @@ type Config struct {
7074
AdminPort int32
7175
StatNameLength int32
7276
}
77+
78+
// SaveConfig saves configuration string in getenvoy
79+
// directory.
80+
func (r *Runtime) SaveConfig(name, config string) (string, error) {
81+
configDir := filepath.Join(r.RootDir, "configs")
82+
if err := os.MkdirAll(configDir, 0750); err != nil {
83+
return "", fmt.Errorf("Unable to create directory %q: %v", configDir, err)
84+
}
85+
filename := name + ".yaml"
86+
err := ioutil.WriteFile(filepath.Join(configDir, filename), []byte(config), 0644)
87+
if err != nil {
88+
return "", fmt.Errorf("Cannot save config file %s: %s", filepath.Join(configDir, filename), err)
89+
}
90+
return filepath.Join(configDir, filename), nil
91+
}

pkg/binary/envoy/runtime.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func NewRuntime(options ...func(*Runtime)) (binary.FetchRunner, error) {
3434
local := filepath.Join(usrDir, ".getenvoy")
3535
runtime := &Runtime{
3636
Config: NewConfig(),
37+
RootDir: local,
3738
fetcher: fetcher{local},
3839
TmplDir: filepath.Join(local, "templates"),
3940
wg: &sync.WaitGroup{},
@@ -60,6 +61,7 @@ type fetcher struct {
6061
type Runtime struct {
6162
fetcher
6263

64+
RootDir string
6365
debugDir string
6466
TmplDir string
6567
Config *Config

pkg/binary/interface.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type Runner interface {
3939
DebugStore() string
4040
SetStdout(w io.Writer)
4141
SetStderr(w io.Writer)
42+
// TODO(cpakulski) Maybe Runner is not the best place for SaveConfig.
43+
// It was added here because it needs location of .getenvoy
44+
// directory.
45+
SaveConfig(name string, config string) (string, error)
4246
}
4347

4448
const (

pkg/cmd/run.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"github.com/tetratelabs/getenvoy/pkg/binary/envoy"
2525
"github.com/tetratelabs/getenvoy/pkg/binary/envoy/controlplane"
2626
"github.com/tetratelabs/getenvoy/pkg/binary/envoy/debug"
27+
"github.com/tetratelabs/getenvoy/pkg/flavors"
28+
_ "github.com/tetratelabs/getenvoy/pkg/flavors/postgres" //nolint
2729
"github.com/tetratelabs/getenvoy/pkg/manifest"
2830
)
2931

@@ -32,6 +34,7 @@ var (
3234
accessLogServerAddress string
3335
mode string
3436
bootstrap string
37+
templateArgs map[string]string
3538
)
3639

3740
// NewRunCmd create a command responsible for starting an Envoy process
@@ -53,18 +56,13 @@ getenvoy run ./envoy -- --config-path ./bootstrap.yaml
5356
5457
# List available Envoy flags.
5558
getenvoy run standard:1.11.1 -- --help
59+
60+
# Run with Postgres specific configuration bootstrapped
61+
getenvoy run postgres:nightly --templateArg endpoints=127.0.0.1:5432,192.168.0.101:5432 --templateArg inport=5555
5662
`,
5763
Args: func(cmd *cobra.Command, args []string) error {
58-
if len(args) == 0 {
59-
return errors.New("missing binary parameter")
60-
}
61-
if err := validateMode(); err != nil {
62-
return err
63-
}
64-
if err := validateBootstrap(); err != nil {
65-
return err
66-
}
67-
return validateRequiresBootstrap()
64+
return validateCmdArgs(args)
65+
6866
},
6967
RunE: func(cmd *cobra.Command, args []string) error {
7068
cfg := envoy.NewConfig(
@@ -88,12 +86,25 @@ getenvoy run standard:1.11.1 -- --help
8886
}
8987

9088
key, manifestErr := manifest.NewKey(args[0])
89+
9190
if manifestErr != nil {
9291
if _, err := os.Stat(args[0]); err != nil {
9392
return fmt.Errorf("%v isn't valid manifest reference or an existing filepath", args[0])
9493
}
9594
return runtime.RunPath(args[0], args[1:])
9695
}
96+
97+
// Check if the templateArgs were passed to the cmd line.
98+
// If they were passed, config must be created based on
99+
// template.
100+
if len(templateArgs) > 0 {
101+
cmdArg, err := processTemplateArgs(key.Flavor, templateArgs, runtime.(*envoy.Runtime))
102+
if err != nil {
103+
return err
104+
}
105+
args = append(args, cmdArg)
106+
}
107+
97108
if !runtime.AlreadyDownloaded(key) {
98109
location, err := manifest.Locate(key, manifestURL)
99110
if err != nil {
@@ -114,6 +125,8 @@ getenvoy run standard:1.11.1 -- --help
114125
"(experimental) location of Envoy's access log server <host|ip:port> (requires bootstrap flag)")
115126
cmd.Flags().StringVar(&mode, "mode", "",
116127
fmt.Sprintf("(experimental) mode to run Envoy in <%v> (requires bootstrap flag)", strings.Join(envoy.SupportedModes, "|")))
128+
cmd.Flags().StringToStringVar(&templateArgs, "templateArg", map[string]string{},
129+
"arguments passed to a config template for substitution")
117130
return cmd
118131
}
119132

@@ -153,6 +166,19 @@ func validateRequiresBootstrap() error {
153166
return nil
154167
}
155168

169+
func validateCmdArgs(args []string) error {
170+
if len(args) == 0 {
171+
return errors.New("missing binary parameter")
172+
}
173+
if err := validateMode(); err != nil {
174+
return err
175+
}
176+
if err := validateBootstrap(); err != nil {
177+
return err
178+
}
179+
return validateRequiresBootstrap()
180+
}
181+
156182
func controlplaneFunc() func(r *envoy.Runtime) {
157183
switch bootstrap {
158184
case istio:
@@ -162,3 +188,18 @@ func controlplaneFunc() func(r *envoy.Runtime) {
162188
return func(r *envoy.Runtime) {}
163189
}
164190
}
191+
192+
// Function creates config file based on template args passed by a user.
193+
// The return value is Envoy command line option which must be passed to Envoy.
194+
func processTemplateArgs(flavor string, templateArgs map[string]string, runtime *envoy.Runtime) (string, error) {
195+
config, err := flavors.CreateConfig(flavor, templateArgs)
196+
if err != nil {
197+
return "", err
198+
}
199+
// Save config in getenvoy directory
200+
path, err := runtime.SaveConfig(flavor, config)
201+
if err != nil {
202+
return "", err
203+
}
204+
return "--config-path " + path, nil
205+
}

pkg/flavors/flavors.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2020 Tetrate
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package flavors
16+
17+
import (
18+
"fmt"
19+
)
20+
21+
// FlavorConfigTemplate represents interface to individual flavors.
22+
type FlavorConfigTemplate interface {
23+
GenerateConfig(params map[string]string) (string, error)
24+
}
25+
26+
// Main repo for templates.
27+
type flavorStore struct {
28+
// This is a map indexed by flavor pointing to the individual
29+
// implementaions of each flavor.
30+
templates map[string]FlavorConfigTemplate
31+
}
32+
33+
var store = flavorStore{templates: make(map[string]FlavorConfigTemplate)}
34+
35+
// AddFlavor function is used by individual flavors (like postgres)
36+
// to add the flavor to main repo.
37+
func AddFlavor(flavor string, configTemplate FlavorConfigTemplate) {
38+
store.templates[flavor] = configTemplate
39+
}
40+
41+
// GetFlavor function returns FlavorConfigTemplate structure associated
42+
// with flavor.
43+
func GetFlavor(flavor string) (FlavorConfigTemplate, error) {
44+
tmplString, ok := store.templates[flavor]
45+
if !ok {
46+
return nil, fmt.Errorf("Cannot find template for flavor %s", flavor)
47+
}
48+
49+
return tmplString, nil
50+
}
51+
52+
// CreateConfig function checks flavor specific parameters, get flavor's template and
53+
// create a config.
54+
func CreateConfig(flavor string, params map[string]string) (string, error) {
55+
flavorData, err := GetFlavor(flavor)
56+
57+
if err != nil {
58+
return "", err
59+
}
60+
61+
return flavorData.GenerateConfig(params)
62+
}

pkg/flavors/flavors_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2020 Tetrate
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package flavors_test
15+
16+
import (
17+
"testing"
18+
19+
"github.com/tetratelabs/getenvoy/pkg/flavors"
20+
)
21+
22+
// Flavor for mocking and testing.
23+
type TestFlavor struct {
24+
Test string
25+
}
26+
27+
var flavor TestFlavor
28+
29+
const testConfig string = "This is UnitTest config"
30+
31+
func (f *TestFlavor) GenerateConfig(params map[string]string) (string, error) {
32+
// Just return predefined value.
33+
// Normally a flavor would use template but
34+
// here using templates is not part of the test.
35+
return testConfig, nil
36+
}
37+
38+
// Test adding and retrieving config template
39+
func TestAdd(t *testing.T) {
40+
flavors.AddFlavor("test", &flavor)
41+
42+
_, err := flavors.GetFlavor("test")
43+
44+
if err != nil {
45+
t.Error("Just added template cannot be located")
46+
}
47+
}
48+
49+
// Test retrieving non-existing template
50+
func TestGetNonExisting(t *testing.T) {
51+
flavors.AddFlavor("test", &flavor)
52+
53+
_, err := flavors.GetFlavor("test1")
54+
55+
if err == nil {
56+
t.Error("Error should be returned for non-existing template")
57+
}
58+
}
59+
60+
// Test creating config.
61+
// Test verifies that after adding TestFlavor to the list
62+
// of known flavors, it can create a proper config.
63+
func TestCreateConfig(t *testing.T) {
64+
flavors.AddFlavor("test", &flavor)
65+
66+
params := map[string]string{"Test": "UnitTest"}
67+
config, err := flavors.CreateConfig("test", params)
68+
69+
if err != nil {
70+
t.Error("Creating config failed with proper parameters")
71+
}
72+
73+
if config != testConfig {
74+
t.Errorf("Created config %s not as expected", config)
75+
}
76+
}

0 commit comments

Comments
 (0)