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