github.com/cloudposse/helm@v2.2.3+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 // DeploymentName is the name of the example deployment file. 40 DeploymentName = "deployment.yaml" 41 // ServiceName is the name of the example service file. 42 ServiceName = "service.yaml" 43 // NotesName is the name of the example NOTES.txt file. 44 NotesName = "NOTES.txt" 45 // HelpersName is the name of the example NOTES.txt file. 46 HelpersName = "_helpers.tpl" 47 ) 48 49 const defaultValues = `# Default values for %s. 50 # This is a YAML-formatted file. 51 # Declare variables to be passed into your templates. 52 replicaCount: 1 53 image: 54 repository: nginx 55 tag: stable 56 pullPolicy: IfNotPresent 57 service: 58 name: nginx 59 type: ClusterIP 60 externalPort: 80 61 internalPort: 80 62 resources: 63 limits: 64 cpu: 100m 65 memory: 128Mi 66 requests: 67 cpu: 100m 68 memory: 128Mi 69 70 ` 71 72 const defaultIgnore = `# Patterns to ignore when building packages. 73 # This supports shell glob matching, relative path matching, and 74 # negation (prefixed with !). Only one pattern per line. 75 .DS_Store 76 # Common VCS dirs 77 .git/ 78 .gitignore 79 .bzr/ 80 .bzrignore 81 .hg/ 82 .hgignore 83 .svn/ 84 # Common backup files 85 *.swp 86 *.bak 87 *.tmp 88 *~ 89 # Various IDEs 90 .project 91 .idea/ 92 *.tmproj 93 ` 94 95 const defaultDeployment = `apiVersion: extensions/v1beta1 96 kind: Deployment 97 metadata: 98 name: {{ template "fullname" . }} 99 labels: 100 chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" 101 spec: 102 replicas: {{ .Values.replicaCount }} 103 template: 104 metadata: 105 labels: 106 app: {{ template "fullname" . }} 107 spec: 108 containers: 109 - name: {{ .Chart.Name }} 110 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 111 imagePullPolicy: {{ .Values.image.pullPolicy }} 112 ports: 113 - containerPort: {{ .Values.service.internalPort }} 114 livenessProbe: 115 httpGet: 116 path: / 117 port: {{ .Values.service.internalPort }} 118 readinessProbe: 119 httpGet: 120 path: / 121 port: {{ .Values.service.internalPort }} 122 resources: 123 {{ toYaml .Values.resources | indent 12 }} 124 ` 125 126 const defaultService = `apiVersion: v1 127 kind: Service 128 metadata: 129 name: {{ template "fullname" . }} 130 labels: 131 chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" 132 spec: 133 type: {{ .Values.service.type }} 134 ports: 135 - port: {{ .Values.service.externalPort }} 136 targetPort: {{ .Values.service.internalPort }} 137 protocol: TCP 138 name: {{ .Values.service.name }} 139 selector: 140 app: {{ template "fullname" . }} 141 ` 142 143 const defaultNotes = `1. Get the application URL by running these commands: 144 {{- if contains "NodePort" .Values.service.type }} 145 export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) 146 export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 147 echo http://$NODE_IP:$NODE_PORT/login 148 {{- else if contains "LoadBalancer" .Values.service.type }} 149 NOTE: It may take a few minutes for the LoadBalancer IP to be available. 150 You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' 151 export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 152 echo http://$SERVICE_IP:{{ .Values.service.externalPort }} 153 {{- else if contains "ClusterIP" .Values.service.type }} 154 export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "fullname" . }}" -o jsonpath="{.items[0].metadata.name}") 155 echo "Visit http://127.0.0.1:8080 to use your application" 156 kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} 157 {{- end }} 158 ` 159 160 const defaultHelpers = `{{/* vim: set filetype=mustache: */}} 161 {{/* 162 Expand the name of the chart. 163 */}} 164 {{- define "name" -}} 165 {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 166 {{- end -}} 167 168 {{/* 169 Create a default fully qualified app name. 170 We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 171 */}} 172 {{- define "fullname" -}} 173 {{- $name := default .Chart.Name .Values.nameOverride -}} 174 {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 175 {{- end -}} 176 ` 177 178 // CreateFrom creates a new chart, but scaffolds it from the src chart. 179 func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { 180 schart, err := Load(src) 181 if err != nil { 182 return fmt.Errorf("could not load %s: %s", src, err) 183 } 184 185 schart.Metadata = chartfile 186 return SaveDir(schart, dest) 187 } 188 189 // Create creates a new chart in a directory. 190 // 191 // Inside of dir, this will create a directory based on the name of 192 // chartfile.Name. It will then write the Chart.yaml into this directory and 193 // create the (empty) appropriate directories. 194 // 195 // The returned string will point to the newly created directory. It will be 196 // an absolute path, even if the provided base directory was relative. 197 // 198 // If dir does not exist, this will return an error. 199 // If Chart.yaml or any directories cannot be created, this will return an 200 // error. In such a case, this will attempt to clean up by removing the 201 // new chart directory. 202 func Create(chartfile *chart.Metadata, dir string) (string, error) { 203 path, err := filepath.Abs(dir) 204 if err != nil { 205 return path, err 206 } 207 208 if fi, err := os.Stat(path); err != nil { 209 return path, err 210 } else if !fi.IsDir() { 211 return path, fmt.Errorf("no such directory %s", path) 212 } 213 214 n := chartfile.Name 215 cdir := filepath.Join(path, n) 216 if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { 217 return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) 218 } 219 if err := os.MkdirAll(cdir, 0755); err != nil { 220 return cdir, err 221 } 222 223 cf := filepath.Join(cdir, ChartfileName) 224 if _, err := os.Stat(cf); err != nil { 225 if err := SaveChartfile(cf, chartfile); err != nil { 226 return cdir, err 227 } 228 } 229 230 for _, d := range []string{TemplatesDir, ChartsDir} { 231 if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { 232 return cdir, err 233 } 234 } 235 236 files := []struct { 237 path string 238 content []byte 239 }{ 240 { 241 // values.yaml 242 path: filepath.Join(cdir, ValuesfileName), 243 content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), 244 }, 245 { 246 // .helmignore 247 path: filepath.Join(cdir, IgnorefileName), 248 content: []byte(defaultIgnore), 249 }, 250 { 251 // deployment.yaml 252 path: filepath.Join(cdir, TemplatesDir, DeploymentName), 253 content: []byte(defaultDeployment), 254 }, 255 { 256 // service.yaml 257 path: filepath.Join(cdir, TemplatesDir, ServiceName), 258 content: []byte(defaultService), 259 }, 260 { 261 // NOTES.txt 262 path: filepath.Join(cdir, TemplatesDir, NotesName), 263 content: []byte(defaultNotes), 264 }, 265 { 266 // _helpers.tpl 267 path: filepath.Join(cdir, TemplatesDir, HelpersName), 268 content: []byte(defaultHelpers), 269 }, 270 } 271 272 for _, file := range files { 273 if _, err := os.Stat(file.path); err == nil { 274 // File exists and is okay. Skip it. 275 continue 276 } 277 if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { 278 return cdir, err 279 } 280 } 281 return cdir, nil 282 }