diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1afa27..4d4eaf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ jobs: go-version: ^1.20 - name: Checkout uses: actions/checkout@v2 + - name: Run golangci-lint uses: golangci/golangci-lint-action@v3 with: @@ -43,3 +44,12 @@ jobs: with: name: build path: iits-chart-creator + + - name: Set up Helm + uses: azure/setup-helm@v1 + with: + version: 'v3.7.1' # setup Helm version explicitly if necessary + + - name: Validate Helm Chart + run: | + helm template blueprints --set deployment.image.repository=myrepo --set deployment.image.tag=mytag \ No newline at end of file diff --git a/README.md b/README.md index 9efc72c..ab03834 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,55 @@ `iits-chart-creator` is a powerful cli helm tool designed to streamline and automate the process of creating and managing Helm charts for iits projects. -Current features: -- ingress -- _helpers.tpl +```shell +Available Commands: + deployment Creates a deployment deployment file + helpers.tpl Creates a helpers.tpl deployment file + ingress Creates a Ingress YAML deployment file + service Creates a service deployment file + service-monitor Creates a service-monitor deployment file + serviceaccount Creates a serviceaccount deployment file +``` + + +## Install -## Usage ```shell helm plugin install https://github.com/iits-consulting/iits-chart-creator #We would recommend to put this also into your .bash_aliases -alias updateChartCreator="helm plugin update helm-chart-creator && helm iits-chart-creator -v" +alias updateCharter="helm plugin update iits-chart-creator && helm iits-chart-creator -v" +alias charter='helm iits-chart-creator infrastructure-charts' +``` + +### Local development +If you want to test it out locally execute the following + +```shell +go build . && mv iits-chart-creator binaries/iits-chart-creator_linux_amd64_v1/ && helm plugin rm iits-chart-creator && helm plugin install . +``` + +## Usage + +```shell +charter + +Creates Infrastructure Charts + +Usage: + iits-chart-creator infrastructure-charts [command] + +Available Commands: + deployment Creates a deployment deployment file + helpers.tpl Creates a helpers.tpl deployment file + ingress Creates a Ingress YAML deployment file + service Creates a service deployment file + service-monitor Creates a service-monitor deployment file + serviceaccount Creates a serviceaccount deployment file + +Flags: + -h, --help help for infrastructure-charts + +Use "iits-chart-creator infrastructure-charts [command] --help" for more information about a command. ``` \ No newline at end of file diff --git a/blueprints/Chart.yaml b/blueprints/Chart.yaml new file mode 100644 index 0000000..b666fe2 --- /dev/null +++ b/blueprints/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: blueprints +type: application +version: 1.4.2 \ No newline at end of file diff --git a/infrastructure-charts/blueprints/_helpers.tpl b/blueprints/templates/infrastructure-charts/_helpers.tpl similarity index 82% rename from infrastructure-charts/blueprints/_helpers.tpl rename to blueprints/templates/infrastructure-charts/_helpers.tpl index c428e84..b04750d 100644 --- a/infrastructure-charts/blueprints/_helpers.tpl +++ b/blueprints/templates/infrastructure-charts/_helpers.tpl @@ -48,3 +48,14 @@ Selector labels {{- define "myService.selectorLabels" -}} app: {{ include "myService.name" . }} {{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "myService.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "myService.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/deployment-values.yaml b/blueprints/templates/infrastructure-charts/deployment-values.yaml new file mode 100644 index 0000000..8c75088 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/deployment-values.yaml @@ -0,0 +1,58 @@ +deployment: + replicaCount: "2" + + image: + repository: #REPLACE_ME + pullPolicy: IfNotPresent + tag: #REPLACE_ME + + imagePullSecrets: [ ] + nameOverride: "" + fullnameOverride: "" + + podAnnotations: { } + + podSecurityContext: + # fsGroup: 2000 + + securityContext: + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + volumes: { } + volumeMounts: { } + + affinity: { } + + ports: + http: + + health: + startupProbe: + path: "/" + # initialDelaySeconds: # defaults to 20 + # periodSeconds: # defaults to 20 + # failureThreshold: # defaults to 3 + # port: # defaults to 80 + liveness: + path: "/" + # initialDelaySeconds: # defaults to 20 + # periodSeconds: # defaults to 20 + # failureThreshold: # defaults to 3 + # port: # defaults to 80 + readiness: + path: "/" + # initialDelaySeconds: # defaults to 20 + # periodSeconds: # defaults to 20 + # failureThreshold: # defaults to 3 + # port: # defaults to 80 + + env: + MUH: "KUH" + + #set the secret where to take env from + envFromSecret: \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/deployment.yaml b/blueprints/templates/infrastructure-charts/deployment.yaml new file mode 100644 index 0000000..b221f7a --- /dev/null +++ b/blueprints/templates/infrastructure-charts/deployment.yaml @@ -0,0 +1,106 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "myService.fullname" . }} + labels: + {{- include "myService.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.deployment.replicaCount }} + revisionHistoryLimit: 3 + selector: + matchLabels: + {{- include "myService.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.deployment.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "myService.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.deployment.onePodForEachNode}} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: "app" + operator: In + values: + - {{ include "myService.fullname" . }} + topologyKey: "kubernetes.io/hostname" + {{- end}} + {{- with .Values.deployment.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "myService.serviceAccountName" . }} + {{- if .Values.deployment.podSecurityContext }} + securityContext: + {{- toYaml .Values.deployment.podSecurityContext | nindent 8 }} + {{- end }} + containers: + - name: {{ .Release.Name }} + {{- if .Values.deployment.securityContext }} + securityContext: + {{- toYaml .Values.deployment.securityContext | nindent 12 }} + {{- end }} + image: "{{ tpl .Values.deployment.image.repository . }}:{{ .Values.deployment.image.tag }}" + imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} + env: + {{- range $key, $value := .Values.deployment.env }} + - name: {{ printf "%s" $key | replace "." "_" | upper | quote }} + value: {{ tpl ($value | toString) $ | quote }} + {{- end }} + {{- if .Values.deployment.envSecretName}} + envFrom: + - secretRef: + name: {{.Values.deployment.envSecretName}} + {{- end }} + ports: + {{- range $name,$values := .Values.deployment.ports }} + - name: {{$name }} + protocol: {{ ($values).protocol | default "TCP"}} + containerPort: {{($values).port | default 80 }} + {{- end }} + {{- with ((.Values.deployment).health).liveness }} + livenessProbe: + httpGet: + path: {{ tpl .path $ | default "/" }} + port: {{ .port | default 80 }} + initialDelaySeconds: {{ .initialDelaySeconds | default 20 }} + periodSeconds: 5 + failureThreshold: {{ .failureThreshold | default 5 }} + {{- end }} + {{- with ((.Values.deployment).health).readiness }} + readinessProbe: + httpGet: + path: {{ tpl .path $ | default "/" }} + port: {{ .port | default 80 }} + initialDelaySeconds: {{ .initialDelaySeconds | default 20 }} + periodSeconds: 5 + failureThreshold: {{ .failureThreshold | default 5 }} + {{- end }} + resources: + {{- toYaml .Values.deployment.resources | nindent 12 }} + {{- with .Values.deployment.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/infrastructure-charts/blueprints/ingress-values.yaml b/blueprints/templates/infrastructure-charts/ingress-values.yaml similarity index 100% rename from infrastructure-charts/blueprints/ingress-values.yaml rename to blueprints/templates/infrastructure-charts/ingress-values.yaml diff --git a/infrastructure-charts/blueprints/ingress.yaml b/blueprints/templates/infrastructure-charts/ingress.yaml similarity index 100% rename from infrastructure-charts/blueprints/ingress.yaml rename to blueprints/templates/infrastructure-charts/ingress.yaml diff --git a/blueprints/templates/infrastructure-charts/service-monitor-values.yaml b/blueprints/templates/infrastructure-charts/service-monitor-values.yaml new file mode 100644 index 0000000..ae20ee7 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/service-monitor-values.yaml @@ -0,0 +1,8 @@ +serviceMonitor: + enabled: true + # Default values, override them if necessary + #labels: {} + #annotations: {} + #portName: "http" + #interval: "10s" + #path: "/metrics" \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/service-monitor.yaml b/blueprints/templates/infrastructure-charts/service-monitor.yaml new file mode 100644 index 0000000..6632abc --- /dev/null +++ b/blueprints/templates/infrastructure-charts/service-monitor.yaml @@ -0,0 +1,22 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "myService.fullname" $ }} + {{- with .Values.serviceMonitor.labels }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "myService.selectorLabels" . | nindent 6 }} + endpoints: + - port: {{ default "http" .Values.serviceMonitor.portName }} + interval: {{ default "10s" .Values.serviceMonitor.interval }} + path: {{ default "/metrics" .Values.serviceMonitor.path }} +{{- end }} \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/service-values.yaml b/blueprints/templates/infrastructure-charts/service-values.yaml new file mode 100644 index 0000000..e5f5461 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/service-values.yaml @@ -0,0 +1,9 @@ +service: + ports: + http: + #These values are the default values, override them if necessary + #targetPort: 80 + ## port name from above + #name: "http" + #protocol: TCP + #port: 80 \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/service.yaml b/blueprints/templates/infrastructure-charts/service.yaml new file mode 100644 index 0000000..6ea0672 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "myService.fullname" . }} + labels: + {{- include "myService.labels" . | nindent 4 }} +spec: + type: {{ (.Values.service).serviceType | default "ClusterIP"}} + ports: + {{- range $name,$values := .Values.service.ports }} + - targetPort: {{ ($values).targetPort | default 80 }} + name: {{ ($values).name | default $name }} + protocol: {{ ($values).protocol | default "TCP"}} + port: {{($values).port | default 80 }} + {{- end }} + selector: + {{- include "myService.selectorLabels" . | nindent 4 }} diff --git a/blueprints/templates/infrastructure-charts/serviceaccount-values.yaml b/blueprints/templates/infrastructure-charts/serviceaccount-values.yaml new file mode 100644 index 0000000..8f845e4 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/serviceaccount-values.yaml @@ -0,0 +1,8 @@ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" \ No newline at end of file diff --git a/blueprints/templates/infrastructure-charts/serviceaccount.yaml b/blueprints/templates/infrastructure-charts/serviceaccount.yaml new file mode 100644 index 0000000..26862c8 --- /dev/null +++ b/blueprints/templates/infrastructure-charts/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "myService.serviceAccountName" . }} + labels: + {{- include "myService.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/blueprints/values.yaml b/blueprints/values.yaml new file mode 100644 index 0000000..68f08c8 --- /dev/null +++ b/blueprints/values.yaml @@ -0,0 +1,110 @@ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +ingress: + enabled: true + host: #REPLACE_ME + annotations: + cert-manager.io/cluster-issuer: letsencrypt + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + # Creates default Ingress with tls and the given host from .Values.ingress.host + defaultIngress: + enabled: true + ## Default values can be overriden + #tls: + # secretName: "myService.fullname" + #path: "/" + #pathType: "Prefix" + #backend: + # name: "myService.fullname" #tpl supported + # port: + # name: "myService.fullname" #tpl supported + + ## If you don't want to use the default Ingress, you can define it here on your own + #tls: + # - hosts: + # - "{{.Values.ingress.host}}" + # secretName: "myService-cert" + #hosts: + # - host: "{{.Values.ingress.host}}" + # paths: + # - path: "/" + # backend: + # name: "{{include "myService.fullname" $}}" + # port: + # name: "{{include "myService.fullname" $}}" + +deployment: + replicaCount: "2" + + image: + repository: #REPLACE_ME + pullPolicy: IfNotPresent + tag: #REPLACE_ME + + imagePullSecrets: [ ] + nameOverride: "" + fullnameOverride: "" + + podAnnotations: { } + + podSecurityContext: + # fsGroup: 2000 + + securityContext: + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + volumes: { } + volumeMounts: { } + + affinity: { } + + ports: + http: + + health: + liveness: + path: "/" + initialDelaySeconds: 20 + failureThreshold: 3 + readiness: + path: "/" + initialDelaySeconds: 20 + failureThreshold: 3 + + env: + MUH: "KUH" + + #set the secret where to take env from + envFromSecret: + +service: + ports: + http: + #These values are the default values, override them if necessary + #targetPort: 80 + ## port name from above + #name: "http" + #protocol: TCP + #port: 80 + +serviceMonitor: + enabled: true + # Default values, override them if necessary + #labels: {} + #annotations: {} + #portName: "http" + #interval: "10s" + #path: "/metrics" \ No newline at end of file diff --git a/kubernetes-resource-manager.go b/kubernetes-resource-manager.go index 5658882..3e53ab2 100644 --- a/kubernetes-resource-manager.go +++ b/kubernetes-resource-manager.go @@ -7,6 +7,8 @@ import ( "path/filepath" ) +const bluePrintPath = "blueprints/templates" + func appendToFile(source, destination string) error { // Read the source file srcFile, err := os.ReadFile(source) @@ -41,10 +43,10 @@ func appendToFile(source, destination string) error { return nil } -func mergeValuesFiles(cmd *cobra.Command) error { +func mergeValuesFiles(cmd *cobra.Command, chartType string) error { folderName, _ := getCurrentFolderName() pluginDir := os.Getenv("HELM_PLUGIN_DIR") - valuesSource := filepath.Join(pluginDir, "infrastructure-charts", "blueprints", cmd.Name()+"-values.yaml") + valuesSource := filepath.Join(pluginDir, bluePrintPath, chartType, cmd.Name()+"-values.yaml") if _, err := os.Stat(valuesSource); os.IsNotExist(err) { return err @@ -68,10 +70,10 @@ func mergeValuesFiles(cmd *cobra.Command) error { } // checkAndCreateResource checks files and creates the required resources -func checkAndCreateResource(cmd *cobra.Command, resource string) { +func checkAndCreateResource(cmd *cobra.Command, kubernetesResourceName string, chartType string) { err := checkFileExist("./Chart.yaml") if err != nil { - fmt.Println("You can only create a resource if you are inside a helm chart.") + fmt.Println("You can only create a kubernetesResourceName if you are inside a helm chart.") return } folderName, err := getCurrentFolderName() @@ -80,16 +82,16 @@ func checkAndCreateResource(cmd *cobra.Command, resource string) { return } pluginDir := os.Getenv("HELM_PLUGIN_DIR") - sourcePath := filepath.Join(pluginDir, "infrastructure-charts", "blueprints", resource) - destinationPath := fmt.Sprintf("./templates/%s", resource) + sourcePath := filepath.Join(pluginDir, bluePrintPath, chartType, kubernetesResourceName) + destinationPath := fmt.Sprintf("./templates/%s", kubernetesResourceName) err = ReplaceInFile(sourcePath, "myService", folderName, destinationPath) if err != nil { - fmt.Printf("Error creating %s file: %v\n", resource, err) + fmt.Printf("Error creating %s file: %v\n", kubernetesResourceName, err) return } - fmt.Printf("%s file created successfully.\n", resource) + fmt.Printf("%s file created successfully.\n", kubernetesResourceName) - err2 := mergeValuesFiles(cmd) + err2 := mergeValuesFiles(cmd, chartType) if err2 != nil { fmt.Printf("Error merging values files: %v\n", err2) return diff --git a/main.go b/main.go index 6c62f72..771f1bd 100644 --- a/main.go +++ b/main.go @@ -5,19 +5,28 @@ import ( ) func main() { + cmdInfrastructureCharts := createInfrastructureChartsCMD() + var rootCmd = &cobra.Command{ + Use: "iits-chart-creator", + } + rootCmd.AddCommand(cmdInfrastructureCharts) + cobra.CheckErr(rootCmd.Execute()) +} + +func createInfrastructureChartsCMD() *cobra.Command { var cmdCreateIngress = &cobra.Command{ Use: "ingress", - Short: "Creates a Ingress YAML file", + Short: "Creates a Ingress YAML deployment file", Run: func(cmd *cobra.Command, args []string) { - checkAndCreateResource(cmd, "ingress.yaml") + checkAndCreateResource(cmd, "ingress.yaml", "infrastructure-charts") }, } var cmdCreateHelpers = &cobra.Command{ Use: "helpers.tpl", - Short: "Creates a helpers.tpl file", + Short: "Creates a helpers.tpl deployment file", Run: func(cmd *cobra.Command, args []string) { - checkAndCreateResource(cmd, "_helpers.tpl") + checkAndCreateResource(cmd, "_helpers.tpl", "infrastructure-charts") }, } @@ -26,10 +35,43 @@ func main() { Short: "Creates Infrastructure Charts", } + var deploymentCreateHelpers = &cobra.Command{ + Use: "deployment", + Short: "Creates a deployment deployment file", + Run: func(cmd *cobra.Command, args []string) { + checkAndCreateResource(cmd, "deployment.yaml", "infrastructure-charts") + }, + } + + var serviceCreateHelpers = &cobra.Command{ + Use: "service", + Short: "Creates a service deployment file", + Run: func(cmd *cobra.Command, args []string) { + checkAndCreateResource(cmd, "service.yaml", "infrastructure-charts") + }, + } + + var saCreateHelpers = &cobra.Command{ + Use: "serviceaccount", + Short: "Creates a serviceaccount deployment file", + Run: func(cmd *cobra.Command, args []string) { + checkAndCreateResource(cmd, "serviceaccount.yaml", "infrastructure-charts") + }, + } + + var serviceMonitorCreateHelpers = &cobra.Command{ + Use: "service-monitor", + Short: "Creates a service-monitor deployment file", + Run: func(cmd *cobra.Command, args []string) { + checkAndCreateResource(cmd, "service-monitor.yaml", "infrastructure-charts") + }, + } + cmdInfrastructureCharts.AddCommand(cmdCreateIngress) cmdInfrastructureCharts.AddCommand(cmdCreateHelpers) - - var rootCmd = &cobra.Command{Use: "helm iits-chart-creator"} - rootCmd.AddCommand(cmdInfrastructureCharts) - cobra.CheckErr(rootCmd.Execute()) + cmdInfrastructureCharts.AddCommand(saCreateHelpers) + cmdInfrastructureCharts.AddCommand(serviceCreateHelpers) + cmdInfrastructureCharts.AddCommand(deploymentCreateHelpers) + cmdInfrastructureCharts.AddCommand(serviceMonitorCreateHelpers) + return cmdInfrastructureCharts }