diff --git a/pkg/deployments/deployments.go b/pkg/deployments/deployments.go index f5acba11..5275478c 100644 --- a/pkg/deployments/deployments.go +++ b/pkg/deployments/deployments.go @@ -17,7 +17,7 @@ import ( "github.com/Azure/draft/pkg/templatewriter" ) -var ( +const ( parentDirName = "deployments" configFileName = "draft.yaml" ) @@ -47,7 +47,7 @@ func (d *Deployments) CopyDeploymentFiles(deployType string, deployConfig *confi return fmt.Errorf("create deployment files for deployment type: %w", err) } - if err := osutil.CopyDir(d.deploymentTemplates, srcDir, d.dest, deployConfig, templateWriter); err != nil { + if err := osutil.CopyDirWithTemplates(d.deploymentTemplates, srcDir, d.dest, deployConfig, templateWriter); err != nil { return err } @@ -94,7 +94,7 @@ func (d *Deployments) PopulateConfigs() { } func CreateDeploymentsFromEmbedFS(deploymentTemplates embed.FS, dest string) *Deployments { - deployMap, err := embedutils.EmbedFStoMap(deploymentTemplates, "deployments") + deployMap, err := embedutils.EmbedFStoMap(deploymentTemplates, parentDirName) if err != nil { log.Fatal(err) } diff --git a/pkg/deployments/deployments_test.go b/pkg/deployments/deployments_test.go new file mode 100644 index 00000000..579da3f6 --- /dev/null +++ b/pkg/deployments/deployments_test.go @@ -0,0 +1,239 @@ +package deployments + +import ( + "embed" + "fmt" + "github.com/Azure/draft/pkg/config" + "github.com/Azure/draft/pkg/embedutils" + "github.com/Azure/draft/pkg/templatewriter/writers" + "io" + "io/fs" + "os" + "testing" + "testing/fstest" + + "github.com/Azure/draft/template" + "github.com/stretchr/testify/assert" +) + +var testFS embed.FS + +func TestCreateDeployments(t *testing.T) { + dest := "." + templateWriter := &writers.LocalFSWriter{} + draftConfig := &config.DraftConfig{ + Variables: []*config.BuilderVar{ + {Name: "APPNAME", Value: "testapp"}, + {Name: "NAMESPACE", Value: "default"}, + {Name: "PORT", Value: "80"}, + {Name: "IMAGENAME", Value: "testimage"}, + {Name: "IMAGETAG", Value: "latest"}, + {Name: "GENERATORLABEL", Value: "draft"}, + {Name: "SERVICEPORT", Value: "80"}, + }, + } + + tests := []struct { + name string + deployType string + shouldError bool + tempDirPath string + tempFileName string + tempPath string + cleanUp func() + }{ + { + name: "helm", + deployType: "helm", + shouldError: false, + tempDirPath: "charts/templates", + tempFileName: "charts/templates/deployment.yaml", + tempPath: "../../test/templates/helm/charts/templates/deployment.yaml", + cleanUp: func() { + os.Remove(".charts") + }, + }, + { + name: "unsupported", + deployType: "unsupported", + shouldError: true, + tempDirPath: "test/templates/unsupported", + tempFileName: "test/templates/unsupported/deployment.yaml", + tempPath: "test/templates/unsupported/deployment.yaml", + cleanUp: func() { + os.Remove("deployments") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println("Creating temp file:", tt.tempFileName) + err := createTempDeploymentFile(tt.tempDirPath, tt.tempFileName, tt.tempPath) + assert.Nil(t, err) + + deployments := CreateDeploymentsFromEmbedFS(template.Deployments, dest) + err = deployments.CopyDeploymentFiles(tt.deployType, draftConfig, templateWriter) + if tt.shouldError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + tt.cleanUp() + }) + } +} + +func TestLoadConfig(t *testing.T) { + fakeFS, err := createMockDeploymentTemplatesFS() + assert.Nil(t, err) + + d, err := createMockDeployments("deployments", fakeFS) + assert.Nil(t, err) + + cases := []loadConfTestCase{ + {"helm", true}, + {"unsupported", false}, + } + + for _, c := range cases { + if c.isNil { + _, err = d.loadConfig(c.deployType) + assert.Nil(t, err) + } else { + _, err = d.loadConfig(c.deployType) + assert.NotNil(t, err) + } + } +} + +func TestPopulateConfigs(t *testing.T) { + fakeFS, err := createMockDeploymentTemplatesFS() + assert.Nil(t, err) + + d, err := createMockDeployments("deployments", fakeFS) + assert.Nil(t, err) + + d.PopulateConfigs() + assert.Equal(t, 3, len(d.configs)) + + d, err = createTestDeploymentEmbed("deployments") + assert.Nil(t, err) + + d.PopulateConfigs() + assert.Equal(t, 3, len(d.configs)) +} + +type loadConfTestCase struct { + deployType string + isNil bool +} + +func createTempDeploymentFile(dirPath, fileName, path string) error { + err := os.MkdirAll(dirPath, 0755) + if err != nil { + return err + } + file, err := os.Create(fileName) + if err != nil { + return err + } + fmt.Printf("file %v\n", file) + defer file.Close() + + var source *os.File + source, err = os.Open(path) + if err != nil { + return err + } + fmt.Printf("source %v\n", source) + defer source.Close() + + _, err = io.Copy(file, source) + if err != nil { + return err + } + return nil +} + +func createMockDeploymentTemplatesFS() (fs.FS, error) { + rootPath := "deplyments/" + embedFiles, err := embedutils.EmbedFStoMapWithFiles(template.Deployments, "deployments") + if err != nil { + return nil, fmt.Errorf("failed to readDir: %w in embeded files", err) + } + + mockFS := fstest.MapFS{} + + for path, file := range embedFiles { + if file.IsDir() { + mockFS[path] = &fstest.MapFile{Mode: fs.ModeDir} + } else { + bytes, err := template.Deployments.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failes to read file: %w", err) + } + mockFS[path] = &fstest.MapFile{Data: bytes} + } + } + + mockFS[rootPath+"emptyDir"] = &fstest.MapFile{Mode: fs.ModeDir} + mockFS[rootPath+"corrupted"] = &fstest.MapFile{Mode: fs.ModeDir} + mockFS[rootPath+"corrupted/draft.yaml"] = &fstest.MapFile{Data: []byte("fake yaml data")} + + return mockFS, nil +} + +func createMockDeployments(dirPath string, mockDeployments fs.FS) (*Deployments, error) { + dest := "." + + deployMap, err := fsToMap(mockDeployments, dirPath) + if err != nil { + return nil, fmt.Errorf("failed fsToMap: %w", err) + } + + d := &Deployments{ + deploys: deployMap, + dest: dest, + configs: make(map[string]*config.DraftConfig), + deploymentTemplates: mockDeployments, + } + + return d, nil +} + +func createTestDeploymentEmbed(dirPath string) (*Deployments, error) { + dest := "." + + deployMap, err := embedutils.EmbedFStoMap(template.Deployments, "deployments") + if err != nil { + return nil, fmt.Errorf("failed to create deployMap: %w", err) + } + + d := &Deployments{ + deploys: deployMap, + dest: dest, + configs: make(map[string]*config.DraftConfig), + deploymentTemplates: template.Deployments, + } + + return d, nil +} + +func fsToMap(fsFS fs.FS, path string) (map[string]fs.DirEntry, error) { + files, err := fs.ReadDir(fsFS, path) + if err != nil { + return nil, fmt.Errorf("failed to ReadDir: %w", err) + } + + mapping := make(map[string]fs.DirEntry) + + for _, f := range files { + if f.IsDir() { + mapping[f.Name()] = f + } + } + + return mapping, nil +} diff --git a/template/deployments/helm/charts/Chart.yaml b/template/deployments/helm/charts/Chart.yaml index b26c2ea9..4480aa68 100644 --- a/template/deployments/helm/charts/Chart.yaml +++ b/template/deployments/helm/charts/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -name: {{APPNAME}} +name: {{.APPNAME}} description: A Helm chart for Kubernetes # A chart can be either an 'application' or a 'library' chart. diff --git a/template/deployments/helm/charts/production.yaml b/template/deployments/helm/charts/production.yaml index 92d65a30..36e67ec8 100644 --- a/template/deployments/helm/charts/production.yaml +++ b/template/deployments/helm/charts/production.yaml @@ -1,8 +1,8 @@ image: - repository: "{{APPNAME}}" + repository: "{{.APPNAME}}" pullPolicy: Always tag: "latest" service: annotations: {} type: LoadBalancer - port: "{{SERVICEPORT}}" + port: "{{.SERVICEPORT}}" diff --git a/template/deployments/helm/charts/templates/_helpers.tpl b/template/deployments/helm/charts/templates/_helpers.tpl index 5298b547..30222bcc 100644 --- a/template/deployments/helm/charts/templates/_helpers.tpl +++ b/template/deployments/helm/charts/templates/_helpers.tpl @@ -1,17 +1,17 @@ {{/* Expand the name of the chart. */}} -{{- define "{{APPNAME}}.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} +{{ printf "{{- define \"%s.name\" -}}" .APPNAME }} +{{`{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }}`}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "{{APPNAME}}.fullname" -}} -{{- if .Values.fullnameOverride }} +{{ printf "{{- define \"%s.fullname\" -}}" .APPNAME }} +{{`{{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} @@ -21,31 +21,29 @@ If release name contains chart name it will be used as a full name. {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} -{{- end }} +{{- end }}`}} {{/* Create chart name and version as used by the chart label. */}} -{{- define "{{APPNAME}}.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} +{{ printf "{{- define \"%s.chart\" -}}" .APPNAME }} +{{`{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }}`}} {{/* Common labels */}} -{{- define "{{APPNAME}}.labels" -}} -helm.sh/chart: {{ include "{{APPNAME}}.chart" . }} -{{ include "{{APPNAME}}.selectorLabels" . }} -{{- if .Chart.AppVersion }} +{{ printf "{{- define \"%s.labels\" -}}" .APPNAME }} +helm.sh/chart: {{ printf "{{ include \"%s.chart\" . }}" .APPNAME }} +{{ printf "{{ include \"%s.selectorLabels\" . }}" .APPNAME }} +{{`{{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} +{{- end }}`}} {{/* Selector labels */}} -{{- define "{{APPNAME}}.selectorLabels" -}} -app.kubernetes.io/name: {{ include "{{APPNAME}}.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} \ No newline at end of file +{{ printf "{{- define \"%s.selectorLabels\" -}}" .APPNAME }} +{{ printf "app.kubernetes.io/name: {{ include \"%s.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}" .APPNAME }} \ No newline at end of file diff --git a/template/deployments/helm/charts/templates/deployment.yaml b/template/deployments/helm/charts/templates/deployment.yaml index e1b38e17..dd1537e0 100644 --- a/template/deployments/helm/charts/templates/deployment.yaml +++ b/template/deployments/helm/charts/templates/deployment.yaml @@ -1,28 +1,35 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "{{APPNAME}}.fullname" . }} + name: {{ printf "{{ include \"%s.fullname\" . }}" .APPNAME }} labels: - {{- include "{{APPNAME}}.labels" . | nindent 4 }} - kubernetes.azure.com/generator: {{GENERATORLABEL}} + {{ printf "{{- include \"%s.labels\" . | nindent 4 }}" .APPNAME }} + {{- ` + kubernetes.azure.com/generator: {{ .Values.generatorLabel }} namespace: {{ .Values.namespace }} +` -}} spec: +{{- ` {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} - {{- end }} + {{- end }} + ` -}} selector: matchLabels: - {{- include "{{APPNAME}}.selectorLabels" . | nindent 6 }} + {{ printf "{{- include \"%s.selectorLabels\" . | nindent 6 }}" .APPNAME }} template: - metadata: + metadata: + {{- ` {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} + ` -}} labels: - {{- include "{{APPNAME}}.selectorLabels" . | nindent 8 }} - namespace: {{ .Values.namespace }} - spec: + {{ printf "{{- include \"%s.selectorLabels\" . | nindent 8 }}" .APPNAME }} + namespace: {{ print "{{ .Values.namespace }}" }} + spec: + {{- ` {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -61,3 +68,4 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} +` -}} \ No newline at end of file diff --git a/template/deployments/helm/charts/templates/service.yaml b/template/deployments/helm/charts/templates/service.yaml index 9ef0293f..5bc87795 100644 --- a/template/deployments/helm/charts/templates/service.yaml +++ b/template/deployments/helm/charts/templates/service.yaml @@ -1,19 +1,23 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "{{APPNAME}}.fullname" . }} + name: {{ printf "{{ include \"%s.fullname\" . }}" .APPNAME }} labels: - {{- include "{{APPNAME}}.labels" . | nindent 4 }} - kubernetes.azure.com/generator: {{GENERATORLABEL}} + {{ printf "{{- include \"%s.labels\" . | nindent 4 }}" .APPNAME }} + {{- ` + kubernetes.azure.com/generator: {{.Values.generatorLabel}} annotations: {{ toYaml .Values.service.annotations | nindent 4 }} namespace: {{ .Values.namespace }} +` -}} spec: +{{- ` type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.containerPort }} protocol: TCP name: svchttp + ` -}} selector: - {{- include "{{APPNAME}}.selectorLabels" . | nindent 4 }} + {{ printf "{{- include \"%s.selectorLabels\" . | nindent 6 }}" .APPNAME }} diff --git a/template/deployments/helm/charts/values.yaml b/template/deployments/helm/charts/values.yaml index 4e0d08d4..321227f8 100644 --- a/template/deployments/helm/charts/values.yaml +++ b/template/deployments/helm/charts/values.yaml @@ -1,16 +1,15 @@ -# Default values for {{APPNAME}}. +# Default values for {{.APPNAME}}. # This is a YAML-formatted file. # Declare variables to be passed into your templates. - replicaCount: 1 -namespace: {{NAMESPACE}} +namespace: {{.NAMESPACE}} -containerPort: {{PORT}} +containerPort: {{.PORT}} image: - repository: {{IMAGENAME}} - tag: {{IMAGETAG}} + repository: {{.IMAGENAME}} + tag: {{.IMAGETAG}} pullPolicy: Always @@ -34,7 +33,7 @@ securityContext: {} service: annotations: {} type: LoadBalancer - port: {{SERVICEPORT}} + port: {{.SERVICEPORT}} resources: {} # We usually recommend not to specify default resources and to leave this as a conscious @@ -60,3 +59,5 @@ nodeSelector: {} tolerations: [] affinity: {} + +generatorLabel: {{.GENERATORLABEL}} \ No newline at end of file diff --git a/template/deployments/kustomize/base/deployment.yaml b/template/deployments/kustomize/base/deployment.yaml index 9b406898..4e07bd64 100644 --- a/template/deployments/kustomize/base/deployment.yaml +++ b/template/deployments/kustomize/base/deployment.yaml @@ -1,24 +1,24 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{APPNAME}} + name: {{.APPNAME}} labels: - app: {{APPNAME}} - kubernetes.azure.com/generator: {{GENERATORLABEL}} - namespace: {{NAMESPACE}} + app: {{.APPNAME}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} + namespace: {{.NAMESPACE}} spec: replicas: 1 selector: matchLabels: - app: {{APPNAME}} + app: {{.APPNAME}} template: metadata: labels: - app: {{APPNAME}} + app: {{.APPNAME}} spec: containers: - - name: {{APPNAME}} - image: {{IMAGENAME}}:{{IMAGETAG}} + - name: {{.APPNAME}} + image: {{.IMAGENAME}}:{{.IMAGETAG}} imagePullPolicy: Always ports: - - containerPort: {{PORT}} + - containerPort: {{.PORT}} \ No newline at end of file diff --git a/template/deployments/kustomize/base/namespace.yaml b/template/deployments/kustomize/base/namespace.yaml index c0c9c3f5..76a4a407 100644 --- a/template/deployments/kustomize/base/namespace.yaml +++ b/template/deployments/kustomize/base/namespace.yaml @@ -1,6 +1,6 @@ kind: Namespace apiVersion: v1 metadata: - name: {{NAMESPACE}} + name: {{.NAMESPACE}} labels: - kubernetes.azure.com/generator: {{GENERATORLABEL}} \ No newline at end of file + kubernetes.azure.com/generator: {{.GENERATORLABEL}} \ No newline at end of file diff --git a/template/deployments/kustomize/base/service.yaml b/template/deployments/kustomize/base/service.yaml index e99c7f41..83481aa4 100644 --- a/template/deployments/kustomize/base/service.yaml +++ b/template/deployments/kustomize/base/service.yaml @@ -1,15 +1,15 @@ apiVersion: v1 kind: Service metadata: - name: {{APPNAME}} - namespace: {{NAMESPACE}} + name: {{.APPNAME}} + namespace: {{.NAMESPACE}} labels: - kubernetes.azure.com/generator: {{GENERATORLABEL}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} spec: type: LoadBalancer selector: - app: {{APPNAME}} + app: {{.APPNAME}} ports: - protocol: TCP - port: {{SERVICEPORT}} - targetPort: {{PORT}} \ No newline at end of file + port: {{.SERVICEPORT}} + targetPort: {{.PORT}} \ No newline at end of file diff --git a/template/deployments/kustomize/overlays/production/deployment.yaml b/template/deployments/kustomize/overlays/production/deployment.yaml index 6d93337e..0929b145 100644 --- a/template/deployments/kustomize/overlays/production/deployment.yaml +++ b/template/deployments/kustomize/overlays/production/deployment.yaml @@ -1,17 +1,17 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{APPNAME}} + name: {{.APPNAME}} labels: - app: {{APPNAME}} - kubernetes.azure.com/generator: {{GENERATORLABEL}} - namespace: {{NAMESPACE}} + app: {{.APPNAME}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} + namespace: {{.NAMESPACE}} spec: selector: matchLabels: - app: {{APPNAME}} + app: {{.APPNAME}} template: spec: containers: - - name: {{APPNAME}} - image: {{IMAGENAME}}:{{IMAGETAG}} \ No newline at end of file + - name: {{.APPNAME}} + image: {{.IMAGENAME}}:{{.IMAGETAG}} \ No newline at end of file diff --git a/template/deployments/kustomize/overlays/production/kustomization.yaml b/template/deployments/kustomize/overlays/production/kustomization.yaml index 456210a1..d13c9cee 100644 --- a/template/deployments/kustomize/overlays/production/kustomization.yaml +++ b/template/deployments/kustomize/overlays/production/kustomization.yaml @@ -1,5 +1,5 @@ namePrefix: production- -namespace: {{NAMESPACE}} +namespace: {{.NAMESPACE}} resources: - ../../base patchesStrategicMerge: diff --git a/template/deployments/kustomize/overlays/production/service.yaml b/template/deployments/kustomize/overlays/production/service.yaml index 7200c7b7..70afda67 100644 --- a/template/deployments/kustomize/overlays/production/service.yaml +++ b/template/deployments/kustomize/overlays/production/service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: {{APPNAME}} - namespace: {{NAMESPACE}} + name: {{.APPNAME}} + namespace: {{.NAMESPACE}} labels: - kubernetes.azure.com/generator: {{GENERATORLABEL}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} spec: type: LoadBalancer diff --git a/template/deployments/manifests/manifests/deployment.yaml b/template/deployments/manifests/manifests/deployment.yaml index 9b406898..4e07bd64 100644 --- a/template/deployments/manifests/manifests/deployment.yaml +++ b/template/deployments/manifests/manifests/deployment.yaml @@ -1,24 +1,24 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{APPNAME}} + name: {{.APPNAME}} labels: - app: {{APPNAME}} - kubernetes.azure.com/generator: {{GENERATORLABEL}} - namespace: {{NAMESPACE}} + app: {{.APPNAME}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} + namespace: {{.NAMESPACE}} spec: replicas: 1 selector: matchLabels: - app: {{APPNAME}} + app: {{.APPNAME}} template: metadata: labels: - app: {{APPNAME}} + app: {{.APPNAME}} spec: containers: - - name: {{APPNAME}} - image: {{IMAGENAME}}:{{IMAGETAG}} + - name: {{.APPNAME}} + image: {{.IMAGENAME}}:{{.IMAGETAG}} imagePullPolicy: Always ports: - - containerPort: {{PORT}} + - containerPort: {{.PORT}} \ No newline at end of file diff --git a/template/deployments/manifests/manifests/service.yaml b/template/deployments/manifests/manifests/service.yaml index e99c7f41..83481aa4 100644 --- a/template/deployments/manifests/manifests/service.yaml +++ b/template/deployments/manifests/manifests/service.yaml @@ -1,15 +1,15 @@ apiVersion: v1 kind: Service metadata: - name: {{APPNAME}} - namespace: {{NAMESPACE}} + name: {{.APPNAME}} + namespace: {{.NAMESPACE}} labels: - kubernetes.azure.com/generator: {{GENERATORLABEL}} + kubernetes.azure.com/generator: {{.GENERATORLABEL}} spec: type: LoadBalancer selector: - app: {{APPNAME}} + app: {{.APPNAME}} ports: - protocol: TCP - port: {{SERVICEPORT}} - targetPort: {{PORT}} \ No newline at end of file + port: {{.SERVICEPORT}} + targetPort: {{.PORT}} \ No newline at end of file