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