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