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
4 changes: 2 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion reposerver/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, e
}
case AppSourceKustomize:
k := kustomize.NewKustomizeApp(appPath)
targetObjs, err = k.Build()
targetObjs, params, err = k.Build(q.Namespace, q.ComponentParameterOverrides)
case AppSourceDirectory:
targetObjs, err = findManifests(appPath)
}
Expand Down
93 changes: 85 additions & 8 deletions util/kustomize/kustomize.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package kustomize

import (
"github.com/argoproj/pkg/exec"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"fmt"
"os/exec"
"strings"

"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
argoexec "github.com/argoproj/pkg/exec"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// Kustomize provides wrapper functionality around the `kustomize` command.
type Kustomize interface {
// Build returns a list of unstructured objects from a `kustomize build` command
Build() ([]*unstructured.Unstructured, error)
// Build returns a list of unstructured objects from a `kustomize build` command and extract supported parameters
Build(namespace string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, error)
}

// NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool.
Expand All @@ -22,10 +26,83 @@ type kustomize struct {
path string
}

func (k *kustomize) Build() ([]*unstructured.Unstructured, error) {
out, err := exec.RunCommand("kustomize", "build", k.path)
func (k *kustomize) Build(namespace string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, error) {
if namespace != "" {
cmd := exec.Command("kustomize", "edit", "set", "namespace", namespace)
cmd.Dir = k.path
_, err := argoexec.RunCommandExt(cmd)
if err != nil {
return nil, nil, err
}
}

for _, override := range overrides {
cmd := exec.Command("kustomize", "edit", "set", "imagetag", fmt.Sprintf("%s:%s", override.Name, override.Value))
cmd.Dir = k.path
_, err := argoexec.RunCommandExt(cmd)
if err != nil {
return nil, nil, err
}
}

out, err := argoexec.RunCommand("kustomize", "build", k.path)
if err != nil {
return nil, nil, err
}

objs, err := kube.SplitYAML(out)
if err != nil {
return nil, err
return nil, nil, err
}

return objs, append(getImageParameters(objs)), nil
}

func getImageParameters(objs []*unstructured.Unstructured) []*v1alpha1.ComponentParameter {
images := make(map[string]string)
for _, obj := range objs {
for _, img := range getImages(obj.Object) {
parts := strings.Split(img, ":")
if len(parts) > 1 {
images[parts[0]] = parts[1]
} else {
images[img] = "latest"
}
}
}
var params []*v1alpha1.ComponentParameter
for img, version := range images {
params = append(params, &v1alpha1.ComponentParameter{
Component: "imagetag",
Name: img,
Value: version,
})
}
return params
}

func getImages(object map[string]interface{}) []string {
var images []string
for k, v := range object {
if array, ok := v.([]interface{}); ok {
if k == "containers" || k == "initContainers" {
for _, obj := range array {
if mapObj, isMapObj := obj.(map[string]interface{}); isMapObj {
if image, hasImage := mapObj["image"]; hasImage {
images = append(images, fmt.Sprintf("%s", image))
}
}
}
} else {
for i := range array {
if mapObj, isMapObj := array[i].(map[string]interface{}); isMapObj {
images = append(images, getImages(mapObj)...)
}
}
}
} else if objMap, ok := v.(map[string]interface{}); ok {
images = append(images, getImages(objMap)...)
}
}
return kube.SplitYAML(out)
return images
}
60 changes: 60 additions & 0 deletions util/kustomize/kustomize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package kustomize

import (
"io/ioutil"
"path"
"testing"

"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"

"github.com/argoproj/pkg/exec"
"github.com/stretchr/testify/assert"
)

func testDataDir() (string, error) {
res, err := ioutil.TempDir("", "kustomize-test")
if err != nil {
return "", err
}
_, err = exec.RunCommand("cp", "-r", "./testdata", res)
if err != nil {
return "", err
}
return path.Join(res, "testdata"), nil
}

func TestKustomizeBuild(t *testing.T) {
appPath, err := testDataDir()
assert.Nil(t, err)

kustomize := NewKustomizeApp(appPath)
objs, params, err := kustomize.Build("mynamespace", []*v1alpha1.ComponentParameter{{
Component: "imagetag",
Name: "k8s.gcr.io/nginx-slim",
Value: "latest",
}})
assert.Nil(t, err)
if err != nil {
assert.Equal(t, len(objs), 2)
assert.Equal(t, len(params), 2)
}
for _, obj := range objs {
switch obj.GetKind() {
case "StatefulSet":
assert.Equal(t, "web", obj.GetName())
case "Deployment":
assert.Equal(t, "nginx-deployment", obj.GetName())
}
assert.Equal(t, "mynamespace", obj.GetNamespace())
}

for _, param := range params {
switch param.Value {
case "nginx":
assert.Equal(t, "1.15.4", param.Value)
case "k8s.gcr.io/nginx-slim":
assert.Equal(t, "latest", param.Value)
}
assert.Equal(t, "imagetag", param.Component)
}
}
21 changes: 21 additions & 0 deletions util/kustomize/testdata/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
ports:
- containerPort: 80
3 changes: 3 additions & 0 deletions util/kustomize/testdata/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resources:
- ./deployment.yaml
- ./statefullset.yaml
34 changes: 34 additions & 0 deletions util/kustomize/testdata/statefullset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi