From 5d43f9e78841ac7d07e789804f5ed91d940bd0a9 Mon Sep 17 00:00:00 2001 From: Francois Poinsot Date: Tue, 5 Mar 2019 10:28:41 +0100 Subject: [PATCH 1/3] just append instead of overriding --- main.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 8b5b313..a2998b4 100644 --- a/main.go +++ b/main.go @@ -6,9 +6,10 @@ import ( "os" "github.com/go-yaml/yaml" - kingpin "gopkg.in/alecthomas/kingpin.v2" + "gopkg.in/alecthomas/kingpin.v2" + "gopkg.in/yaml.v2" "k8s.io/api/apps/v1beta1" - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json" k8syaml "k8s.io/apimachinery/pkg/util/yaml" @@ -54,7 +55,7 @@ func main() { cloudSQLProxyContainer.Command = []string{"/cloud_sql_proxy", fmt.Sprintf("-instances=%s:%s:%s", *project, *region, *instance), "-log_debug_stdout=true", fmt.Sprintf("-verbose=%s", *verbose), "-credential_file=/secrets/cloudsql/credentials.json"} cloudSQLProxyContainer.Resources = v1.ResourceRequirements{Requests: requestResources, Limits: limitResources} cloudSQLProxyContainer.SecurityContext = &securityContext - cloudSQLProxyContainer.VolumeMounts = []v1.VolumeMount{volumeMount} + cloudSQLProxyContainer.VolumeMounts = append(cloudSQLProxyContainer.VolumeMounts, volumeMount) } b, err := yaml.Marshal(&cloudSQLProxyContainer) @@ -79,14 +80,14 @@ func main() { } k8syaml.NewYAMLOrJSONDecoder(f, 4096).Decode(&deploy) - deploy.Spec.Template.Spec.Volumes = []v1.Volume{v1.Volume{ + deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes, v1.Volume{ Name: "cloudsql-proxy-credentials", VolumeSource: v1.VolumeSource{ Secret: &v1.SecretVolumeSource{ SecretName: "cloudsql-proxy-credentials", }, }, - }} + }) deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, cloudSQLProxyContainer) serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, nil, nil) From 16609a908ee5af6bc61f876d0945236d3716a421 Mon Sep 17 00:00:00 2001 From: Francois Poinsot Date: Tue, 5 Mar 2019 10:35:59 +0100 Subject: [PATCH 2/3] handle multiple resources in the same file --- .gitignore | 2 + main.go | 151 ++++++++++++++++++++++++++++++++++--------------- main_test.go | 88 ++++++++++++++++++++++++++++ test/test.yaml | 15 +++++ 4 files changed, 209 insertions(+), 47 deletions(-) create mode 100644 main_test.go create mode 100644 test/test.yaml diff --git a/.gitignore b/.gitignore index b5d942a..d32874d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .DS_Store .releases cloudsql-proxy-inject* + +.idea \ No newline at end of file diff --git a/main.go b/main.go index a2998b4..034f0e0 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,14 @@ package main import ( - "encoding/json" + "bytes" "fmt" + "github.com/pkg/errors" + "io" + "io/ioutil" "os" - "github.com/go-yaml/yaml" "gopkg.in/alecthomas/kingpin.v2" - "gopkg.in/yaml.v2" "k8s.io/api/apps/v1beta1" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -30,55 +31,23 @@ var ( func main() { kingpin.Parse() + runInjector() +} - var cloudSQLProxyContainer v1.Container - { - requestResources, limitResources := setResources(*cpuRequest, *memoryRequest, *cpuLimit, *memoryLimit) - - var runAsUser int64 = 2 - var allowPrivilegeEscalation = false - - securityContext := v1.SecurityContext{ - RunAsUser: &runAsUser, - AllowPrivilegeEscalation: &allowPrivilegeEscalation, - } - - volumeMount := v1.VolumeMount{ - Name: "cloudsql-proxy-credentials", - MountPath: "/secrets/cloudsql", - ReadOnly: true, - } - - cloudSQLProxyContainer = v1.Container{} - cloudSQLProxyContainer.Name = "cloudsql-proxy" - cloudSQLProxyContainer.Image = fmt.Sprintf("gcr.io/cloudsql-docker/gce-proxy:%s", *proxyVersion) - cloudSQLProxyContainer.Command = []string{"/cloud_sql_proxy", fmt.Sprintf("-instances=%s:%s:%s", *project, *region, *instance), "-log_debug_stdout=true", fmt.Sprintf("-verbose=%s", *verbose), "-credential_file=/secrets/cloudsql/credentials.json"} - cloudSQLProxyContainer.Resources = v1.ResourceRequirements{Requests: requestResources, Limits: limitResources} - cloudSQLProxyContainer.SecurityContext = &securityContext - cloudSQLProxyContainer.VolumeMounts = append(cloudSQLProxyContainer.VolumeMounts, volumeMount) - } - - b, err := yaml.Marshal(&cloudSQLProxyContainer) - if err != nil { - panic(err) - } - f, err := os.Open(*path) - if err != nil { - panic(err) - } - defer f.Close() +func runInjector() { + cloudSQLProxyContainer := getCloudContainer() - b, err = json.Marshal(&cloudSQLProxyContainer) - if err != nil { - panic(err) - } + // split the file bytes by resources + // a file may contains multiple resources, separated by "---" + allK8SResources := getAllResourcesBytes(*path) + // separate deployment from others resources + deploymentBytes, otherResources := extractDeploymentBytes(allK8SResources) - deploy := &v1beta1.Deployment{} - err = json.Unmarshal(b, &deploy) + deploy := v1beta1.Deployment{} + err := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(deploymentBytes), 4096).Decode(&deploy) if err != nil { panic(err) } - k8syaml.NewYAMLOrJSONDecoder(f, 4096).Decode(&deploy) deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes, v1.Volume{ Name: "cloudsql-proxy-credentials", @@ -91,7 +60,12 @@ func main() { deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, cloudSQLProxyContainer) serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, nil, nil) - serializer.Encode(deploy, os.Stdout) + + outputBytes := bytes.NewBuffer(nil) + serializer.Encode(&deploy, outputBytes) + putItBack(otherResources, outputBytes) + + os.Stdout.Write(outputBytes.Bytes()) } func setResources(cpuRequest, memoryRequest, cpuLimit, memoryLimit string) (request v1.ResourceList, limit v1.ResourceList) { @@ -124,3 +98,86 @@ func setResources(cpuRequest, memoryRequest, cpuLimit, memoryLimit string) (requ return request, limit } + +func getAllResourcesBytes(filepath string) [][]byte { + f, err := os.Open(filepath) + if err != nil { + panic(err) + } + defer f.Close() + + fileBytes, err := ioutil.ReadAll(f) + if err != nil { + panic(err) + } + + return bytes.Split(fileBytes, []byte("\n---")) +} + +func extractDeploymentBytes(allK8SResources [][]byte) (deploymentBytes []byte, otherResources [][]byte) { + // find the deployment in the list of resources + for _, resourceBytes := range allK8SResources { + // Because interpreter read only JSON... + resourceJSON, err := k8syaml.ToJSON(resourceBytes) + if err != nil { + panic(err) + } + schema, err := k8sjson.DefaultMetaFactory.Interpret(resourceJSON) + if err != nil { + panic(err) + } + + // Is this a deployment or something else + if schema.Kind == "Deployment" { + deploymentBytes = resourceBytes + } else { + otherResources = append(otherResources, resourceBytes) + } + } + + if len(deploymentBytes) <= 0 { + panic(errors.New("could not find deployment resource in given file")) + } + + return deploymentBytes, otherResources +} + +func getCloudContainer() v1.Container { + var cloudSQLProxyContainer v1.Container + { + requestResources, limitResources := setResources(*cpuRequest, *memoryRequest, *cpuLimit, *memoryLimit) + + var runAsUser int64 = 2 + var allowPrivilegeEscalation = false + + securityContext := v1.SecurityContext{ + RunAsUser: &runAsUser, + AllowPrivilegeEscalation: &allowPrivilegeEscalation, + } + + volumeMount := v1.VolumeMount{ + Name: "cloudsql-proxy-credentials", + MountPath: "/secrets/cloudsql", + ReadOnly: true, + } + + cloudSQLProxyContainer = v1.Container{} + cloudSQLProxyContainer.Name = "cloudsql-proxy" + cloudSQLProxyContainer.Image = fmt.Sprintf("gcr.io/cloudsql-docker/gce-proxy:%s", *proxyVersion) + cloudSQLProxyContainer.Command = []string{"/cloud_sql_proxy", fmt.Sprintf("-instances=%s:%s:%s", *project, *region, *instance), "-log_debug_stdout=true", fmt.Sprintf("-verbose=%s", *verbose), "-credential_file=/secrets/cloudsql/credentials.json"} + cloudSQLProxyContainer.Resources = v1.ResourceRequirements{Requests: requestResources, Limits: limitResources} + cloudSQLProxyContainer.SecurityContext = &securityContext + cloudSQLProxyContainer.VolumeMounts = append(cloudSQLProxyContainer.VolumeMounts, volumeMount) + } + + + return cloudSQLProxyContainer +} + +// Put the remaining bytes that are not the deployment, back in the output +func putItBack(otherResources [][]byte, w io.Writer) { + for _, resourceBytes := range otherResources { + w.Write([]byte("\n---\n")) + w.Write(resourceBytes) + } +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a8f4dee --- /dev/null +++ b/main_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "bytes" + "github.com/magiconair/properties/assert" + "github.com/stretchr/testify/require" + "io" + "os" + "testing" +) + +func Test_runInjector(t *testing.T) { + + expectedOutput := `apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + creationTimestamp: null + name: test +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + spec: + containers: + - command: + - /cloud_sql_proxy + - -instances=project-test:region-test:instance-test + - -credential_file=/secrets/cloudsql/credentials.json + image: gcr.io/cloudsql-docker/gce-proxy:1.11 + name: cloudsql-proxy + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 5m + memory: 8Mi + securityContext: + allowPrivilegeEscalation: false + runAsUser: 2 + volumeMounts: + - mountPath: /secrets/cloudsql + name: cloudsql-proxy-credentials + readOnly: true + volumes: + - name: cloudsql-proxy-credentials + secret: + secretName: cloudsql-proxy-credentials +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + name: test-svc +spec: + ports: + - name: web + port: 8080` + + // Just to trick to get control other stdout + // r and w are linked => whatever is written in w is readable in r + oldStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + *path = "./test/test.yaml" + *instance = "instance-test" + *region = "region-test" + *project = "project-test" + *cpuRequest = "5m" + *memoryRequest = "8Mi" + *cpuLimit = "100m" + *memoryLimit = "128Mi" + *proxyVersion = "1.11" + + runInjector() + os.Stdout = oldStdout + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + + assert.Equal(t, expectedOutput, buf.String()) +} diff --git a/test/test.yaml b/test/test.yaml new file mode 100644 index 0000000..b030932 --- /dev/null +++ b/test/test.yaml @@ -0,0 +1,15 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: test +spec: + replicas: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: test-svc +spec: + ports: + - name: web + port: 8080 \ No newline at end of file From bcd49c079a161763514de5522acb25b63758a56c Mon Sep 17 00:00:00 2001 From: Francois Poinsot Date: Tue, 5 Mar 2019 17:58:12 +0100 Subject: [PATCH 3/3] also test the volume bug --- main_test.go | 9 ++++++++- test/test.yaml | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/main_test.go b/main_test.go index a8f4dee..37312f2 100644 --- a/main_test.go +++ b/main_test.go @@ -24,9 +24,14 @@ spec: creationTimestamp: null spec: containers: + - image: some-image + name: name-test + resources: {} - command: - /cloud_sql_proxy - -instances=project-test:region-test:instance-test + - -log_debug_stdout=true + - -verbose= - -credential_file=/secrets/cloudsql/credentials.json image: gcr.io/cloudsql-docker/gce-proxy:1.11 name: cloudsql-proxy @@ -45,6 +50,9 @@ spec: name: cloudsql-proxy-credentials readOnly: true volumes: + - name: test-volume + secret: + secretName: test-secret - name: cloudsql-proxy-credentials secret: secretName: cloudsql-proxy-credentials @@ -83,6 +91,5 @@ spec: w.Close() var buf bytes.Buffer io.Copy(&buf, r) - assert.Equal(t, expectedOutput, buf.String()) } diff --git a/test/test.yaml b/test/test.yaml index b030932..d0348a5 100644 --- a/test/test.yaml +++ b/test/test.yaml @@ -4,6 +4,15 @@ metadata: name: test spec: replicas: 1 + template: + spec: + containers: + - name: name-test + image: some-image + volumes: + - name: test-volume + secret: + secretName: test-secret --- apiVersion: v1 kind: Service