Skip to content
Snippets Groups Projects
Commit 5a86720e1bdf authored by steeve.chailloux's avatar steeve.chailloux
Browse files

build helm dependencies prior to build helm charts

parent 58fea55a1052
No related branches found
No related tags found
1 merge request!4Few updates from downstream
Showing with 252 additions and 0 deletions
charts:
hcl1:
type: helm
path: ./hcl1
#!/bin/bash
# Local build demo file
# Demonstrate that leaves dependencies should be built before root ones
set -ex
pushd ./hcl2
helm dependency build
popd
pushd ./hcl1
helm dependency build
helm template .
popd
rm hcl*/{charts,Chart.lock} -r
apiVersion: v2
name: hcl1
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: hcl2
repository: file://../hcl2
version: "*"
apiVersion: v1
kind: ConfigMap
metadata:
name: hcl1
data:
helm: level 1
nothing: special
apiVersion: v2
name: hcl2
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: hcl3
repository: file://../hcl3
version: "*"
apiVersion: v1
kind: ConfigMap
metadata:
name: hcl2
data:
helm: level 2
nothing: special
apiVersion: v2
name: hcl3
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"
apiVersion: v1
kind: ConfigMap
metadata:
name: hcl3
data:
helm: level 3
nothing: special
package runner
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/go-cmd/cmd"
"gopkg.in/yaml.v3"
)
type HelmDependency struct {
Name string
Repository string
}
type HelmChart struct {
Dependencies []HelmDependency
}
func (c CmdConfig) HelmDependencyBuild() error {
paths, err := c.HelmChartsPaths()
if err != nil {
return err
}
c.Logger.Debug().Strs("paths", paths).Msg("found helm dependencies")
for _, p := range paths {
if err := c.HelmBuildDependency(p); err != nil {
return err
}
}
return nil
}
func (c CmdConfig) HelmBuildDependency(path string) error {
args := []string{"dependency", "build", path}
apiCmd := cmd.NewCmd(helmCmd, args...)
stdOut, stdErr, err := RunCMD(apiCmd)
if err != nil {
c.Logger.Err(err).
Str("command", helmCmd).
Str("args", strings.Join(args, " ")).
Str("sdtout", strings.Join(stdOut, "\n")).
Str("stderr", strings.Join(stdErr, "\n")).
Msg("failed to run command")
// Error must be pretty printed to end users /!\
fmt.Printf("\n%s\n\n", strings.Join(stdErr, "\n"))
return fmt.Errorf("failed to run command: %w", err)
}
c.Logger.Debug().
Strs("stdout", stdOut).
Str("path", path).
Msg("helm dependencies successfully built")
return nil
}
func (c CmdConfig) HelmChartsPaths() ([]string, error) {
var allPaths []string
for name, chart := range c.Spec.Charts {
if chart.Type == "helm" {
c.Logger.Debug().
Str("chart", name).
Str("type", chart.Type).
Str("path", chart.Path).
Msg("search helm dependencies for")
paths, err := c.pathsByChart(chart.Path)
if err != nil {
return nil, err
}
for _, p := range paths {
// Avoid infinite loop with circular dependencies.
// Also improve the performance by templating only
// once any given chart in case the dependency is
// used multiple times.
if !contains(allPaths, p) {
allPaths = append(allPaths, p)
}
}
}
}
return allPaths, nil
}
func (c CmdConfig) pathsByChart(path string) ([]string, error) {
var allPaths []string
helmChart, err := getHelmChart(path)
if err != nil {
return nil, err
}
for _, dependency := range helmChart.Dependencies {
c.Logger.Debug().
Str("chart", dependency.Name).
Str("repository", dependency.Repository).
Msg("found helm dependency")
if strings.HasPrefix(dependency.Repository, "file://") {
subChartPath := filepath.Join(
path, strings.TrimPrefix(dependency.Repository, "file://"))
subChartsDependenciesPaths, err := c.pathsByChart(subChartPath)
if err != nil {
return nil, err
}
allPaths = append(allPaths, subChartsDependenciesPaths...)
}
}
allPaths = append(allPaths, path)
return allPaths, nil
}
func getHelmChart(path string) (*HelmChart, error) {
helmChart := HelmChart{}
for _, ext := range []string{"yaml", "yml"} {
helmChartFile := filepath.Join(path, fmt.Sprintf("%s.%s", "Chart", ext))
fileInfo, err := os.Stat(helmChartFile)
if err != nil || fileInfo.IsDir() {
continue
}
helmChartContent, err := os.ReadFile(helmChartFile)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(helmChartContent, &helmChart)
if err != nil {
return nil, err
}
return &helmChart, nil
}
return nil, fmt.Errorf("helm Chart.yaml file not found in %s", path)
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
package runner_test
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"orus.io/orus-io/beaver/runner"
"orus.io/orus-io/beaver/testutils"
)
func TestHelmDependencyBuild(t *testing.T) {
fixtures = "fixtures/f4"
tl := testutils.NewTestLogger(t)
absConfigDir, err := filepath.Abs(fixtures)
require.NoError(t, err)
tmpDir, err := os.MkdirTemp(os.TempDir(), "beaver-")
require.NoError(t, err)
c := runner.NewCmdConfig(tl.Logger(), absConfigDir, "base", false, false, "", "")
require.NoError(t, c.Initialize(tmpDir))
chartsPaths, err := c.HelmChartsPaths()
require.NoError(t, err)
require.Equal(t, 3, len(chartsPaths))
assert.True(t, strings.HasSuffix(chartsPaths[0], "hcl3"))
assert.True(t, strings.HasSuffix(chartsPaths[1], "hcl2"))
assert.True(t, strings.HasSuffix(chartsPaths[2], "hcl1"))
buildDir := filepath.Join(fixtures, "build")
defer func() {
require.NoError(t, runner.CleanDir(buildDir))
}()
r := runner.NewRunner(c)
require.NoError(t, r.Build(tmpDir))
}
...@@ -42,6 +42,9 @@ ...@@ -42,6 +42,9 @@
return fmt.Errorf("cannot prepare variables: %w", err) return fmt.Errorf("cannot prepare variables: %w", err)
} }
var outputDir string var outputDir string
if err := r.config.HelmDependencyBuild(); err != nil {
return err
}
if r.config.Output == "" { if r.config.Output == "" {
w := bytes.NewBuffer([]byte{}) w := bytes.NewBuffer([]byte{})
if err := hydrateString(r.config.Namespace, w, variables); err != nil { if err := hydrateString(r.config.Namespace, w, variables); err != nil {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment