github.com/Beeketing/helm@v2.12.1+incompatible/pkg/chartutil/create.go (about)

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