github.com/strongmonkey/helm@v2.7.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 "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 */}} 241 {{- define "<CHARTNAME>.fullname" -}} 242 {{- $name := default .Chart.Name .Values.nameOverride -}} 243 {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 244 {{- end -}} 245 ` 246 247 // CreateFrom creates a new chart, but scaffolds it from the src chart. 248 func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { 249 schart, err := Load(src) 250 if err != nil { 251 return fmt.Errorf("could not load %s: %s", src, err) 252 } 253 254 schart.Metadata = chartfile 255 return SaveDir(schart, dest) 256 } 257 258 // Create creates a new chart in a directory. 259 // 260 // Inside of dir, this will create a directory based on the name of 261 // chartfile.Name. It will then write the Chart.yaml into this directory and 262 // create the (empty) appropriate directories. 263 // 264 // The returned string will point to the newly created directory. It will be 265 // an absolute path, even if the provided base directory was relative. 266 // 267 // If dir does not exist, this will return an error. 268 // If Chart.yaml or any directories cannot be created, this will return an 269 // error. In such a case, this will attempt to clean up by removing the 270 // new chart directory. 271 func Create(chartfile *chart.Metadata, dir string) (string, error) { 272 path, err := filepath.Abs(dir) 273 if err != nil { 274 return path, err 275 } 276 277 if fi, err := os.Stat(path); err != nil { 278 return path, err 279 } else if !fi.IsDir() { 280 return path, fmt.Errorf("no such directory %s", path) 281 } 282 283 n := chartfile.Name 284 cdir := filepath.Join(path, n) 285 if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { 286 return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) 287 } 288 if err := os.MkdirAll(cdir, 0755); err != nil { 289 return cdir, err 290 } 291 292 cf := filepath.Join(cdir, ChartfileName) 293 if _, err := os.Stat(cf); err != nil { 294 if err := SaveChartfile(cf, chartfile); err != nil { 295 return cdir, err 296 } 297 } 298 299 for _, d := range []string{TemplatesDir, ChartsDir} { 300 if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { 301 return cdir, err 302 } 303 } 304 305 files := []struct { 306 path string 307 content []byte 308 }{ 309 { 310 // values.yaml 311 path: filepath.Join(cdir, ValuesfileName), 312 content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), 313 }, 314 { 315 // .helmignore 316 path: filepath.Join(cdir, IgnorefileName), 317 content: []byte(defaultIgnore), 318 }, 319 { 320 // ingress.yaml 321 path: filepath.Join(cdir, TemplatesDir, IngressFileName), 322 content: []byte(strings.Replace(defaultIngress, "<CHARTNAME>", chartfile.Name, -1)), 323 }, 324 { 325 // deployment.yaml 326 path: filepath.Join(cdir, TemplatesDir, DeploymentName), 327 content: []byte(strings.Replace(defaultDeployment, "<CHARTNAME>", chartfile.Name, -1)), 328 }, 329 { 330 // service.yaml 331 path: filepath.Join(cdir, TemplatesDir, ServiceName), 332 content: []byte(strings.Replace(defaultService, "<CHARTNAME>", chartfile.Name, -1)), 333 }, 334 { 335 // NOTES.txt 336 path: filepath.Join(cdir, TemplatesDir, NotesName), 337 content: []byte(strings.Replace(defaultNotes, "<CHARTNAME>", chartfile.Name, -1)), 338 }, 339 { 340 // _helpers.tpl 341 path: filepath.Join(cdir, TemplatesDir, HelpersName), 342 content: []byte(strings.Replace(defaultHelpers, "<CHARTNAME>", chartfile.Name, -1)), 343 }, 344 } 345 346 for _, file := range files { 347 if _, err := os.Stat(file.path); err == nil { 348 // File exists and is okay. Skip it. 349 continue 350 } 351 if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { 352 return cdir, err 353 } 354 } 355 return cdir, nil 356 }