diff --git a/operator/Dockerfile b/operator/Dockerfile index 4e9dc3cd..ed8a8496 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -11,7 +11,7 @@ COPY . . RUN go mod download # Build -RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -a -o manager main.go config.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/operator/Makefile b/operator/Makefile index 1aa9dce6..16f14ab4 100755 --- a/operator/Makefile +++ b/operator/Makefile @@ -46,11 +46,11 @@ clean: # Build manager binary manager: generate fmt vet - go build -o bin/manager main.go + go build -o bin/manager main.go ./config.go # Run against the configured Kubernetes cluster in ~/.kube/config run: generate fmt vet manifests - go run ./main.go + go run ./main.go ./config.go # Install CRDs into a cluster install: manifests kustomize diff --git a/operator/config.go b/operator/config.go new file mode 100644 index 00000000..1a92e1c9 --- /dev/null +++ b/operator/config.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Sidecar SidecarConfig `yaml:"sidecar,omitempty"` +} + +type SidecarConfig struct { + Image string `yaml:"image,omitempty"` +} + +func ReadConfig(configPath string) (Config, error) { + // Set default values + config := Config{ + Sidecar: SidecarConfig{ + Image: "sumologic/tailing-sidecar:latest", + }, + } + + content, err := os.ReadFile(configPath) + if err != nil { + return config, err + } + err = yaml.Unmarshal(content, &config) + if err != nil { + return config, err + } + return config, err +} + +func (c *Config) Validate() error { + return nil +} diff --git a/operator/config_test.go b/operator/config_test.go new file mode 100644 index 00000000..8e36e6bd --- /dev/null +++ b/operator/config_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReadConfig(t *testing.T) { + testCases := []struct { + name string + content string + expected Config + expectedError error + }{ + { + name: "empty file", + content: ``, + expected: Config{ + Sidecar: SidecarConfig{ + Image: "sumologic/tailing-sidecar:latest", + }, + }, + expectedError: nil, + }, + { + name: "defaults", + content: ` +sidecar: + commands: + - test + - command`, + expected: Config{ + Sidecar: SidecarConfig{ + Image: "sumologic/tailing-sidecar:latest", + }, + }, + expectedError: nil, + }, + { + name: "overwrite defaults", + content: ` +sidecar: + image: my-new-image`, + expected: Config{ + Sidecar: SidecarConfig{ + Image: "my-new-image", + }, + }, + expectedError: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + file, err := ioutil.TempFile(".", "prefix") + require.NoError(t, err) + defer os.Remove(file.Name()) + + _, err = file.WriteString(tt.content) + require.NoError(t, err) + config, err := ReadConfig(file.Name()) + + if tt.expectedError != nil { + require.Error(t, tt.expectedError, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expected, config) + }) + } +} + +func TestReadConfigInvalidFile(t *testing.T) { + _, err := ReadConfig("non-existing-file") + require.Error(t, err) + require.EqualError(t, err, "open non-existing-file: no such file or directory") +} diff --git a/operator/go.mod b/operator/go.mod index 16b169c8..17f0ef2f 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -7,13 +7,17 @@ require ( github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.27.6 + github.com/stretchr/testify v1.8.1 gomodules.xyz/jsonpatch/v2 v2.2.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.3 k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.26.3 sigs.k8s.io/controller-runtime v0.14.6 ) +require github.com/pmezard/go-difflib v1.0.0 // indirect + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect @@ -63,7 +67,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/component-base v0.26.1 // indirect k8s.io/klog/v2 v2.90.1 // indirect diff --git a/operator/main.go b/operator/main.go index b6920985..568de469 100644 --- a/operator/main.go +++ b/operator/main.go @@ -50,15 +50,40 @@ func main() { var metricsAddr string var enableLeaderElection bool var tailingSidecarImage string + var configPath string + var config Config + var err error + flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - flag.StringVar(&tailingSidecarImage, "tailing-sidecar-image", "sumologic/tailing-sidecar:latest", "tailing sidecar image") + flag.StringVar(&tailingSidecarImage, "tailing-sidecar-image", "", "tailing sidecar image") + flag.StringVar(&configPath, "config", "", "Path to the configuration file") flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + if configPath != "" { + config, err = ReadConfig(configPath) + + if err != nil { + setupLog.Error(err, "unable to read configuration", "configPath", configPath) + os.Exit(1) + } + } else { + config = Config{} + } + + if err := config.Validate(); err != nil { + setupLog.Error(err, "configuration error", "configPath", configPath) + os.Exit(1) + } + + if tailingSidecarImage != "" { + config.Sidecar.Image = tailingSidecarImage + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, @@ -84,7 +109,7 @@ func main() { mgr.GetWebhookServer().Register("/add-tailing-sidecars-v1-pod", &webhook.Admission{ Handler: &handler.PodExtender{ Client: mgr.GetClient(), - TailingSidecarImage: tailingSidecarImage, + TailingSidecarImage: config.Sidecar.Image, }, })