mirror of https://github.com/helm/helm
parent
f3b0fb3cae
commit
84e61c0391
@ -0,0 +1,107 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/helm/helm/manifest"
|
||||
)
|
||||
|
||||
// Chart represents a complete chart.
|
||||
//
|
||||
// A chart consists of the following parts:
|
||||
//
|
||||
// - Chart.yaml: In code, we refer to this as the Chartfile
|
||||
// - manifests/*.yaml: The Kubernetes manifests
|
||||
//
|
||||
// On the Chart object, the manifests are sorted by type into a handful of
|
||||
// recognized Kubernetes API v1 objects.
|
||||
//
|
||||
// TODO: Investigate treating these as unversioned.
|
||||
type Chart struct {
|
||||
Chartfile *Chartfile
|
||||
|
||||
// Kind is a map of Kind to an array of manifests.
|
||||
//
|
||||
// For example, Kind["Pod"] has an array of Pod manifests.
|
||||
Kind map[string][]*manifest.Manifest
|
||||
|
||||
// Manifests is an array of Manifest objects.
|
||||
Manifests []*manifest.Manifest
|
||||
}
|
||||
|
||||
// Load loads an entire chart.
|
||||
//
|
||||
// This includes the Chart.yaml (*Chartfile) and all of the manifests.
|
||||
//
|
||||
// If you are just reading the Chart.yaml file, it is substantially more
|
||||
// performant to use LoadChartfile.
|
||||
func Load(chart string) (*Chart, error) {
|
||||
if fi, err := os.Stat(chart); err != nil {
|
||||
return nil, err
|
||||
} else if !fi.IsDir() {
|
||||
return nil, fmt.Errorf("Chart %s is not a directory.", chart)
|
||||
}
|
||||
|
||||
cf, err := LoadChartfile(filepath.Join(chart, "Chart.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Chart{
|
||||
Chartfile: cf,
|
||||
Kind: map[string][]*manifest.Manifest{},
|
||||
}
|
||||
|
||||
ms, err := manifest.ParseDir(chart)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
c.attachManifests(ms)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
const (
|
||||
// AnnFile is the annotation key for a file's origin.
|
||||
AnnFile = "chart.helm.sh/file"
|
||||
|
||||
// AnnChartVersion is the annotation key for a chart's version.
|
||||
AnnChartVersion = "chart.helm.sh/version"
|
||||
|
||||
// AnnChartDesc is the annotation key for a chart's description.
|
||||
AnnChartDesc = "chart.helm.sh/description"
|
||||
|
||||
// AnnChartName is the annotation key for a chart name.
|
||||
AnnChartName = "chart.helm.sh/name"
|
||||
)
|
||||
|
||||
// attachManifests sorts manifests into their respective categories, adding to the Chart.
|
||||
func (c *Chart) attachManifests(manifests []*manifest.Manifest) {
|
||||
c.Manifests = manifests
|
||||
for _, m := range manifests {
|
||||
c.Kind[m.Kind] = append(c.Kind[m.Kind], m)
|
||||
}
|
||||
}
|
||||
|
||||
// UnknownKinds returns a list of kinds that this chart contains, but which were not in the passed in array.
|
||||
//
|
||||
// A Chart will store all kinds that are given to it. This makes it possible to get a list of kinds that are not
|
||||
// known beforehand.
|
||||
func (c *Chart) UnknownKinds(known []string) []string {
|
||||
lookup := make(map[string]bool, len(known))
|
||||
for _, k := range known {
|
||||
lookup[k] = true
|
||||
}
|
||||
|
||||
u := []string{}
|
||||
for n := range c.Kind {
|
||||
if _, ok := lookup[n]; !ok {
|
||||
u = append(u, n)
|
||||
}
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testfile = "../testdata/test-Chart.yaml"
|
||||
const testchart = "../testdata/charts/kitchensink"
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
c, err := Load(testchart)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load chart: %s", err)
|
||||
}
|
||||
|
||||
if c.Chartfile.Name != "kitchensink" {
|
||||
t.Errorf("Expected chart name to be 'kitchensink'. Got '%s'.", c.Chartfile.Name)
|
||||
}
|
||||
if c.Chartfile.Dependencies[0].Version != "~10.21" {
|
||||
d := c.Chartfile.Dependencies[0].Version
|
||||
t.Errorf("Expected dependency 0 to have version '~10.21'. Got '%s'.", d)
|
||||
}
|
||||
|
||||
if len(c.Kind["Pod"]) != 3 {
|
||||
t.Errorf("Expected 3 pods, got %d", len(c.Kind["Pod"]))
|
||||
}
|
||||
|
||||
if len(c.Kind["ReplicationController"]) == 0 {
|
||||
t.Error("No RCs found")
|
||||
}
|
||||
if len(c.Kind["Namespace"]) == 0 {
|
||||
t.Errorf("No namespaces found")
|
||||
}
|
||||
|
||||
if len(c.Kind["Secret"]) == 0 {
|
||||
t.Error("Is it secret? Is it safe? NO!")
|
||||
}
|
||||
|
||||
if len(c.Kind["PersistentVolume"]) == 0 {
|
||||
t.Errorf("No volumes.")
|
||||
}
|
||||
|
||||
if len(c.Kind["Service"]) == 0 {
|
||||
t.Error("No service. Just like [insert mobile provider name here]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadChart(t *testing.T) {
|
||||
f, err := LoadChartfile(testfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error loading %s: %s", testfile, err)
|
||||
}
|
||||
|
||||
if f.Name != "alpine-pod" {
|
||||
t.Errorf("Expected alpine-pod, got %s", f.Name)
|
||||
}
|
||||
|
||||
if len(f.Maintainers) != 2 {
|
||||
t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers))
|
||||
}
|
||||
|
||||
if len(f.Dependencies) != 2 {
|
||||
t.Errorf("Expected 2 dependencies, got %d", len(f.Dependencies))
|
||||
}
|
||||
|
||||
if f.Dependencies[1].Name != "bar" {
|
||||
t.Errorf("Expected second dependency to be bar: %q", f.Dependencies[1].Name)
|
||||
}
|
||||
|
||||
if f.PreInstall["mykeys"] != "generate-keypair foo" {
|
||||
t.Errorf("Expected map value for mykeys.")
|
||||
}
|
||||
|
||||
if f.Source[0] != "https://example.com/helm" {
|
||||
t.Errorf("Expected https://example.com/helm, got %s", f.Source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionOK(t *testing.T) {
|
||||
f, err := LoadChartfile(testfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error loading %s: %s", testfile, err)
|
||||
}
|
||||
|
||||
// These are canaries. The SemVer package exhuastively tests the
|
||||
// various permutations. This will alert us if we wired it up
|
||||
// incorrectly.
|
||||
|
||||
d := f.Dependencies[1]
|
||||
if d.VersionOK("1.0.0") {
|
||||
t.Errorf("1.0.0 should have been marked out of range")
|
||||
}
|
||||
|
||||
if !d.VersionOK("1.2.3") {
|
||||
t.Errorf("Version 1.2.3 should have been marked in-range")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUnknownKinds(t *testing.T) {
|
||||
known := []string{"Pod"}
|
||||
c, err := Load(testchart)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load chart: %s", err)
|
||||
}
|
||||
|
||||
unknown := c.UnknownKinds(known)
|
||||
if len(unknown) < 5 {
|
||||
t.Errorf("Expected at least 5 unknown chart types, got %d.", len(unknown))
|
||||
}
|
||||
|
||||
for _, k := range unknown {
|
||||
if k == "Pod" {
|
||||
t.Errorf("Pod is not an unknown kind.")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Chartfile describes a Helm Chart (e.g. Chart.yaml)
|
||||
type Chartfile struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Version string `yaml:"version"`
|
||||
Keywords []string `yaml:"keywords,omitempty"`
|
||||
Maintainers []*Maintainer `yaml:"maintainers,omitempty"`
|
||||
Source []string `yaml:"source,omitempty"`
|
||||
Home string `yaml:"home"`
|
||||
Dependencies []*Dependency `yaml:"dependencies,omitempty"`
|
||||
Environment []*EnvConstraint `yaml:"environment,omitempty"`
|
||||
}
|
||||
|
||||
// Maintainer describes a chart maintainer.
|
||||
type Maintainer struct {
|
||||
Name string `yaml:"name"`
|
||||
Email string `yaml:"email,omitempty"`
|
||||
}
|
||||
|
||||
// Dependency describes a specific dependency.
|
||||
type Dependency struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Version string `yaml:"version"`
|
||||
Location string `yaml:"location"`
|
||||
}
|
||||
|
||||
// Specify environmental constraints.
|
||||
type EnvConstraint struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Extensions []string `yaml:"extensions,omitempty"`
|
||||
APIGroups []string `yaml:"apiGroups,omitempty"`
|
||||
}
|
||||
|
||||
// LoadChartfile loads a Chart.yaml file into a *Chart.
|
||||
func LoadChartfile(filename string) (*Chartfile, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var y Chartfile
|
||||
return &y, yaml.Unmarshal(b, &y)
|
||||
}
|
||||
|
||||
// Save saves a Chart.yaml file
|
||||
func (c *Chartfile) Save(filename string) error {
|
||||
b, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename, b, 0644)
|
||||
}
|
||||
|
||||
// VersionOK returns true if the given version meets the constraints.
|
||||
//
|
||||
// It returns false if the version string or constraint is unparsable or if the
|
||||
// version does not meet the constraint.
|
||||
func (d *Dependency) VersionOK(version string) bool {
|
||||
c, err := semver.NewConstraint(d.Version)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
v, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.Check(v)
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testChart = `#helm:generate foo
|
||||
name: frobniz
|
||||
description: This is a frobniz.
|
||||
version: 1.2.3-alpha.1+12345
|
||||
keywords:
|
||||
- frobnitz
|
||||
- sprocket
|
||||
- dodad
|
||||
maintainers:
|
||||
- name: The Helm Team
|
||||
email: helm@example.com
|
||||
- name: Someone Else
|
||||
email: nobody@example.com
|
||||
source: https://example.com/foo/bar
|
||||
home: http://example.com
|
||||
dependencies:
|
||||
- name: thingerbob
|
||||
location: https://example.com/charts/thingerbob-3.2.1.tgz
|
||||
version: ^3
|
||||
environment:
|
||||
- name: Kubernetes
|
||||
version: ~1.1
|
||||
extensions:
|
||||
- extensions/v1beta1
|
||||
- extensions/v1beta1/daemonset
|
||||
apiGroups:
|
||||
- 3rdParty
|
||||
`
|
||||
|
||||
func TestLoadChartfile(t *testing.T) {
|
||||
out, err := ioutil.TempFile("", "chartfile-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tname := out.Name()
|
||||
defer func() {
|
||||
os.Remove(tname)
|
||||
}()
|
||||
|
||||
out.Write([]byte(testChart))
|
||||
out.Close()
|
||||
|
||||
c, err := LoadChartfile(tname)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to open %s: %s", tname, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.Environment[0].Extensions) != 2 {
|
||||
t.Errorf("Expected two extensions, got %d", len(c.Environment[0].Extensions))
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
hash: db55a031aaa2f352fa5e9e4fda871039afb80e383a57fc77e4b35114d47cca8a
|
||||
updated: 2016-01-26T17:30:54.243252416-07:00
|
||||
imports:
|
||||
- name: github.com/emicklei/go-restful
|
||||
version: b86acf97a74ed7603ac78d012f5535b4d587b156
|
||||
- name: github.com/ghodss/yaml
|
||||
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
||||
- name: github.com/golang/glog
|
||||
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
||||
- name: github.com/golang/protobuf
|
||||
version: 6aaa8d47701fa6cf07e914ec01fde3d4a1fe79c3
|
||||
- name: github.com/google/go-github
|
||||
version: b8b4ac742977310ff6e75140a403a38dab109977
|
||||
subpackages:
|
||||
- /github
|
||||
- name: github.com/google/go-querystring
|
||||
version: 2a60fc2ba6c19de80291203597d752e9ba58e4c0
|
||||
- name: github.com/gorilla/context
|
||||
version: 1c83b3eabd45b6d76072b66b746c20815fb2872d
|
||||
- name: github.com/gorilla/handlers
|
||||
version: 8f2758070a82adb7a3ad6b223a0b91878f32d400
|
||||
- name: github.com/gorilla/mux
|
||||
version: 26a6070f849969ba72b72256e9f14cf519751690
|
||||
- name: github.com/gorilla/schema
|
||||
version: 14c555599c2a4f493c1e13fd1ea6fdf721739028
|
||||
- name: github.com/Masterminds/semver
|
||||
version: c4f7ef0702f269161a60489ccbbc9f1241ad1265
|
||||
- name: github.com/mjibson/appstats
|
||||
version: 0542d5f0e87ea3a8fa4174322b9532f5d04f9fa8
|
||||
- name: golang.org/x/crypto
|
||||
version: 1f22c0103821b9390939b6776727195525381532
|
||||
- name: golang.org/x/net
|
||||
version: 04b9de9b512f58addf28c9853d50ebef61c3953e
|
||||
- name: golang.org/x/oauth2
|
||||
version: 8a57ed94ffd43444c0879fe75701732a38afc985
|
||||
- name: golang.org/x/text
|
||||
version: 6d3c22c4525a4da167968fa2479be5524d2e8bd0
|
||||
- name: google.golang.com/appengine
|
||||
version: ""
|
||||
repo: https://google.golang.com/appengine
|
||||
- name: google.golang.org/api
|
||||
version: 0caa37974a5f5ae67172acf68b4970f7864f994c
|
||||
- name: google.golang.org/appengine
|
||||
version: 6bde959377a90acb53366051d7d587bfd7171354
|
||||
- name: google.golang.org/cloud
|
||||
version: fb10e8da373d97f6ba5e648299a10b3b91f14cd5
|
||||
- name: google.golang.org/grpc
|
||||
version: e29d659177655e589850ba7d3d83f7ce12ef23dd
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
|
||||
devImports: []
|
@ -0,0 +1,11 @@
|
||||
package: github.com/kubernetes/deployment-manager
|
||||
import:
|
||||
- package: github.com/emicklei/go-restful
|
||||
- package: github.com/ghodss/yaml
|
||||
- package: github.com/google/go-github
|
||||
subpackages:
|
||||
- /github
|
||||
- package: github.com/gorilla/handlers
|
||||
- package: github.com/gorilla/mux
|
||||
- package: gopkg.in/yaml.v2
|
||||
- package: github.com/Masterminds/semver
|
@ -0,0 +1,51 @@
|
||||
/* Package log provides simple convenience wrappers for logging.
|
||||
|
||||
Following convention, this provides functions for logging warnings, errors, information
|
||||
and debugging.
|
||||
*/
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// LogReceiver can receive log messages from this package.
|
||||
type LogReceiver interface {
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// Logger is the destination for this package.
|
||||
//
|
||||
// The logger that this prints to.
|
||||
var Logger LogReceiver = log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
// IsDebugging controls debugging output.
|
||||
//
|
||||
// If this is true, debugging messages will be printed. Expensive debugging
|
||||
// operations can be wrapped in `if log.IsDebugging {}`.
|
||||
var IsDebugging bool = false
|
||||
|
||||
// Err prints an error of severity ERROR to the log.
|
||||
func Err(msg string, v ...interface{}) {
|
||||
Logger.Printf("[ERROR] "+msg+"\n", v...)
|
||||
}
|
||||
|
||||
// Warn prints an error severity WARN to the log.
|
||||
func Warn(msg string, v ...interface{}) {
|
||||
Logger.Printf("[WARN] "+msg+"\n", v...)
|
||||
}
|
||||
|
||||
// Info prints an error of severity INFO to the log.
|
||||
func Info(msg string, v ...interface{}) {
|
||||
Logger.Printf("[INFO] "+msg+"\n", v...)
|
||||
}
|
||||
|
||||
// Debug prints an error severity DEBUG to the log.
|
||||
//
|
||||
// Debug will only print if IsDebugging is true.
|
||||
func Debug(msg string, v ...interface{}) {
|
||||
if IsDebugging {
|
||||
Logger.Printf("[DEBUG] "+msg+"\n", v...)
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type LoggerMock struct {
|
||||
b bytes.Buffer
|
||||
}
|
||||
|
||||
func (l *LoggerMock) Printf(m string, v ...interface{}) {
|
||||
l.b.Write([]byte(fmt.Sprintf(m, v...)))
|
||||
}
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
l := &LoggerMock{}
|
||||
Logger = l
|
||||
IsDebugging = true
|
||||
|
||||
Err("%s%s%s", "a", "b", "c")
|
||||
expect := "[ERROR] abc\n"
|
||||
if l.b.String() != expect {
|
||||
t.Errorf("Expected %q, got %q", expect, l.b.String())
|
||||
}
|
||||
l.b.Reset()
|
||||
|
||||
tests := map[string]func(string, ...interface{}){
|
||||
"[WARN] test\n": Warn,
|
||||
"[INFO] test\n": Info,
|
||||
"[DEBUG] test\n": Debug,
|
||||
}
|
||||
|
||||
for expect, f := range tests {
|
||||
f("test")
|
||||
if l.b.String() != expect {
|
||||
t.Errorf("Expected %q, got %q", expect, l.b.String())
|
||||
}
|
||||
l.b.Reset()
|
||||
}
|
||||
|
||||
IsDebugging = false
|
||||
Debug("HELLO")
|
||||
if l.b.String() != "" {
|
||||
t.Errorf("Expected debugging to disable. Got %q", l.b.String())
|
||||
}
|
||||
l.b.Reset()
|
||||
}
|
Loading…
Reference in new issue