Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helm plugin #55

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ node_modules
/src/api.ts
/src/shapes.ts

/plugins/jk-plugin-helm/jk-plugin-helm

/examples/*/package-lock.json
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.PHONY: all dist clean gen test copy-schemas
.PHONY: all dist clean gen test copy-schemas install FORCE

all: gen
PLUGINS = \
plugins/jk-plugin-helm/jk-plugin-helm \
$(NULL)

all: gen $(PLUGINS)

gen:
GO111MODULE=on go run ./cmd/apigen/ cmd/apigen/specs/swagger-v1.13.0.json cmd/apigen/templates ./src/
Expand Down Expand Up @@ -28,3 +32,10 @@ copy-schemas:
for d in schemas/*-local; do \
cp -R "$$d" src/schemas/; \
done

plugins/jk-plugin-helm/jk-plugin-helm: FORCE
cd $(@D) && GO111MODULE=on go build -o $(@F) -ldflags '-s -w' .

D := $(shell go env GOPATH)/bin
install: $(PLUGINS)
$(foreach p,$(PLUGINS),cp $(p) $(D))
19 changes: 19 additions & 0 deletions plugins/jk-plugin-helm/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module github.com/jkcfg/kubernetes/plugins/jk-plugin-helm

go 1.12

require (
github.com/hashicorp/go-hclog v0.10.0
github.com/hashicorp/go-plugin v1.0.1
github.com/jkcfg/jk v0.0.0-00010101000000-000000000000
github.com/pkg/errors v0.8.1
helm.sh/helm/v3 v3.0.0
sigs.k8s.io/yaml v1.1.0
)

replace (
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
github.com/jkcfg/jk => ../../../jk
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48

)
632 changes: 632 additions & 0 deletions plugins/jk-plugin-helm/go.sum

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions plugins/jk-plugin-helm/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"io"

"github.com/pkg/errors"

"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"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"
)

func runInstall(client *action.Install, params *Input, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)

client.DryRun = true
client.Replace = true // Skip the name check
client.ClientOnly = true

if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0"
}

client.ReleaseName = params.Release.Name
client.Namespace = params.Release.Namespace

cp, err := client.ChartPathOptions.LocateChart(params.Chart, settings)
if err != nil {
return nil, err
}

debug("CHART PATH: %s", cp)

// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp)
if err != nil {
return nil, err
}

validInstallableChart, err := isChartInstallable(chartRequested)
if !validInstallableChart {
return nil, err
}

if req := chartRequested.Metadata.Dependencies; req != nil {
// If CheckDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209
if err := action.CheckDependencies(chartRequested, req); err != nil {
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: cp,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: getter.All(settings),
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
}
if err := man.Update(); err != nil {
return nil, err
}
} else {
return nil, err
}
}
}

return client.Run(chartRequested, params.Values)
}

// isChartInstallable validates if a chart can be installed
//
// Application chart type is only installable
func isChartInstallable(ch *chart.Chart) (bool, error) {
switch ch.Metadata.Type {
case "", "application":
return true, nil
}
return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type)
}
143 changes: 143 additions & 0 deletions plugins/jk-plugin-helm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"regexp"

"github.com/pkg/errors"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"

"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver"

"sigs.k8s.io/yaml"

"github.com/jkcfg/jk/pkg/plugin/renderer"
)

var log hclog.Logger = hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
Output: os.Stderr,
})

func debug(format string, v ...interface{}) {
log.Debug(fmt.Sprintf(format, v...))
}

// Helm renders helm charts.
type Helm struct {
}

var settings = cli.New()

// Input is the input parameters given to the plugin.
type Input struct {
Release struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
} `json:"release"`
Chart string `json:"chart"`
Values map[string]interface{} `json:"values"`
}

// OutputFile is one file in the render output.
type OutputFile struct {
Source string `json:"source"`
Object map[string]interface{} `json:"object"`
}

// Output is the output of the render function.
type Output struct {
Files []OutputFile `json:"files"`
}

func (i *Input) validate() error {
if i.Chart == "" {
return errors.New("missing chart name: provide 'chart' input parameter")
}

if i.Release.Name == "" {
return errors.New("missing release name: provide a 'release.name' input parameter")
}

if i.Release.Namespace == "" {
i.Release.Namespace = "default"
}

return nil
}

// Render implements renderer.Renderer.
func (h *Helm) Render(input []byte) ([]byte, error) {
var params Input
if err := json.Unmarshal(input, &params); err != nil {
return nil, errors.New("unable to unmarshal input parameters")
}
if err := params.validate(); err != nil {
return nil, err
}

cfg := action.Configuration{
Releases: storage.Init(driver.NewMemory()),
KubeClient: &fake.PrintingKubeClient{Out: ioutil.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
}

client := action.NewInstall(&cfg)
rel, err := runInstall(client, &params, os.Stdout)
if err != nil {
return nil, err
}

// Prepare the Output.
splitManifests := releaseutil.SplitManifests(rel.Manifest)
manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
var output Output
for _, manifest := range splitManifests {
var file OutputFile

// Retrieve the manifest name.
submatch := manifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) != 0 {
file.Source = submatch[1]
}

if err := yaml.Unmarshal([]byte(manifest), &file.Object); err != nil {
return nil, errors.Wrapf(err, "unable to parse YAML ('%s')", file.Source)
}

output.Files = append(output.Files, file)
}

data, err := json.Marshal(output)
if err != nil {
return nil, errors.Wrap(err, "unable to serialize output")
}

return data, nil
}

func main() {
r := &Helm{}

// pluginMap is the map of plugins we can dispense.
var pluginMap = map[string]plugin.Plugin{
"renderer": &renderer.Plugin{Impl: r},
}

plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: renderer.RendererV1,
Plugins: pluginMap,
})
}
45 changes: 45 additions & 0 deletions src/helm/chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { render } from '@jkcfg/std/render';

interface RenderedTemplate {
source?: string;
object: object;
}

class RenderedChart {
files: RenderedTemplate[];

objects(): object[] {
return this.files.map(entry => entry.object);
}
}

interface Release {
name: string;
namespace?: string;
}

class Chart {
name: string
version?: string

constructor(name: string) {
this.name = name;
}

render(release: Release, values: object): Promise<RenderedChart> {
return render('helm.json', {
chart: this.name,
release,
values,
}).then((data: RenderedChart) => {
const chart = new RenderedChart()
chart.files = data.files;
return chart;
})
}
}

export {
RenderedChart,
Chart,
};
1 change: 1 addition & 0 deletions src/helm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './chart';