appmanager: with web interface to upload chart archives
diff --git a/appmanager/helm.go b/appmanager/helm.go
new file mode 100644
index 0000000..5fd7e8e
--- /dev/null
+++ b/appmanager/helm.go
@@ -0,0 +1,113 @@
+package appmanager
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"github.com/golang/glog"
+	"gopkg.in/yaml.v2"
+)
+
+type Chart struct {
+	Name string `yaml:"name"`
+}
+
+type HelmChart struct {
+	Chart
+	chartDir string
+	Schema   *Schema
+	Yamls    []string
+}
+
+func HelmChartFromDir(chartDir string) (*HelmChart, error) {
+	var chart HelmChart
+	chart.chartDir = chartDir
+	c, err := ReadChart(path.Join(chartDir, "Chart.yaml"))
+	if err != nil {
+		return nil, err
+	}
+	chart.Chart = *c
+	schema, err := ReadSchema(path.Join(chartDir, "Schema.yaml"))
+	if err != nil && os.IsNotExist(err) {
+		return nil, err
+	}
+	chart.Schema = schema
+	return &chart, nil
+}
+
+func HelmChartFromTar(chartTar string) (*HelmChart, error) {
+	if !strings.HasSuffix(chartTar, ".tar.gz") {
+		return nil, errors.New("Expected .tar.gz file")
+	}
+	dir := filepath.Dir(chartTar)
+	archive := filepath.Base(chartTar)
+	extractDir := strings.TrimSuffix(archive, ".tar.gz")
+	cmd := exec.Command(fmt.Sprintf("cd %s && rm -rf %s && tar -ztvf %s -C %s", dir, extractDir, archive, extractDir))
+	if err := cmd.Run(); err != nil {
+		return nil, err
+	}
+	return HelmChartFromDir(dir + "/" + extractDir)
+}
+
+func (h *HelmChart) Install(
+	helmBin string,
+	values map[string]string) error {
+	namespace := fmt.Sprintf("app-%s", h.Chart.Name)
+	cmd := generateHelmInstallCmd(helmBin, h.chartDir, namespace, values)
+	glog.Info(cmd.String())
+	var stdout strings.Builder
+	var stderr strings.Builder
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err := cmd.Run()
+	if err != nil {
+		return errors.New(stderr.String())
+	}
+	glog.Info(stdout.String())
+	return nil
+}
+
+func generateHelmInstallCmd(
+	helmBin string,
+	archive string,
+	namespace string,
+	values map[string]string) *exec.Cmd {
+	cmd := exec.Command(helmBin)
+	cmd.Args = append(cmd.Args, "install")
+	cmd.Args = append(cmd.Args, fmt.Sprintf("--namespace=%s", namespace))
+	cmd.Args = append(cmd.Args, "--generate-name")
+	cmd.Args = append(cmd.Args, fmt.Sprintf("%s", archive))
+	// TODO(giolekva): validate values
+	for key, value := range values {
+		cmd.Args = append(cmd.Args, fmt.Sprintf("--set=%s=%s", key, value))
+	}
+	return cmd
+}
+
+func ChartFromYaml(str string) (*Chart, error) {
+	var s Chart
+	err := yaml.Unmarshal([]byte(str), &s)
+	if err != nil {
+		return nil, err
+	}
+	return &s, nil
+}
+
+func ReadChart(chartFile string) (*Chart, error) {
+	f, err := os.Open(chartFile)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	b, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, err
+	}
+	return ChartFromYaml(string(b))
+}