update vendor

Signed-off-by: Roland.Ma <rolandma@yunify.com>
This commit is contained in:
Roland.Ma
2021-08-11 07:10:14 +00:00
parent a18f72b565
commit ea8f47c73a
2901 changed files with 269317 additions and 43103 deletions

View File

@@ -0,0 +1,104 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"fmt"
"strconv"
"k8s.io/client-go/kubernetes/scheme"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
helmversion "helm.sh/helm/v3/internal/version"
)
const (
k8sVersionMajor = 1
k8sVersionMinor = 20
)
var (
// DefaultVersionSet is the default version set, which includes only Core V1 ("v1").
DefaultVersionSet = allKnownVersions()
// DefaultCapabilities is the default set of capabilities.
DefaultCapabilities = &Capabilities{
KubeVersion: KubeVersion{
Version: fmt.Sprintf("v%d.%d.0", k8sVersionMajor, k8sVersionMinor),
Major: strconv.Itoa(k8sVersionMajor),
Minor: strconv.Itoa(k8sVersionMinor),
},
APIVersions: DefaultVersionSet,
HelmVersion: helmversion.Get(),
}
)
// Capabilities describes the capabilities of the Kubernetes cluster.
type Capabilities struct {
// KubeVersion is the Kubernetes version.
KubeVersion KubeVersion
// APIversions are supported Kubernetes API versions.
APIVersions VersionSet
// HelmVersion is the build information for this helm version
HelmVersion helmversion.BuildInfo
}
// KubeVersion is the Kubernetes version.
type KubeVersion struct {
Version string // Kubernetes version
Major string // Kubernetes major version
Minor string // Kubernetes minor version
}
// String implements fmt.Stringer
func (kv *KubeVersion) String() string { return kv.Version }
// GitVersion returns the Kubernetes version string.
//
// Deprecated: use KubeVersion.Version.
func (kv *KubeVersion) GitVersion() string { return kv.Version }
// VersionSet is a set of Kubernetes API versions.
type VersionSet []string
// Has returns true if the version string is in the set.
//
// vs.Has("apps/v1")
func (v VersionSet) Has(apiVersion string) bool {
for _, x := range v {
if x == apiVersion {
return true
}
}
return false
}
func allKnownVersions() VersionSet {
// We should register the built in extension APIs as well so CRDs are
// supported in the default version set. This has caused problems with `helm
// template` in the past, so let's be safe
apiextensionsv1beta1.AddToScheme(scheme.Scheme)
apiextensionsv1.AddToScheme(scheme.Scheme)
groups := scheme.Scheme.PrioritizedVersionsAllGroups()
vs := make(VersionSet, 0, len(groups))
for _, gv := range groups {
vs = append(vs, gv.String())
}
return vs
}

View File

@@ -0,0 +1,93 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
)
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
func LoadChartfile(filename string) (*chart.Metadata, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
y := new(chart.Metadata)
err = yaml.Unmarshal(b, y)
return y, err
}
// SaveChartfile saves the given metadata as a Chart.yaml file at the given path.
//
// 'filename' should be the complete path and filename ('foo/Chart.yaml')
func SaveChartfile(filename string, cf *chart.Metadata) error {
// Pull out the dependencies of a v1 Chart, since there's no way
// to tell the serializer to skip a field for just this use case
savedDependencies := cf.Dependencies
if cf.APIVersion == chart.APIVersionV1 {
cf.Dependencies = nil
}
out, err := yaml.Marshal(cf)
if cf.APIVersion == chart.APIVersionV1 {
cf.Dependencies = savedDependencies
}
if err != nil {
return err
}
return ioutil.WriteFile(filename, out, 0644)
}
// IsChartDir validate a chart directory.
//
// Checks for a valid Chart.yaml.
func IsChartDir(dirName string) (bool, error) {
if fi, err := os.Stat(dirName); err != nil {
return false, err
} else if !fi.IsDir() {
return false, errors.Errorf("%q is not a directory", dirName)
}
chartYaml := filepath.Join(dirName, ChartfileName)
if _, err := os.Stat(chartYaml); os.IsNotExist(err) {
return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName)
}
chartYamlContent, err := ioutil.ReadFile(chartYaml)
if err != nil {
return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
}
chartContent := new(chart.Metadata)
if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil {
return false, err
}
if chartContent == nil {
return false, errors.Errorf("chart metadata (%s) missing", ChartfileName)
}
if chartContent.Name == "" {
return false, errors.Errorf("invalid chart (%s): name must not be empty", ChartfileName)
}
return true, nil
}

View File

