Skip to content

Commit

Permalink
add functionality to import helm charts
Browse files Browse the repository at this point in the history
  • Loading branch information
redradrat committed Oct 14, 2020
1 parent e513099 commit c3884cc
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 67 deletions.
63 changes: 63 additions & 0 deletions kable/cmd/helmConcept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
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 cmd

import (
"errors"

"github.com/redradrat/kable/kable/helm"

"github.com/spf13/cobra"
)

var chartVersion string
var chartRepo string
var dir string

// helmImportCmd represents the import command
var helmConceptCmd = &cobra.Command{
Use: "concept",
Short: "Create a concept, wrapping a helm chart from a git repo",
Example: "kable helm concept --directory sentry sentry --repo stable --version 4.3.0",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires exactly ONE argument")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
PrintMsg("Creating concept from helm chart '%s'...", args[0])
if err := helm.InitHelmConcept(helm.HelmChart{Name: args[0], Version: chartVersion, Repo: chartRepo}, dir); err != nil {
PrintError("unable to import helm chart: %s", err)
}
},
}

func init() {
helmCmd.AddCommand(helmConceptCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// helmImportCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
helmConceptCmd.Flags().StringVarP(&chartVersion, "version", "v", "", "The version of the helm chart.")
helmConceptCmd.Flags().StringVarP(&chartRepo, "repo", "r", "stable", "The repo where the helm chart resides.")
helmConceptCmd.Flags().StringVarP(&dir, "directory", "d", ".", "The directory to create the concept in.")
}
16 changes: 8 additions & 8 deletions kable/cmd/helmImport.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package cmd

import (
"errors"
"path/filepath"
"strings"

"github.com/redradrat/kable/kable/concepts"

Expand All @@ -27,13 +25,13 @@ import (
"github.com/spf13/cobra"
)

var subdir string
var importSubdir string

// helmImportCmd represents the import command
var helmImportCmd = &cobra.Command{
Use: "import",
Short: "Import a helm chart from a git repo into the concept (need to be in directory of the concept",
Example: "kable helm import https://github.com/helm/charts --subdir stable/sentry",
Short: "Import a helm chart from a helm repo into the concept",
Example: "kable helm import sentry --repo stable --version 4.3.0",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires exactly ONE argument")
Expand All @@ -44,8 +42,8 @@ var helmImportCmd = &cobra.Command{
if _, err := concepts.GetConcept("."); err != nil {
PrintError("current directory is not a concept directory: %s", err)
}
PrintMsg("Importing helm chart '%s' into current concept...", filepath.Base(filepath.Join(strings.TrimSuffix(args[0], ".git"), subdir)))
if err := helm.ImportHelmChart(helm.HelmChart{URL: args[0], Subdir: &subdir}, "."); err != nil {
PrintMsg("Importing helm chart '%s' into current concept...", args[0])
if err := helm.ImportHelmChart(helm.HelmChart{Name: args[0], Version: chartVersion, Repo: chartRepo}, dir); err != nil {
PrintError("unable to import helm chart: %s", err)
}
},
Expand All @@ -62,5 +60,7 @@ func init() {

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
helmImportCmd.Flags().StringVarP(&subdir, "subdir", "s", "", "The subdirectory in the repo, where the chart resides.")
helmImportCmd.Flags().StringVarP(&chartVersion, "version", "v", "", "The version of the helm chart.")
helmImportCmd.Flags().StringVarP(&chartRepo, "repo", "r", ".", "The repo where the helm chart resides.")
helmImportCmd.Flags().StringVarP(&dir, "directory", "d", ".", "The directory of the concept.")
}
2 changes: 1 addition & 1 deletion kable/cmd/initConcept.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var initConceptCmd = &cobra.Command{
PrintError("given concept type is unsupported")
}

if err := concepts.InitConcept(name, concepts.ConceptType(conceptTypeInput)); err != nil {
if err := concepts.InitConcept(".", name, concepts.ConceptType(conceptTypeInput)); err != nil {
PrintError("unable to initialize concept dir: %s", err)
}
PrintSuccess("Successfully initialized Concept!")
Expand Down
24 changes: 15 additions & 9 deletions kable/concepts/concepts.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func ListConcepts() ([]ConceptRepoInfo, error) {
return repoList, nil
}

func InitConcept(name string, conceptType ConceptType) error {
func InitConcept(workdir, name string, conceptType ConceptType) error {
cpt := Concept{
ApiVersion: 1,
Type: conceptType,
Expand All @@ -332,27 +332,33 @@ func InitConcept(name string, conceptType ConceptType) error {
},
}

if workdir != "." {
if err := os.MkdirAll(workdir, os.ModePerm); err != nil {
return err
}
}

switch conceptType {
case ConceptJsonnetType:
if err := createFile(JsonnetMainTemplate, ConceptMainJsonnet); err != nil {
if err := createFile(JsonnetMainTemplate, filepath.Join(workdir, ConceptMainJsonnet)); err != nil {
return err
}
if err := createFile(JsonnetDepTemplate, ConceptJsonnetfile); err != nil {
if err := createFile(JsonnetDepTemplate, filepath.Join(workdir, ConceptJsonnetfile)); err != nil {
return err
}
if err := createFile(JsonnetMakeFile, ConceptMakefile); err != nil {
if err := createFile(JsonnetMakeFile, filepath.Join(workdir, ConceptMakefile)); err != nil {
return err
}
if err := createFile(JsonnetGitignoreFile, ConceptGitignorefile); err != nil {
if err := createFile(JsonnetGitignoreFile, filepath.Join(workdir, ConceptGitignorefile)); err != nil {
return err
}
if err := os.MkdirAll(ConceptLibDir, os.ModePerm); err != nil {
if err := os.MkdirAll(filepath.Join(workdir, ConceptLibDir), os.ModePerm); err != nil {
return err
}
if err := createFile(JsonnetMainLibTemplate, filepath.Join(ConceptLibDir, ConceptMainlibsonnet)); err != nil {
if err := createFile(JsonnetMainLibTemplate, filepath.Join(filepath.Join(workdir, ConceptLibDir), ConceptMainlibsonnet)); err != nil {
return err
}
if err := createFile(JsonnetLibTemplate, filepath.Join(ConceptLibDir, ConceptKlibsonnet)); err != nil {
if err := createFile(JsonnetLibTemplate, filepath.Join(filepath.Join(workdir, ConceptLibDir), ConceptKlibsonnet)); err != nil {
return err
}

Expand All @@ -365,7 +371,7 @@ func InitConcept(name string, conceptType ConceptType) error {
return errors.ConceptTypeUnsupportedError
}

if err := createJson(cpt, "./concept.json"); err != nil {
if err := createJson(cpt, filepath.Join(workdir, "concept.json")); err != nil {
return err
}
return nil
Expand Down
151 changes: 102 additions & 49 deletions kable/helm/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ package helm
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"text/template"

"github.com/jsonnet-bundler/jsonnet-bundler/spec/v1/deps"
"github.com/grafana/tanka/pkg/helm"

"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
"github.com/redradrat/kable/kable/concepts"

"github.com/otiai10/copy"
"github.com/redradrat/kable/kable/config"
"github.com/jsonnet-bundler/jsonnet-bundler/spec/v1/deps"

"github.com/redradrat/kable/kable/errors"
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
)

const jsonnetTpl = `local helm = (import "github.com/grafana/jsonnet-libs/helm-util/helm.libsonnet").new(std.thisFile);
{
{{.Name}}: helm.template("{{.Name}}", "./charts/{{.Name}}", {
{{.Name}}: helm.template("{{.Name}}", "../charts/{{.Name}}", {
namespace: "default",
values: {
foo: { bar: baz }
Expand All @@ -32,46 +31,107 @@ const jsonnetTpl = `local helm = (import "github.com/grafana/jsonnet-libs/helm-u
}
`

const helmConceptLibTpl = `local helm = (import "github.com/grafana/jsonnet-libs/helm-util/helm.libsonnet").new(std.thisFile);
{
_values:: {
foo: error "missing value foo"
},
{{.Name}}: helm.template("{{.Name}}", "../charts/{{.Name}}", {
namespace: "default",
values: $._values
})
}
`
const helmConceptMainTpl = `local lib = import "lib/{{.Name}}.libsonnet";
local values = {
foo: "test"
};
lib + { _values+: values }
`

type HelmChart struct {
URL string
Subdir *string
Repo string
Name string
Version string
}

func ImportHelmChart(chart HelmChart, out string) error {
basedir := config.CacheDir
path, err := cloneAndCheckRepo(chart.URL, basedir)
func (hc HelmChart) Requirement() string {
return fmt.Sprintf("%s/%s@%s", hc.Repo, hc.Name, hc.Version)
}

func InitHelmConcept(chart HelmChart, out string) error {

wd, err := os.Getwd()
if err != nil {
return err
}
if err := os.MkdirAll(out, os.ModePerm); err != nil {
return err
}
if err := os.Chdir(out); err != nil {
return err
}

var chartPath string
if chart.Subdir != nil {
chartPath = filepath.Join(path, *chart.Subdir)
} else {
chartPath = path
// Initialize a concept
if err := concepts.InitConcept(".", chart.Name, concepts.ConceptJsonnetType); err != nil {
return err
}
libpath := filepath.Join(concepts.ConceptLibDir, concepts.ConceptMainlibsonnet)
if err := os.Remove(libpath); err != nil && !os.IsNotExist(err) {
return err
}
mainpath := filepath.Join(concepts.ConceptMainJsonnet)
if err := os.Remove(mainpath); err != nil && !os.IsNotExist(err) {
return err
}
if err := ImportHelmChart(chart, "."); err != nil {
return err
}

if !isHelmRepo(chartPath) {
return errors.NotHelmChartError
librender, err := templateString(chart.Name, helmConceptLibTpl)
mainrender, err := templateString(chart.Name, helmConceptMainTpl)

if err := ioutil.WriteFile(filepath.Join(concepts.ConceptLibDir, chart.Name+".libsonnet"), librender, 0644); err != nil {
return err
}
if err := ioutil.WriteFile(mainpath, mainrender, 0644); err != nil {
return err
}

chartName := filepath.Base(chartPath)
if err := copy.Copy(chartPath, filepath.Join(out, "/charts/", chartName)); err != nil {
if err := os.Chdir(wd); err != nil {
return err
}

tpl, err := template.New("jsonnet").Parse(jsonnetTpl)
return nil
}

func ImportHelmChart(helmChart HelmChart, out string) error {

// TODO (redradrat): Try to make upstream's logging configurable (silent)
cf, err := helm.InitChartfile(filepath.Join(out, "chartfile.yaml"))
if err != nil {
if os.IsExist(err) {
cf, err = helm.LoadChartfile(filepath.Join(out, "chartfile.yaml"))
if err != nil {
return err
}
}
return err
}
buf := bytes.Buffer{}
if err := tpl.Execute(&buf, struct {
Name string
}{Name: chartName}); err != nil {

if err := cf.Add([]string{helmChart.Requirement()}); err != nil {
return err
}

if err := ioutil.WriteFile(filepath.Join(out, "/lib/", chartName+".jsonnet"), buf.Bytes(), os.ModePerm); err != nil {
libsonnetPath := filepath.Join(out, "/lib/", helmChart.Name+".libsonnet")
render, err := templateString(helmChart.Name, jsonnetTpl)
if err != nil {
return err
}
if err := ioutil.WriteFile(libsonnetPath, render, 0644); err != nil {
return err
}

Expand All @@ -97,9 +157,8 @@ func ImportHelmChart(chart HelmChart, out string) error {
},
Version: "master",
}
name := libDep.Name()
if _, ok := bundle.Dependencies[name]; !ok {
bundle.Dependencies[name] = libDep
if _, ok := bundle.Dependencies[libDep.Name()]; !ok {
bundle.Dependencies[libDep.Name()] = libDep

b, err := json.MarshalIndent(bundle, "", " ")
if err != nil {
Expand All @@ -111,6 +170,9 @@ func ImportHelmChart(chart HelmChart, out string) error {
return err
}

if err := os.Chdir(out); err != nil {
return err
}
if err := exec.Command("jb", "install").Run(); err != nil {
return err
}
Expand All @@ -120,25 +182,16 @@ func ImportHelmChart(chart HelmChart, out string) error {
}
}

func cloneAndCheckRepo(gitUrl, dir string) (string, error) {
uri, err := url.Parse(gitUrl)
func templateString(chart, s string) ([]byte, error) {
tpl, err := template.New("jsonnet").Parse(s)
if err != nil {
return "", err
}

if !(uri.Scheme == "http" || uri.Scheme == "https") {
return "", errors.UnsupportedURISchemeError
return nil, err
}

path := filepath.Join(dir, filepath.Base(uri.Path))
if err := Checkout(gitUrl, path); err != nil {
return "", err
buf := bytes.Buffer{}
if err := tpl.Execute(&buf, struct {
Name string
}{Name: chart}); err != nil {
return nil, err
}

return path, nil
}

func isHelmRepo(path string) bool {
_, err := os.Stat(filepath.Join(path, "Chart.yaml"))
return err == nil
return buf.Bytes(), nil
}

0 comments on commit c3884cc

Please sign in to comment.