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