@@ -0,0 +1,207 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"log"
"github.com/mitchellh/copystructure"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"
)
// CoalesceValues coalesces all of the values in a chart (and its subcharts).
//
// Values are coalesced together using the following rules:
//
// - Values in a higher level chart always override values in a lower-level
// dependency chart
// - Scalar values and arrays are replaced, maps are merged
// - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies.
func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) {
v, err := copystructure.Copy(vals)
if err != nil {
return vals, err
}
valsCopy := v.(map[string]interface{})
// if we have an empty map, make sure it is initialized
if valsCopy == nil {
valsCopy = make(map[string]interface{})
}
return coalesce(chrt, valsCopy)
}
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
//
// This is a helper function for CoalesceValues.
func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
coalesceValues(ch, dest)
return coalesceDeps(ch, dest)
}
// coalesceDeps coalesces the dependencies of the given chart.
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
for _, subchart := range chrt.Dependencies() {
if c, ok := dest[subchart.Name()]; !ok {
// If dest doesn't already have the key, create it.
dest[subchart.Name()] = make(map[string]interface{})
} else if !istable(c) {
return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c)
}
if dv, ok := dest[subchart.Name()]; ok {
dvmap := dv.(map[string]interface{})
// Get globals out of dest and merge them into dvmap.
coalesceGlobals(dvmap, dest)
// Now coalesce the rest of the values.
var err error
dest[subchart.Name()], err = coalesce(subchart, dvmap)
if err != nil {
return dest, err
}
}
}
return dest, nil
}
// coalesceGlobals copies the globals out of src and merges them into dest.
//
// For convenience, returns dest.
func coalesceGlobals(dest, src map[string]interface{}) {
var dg, sg map[string]interface{}
if destglob, ok := dest[GlobalKey]; !ok {
dg = make(map[string]interface{})
} else if dg, ok = destglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
return
}
if srcglob, ok := src[GlobalKey]; !ok {
sg = make(map[string]interface{})
} else if sg, ok = srcglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
return
}
// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
// reverses that decision. It may somehow be possible to introduce a loop
// here, but I haven't found a way. So for the time being, let's allow
// tables in globals.
for key, val := range sg {
if istable(val) {
vv := copyMap(val.(map[string]interface{}))
if destv, ok := dg[key]; !ok {
// Here there is no merge. We're just adding.
dg[key] = vv
} else {
if destvmap, ok := destv.(map[string]interface{}); !ok {
log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
} else {
// Basically, we reverse order of coalesce here to merge
// top-down.
CoalesceTables(vv, destvmap)
dg[key] = vv
continue
}
}
} else if dv, ok := dg[key]; ok && istable(dv) {
// It's not clear if this condition can actually ever trigger.
log.Printf("key %s is table. Skipping", key)
continue
}
// TODO: Do we need to do any additional checking on the value?
dg[key] = val
}
dest[GlobalKey] = dg
}
func copyMap(src map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{}, len(src))
for k, v := range src {
m[k] = v
}
return m
}
// coalesceValues builds up a values map for a particular chart.
//
// Values in v will override the values in the chart.
func coalesceValues(c *chart.Chart, v map[string]interface{}) {
for key, val := range c.Values {
if value, ok := v[key]; ok {
if value == nil {
// When the YAML value is null, we remove the value's key.
// This allows Helm's various sources of values (value files or --set) to
// remove incompatible keys from any previous chart, file, or set values.
delete(v, key)
} else if dest, ok := value.(map[string]interface{}); ok {
// if v[key] is a table, merge nv's val table into v[key].
src, ok := val.(map[string]interface{})
if !ok {
// If the original value is nil, there is nothing to coalesce, so we don't print
// the warning but simply continue
if val != nil {
log.Printf("warning: skipped value for %s: Not a table.", key)
}
continue
}
// Because v has higher precedence than nv, dest values override src
// values.
CoalesceTables(dest, src)
}
} else {
// If the key is not in v, copy it from nv.
v[key] = val
}
}
}
// CoalesceTables merges a source map into a destination map.
//
// dest is considered authoritative.
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
// When --reuse-values is set but there are no modifications yet, return new values
if src == nil {
return dst
}
if dst == nil {
return src
}
// Because dest has higher precedence than src, dest values override src
// values.
for key, val := range src {
if dv, ok := dst[key]; ok && dv == nil {
delete(dst, key)
} else if !ok {
dst[key] = val
} else if istable(val) {
if istable(dv) {
CoalesceTables(dv.(map[string]interface{}), val.(map[string]interface{}))
} else {
log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
}
} else if istable(dv) && val != nil {
log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
}
}
return dst
}

View File

@@ -0,0 +1,34 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import "github.com/Masterminds/semver/v3"
// IsCompatibleRange compares a version to a constraint.
// It returns true if the version matches the constraint, and false in all other cases.
func IsCompatibleRange(constraint, ver string) bool {
sv, err := semver.NewVersion(ver)
if err != nil {
return false
}
c, err := semver.NewConstraint(constraint)
if err != nil {
return false
}
return c.Check(sv)
}

