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