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