github.com/cloudposse/helm@v2.2.3+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  	// DeploymentName is the name of the example deployment file.
    40  	DeploymentName = "deployment.yaml"
    41  	// ServiceName is the name of the example service file.
    42  	ServiceName = "service.yaml"
    43  	// NotesName is the name of the example NOTES.txt file.
    44  	NotesName = "NOTES.txt"
    45  	// HelpersName is the name of the example NOTES.txt file.
    46  	HelpersName = "_helpers.tpl"
    47  )
    48  
    49  const defaultValues = `# Default values for %s.
    50  # This is a YAML-formatted file.
    51  # Declare variables to be passed into your templates.
    52  replicaCount: 1
    53  image:
    54    repository: nginx
    55    tag: stable
    56    pullPolicy: IfNotPresent
    57  service:
    58    name: nginx
    59    type: ClusterIP
    60    externalPort: 80
    61    internalPort: 80
    62  resources:
    63    limits:
    64      cpu: 100m
    65      memory: 128Mi
    66    requests:
    67      cpu: 100m
    68      memory: 128Mi
    69  
    70  `
    71  
    72  const defaultIgnore = `# Patterns to ignore when building packages.
    73  # This supports shell glob matching, relative path matching, and
    74  # negation (prefixed with !). Only one pattern per line.
    75  .DS_Store
    76  # Common VCS dirs
    77  .git/
    78  .gitignore
    79  .bzr/
    80  .bzrignore
    81  .hg/
    82  .hgignore
    83  .svn/
    84  # Common backup files
    85  *.swp
    86  *.bak
    87  *.tmp
    88  *~
    89  # Various IDEs
    90  .project
    91  .idea/
    92  *.tmproj
    93  `
    94  
    95  const defaultDeployment = `apiVersion: extensions/v1beta1
    96  kind: Deployment
    97  metadata:
    98    name: {{ template "fullname" . }}
    99    labels:
   100      chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
   101  spec:
   102    replicas: {{ .Values.replicaCount }}
   103    template:
   104      metadata:
   105        labels:
   106          app: {{ template "fullname" . }}
   107      spec:
   108        containers:
   109        - name: {{ .Chart.Name }}
   110          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
   111          imagePullPolicy: {{ .Values.image.pullPolicy }}
   112          ports:
   113          - containerPort: {{ .Values.service.internalPort }}
   114          livenessProbe:
   115            httpGet:
   116              path: /
   117              port: {{ .Values.service.internalPort }}
   118          readinessProbe:
   119            httpGet:
   120              path: /
   121              port: {{ .Values.service.internalPort }}
   122          resources:
   123  {{ toYaml .Values.resources | indent 12 }}
   124  `
   125  
   126  const defaultService = `apiVersion: v1
   127  kind: Service
   128  metadata:
   129    name: {{ template "fullname" . }}
   130    labels:
   131      chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
   132  spec:
   133    type: {{ .Values.service.type }}
   134    ports:
   135    - port: {{ .Values.service.externalPort }}
   136      targetPort: {{ .Values.service.internalPort }}
   137      protocol: TCP
   138      name: {{ .Values.service.name }}
   139    selector:
   140      app: {{ template "fullname" . }}
   141  `
   142  
   143  const defaultNotes = `1. Get the application URL by running these commands:
   144  {{- if contains "NodePort" .Values.service.type }}
   145    export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }})
   146    export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
   147    echo http://$NODE_IP:$NODE_PORT/login
   148  {{- else if contains "LoadBalancer" .Values.service.type }}
   149       NOTE: It may take a few minutes for the LoadBalancer IP to be available.
   150             You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}'
   151    export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
   152    echo http://$SERVICE_IP:{{ .Values.service.externalPort }}
   153  {{- else if contains "ClusterIP"  .Values.service.type }}
   154    export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "fullname" . }}" -o jsonpath="{.items[0].metadata.name}")
   155    echo "Visit http://127.0.0.1:8080 to use your application"
   156    kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }}
   157  {{- end }}
   158  `
   159  
   160  const defaultHelpers = `{{/* vim: set filetype=mustache: */}}
   161  {{/*
   162  Expand the name of the chart.
   163  */}}
   164  {{- define "name" -}}
   165  {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
   166  {{- end -}}
   167  
   168  {{/*
   169  Create a default fully qualified app name.
   170  We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
   171  */}}
   172  {{- define "fullname" -}}
   173  {{- $name := default .Chart.Name .Values.nameOverride -}}
   174  {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
   175  {{- end -}}
   176  `
   177  
   178  // CreateFrom creates a new chart, but scaffolds it from the src chart.
   179  func CreateFrom(chartfile *chart.Metadata, dest string, src string) error {
   180  	schart, err := Load(src)
   181  	if err != nil {
   182  		return fmt.Errorf("could not load %s: %s", src, err)
   183  	}
   184  
   185  	schart.Metadata = chartfile
   186  	return SaveDir(schart, dest)
   187  }
   188  
   189  // Create creates a new chart in a directory.
   190  //
   191  // Inside of dir, this will create a directory based on the name of
   192  // chartfile.Name. It will then write the Chart.yaml into this directory and
   193  // create the (empty) appropriate directories.
   194  //
   195  // The returned string will point to the newly created directory. It will be
   196  // an absolute path, even if the provided base directory was relative.
   197  //
   198  // If dir does not exist, this will return an error.
   199  // If Chart.yaml or any directories cannot be created, this will return an
   200  // error. In such a case, this will attempt to clean up by removing the
   201  // new chart directory.
   202  func Create(chartfile *chart.Metadata, dir string) (string, error) {
   203  	path, err := filepath.Abs(dir)
   204  	if err != nil {
   205  		return path, err
   206  	}
   207  
   208  	if fi, err := os.Stat(path); err != nil {
   209  		return path, err
   210  	} else if !fi.IsDir() {
   211  		return path, fmt.Errorf("no such directory %s", path)
   212  	}
   213  
   214  	n := chartfile.Name
   215  	cdir := filepath.Join(path, n)
   216  	if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
   217  		return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir)
   218  	}
   219  	if err := os.MkdirAll(cdir, 0755); err != nil {
   220  		return cdir, err
   221  	}
   222  
   223  	cf := filepath.Join(cdir, ChartfileName)
   224  	if _, err := os.Stat(cf); err != nil {
   225  		if err := SaveChartfile(cf, chartfile); err != nil {
   226  			return cdir, err
   227  		}
   228  	}
   229  
   230  	for _, d := range []string{TemplatesDir, ChartsDir} {
   231  		if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
   232  			return cdir, err
   233  		}
   234  	}
   235  
   236  	files := []struct {
   237  		path    string
   238  		content []byte
   239  	}{
   240  		{
   241  			// values.yaml
   242  			path:    filepath.Join(cdir, ValuesfileName),
   243  			content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)),
   244  		},
   245  		{
   246  			// .helmignore
   247  			path:    filepath.Join(cdir, IgnorefileName),
   248  			content: []byte(defaultIgnore),
   249  		},
   250  		{
   251  			// deployment.yaml
   252  			path:    filepath.Join(cdir, TemplatesDir, DeploymentName),
   253  			content: []byte(defaultDeployment),
   254  		},
   255  		{
   256  			// service.yaml
   257  			path:    filepath.Join(cdir, TemplatesDir, ServiceName),
   258  			content: []byte(defaultService),
   259  		},
   260  		{
   261  			// NOTES.txt
   262  			path:    filepath.Join(cdir, TemplatesDir, NotesName),
   263  			content: []byte(defaultNotes),
   264  		},
   265  		{
   266  			// _helpers.tpl
   267  			path:    filepath.Join(cdir, TemplatesDir, HelpersName),
   268  			content: []byte(defaultHelpers),
   269  		},
   270  	}
   271  
   272  	for _, file := range files {
   273  		if _, err := os.Stat(file.path); err == nil {
   274  			// File exists and is okay. Skip it.
   275  			continue
   276  		}
   277  		if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil {
   278  			return cdir, err
   279  		}
   280  	}
   281  	return cdir, nil
   282  }