github.com/vtuson/helm@v2.8.2+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 {{- $servicePort := .Values.service.port -}} 124 {{- $ingressPath := .Values.ingress.path -}} 125 apiVersion: extensions/v1beta1 126 kind: Ingress 127 metadata: 128 name: {{ $fullName }} 129 labels: 130 app: {{ template "<CHARTNAME>.name" . }} 131 chart: {{ template "<CHARTNAME>.chart" . }} 132 release: {{ .Release.Name }} 133 heritage: {{ .Release.Service }} 134 {{- with .Values.ingress.annotations }} 135 annotations: 136 {{ toYaml . | indent 4 }} 137 {{- end }} 138 spec: 139 {{- if .Values.ingress.tls }} 140 tls: 141 {{- range .Values.ingress.tls }} 142 - hosts: 143 {{- range .hosts }} 144 - {{ . }} 145 {{- end }} 146 secretName: {{ .secretName }} 147 {{- end }} 148 {{- end }} 149 rules: 150 {{- range .Values.ingress.hosts }} 151 - host: {{ . }} 152 http: 153 paths: 154 - path: {{ $ingressPath }} 155 backend: 156 serviceName: {{ $fullName }} 157 servicePort: http 158 {{- end }} 159 {{- end }} 160 ` 161 162 const defaultDeployment = `apiVersion: apps/v1beta2 163 kind: Deployment 164 metadata: 165 name: {{ template "<CHARTNAME>.fullname" . }} 166 labels: 167 app: {{ template "<CHARTNAME>.name" . }} 168 chart: {{ template "<CHARTNAME>.chart" . }} 169 release: {{ .Release.Name }} 170 heritage: {{ .Release.Service }} 171 spec: 172 replicas: {{ .Values.replicaCount }} 173 selector: 174 matchLabels: 175 app: {{ template "<CHARTNAME>.name" . }} 176 release: {{ .Release.Name }} 177 template: 178 metadata: 179 labels: 180 app: {{ template "<CHARTNAME>.name" . }} 181 release: {{ .Release.Name }} 182 spec: 183 containers: 184 - name: {{ .Chart.Name }} 185 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 186 imagePullPolicy: {{ .Values.image.pullPolicy }} 187 ports: 188 - name: http 189 containerPort: 80 190 protocol: TCP 191 livenessProbe: 192 httpGet: 193 path: / 194 port: http 195 readinessProbe: 196 httpGet: 197 path: / 198 port: http 199 resources: 200 {{ toYaml .Values.resources | indent 12 }} 201 {{- with .Values.nodeSelector }} 202 nodeSelector: 203 {{ toYaml . | indent 8 }} 204 {{- end }} 205 {{- with .Values.affinity }} 206 affinity: 207 {{ toYaml . | indent 8 }} 208 {{- end }} 209 {{- with .Values.tolerations }} 210 tolerations: 211 {{ toYaml . | indent 8 }} 212 {{- end }} 213 ` 214 215 const defaultService = `apiVersion: v1 216 kind: Service 217 metadata: 218 name: {{ template "<CHARTNAME>.fullname" . }} 219 labels: 220 app: {{ template "<CHARTNAME>.name" . }} 221 chart: {{ template "<CHARTNAME>.chart" . }} 222 release: {{ .Release.Name }} 223 heritage: {{ .Release.Service }} 224 spec: 225 type: {{ .Values.service.type }} 226 ports: 227 - port: {{ .Values.service.port }} 228 targetPort: http 229 protocol: TCP 230 name: http 231 selector: 232 app: {{ template "<CHARTNAME>.name" . }} 233 release: {{ .Release.Name }} 234 ` 235 236 const defaultNotes = `1. Get the application URL by running these commands: 237 {{- if .Values.ingress.enabled }} 238 {{- range .Values.ingress.hosts }} 239 http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} 240 {{- end }} 241 {{- else if contains "NodePort" .Values.service.type }} 242 export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "<CHARTNAME>.fullname" . }}) 243 export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 244 echo http://$NODE_IP:$NODE_PORT 245 {{- else if contains "LoadBalancer" .Values.service.type }} 246 NOTE: It may take a few minutes for the LoadBalancer IP to be available. 247 You can watch the status of by running 'kubectl get svc -w {{ template "<CHARTNAME>.fullname" . }}' 248 export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "<CHARTNAME>.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 249 echo http://$SERVICE_IP:{{ .Values.service.port }} 250 {{- else if contains "ClusterIP" .Values.service.type }} 251 export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "<CHARTNAME>.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 252 echo "Visit http://127.0.0.1:8080 to use your application" 253 kubectl port-forward $POD_NAME 8080:80 254 {{- end }} 255 ` 256 257 const defaultHelpers = `{{/* vim: set filetype=mustache: */}} 258 {{/* 259 Expand the name of the chart. 260 */}} 261 {{- define "<CHARTNAME>.name" -}} 262 {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 263 {{- end -}} 264 265 {{/* 266 Create a default fully qualified app name. 267 We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 268 If release name contains chart name it will be used as a full name. 269 */}} 270 {{- define "<CHARTNAME>.fullname" -}} 271 {{- if .Values.fullnameOverride -}} 272 {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 273 {{- else -}} 274 {{- $name := default .Chart.Name .Values.nameOverride -}} 275 {{- if contains $name .Release.Name -}} 276 {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 277 {{- else -}} 278 {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 279 {{- end -}} 280 {{- end -}} 281 {{- end -}} 282 283 {{/* 284 Create chart name and version as used by the chart label. 285 */}} 286 {{- define "<CHARTNAME>.chart" -}} 287 {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 288 {{- end -}} 289 ` 290 291 // CreateFrom creates a new chart, but scaffolds it from the src chart. 292 func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { 293 schart, err := Load(src) 294 if err != nil { 295 return fmt.Errorf("could not load %s: %s", src, err) 296 } 297 298 schart.Metadata = chartfile 299 300 var updatedTemplates []*chart.Template 301 302 for _, template := range schart.Templates { 303 newData := Transform(string(template.Data), "<CHARTNAME>", schart.Metadata.Name) 304 updatedTemplates = append(updatedTemplates, &chart.Template{Name: template.Name, Data: newData}) 305 } 306 307 schart.Templates = updatedTemplates 308 schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "<CHARTNAME>", schart.Metadata.Name))} 309 310 return SaveDir(schart, dest) 311 } 312 313 // Create creates a new chart in a directory. 314 // 315 // Inside of dir, this will create a directory based on the name of 316 // chartfile.Name. It will then write the Chart.yaml into this directory and 317 // create the (empty) appropriate directories. 318 // 319 // The returned string will point to the newly created directory. It will be 320 // an absolute path, even if the provided base directory was relative. 321 // 322 // If dir does not exist, this will return an error. 323 // If Chart.yaml or any directories cannot be created, this will return an 324 // error. In such a case, this will attempt to clean up by removing the 325 // new chart directory. 326 func Create(chartfile *chart.Metadata, dir string) (string, error) { 327 path, err := filepath.Abs(dir) 328 if err != nil { 329 return path, err 330 } 331 332 if fi, err := os.Stat(path); err != nil { 333 return path, err 334 } else if !fi.IsDir() { 335 return path, fmt.Errorf("no such directory %s", path) 336 } 337 338 n := chartfile.Name 339 cdir := filepath.Join(path, n) 340 if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { 341 return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) 342 } 343 if err := os.MkdirAll(cdir, 0755); err != nil { 344 return cdir, err 345 } 346 347 cf := filepath.Join(cdir, ChartfileName) 348 if _, err := os.Stat(cf); err != nil { 349 if err := SaveChartfile(cf, chartfile); err != nil { 350 return cdir, err 351 } 352 } 353 354 for _, d := range []string{TemplatesDir, ChartsDir} { 355 if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { 356 return cdir, err 357 } 358 } 359 360 files := []struct { 361 path string 362 content []byte 363 }{ 364 { 365 // values.yaml 366 path: filepath.Join(cdir, ValuesfileName), 367 content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), 368 }, 369 { 370 // .helmignore 371 path: filepath.Join(cdir, IgnorefileName), 372 content: []byte(defaultIgnore), 373 }, 374 { 375 // ingress.yaml 376 path: filepath.Join(cdir, TemplatesDir, IngressFileName), 377 content: Transform(defaultIngress, "<CHARTNAME>", chartfile.Name), 378 }, 379 { 380 // deployment.yaml 381 path: filepath.Join(cdir, TemplatesDir, DeploymentName), 382 content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name), 383 }, 384 { 385 // service.yaml 386 path: filepath.Join(cdir, TemplatesDir, ServiceName), 387 content: Transform(defaultService, "<CHARTNAME>", chartfile.Name), 388 }, 389 { 390 // NOTES.txt 391 path: filepath.Join(cdir, TemplatesDir, NotesName), 392 content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name), 393 }, 394 { 395 // _helpers.tpl 396 path: filepath.Join(cdir, TemplatesDir, HelpersName), 397 content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name), 398 }, 399 } 400 401 for _, file := range files { 402 if _, err := os.Stat(file.path); err == nil { 403 // File exists and is okay. Skip it. 404 continue 405 } 406 if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { 407 return cdir, err 408 } 409 } 410 return cdir, nil 411 }