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