Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store
.releases
cloudsql-proxy-inject*

.idea
158 changes: 108 additions & 50 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package main

import (
"encoding/json"
"bytes"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"

"github.com/go-yaml/yaml"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/alecthomas/kingpin.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"
Expand All @@ -29,68 +31,41 @@ 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 = []v1.VolumeMount{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 = []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)
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) {
Expand Down Expand Up @@ -123,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)
}
}
95 changes: 95 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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:
- 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
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: test-volume
secret:
secretName: test-secret
- 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())
}
24 changes: 24 additions & 0 deletions test/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: extensions/v1beta1
kind: Deployment
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
metadata:
name: test-svc
spec:
ports:
- name: web
port: 8080