View File

@@ -0,0 +1,664 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
// chartName is a regular expression for testing the supplied name of a chart.
// This regular expression is probably stricter than it needs to be. We can relax it
// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be
// problematic.
var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
const (
// ChartfileName is the default Chart file name.
ChartfileName = "Chart.yaml"
// ValuesfileName is the default values file name.
ValuesfileName = "values.yaml"
// SchemafileName is the default values schema file name.
SchemafileName = "values.schema.json"
// TemplatesDir is the relative directory name for templates.
TemplatesDir = "templates"
// ChartsDir is the relative directory name for charts dependencies.
ChartsDir = "charts"
// TemplatesTestsDir is the relative directory name for tests.
TemplatesTestsDir = TemplatesDir + sep + "tests"
// IgnorefileName is the name of the Helm ignore file.
IgnorefileName = ".helmignore"
// IngressFileName is the name of the example ingress file.
IngressFileName = TemplatesDir + sep + "ingress.yaml"
// DeploymentName is the name of the example deployment file.
DeploymentName = TemplatesDir + sep + "deployment.yaml"
// ServiceName is the name of the example service file.
ServiceName = TemplatesDir + sep + "service.yaml"
// ServiceAccountName is the name of the example serviceaccount file.
ServiceAccountName = TemplatesDir + sep + "serviceaccount.yaml"
// HorizontalPodAutoscalerName is the name of the example hpa file.
HorizontalPodAutoscalerName = TemplatesDir + sep + "hpa.yaml"
// NotesName is the name of the example NOTES.txt file.
NotesName = TemplatesDir + sep + "NOTES.txt"
// HelpersName is the name of the example helpers file.
HelpersName = TemplatesDir + sep + "_helpers.tpl"
// TestConnectionName is the name of the example test file.
TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml"
)
// maxChartNameLength is lower than the limits we know of with certain file systems,
// and with certain Kubernetes fields.
const maxChartNameLength = 250
const sep = string(filepath.Separator)
const defaultChartfile = `apiVersion: v2
name: %s
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
`
const defaultValues = `# Default values for %s.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
`
const defaultIgnore = `# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
`
const defaultIngress = `{{- if .Values.ingress.enabled -}}
{{- $fullName := include "<CHARTNAME>.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
`
const defaultDeployment = `apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "<CHARTNAME>.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
`
const defaultService = `apiVersion: v1
kind: Service
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 4 }}
`
const defaultServiceAccount = `{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "<CHARTNAME>.serviceAccountName" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
`
const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "<CHARTNAME>.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
`
const defaultNotes = `1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "<CHARTNAME>.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "<CHARTNAME>.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "<CHARTNAME>.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "<CHARTNAME>.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
`
const defaultHelpers = `{{/*
Expand the name of the chart.
*/}}
{{- define "<CHARTNAME>.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "<CHARTNAME>.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "<CHARTNAME>.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "<CHARTNAME>.labels" -}}
helm.sh/chart: {{ include "<CHARTNAME>.chart" . }}
{{ include "<CHARTNAME>.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "<CHARTNAME>.selectorLabels" -}}
app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "<CHARTNAME>.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "<CHARTNAME>.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
`
const defaultTestConnection = `apiVersion: v1
kind: Pod
metadata:
name: "{{ include "<CHARTNAME>.fullname" . }}-test-connection"
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "<CHARTNAME>.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
`
// Stderr is an io.Writer to which error messages can be written
//
// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward
// compatibility.
var Stderr io.Writer = os.Stderr
// CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart, err := loader.Load(src)
if err != nil {
return errors.Wrapf(err, "could not load %s", src)
}
schart.Metadata = chartfile
var updatedTemplates []*chart.File
for _, template := range schart.Templates {
newData := transform(string(template.Data), schart.Name())
updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData})
}
schart.Templates = updatedTemplates
b, err := yaml.Marshal(schart.Values)
if err != nil {
return errors.Wrap(err, "reading values file")
}
var m map[string]interface{}
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
return errors.Wrap(err, "transforming values file")
}
schart.Values = m
// SaveDir looks for the file values.yaml when saving rather than the values
// key in order to preserve the comments in the YAML. The name placeholder
// needs to be replaced on that file.
for _, f := range schart.Raw {
if f.Name == ValuesfileName {
f.Data = transform(string(f.Data), schart.Name())
}
}
return SaveDir(schart, dest)
}
// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of
// chartfile.Name. It will then write the Chart.yaml into this directory and
// create the (empty) appropriate directories.
//
// The returned string will point to the newly created directory. It will be
// an absolute path, even if the provided base directory was relative.
//
// If dir does not exist, this will return an error.
// If Chart.yaml or any directories cannot be created, this will return an
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil {
return "", err
}
path, err := filepath.Abs(dir)
if err != nil {
return path, err
}
if fi, err := os.Stat(path); err != nil {
return path, err
} else if !fi.IsDir() {
return path, errors.Errorf("no such directory %s", path)
}
cdir := filepath.Join(path, name)
if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
return cdir, errors.Errorf("file %s already exists and is not a directory", cdir)
}
files := []struct {
path string
content []byte
}{
{
// Chart.yaml
path: filepath.Join(cdir, ChartfileName),
content: []byte(fmt.Sprintf(defaultChartfile, name)),
},
{
// values.yaml
path: filepath.Join(cdir, ValuesfileName),
content: []byte(fmt.Sprintf(defaultValues, name)),
},
{
// .helmignore
path: filepath.Join(cdir, IgnorefileName),
content: []byte(defaultIgnore),
},
{
// ingress.yaml
path: filepath.Join(cdir, IngressFileName),
content: transform(defaultIngress, name),
},
{
// deployment.yaml
path: filepath.Join(cdir, DeploymentName),
content: transform(defaultDeployment, name),
},
{
// service.yaml
path: filepath.Join(cdir, ServiceName),
content: transform(defaultService, name),
},
{
// serviceaccount.yaml
path: filepath.Join(cdir, ServiceAccountName),
content: transform(defaultServiceAccount, name),
},
{
// hpa.yaml
path: filepath.Join(cdir, HorizontalPodAutoscalerName),
content: transform(defaultHorizontalPodAutoscaler, name),
},
{
// NOTES.txt
path: filepath.Join(cdir, NotesName),
content: transform(defaultNotes, name),
},
{
// _helpers.tpl
path: filepath.Join(cdir, HelpersName),
content: transform(defaultHelpers, name),
},
{
// test-connection.yaml
path: filepath.Join(cdir, TestConnectionName),
content: transform(defaultTestConnection, name),
},
}
for _, file := range files {
if _, err := os.Stat(file.path); err == nil {
// There is no handle to a preferred output stream here.
fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path)
}
if err := writeFile(file.path, file.content); err != nil {
return cdir, err
}
}
// Need to add the ChartsDir explicitly as it does not contain any file OOTB
if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil {
return cdir, err
}
return cdir, nil
}
// transform performs a string replacement of the specified source for
// a given key with the replacement string
func transform(src, replacement string) []byte {
return []byte(strings.ReplaceAll(src, "<CHARTNAME>", replacement))
}
func writeFile(name string, content []byte) error {
if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
return err
}
return ioutil.WriteFile(name, content, 0644)
}
func validateChartName(name string) error {
if name == "" || len(name) > maxChartNameLength {
return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength)
}
if !chartName.MatchString(name) {
return fmt.Errorf("chart name must match the regular expression %q", chartName.String())
}
return nil
}

View File

@@ -0,0 +1,285 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"log"
"strings"
"helm.sh/helm/v3/pkg/chart"
)
// ProcessDependencies checks through this chart's dependencies, processing accordingly.
func ProcessDependencies(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil {
return err
}
return processDependencyImportValues(c)
}
// processDependencyConditions disables charts based on condition path value in values
func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) {
if reqs == nil {
return
}
for _, r := range reqs {
for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") {
if len(c) > 0 {
// retrieve value
vv, err := cvals.PathValue(cpath + c)
if err == nil {
// if not bool, warn
if bv, ok := vv.(bool); ok {
r.Enabled = bv
break
} else {
log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
}
} else if _, ok := err.(ErrNoValue); !ok {
// this is a real error
log.Printf("Warning: PathValue returned error %v", err)
}
}
}
}
}
// processDependencyTags disables charts based on tags in values
func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
if reqs == nil {
return
}
vt, err := cvals.Table("tags")
if err != nil {
return
}
for _, r := range reqs {
var hasTrue, hasFalse bool
for _, k := range r.Tags {
if b, ok := vt[k]; ok {
// if not bool, warn
if bv, ok := b.(bool); ok {
if bv {
hasTrue = true
} else {
hasFalse = true
}
} else {
log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
}
}
}
if !hasTrue && hasFalse {
r.Enabled = false
} else if hasTrue || !hasTrue && !hasFalse {
r.Enabled = true
}
}
}
func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart {
for _, c := range charts {
if c == nil {
continue
}
if c.Name() != dep.Name {
continue
}
if !IsCompatibleRange(dep.Version, c.Metadata.Version) {
continue
}
out := *c
md := *c.Metadata
out.Metadata = &md
if dep.Alias != "" {
md.Name = dep.Alias
}
return &out
}
return nil
}
// processDependencyEnabled removes disabled charts from dependencies
func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error {
if c.Metadata.Dependencies == nil {
return nil
}
var chartDependencies []*chart.Chart
// If any dependency is not a part of Chart.yaml
// then this should be added to chartDependencies.
// However, if the dependency is already specified in Chart.yaml
// we should not add it, as it would be anyways processed from Chart.yaml
Loop:
for _, existing := range c.Dependencies() {
for _, req := range c.Metadata.Dependencies {
if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) {
continue Loop
}
}
chartDependencies = append(chartDependencies, existing)
}
for _, req := range c.Metadata.Dependencies {
if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
chartDependencies = append(chartDependencies, chartDependency)
}
if req.Alias != "" {
req.Name = req.Alias
}
}
c.SetDependencies(chartDependencies...)
// set all to true
for _, lr := range c.Metadata.Dependencies {
lr.Enabled = true
}
cvals, err := CoalesceValues(c, v)
if err != nil {
return err
}
// flag dependencies as enabled/disabled
processDependencyTags(c.Metadata.Dependencies, cvals)
processDependencyConditions(c.Metadata.Dependencies, cvals, path)
// make a map of charts to remove
rm := map[string]struct{}{}
for _, r := range c.Metadata.Dependencies {
if !r.Enabled {
// remove disabled chart
rm[r.Name] = struct{}{}
}
}
// don't keep disabled charts in new slice
cd := []*chart.Chart{}
copy(cd, c.Dependencies()[:0])
for _, n := range c.Dependencies() {
if _, ok := rm[n.Metadata.Name]; !ok {
cd = append(cd, n)
}
}
// don't keep disabled charts in metadata
cdMetadata := []*chart.Dependency{}
copy(cdMetadata, c.Metadata.Dependencies[:0])
for _, n := range c.Metadata.Dependencies {
if _, ok := rm[n.Name]; !ok {
cdMetadata = append(cdMetadata, n)
}
}
// recursively call self to process sub dependencies
for _, t := range cd {
subpath := path + t.Metadata.Name + "."
if err := processDependencyEnabled(t, cvals, subpath); err != nil {
return err
}
}
// set the correct dependencies in metadata
c.Metadata.Dependencies = nil
c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...)
c.SetDependencies(cd...)
return nil
}
// pathToMap creates a nested map given a YAML path in dot notation.
func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
if path == "." {
return data
}
return set(parsePath(path), data)
}
func set(path []string, data map[string]interface{}) map[string]interface{} {
if len(path) == 0 {
return nil
}
cur := data
for i := len(path) - 1; i >= 0; i-- {
cur = map[string]interface{}{path[i]: cur}
}
return cur
}
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
func processImportValues(c *chart.Chart) error {
if c.Metadata.Dependencies == nil {
return nil
}
// combine chart values and empty config to get Values
cvals, err := CoalesceValues(c, nil)
if err != nil {
return err
}
b := make(map[string]interface{})
// import values from each dependency if specified in import-values
for _, r := range c.Metadata.Dependencies {
var outiv []interface{}
for _, riv := range r.ImportValues {
switch iv := riv.(type) {
case map[string]interface{}:
child := iv["child"].(string)
parent := iv["parent"].(string)
outiv = append(outiv, map[string]string{
"child": child,
"parent": parent,
})
// get child table
vv, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
continue
}
// create value map from child to be merged into parent
b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap()))
case string:
child := "exports." + iv
outiv = append(outiv, map[string]string{
"child": child,
"parent": ".",
})
vm, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
b = CoalesceTables(b, vm.AsMap())
}
}
// set our formatted import values
r.ImportValues = outiv
}
// set the new values
c.Values = CoalesceTables(b, cvals)
return nil
}
// processDependencyImportValues imports specified chart values from child to parent.
func processDependencyImportValues(c *chart.Chart) error {
for _, d := range c.Dependencies() {
// recurse
if err := processDependencyImportValues(d); err != nil {
return err
}
}
return processImportValues(c)
}

View File

@@ -0,0 +1,44 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package chartutil contains tools for working with charts.
Charts are described in the chart package (pkg/chart).
This package provides utilities for serializing and deserializing charts.
A chart can be represented on the file system in one of two ways:
- As a directory that contains a Chart.yaml file and other chart things.
- As a tarred gzipped file containing a directory that then contains a
Chart.yaml file.
This package provides utilities for working with those file formats.
The preferred way of loading a chart is using 'loader.Load`:
chart, err := loader.Load(filename)
This will attempt to discover whether the file at 'filename' is a directory or
a chart archive. It will then load accordingly.
For accepting raw compressed tar file data from an io.Reader, the
'loader.LoadArchive()' will read in the data, uncompress it, and unpack it
into a Chart.
When creating charts in memory, use the 'helm.sh/helm/pkg/chart'
package directly.
*/
package chartutil // import "helm.sh/helm/v3/pkg/chartutil"

View File

@@ -0,0 +1,35 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"fmt"
)
// ErrNoTable indicates that a chart does not have a matching table.
type ErrNoTable struct {
Key string
}
func (e ErrNoTable) Error() string { return fmt.Sprintf("%q is not a table", e.Key) }
// ErrNoValue indicates that Values does not contain a key with a value
type ErrNoValue struct {
Key string
}
func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) }

View File

@@ -0,0 +1,91 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"io"
"io/ioutil"
"os"
"path/filepath"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
// Expand uncompresses and extracts a chart into the specified directory.
func Expand(dir string, r io.Reader) error {
files, err := loader.LoadArchiveFiles(r)
if err != nil {
return err
}
// Get the name of the chart
var chartName string
for _, file := range files {
if file.Name == "Chart.yaml" {
ch := &chart.Metadata{}
if err := yaml.Unmarshal(file.Data, ch); err != nil {
return errors.Wrap(err, "cannot load Chart.yaml")
}
chartName = ch.Name
}
}
if chartName == "" {
return errors.New("chart name not specified")
}
// Find the base directory
chartdir, err := securejoin.SecureJoin(dir, chartName)
if err != nil {
return err
}
// Copy all files verbatim. We don't parse these files because parsing can remove
// comments.
for _, file := range files {
outpath, err := securejoin.SecureJoin(chartdir, file.Name)
if err != nil {
return err
}
// Make sure the necessary subdirs get created.
basedir := filepath.Dir(outpath)
if err := os.MkdirAll(basedir, 0755); err != nil {
return err
}
if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil {
return err
}
}
return nil
}
// ExpandFile expands the src file into the dest directory.
func ExpandFile(dest, src string) error {
h, err := os.Open(src)
if err != nil {
return err
}
defer h.Close()
return Expand(dest, h)
}

View File

@@ -0,0 +1,87 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"bytes"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
)
// ValidateAgainstSchema checks that values does not violate the structure laid out in schema
func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error {
var sb strings.Builder
if chrt.Schema != nil {
err := ValidateAgainstSingleSchema(values, chrt.Schema)
if err != nil {
sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name()))
sb.WriteString(err.Error())
}
}
// For each dependency, recursively call this function with the coalesced values
for _, subchart := range chrt.Dependencies() {
subchartValues := values[subchart.Name()].(map[string]interface{})
if err := ValidateAgainstSchema(subchart, subchartValues); err != nil {
sb.WriteString(err.Error())
}
}
if sb.Len() > 0 {
return errors.New(sb.String())
}
return nil
}
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) error {
valuesData, err := yaml.Marshal(values)
if err != nil {
return err
}
valuesJSON, err := yaml.YAMLToJSON(valuesData)
if err != nil {
return err
}
if bytes.Equal(valuesJSON, []byte("null")) {
valuesJSON = []byte("{}")
}
schemaLoader := gojsonschema.NewBytesLoader(schemaJSON)
valuesLoader := gojsonschema.NewBytesLoader(valuesJSON)
result, err := gojsonschema.Validate(schemaLoader, valuesLoader)
if err != nil {
return err
}
if !result.Valid() {
var sb strings.Builder
for _, desc := range result.Errors() {
sb.WriteString(fmt.Sprintf("- %s\n", desc))
}
return errors.New(sb.String())
}
return nil
}

