github.com/Beeketing/helm@v2.12.1+incompatible/pkg/chartutil/create.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package chartutil 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 25 "k8s.io/helm/pkg/proto/hapi/chart" 26 ) 27 28 const ( 29 // ChartfileName is the default Chart file name. 30 ChartfileName = "Chart.yaml" 31 // ValuesfileName is the default values file name. 32 ValuesfileName = "values.yaml" 33 // TemplatesDir is the relative directory name for templates. 34 TemplatesDir = "templates" 35 // ChartsDir is the relative directory name for charts dependencies. 36 ChartsDir = "charts" 37 // IgnorefileName is the name of the Helm ignore file. 38 IgnorefileName = ".helmignore" 39 // IngressFileName is the name of the example ingress file. 40 IngressFileName = "ingress.yaml" 41 // DeploymentName is the name of the example deployment file. 42 DeploymentName = "deployment.yaml" 43 // ServiceName is the name of the example service file. 44 ServiceName = "service.yaml" 45 // NotesName is the name of the example NOTES.txt file. 46 NotesName = "NOTES.txt" 47 // HelpersName is the name of the example helpers file. 48 HelpersName = "_helpers.tpl" 49 // TemplatesTestsDir is the relative directory name for templates tests. 50 TemplatesTestsDir = "templates/tests" 51 // TestConnectionName is the name of the example connection test file. 52 TestConnectionName = "test-connection.yaml" 53 ) 54 55 const defaultValues = `# Default values for %s. 56 # This is a YAML-formatted file. 57 # Declare variables to be passed into your templates. 58 59 replicaCount: 1 60 61 image: 62 repository: nginx 63 tag: stable 64 pullPolicy: IfNotPresent 65 66 nameOverride: "" 67 fullnameOverride: "" 68 69 service: 70 type: ClusterIP 71 port: 80 72 73 ingress: 74 enabled: false 75 annotations: {} 76 # kubernetes.io/ingress.class: nginx 77 # kubernetes.io/tls-acme: "true" 78 paths: [] 79 hosts: 80 - chart-example.local 81 tls: [] 82 # - secretName: chart-example-tls 83 # hosts: 84 # - chart-example.local 85 86 resources: {} 87 # We usually recommend not to specify default resources and to leave this as a conscious 88 # choice for the user. This also increases chances charts run on environments with little 89 # resources, such as Minikube. If you do want to specify resources, uncomment the following 90 # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 91 # limits: 92 # cpu: 100m 93 # memory: 128Mi 94 # requests: 95 # cpu: 100m 96 # memory: 128Mi 97 98 nodeSelector: {} 99 100 tolerations: [] 101 102 affinity: {} 103 ` 104 105 const defaultIgnore = `# Patterns to ignore when building packages. 106 # This supports shell glob matching, relative path matching, and 107 # negation (prefixed with !). Only one pattern per line. 108 .DS_Store 109 # Common VCS dirs 110 .git/ 111 .gitignore 112 .bzr/ 113 .bzrignore 114 .hg/ 115 .hgignore 116 .svn/ 117 # Common backup files 118 *.swp 119 *.bak 120 *.tmp 121 *~ 122 # Various IDEs 123 .project 124 .idea/ 125 *.tmproj 126 .vscode/ 127 ` 128 129 const defaultIngress = `{{- if .Values.ingress.enabled -}} 130 {{- $fullName := include "<CHARTNAME>.fullname" . -}} 131 {{- $ingressPaths := .Values.ingress.paths -}} 132 apiVersion: extensions/v1beta1 133 kind: Ingress 134 metadata: 135 name: {{ $fullName }} 136 labels: 137 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 138 helm.sh/chart: {{ include "<CHARTNAME>.chart" . }} 139 app.kubernetes.io/instance: {{ .Release.Name }} 140 app.kubernetes.io/managed-by: {{ .Release.Service }} 141 {{- with .Values.ingress.annotations }} 142 annotations: 143 {{- toYaml . | nindent 4 }} 144 {{- end }} 145 spec: 146 {{- if .Values.ingress.tls }} 147 tls: 148 {{- range .Values.ingress.tls }} 149 - hosts: 150 {{- range .hosts }} 151 - {{ . | quote }} 152 {{- end }} 153 secretName: {{ .secretName }} 154 {{- end }} 155 {{- end }} 156 rules: 157 {{- range .Values.ingress.hosts }} 158 - host: {{ . | quote }} 159 http: 160 paths: 161 {{- range $ingressPaths }} 162 - path: {{ . }} 163 backend: 164 serviceName: {{ $fullName }} 165 servicePort: http 166 {{- end }} 167 {{- end }} 168 {{- end }} 169 ` 170 171 const defaultDeployment = `apiVersion: apps/v1 172 kind: Deployment 173 metadata: 174 name: {{ include "<CHARTNAME>.fullname" . }} 175 labels: 176 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 177 helm.sh/chart: {{ include "<CHARTNAME>.chart" . }} 178 app.kubernetes.io/instance: {{ .Release.Name }} 179 app.kubernetes.io/managed-by: {{ .Release.Service }} 180 spec: 181 replicas: {{ .Values.replicaCount }} 182 selector: 183 matchLabels: 184 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 185 app.kubernetes.io/instance: {{ .Release.Name }} 186 template: 187 metadata: 188 labels: 189 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 190 app.kubernetes.io/instance: {{ .Release.Name }} 191 spec: 192 containers: 193 - name: {{ .Chart.Name }} 194 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 195 imagePullPolicy: {{ .Values.image.pullPolicy }} 196 ports: 197 - name: http 198 containerPort: 80 199 protocol: TCP 200 livenessProbe: 201 httpGet: 202 path: / 203 port: http 204 readinessProbe: 205 httpGet: 206 path: / 207 port: http 208 resources: 209 {{- toYaml .Values.resources | nindent 12 }} 210 {{- with .Values.nodeSelector }} 211 nodeSelector: 212 {{- toYaml . | nindent 8 }} 213 {{- end }} 214 {{- with .Values.affinity }} 215 affinity: 216 {{- toYaml . | nindent 8 }} 217 {{- end }} 218 {{- with .Values.tolerations }} 219 tolerations: 220 {{- toYaml . | nindent 8 }} 221 {{- end }} 222 ` 223 224 const defaultService = `apiVersion: v1 225 kind: Service 226 metadata: 227 name: {{ include "<CHARTNAME>.fullname" . }} 228 labels: 229 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 230 helm.sh/chart: {{ include "<CHARTNAME>.chart" . }} 231 app.kubernetes.io/instance: {{ .Release.Name }} 232 app.kubernetes.io/managed-by: {{ .Release.Service }} 233 spec: 234 type: {{ .Values.service.type }} 235 ports: 236 - port: {{ .Values.service.port }} 237 targetPort: http 238 protocol: TCP 239 name: http 240 selector: 241 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 242 app.kubernetes.io/instance: {{ .Release.Name }} 243 ` 244 245 const defaultNotes = `1. Get the application URL by running these commands: 246 {{- if .Values.ingress.enabled }} 247 {{- range $host := .Values.ingress.hosts }} 248 {{- range $.Values.ingress.paths }} 249 http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ . }} 250 {{- end }} 251 {{- end }} 252 {{- else if contains "NodePort" .Values.service.type }} 253 export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "<CHARTNAME>.fullname" . }}) 254 export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 255 echo http://$NODE_IP:$NODE_PORT 256 {{- else if contains "LoadBalancer" .Values.service.type }} 257 NOTE: It may take a few minutes for the LoadBalancer IP to be available. 258 You can watch the status of by running 'kubectl get svc -w {{ include "<CHARTNAME>.fullname" . }}' 259 export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "<CHARTNAME>.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 260 echo http://$SERVICE_IP:{{ .Values.service.port }} 261 {{- else if contains "ClusterIP" .Values.service.type }} 262 export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "<CHARTNAME>.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 263 echo "Visit http://127.0.0.1:8080 to use your application" 264 kubectl port-forward $POD_NAME 8080:80 265 {{- end }} 266 ` 267 268 const defaultHelpers = `{{/* vim: set filetype=mustache: */}} 269 {{/* 270 Expand the name of the chart. 271 */}} 272 {{- define "<CHARTNAME>.name" -}} 273 {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 274 {{- end -}} 275 276 {{/* 277 Create a default fully qualified app name. 278 We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 279 If release name contains chart name it will be used as a full name. 280 */}} 281 {{- define "<CHARTNAME>.fullname" -}} 282 {{- if .Values.fullnameOverride -}} 283 {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 284 {{- else -}} 285 {{- $name := default .Chart.Name .Values.nameOverride -}} 286 {{- if contains $name .Release.Name -}} 287 {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 288 {{- else -}} 289 {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 290 {{- end -}} 291 {{- end -}} 292 {{- end -}} 293 294 {{/* 295 Create chart name and version as used by the chart label. 296 */}} 297 {{- define "<CHARTNAME>.chart" -}} 298 {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 299 {{- end -}} 300 ` 301 302 const defaultTestConnection = `apiVersion: v1 303 kind: Pod 304 metadata: 305 name: "{{ include "<CHARTNAME>.fullname" . }}-test-connection" 306 labels: 307 app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} 308 helm.sh/chart: {{ include "<CHARTNAME>.chart" . }} 309 app.kubernetes.io/instance: {{ .Release.Name }} 310 app.kubernetes.io/managed-by: {{ .Release.Service }} 311 annotations: 312 "helm.sh/hook": test-success 313 spec: 314 containers: 315 - name: wget 316 image: busybox 317 command: ['wget'] 318 args: ['{{ include "<CHARTNAME>.fullname" . }}:{{ .Values.service.port }}'] 319 restartPolicy: Never 320 ` 321 322 // CreateFrom creates a new chart, but scaffolds it from the src chart. 323 func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { 324 schart, err := Load(src) 325 if err != nil { 326 return fmt.Errorf("could not load %s: %s", src, err) 327 } 328 329 schart.Metadata = chartfile 330 331 var updatedTemplates []*chart.Template 332 333 for _, template := range schart.Templates { 334 newData := Transform(string(template.Data), "<CHARTNAME>", schart.Metadata.Name) 335 updatedTemplates = append(updatedTemplates, &chart.Template{Name: template.Name, Data: newData}) 336 } 337 338 schart.Templates = updatedTemplates 339 if schart.Values != nil { 340 schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "<CHARTNAME>", schart.Metadata.Name))} 341 } 342 return SaveDir(schart, dest) 343 } 344 345 // Create creates a new chart in a directory. 346 // 347 // Inside of dir, this will create a directory based on the name of 348 // chartfile.Name. It will then write the Chart.yaml into this directory and 349 // create the (empty) appropriate directories. 350 // 351 // The returned string will point to the newly created directory. It will be 352 // an absolute path, even if the provided base directory was relative. 353 // 354 // If dir does not exist, this will return an error. 355 // If Chart.yaml or any directories cannot be created, this will return an 356 // error. In such a case, this will attempt to clean up by removing the 357 // new chart directory. 358 func Create(chartfile *chart.Metadata, dir string) (string, error) { 359 path, err := filepath.Abs(dir) 360 if err != nil { 361 return path, err 362 } 363 364 if fi, err := os.Stat(path); err != nil { 365 return path, err 366 } else if !fi.IsDir() { 367 return path, fmt.Errorf("no such directory %s", path) 368 } 369 370 n := chartfile.Name 371 cdir := filepath.Join(path, n) 372 if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { 373 return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) 374 } 375 if err := os.MkdirAll(cdir, 0755); err != nil { 376 return cdir, err 377 } 378 379 cf := filepath.Join(cdir, ChartfileName) 380 if _, err := os.Stat(cf); err != nil { 381 if err := SaveChartfile(cf, chartfile); err != nil { 382 return cdir, err 383 } 384 } 385 386 for _, d := range []string{TemplatesDir, TemplatesTestsDir, ChartsDir} { 387 if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { 388 return cdir, err 389 } 390 } 391 392 files := []struct { 393 path string 394 content []byte 395 }{ 396 { 397 // values.yaml 398 path: filepath.Join(cdir, ValuesfileName), 399 content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), 400 }, 401 { 402 // .helmignore 403 path: filepath.Join(cdir, IgnorefileName), 404 content: []byte(defaultIgnore), 405 }, 406 { 407 // ingress.yaml 408 path: filepath.Join(cdir, TemplatesDir, IngressFileName), 409 content: Transform(defaultIngress, "<CHARTNAME>", chartfile.Name), 410 }, 411 { 412 // deployment.yaml 413 path: filepath.Join(cdir, TemplatesDir, DeploymentName), 414 content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name), 415 }, 416 { 417 // service.yaml 418 path: filepath.Join(cdir, TemplatesDir, ServiceName), 419 content: Transform(defaultService, "<CHARTNAME>", chartfile.Name), 420 }, 421 { 422 // NOTES.txt 423 path: filepath.Join(cdir, TemplatesDir, NotesName), 424 content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name), 425 }, 426 { 427 // _helpers.tpl 428 path: filepath.Join(cdir, TemplatesDir, HelpersName), 429 content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name), 430 }, 431 { 432 // test-connection.yaml 433 path: filepath.Join(cdir, TemplatesTestsDir, TestConnectionName), 434 content: Transform(defaultTestConnection, "<CHARTNAME>", chartfile.Name), 435 }, 436 } 437 438 for _, file := range files { 439 if _, err := os.Stat(file.path); err == nil { 440 // File exists and is okay. Skip it. 441 continue 442 } 443 if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { 444 return cdir, err 445 } 446 } 447 return cdir, nil 448 }