github.com/caicloud/helm@v2.5.0+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 Ingress record (should used with service.type: ClusterIP). 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.hostname }} 209 http://{{- .Values.ingress.hostname }} 210 {{- else if contains "NodePort" .Values.service.type }} 211 export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) 212 export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 213 echo http://$NODE_IP:$NODE_PORT 214 {{- else if contains "LoadBalancer" .Values.service.type }} 215 NOTE: It may take a few minutes for the LoadBalancer IP to be available. 216 You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' 217 export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 218 echo http://$SERVICE_IP:{{ .Values.service.externalPort }} 219 {{- else if contains "ClusterIP" .Values.service.type }} 220 export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 221 echo "Visit http://127.0.0.1:8080 to use your application" 222 kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} 223 {{- end }} 224 ` 225 226 const defaultHelpers = `{{/* vim: set filetype=mustache: */}} 227 {{/* 228 Expand the name of the chart. 229 */}} 230 {{- define "name" -}} 231 {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 232 {{- end -}} 233 234 {{/* 235 Create a default fully qualified app name. 236 We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 237 */}} 238 {{- define "fullname" -}} 239 {{- $name := default .Chart.Name .Values.nameOverride -}} 240 {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 241 {{- end -}} 242 ` 243 244 // CreateFrom creates a new chart, but scaffolds it from the src chart. 245 func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { 246 schart, err := Load(src) 247 if err != nil { 248 return fmt.Errorf("could not load %s: %s", src, err) 249 } 250 251 schart.Metadata = chartfile 252 return SaveDir(schart, dest) 253 } 254 255 // Create creates a new chart in a directory. 256 // 257 // Inside of dir, this will create a directory based on the name of 258 // chartfile.Name. It will then write the Chart.yaml into this directory and 259 // create the (empty) appropriate directories. 260 // 261 // The returned string will point to the newly created directory. It will be 262 // an absolute path, even if the provided base directory was relative. 263 // 264 // If dir does not exist, this will return an error. 265 // If Chart.yaml or any directories cannot be created, this will return an 266 // error. In such a case, this will attempt to clean up by removing the 267 // new chart directory. 268 func Create(chartfile *chart.Metadata, dir string) (string, error) { 269 path, err := filepath.Abs(dir) 270 if err != nil { 271 return path, err 272 } 273 274 if fi, err := os.Stat(path); err != nil { 275 return path, err 276 } else if !fi.IsDir() { 277 return path, fmt.Errorf("no such directory %s", path) 278 } 279 280 n := chartfile.Name 281 cdir := filepath.Join(path, n) 282 if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { 283 return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) 284 } 285 if err := os.MkdirAll(cdir, 0755); err != nil { 286 return cdir, err 287 } 288 289 cf := filepath.Join(cdir, ChartfileName) 290 if _, err := os.Stat(cf); err != nil { 291 if err := SaveChartfile(cf, chartfile); err != nil { 292 return cdir, err 293 } 294 } 295 296 for _, d := range []string{TemplatesDir, ChartsDir} { 297 if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { 298 return cdir, err 299 } 300 } 301 302 files := []struct { 303 path string 304 content []byte 305 }{ 306 { 307 // values.yaml 308 path: filepath.Join(cdir, ValuesfileName), 309 content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), 310 }, 311 { 312 // .helmignore 313 path: filepath.Join(cdir, IgnorefileName), 314 content: []byte(defaultIgnore), 315 }, 316 { 317 // ingress.yaml 318 path: filepath.Join(cdir, TemplatesDir, IngressFileName), 319 content: []byte(defaultIngress), 320 }, 321 { 322 // deployment.yaml 323 path: filepath.Join(cdir, TemplatesDir, DeploymentName), 324 content: []byte(defaultDeployment), 325 }, 326 { 327 // service.yaml 328 path: filepath.Join(cdir, TemplatesDir, ServiceName), 329 content: []byte(defaultService), 330 }, 331 { 332 // NOTES.txt 333 path: filepath.Join(cdir, TemplatesDir, NotesName), 334 content: []byte(defaultNotes), 335 }, 336 { 337 // _helpers.tpl 338 path: filepath.Join(cdir, TemplatesDir, HelpersName), 339 content: []byte(defaultHelpers), 340 }, 341 } 342 343 for _, file := range files { 344 if _, err := os.Stat(file.path); err == nil { 345 // File exists and is okay. Skip it. 346 continue 347 } 348 if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { 349 return cdir, err 350 } 351 } 352 return cdir, nil 353 }