View File

@@ -0,0 +1,244 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"archive/tar"
"compress/gzip"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
)
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// SaveDir saves a chart as files in a directory.
//
// This takes the chart name, and creates a new subdirectory inside of the given dest
// directory, writing the chart's contents to that subdirectory.
func SaveDir(c *chart.Chart, dest string) error {
// Create the chart directory
outdir := filepath.Join(dest, c.Name())
if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
return errors.Errorf("file %s already exists and is not a directory", outdir)
}
if err := os.MkdirAll(outdir, 0755); err != nil {
return err
}
// Save the chart file.
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
return err
}
// Save values.yaml
for _, f := range c.Raw {
if f.Name == ValuesfileName {
vf := filepath.Join(outdir, ValuesfileName)
if err := writeFile(vf, f.Data); err != nil {
return err
}
}
}
// Save values.schema.json if it exists
if c.Schema != nil {
filename := filepath.Join(outdir, SchemafileName)
if err := writeFile(filename, c.Schema); err != nil {
return err
}
}
// Save templates and files
for _, o := range [][]*chart.File{c.Templates, c.Files} {
for _, f := range o {
n := filepath.Join(outdir, f.Name)
if err := writeFile(n, f.Data); err != nil {
return err
}
}
}
// Save dependencies
base := filepath.Join(outdir, ChartsDir)
for _, dep := range c.Dependencies() {
// Here, we write each dependency as a tar file.
if _, err := Save(dep, base); err != nil {
return errors.Wrapf(err, "saving %s", dep.ChartFullPath())
}
}
return nil
}
// Save creates an archived chart to the given directory.
//
// This takes an existing chart and a destination directory.
//
// If the directory is /foo, and the chart is named bar, with version 1.0.0, this
// will generate /foo/bar-1.0.0.tgz.
//
// This returns the absolute path to the chart archive file.
func Save(c *chart.Chart, outDir string) (string, error) {
if err := c.Validate(); err != nil {
return "", errors.Wrap(err, "chart validation")
}
filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
filename = filepath.Join(outDir, filename)
dir := filepath.Dir(filename)
if stat, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
if err2 := os.MkdirAll(dir, 0755); err2 != nil {
return "", err2
}
} else {
return "", errors.Wrapf(err, "stat %s", dir)
}
} else if !stat.IsDir() {
return "", errors.Errorf("is not a directory: %s", dir)
}
f, err := os.Create(filename)
if err != nil {
return "", err
}
// Wrap in gzip writer
zipper := gzip.NewWriter(f)
zipper.Header.Extra = headerBytes
zipper.Header.Comment = "Helm"
// Wrap in tar writer
twriter := tar.NewWriter(zipper)
rollback := false
defer func() {
twriter.Close()
zipper.Close()
f.Close()
if rollback {
os.Remove(filename)
}
}()
if err := writeTarContents(twriter, c, ""); err != nil {
rollback = true
return filename, err
}
return filename, nil
}
func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
base := filepath.Join(prefix, c.Name())
// Pull out the dependencies of a v1 Chart, since there's no way
// to tell the serializer to skip a field for just this use case
savedDependencies := c.Metadata.Dependencies
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Metadata.Dependencies = nil
}
// Save Chart.yaml
cdata, err := yaml.Marshal(c.Metadata)
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Metadata.Dependencies = savedDependencies
}
if err != nil {
return err
}
if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil {
return err
}
// Save Chart.lock
// TODO: remove the APIVersion check when APIVersionV1 is not used anymore
if c.Metadata.APIVersion == chart.APIVersionV2 {
if c.Lock != nil {
ldata, err := yaml.Marshal(c.Lock)
if err != nil {
return err
}
if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil {
return err
}
}
}
// Save values.yaml
for _, f := range c.Raw {
if f.Name == ValuesfileName {
if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data); err != nil {
return err
}
}
}
// Save values.schema.json if it exists
if c.Schema != nil {
if !json.Valid(c.Schema) {
return errors.New("Invalid JSON in " + SchemafileName)
}
if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil {
return err
}
}
// Save templates
for _, f := range c.Templates {
n := filepath.Join(base, f.Name)
if err := writeToTar(out, n, f.Data); err != nil {
return err
}
}
// Save files
for _, f := range c.Files {
n := filepath.Join(base, f.Name)
if err := writeToTar(out, n, f.Data); err != nil {
return err
}
}
// Save dependencies
for _, dep := range c.Dependencies() {
if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil {
return err
}
}
return nil
}
// writeToTar writes a single file to a tar archive.
func writeToTar(out *tar.Writer, name string, body []byte) error {
// TODO: Do we need to create dummy parent directory names if none exist?
h := &tar.Header{
Name: filepath.ToSlash(name),
Mode: 0644,
Size: int64(len(body)),
ModTime: time.Now(),
}
if err := out.WriteHeader(h); err != nil {
return err
}
_, err := out.Write(body)
return err
}

