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