package helm
import (
"bytes"
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"sigs.k8s.io/yaml"
"github.com/gitpod-io/gitpod/installer/pkg/common"
"github.com/gitpod-io/gitpod/installer/third_party/charts"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/release"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type TemplateConfig struct {
Namespace string
}
func getContext(settings Settings) context.Context {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
cSignal := make(chan os.Signal)
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
go func() {
<-cSignal
settings.Write("Release of Gitpod has been cancelled.")
cancel()
}()
return ctx
}
func installDependencies(settings Settings) error {
client := action.NewDependency()
man := &downloader.Manager{
Out: &bytes.Buffer{},
ChartPath: settings.Chart,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Verify: downloader.VerifyNever,
RegistryClient: settings.ActionConfig.RegistryClient,
Getters: getter.All(settings.Env),
RepositoryConfig: settings.Env.RepositoryConfig,
RepositoryCache: settings.Env.RepositoryCache,
Debug: false,
}
err := man.Update()
if err != nil {
return fmt.Errorf("error pulling Helm dependency for %s: %w", settings.Chart, err)
}
return nil
}
func runInstall(settings Settings, client *action.Install) (*release.Release, error) {
name, _, err := client.NameAndChart([]string{
settings.Config.Name,
settings.Chart,
})
if err != nil {
return nil, err
}
client.ReleaseName = name
p := getter.All(settings.Env)
vals, err := settings.Values.MergeValues(p)
if err != nil {
return nil, err
}
chartRequested, err := loader.Load(settings.Chart)
if err != nil {
return nil, err
}
return client.RunWithContext(getContext(settings), chartRequested, vals)
}
func writeCharts(chart *charts.Chart) (string, error) {
dir, err := os.MkdirTemp("", chart.Name)
if err != nil {
return "", err
}
err = chart.Export(dir)
if err != nil {
return "", err
}
return dir, nil
}
func AffinityYaml(orLabels ...string) ([]byte, error) {
affinities := nodeAffinity(orLabels...)
marshal, err := yaml.Marshal(affinities)
if err != nil {
return nil, err
}
return marshal, nil
}
func WithTolerationWorkspaceComponentNotReadyYaml(ctx *common.RenderContext) ([]byte, error) {
tolerations := common.WithTolerationWorkspaceComponentNotReady(ctx)
marshal, err := yaml.Marshal(tolerations)
if err != nil {
return nil, err
}
return marshal, nil
}
func nodeAffinity(orLabels ...string) *corev1.Affinity {
var terms []corev1.NodeSelectorTerm
for _, lbl := range orLabels {
terms = append(terms, corev1.NodeSelectorTerm{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: lbl,
Operator: corev1.NodeSelectorOpExists,
},
},
})
}
return &corev1.Affinity{
NodeAffinity: &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: terms,
},
},
}
}
func ImagePullSecrets(key string, ctx *common.RenderContext) string {
if len(ctx.Config.ImagePullSecrets) > 0 {
var pullSecrets []string
for _, i := range ctx.Config.ImagePullSecrets {
pullSecrets = append(pullSecrets, i.Name)
}
return KeyValueArray(key, pullSecrets)
}
return ""
}
func ImportTemplate(chart *charts.Chart, templateCfg TemplateConfig, pkgConfig PkgConfig) common.HelmFunc {
return func(cfg *common.RenderContext) (r []string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("cannot import template %s: %w", chart.Name, err)
}
}()
helmConfig, err := pkgConfig(cfg)
if err != nil {
return nil, err
}
if !helmConfig.Enabled {
return nil, nil
}
dir, err := writeCharts(chart)
if err != nil {
return nil, err
}
settings := SettingsFactory(
&Config{
Debug: false,
Name: chart.Name,
Namespace: cfg.Namespace,
},
dir,
helmConfig.Values,
)
if _, err := os.Stat(filepath.Join(dir, "charts")); err != nil {
err = installDependencies(settings)
if err != nil {
return nil, err
}
}
client := action.NewInstall(settings.ActionConfig)
client.DryRun = true
client.ReleaseName = "RELEASE-NAME"
client.Replace = true
client.ClientOnly = true
if templateCfg.Namespace == "" {
client.Namespace = cfg.Namespace
} else {
client.Namespace = templateCfg.Namespace
}
rel, err := runInstall(settings, client)
if err != nil {
return nil, err
}
if rel == nil {
return nil, fmt.Errorf("release for %s generated an empty value", settings.Config.Name)
}
var templates []string
for _, obj := range chart.AdditionalFiles {
b, err := chart.Content.ReadFile(obj)
if err != nil {
return nil, err
}
templates = append(templates, string(b))
}
return append(templates, rel.Manifest), nil
}
}
func CustomizeAnnotation(registryValues []string, prefix string, ctx *common.RenderContext, component string, typeMeta metav1.TypeMeta, existingAnnotations ...func() map[string]string) []string {
annotations := common.CustomizeAnnotation(ctx, component, typeMeta, existingAnnotations...)
if len(annotations) > 0 {
for k, v := range annotations {
key := strings.Replace(k, ".", "\\.", -1)
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s.%s", prefix, key), v))
}
}
return registryValues
}
func CustomizeLabel(registryValues []string, prefix string, ctx *common.RenderContext, component string, typeMeta metav1.TypeMeta, existingLabels ...func() map[string]string) []string {
labels := common.CustomizeLabel(ctx, component, typeMeta, existingLabels...)
for k := range common.DefaultLabels(component) {
delete(labels, k)
}
if len(labels) > 0 {
for k, v := range labels {
key := strings.Replace(k, ".", "\\.", -1)
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s.%s", prefix, key), v))
}
}
return registryValues
}
func CustomizeEnvvar(registryValues []string, prefix string, ctx *common.RenderContext, component string, existingEnvvars ...[]corev1.EnvVar) []string {
envvars := common.CustomizeEnvvar(ctx, component, func() []corev1.EnvVar {
envs := make([]corev1.EnvVar, 0)
for _, e := range existingEnvvars {
envs = append(envs, e...)
}
return envs
}())
if len(envvars) > 0 {
for k, v := range envvars {
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s[%d].name", prefix, k), v.Name))
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s[%d].value", prefix, k), v.Value))
}
}
return registryValues
}