View File

@@ -0,0 +1,104 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"fmt"
"regexp"
"github.com/pkg/errors"
)
// validName is a regular expression for resource names.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
var (
// errMissingName indicates that a release (name) was not provided.
errMissingName = errors.New("no name provided")
// errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New(fmt.Sprintf(
"invalid release name, must match regex %s and the length must not be longer than 53",
validName.String()))
// errInvalidKubernetesName indicates that the name does not meet the Kubernetes
// restrictions on metadata names.
errInvalidKubernetesName = errors.New(fmt.Sprintf(
"invalid metadata name, must match regex %s and the length must not be longer than 253",
validName.String()))
)
const (
// maxNameLen is the maximum length Helm allows for a release name
maxReleaseNameLen = 53
// maxMetadataNameLen is the maximum length Kubernetes allows for any name.
maxMetadataNameLen = 253
)
// ValidateReleaseName performs checks for an entry for a Helm release name
//
// For Helm to allow a name, it must be below a certain character count (53) and also match
// a reguar expression.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
func ValidateReleaseName(name string) error {
// This case is preserved for backwards compatibility
if name == "" {
return errMissingName
}
if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
return errInvalidName
}
return nil
}
// ValidateMetadataName validates the name field of a Kubernetes metadata object.
//
// Empty strings, strings longer than 253 chars, or strings that don't match the regexp
// will fail.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
func ValidateMetadataName(name string) error {
if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) {
return errInvalidKubernetesName
}
return nil
}

