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