View File

@@ -0,0 +1,212 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"fmt"
"io"
"io/ioutil"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
)
// GlobalKey is the name of the Values key that is used for storing global vars.
const GlobalKey = "global"
// Values represents a collection of chart values.
type Values map[string]interface{}
// YAML encodes the Values into a YAML string.
func (v Values) YAML() (string, error) {
b, err := yaml.Marshal(v)
return string(b), err
}
// Table gets a table (YAML subsection) from a Values object.
//
// The table is returned as a Values.
//
// Compound table names may be specified with dots:
//
// foo.bar
//
// The above will be evaluated as "The table bar inside the table
// foo".
//
// An ErrNoTable is returned if the table does not exist.
func (v Values) Table(name string) (Values, error) {
table := v
var err error
for _, n := range parsePath(name) {
if table, err = tableLookup(table, n); err != nil {
break
}
}
return table, err
}
// AsMap is a utility function for converting Values to a map[string]interface{}.
//
// It protects against nil map panics.
func (v Values) AsMap() map[string]interface{} {
if v == nil || len(v) == 0 {
return map[string]interface{}{}
}
return v
}
// Encode writes serialized Values information to the given io.Writer.
func (v Values) Encode(w io.Writer) error {
out, err := yaml.Marshal(v)
if err != nil {
return err
}
_, err = w.Write(out)
return err
}
func tableLookup(v Values, simple string) (Values, error) {
v2, ok := v[simple]
if !ok {
return v, ErrNoTable{simple}
}
if vv, ok := v2.(map[string]interface{}); ok {
return vv, nil
}
// This catches a case where a value is of type Values, but doesn't (for some
// reason) match the map[string]interface{}. This has been observed in the
// wild, and might be a result of a nil map of type Values.
if vv, ok := v2.(Values); ok {
return vv, nil
}
return Values{}, ErrNoTable{simple}
}
// ReadValues will parse YAML byte data into a Values.
func ReadValues(data []byte) (vals Values, err error) {
err = yaml.Unmarshal(data, &vals)
if len(vals) == 0 {
vals = Values{}
}
return vals, err
}
// ReadValuesFile will parse a YAML file into a map of values.
func ReadValuesFile(filename string) (Values, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return map[string]interface{}{}, err
}
return ReadValues(data)
}
// ReleaseOptions represents the additional release options needed
// for the composition of the final values struct
type ReleaseOptions struct {
Name string
Namespace string
Revision int
IsUpgrade bool
IsInstall bool
}
// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
//
// This takes both ReleaseOptions and Capabilities to merge into the render values.
func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) {
if caps == nil {
caps = DefaultCapabilities
}
top := map[string]interface{}{
"Chart": chrt.Metadata,
"Capabilities": caps,
"Release": map[string]interface{}{
"Name": options.Name,
"Namespace": options.Namespace,
"IsUpgrade": options.IsUpgrade,
"IsInstall": options.IsInstall,
"Revision": options.Revision,
"Service": "Helm",
},
}
vals, err := CoalesceValues(chrt, chrtVals)
if err != nil {
return top, err
}
if err := ValidateAgainstSchema(chrt, vals); err != nil {
errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s"
return top, fmt.Errorf(errFmt, err.Error())
}
top["Values"] = vals
return top, nil
}
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
func istable(v interface{}) bool {
_, ok := v.(map[string]interface{})
return ok
}
// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
// Given the following YAML data the value at path "chapter.one.title" is "Loomings".
//
// chapter:
// one:
// title: "Loomings"
func (v Values) PathValue(path string) (interface{}, error) {
if path == "" {
return nil, errors.New("YAML path cannot be empty")
}
return v.pathValue(parsePath(path))
}
func (v Values) pathValue(path []string) (interface{}, error) {
if len(path) == 1 {
// if exists must be root key not table
if _, ok := v[path[0]]; ok && !istable(v[path[0]]) {
return v[path[0]], nil
}
return nil, ErrNoValue{path[0]}
}
key, path := path[len(path)-1], path[:len(path)-1]
// get our table for table path
t, err := v.Table(joinPath(path...))
if err != nil {
return nil, ErrNoValue{key}
}
// check table for key and ensure value is not a table
if k, ok := t[key]; ok && !istable(k) {
return k, nil
}
return nil, ErrNoValue{key}
}
func parsePath(key string) []string { return strings.Split(key, ".") }
func joinPath(path ...string) string { return strings.Join(path, ".") }