You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
12 KiB
Go
438 lines
12 KiB
Go
/*
|
|
Copyright The Helm Authors.
|
|
|
|
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 helm // import "k8s.io/helm/pkg/helm"
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
"golang.org/x/net/context"
|
|
"k8s.io/helm/pkg/chartutil"
|
|
"k8s.io/helm/pkg/manifest"
|
|
"k8s.io/helm/pkg/proto/hapi/chart"
|
|
"k8s.io/helm/pkg/proto/hapi/release"
|
|
rls "k8s.io/helm/pkg/proto/hapi/services"
|
|
"k8s.io/helm/pkg/proto/hapi/version"
|
|
"k8s.io/helm/pkg/renderutil"
|
|
storageerrors "k8s.io/helm/pkg/storage/errors"
|
|
)
|
|
|
|
// FakeClient implements Interface
|
|
type FakeClient struct {
|
|
Rels []*release.Release
|
|
Responses map[string]release.TestRun_Status
|
|
Opts options
|
|
RenderManifests bool
|
|
}
|
|
|
|
// Option returns the fake release client
|
|
func (c *FakeClient) Option(opts ...Option) Interface {
|
|
for _, opt := range opts {
|
|
opt(&c.Opts)
|
|
}
|
|
return c
|
|
}
|
|
|
|
var _ Interface = &FakeClient{}
|
|
var _ Interface = (*FakeClient)(nil)
|
|
|
|
// ListReleases lists the current releases
|
|
func (c *FakeClient) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
|
|
reqOpts := c.Opts
|
|
for _, opt := range opts {
|
|
opt(&reqOpts)
|
|
}
|
|
req := &reqOpts.listReq
|
|
rels := c.Rels
|
|
count := int64(len(c.Rels))
|
|
var next string
|
|
limit := req.GetLimit()
|
|
// TODO: Handle all other options.
|
|
if limit != 0 && limit < count {
|
|
rels = rels[:limit]
|
|
count = limit
|
|
next = c.Rels[limit].GetName()
|
|
}
|
|
|
|
resp := &rls.ListReleasesResponse{
|
|
Count: count,
|
|
Releases: rels,
|
|
}
|
|
if next != "" {
|
|
resp.Next = next
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// InstallRelease creates a new release and returns a InstallReleaseResponse containing that release
|
|
func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
|
|
chart := &chart.Chart{}
|
|
return c.InstallReleaseFromChart(chart, ns, opts...)
|
|
}
|
|
|
|
// InstallReleaseWithContext creates a new release and returns a InstallReleaseResponse containing that release and accepts a context
|
|
func (c *FakeClient) InstallReleaseWithContext(ctx context.Context, chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
|
|
return c.InstallRelease(chStr, ns, opts...)
|
|
}
|
|
|
|
// InstallReleaseFromChartWithContext adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release and accepts a context
|
|
func (c *FakeClient) InstallReleaseFromChartWithContext(ctx context.Context, chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
|
|
return c.InstallReleaseFromChart(chart, ns, opts...)
|
|
}
|
|
|
|
// InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release
|
|
func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
|
|
for _, opt := range opts {
|
|
opt(&c.Opts)
|
|
}
|
|
|
|
releaseName := c.Opts.instReq.Name
|
|
releaseDescription := c.Opts.instReq.Description
|
|
|
|
// Check to see if the release already exists.
|
|
rel, err := c.ReleaseStatus(releaseName, nil)
|
|
if err == nil && rel != nil {
|
|
return nil, errors.New("cannot re-use a name that is still in use")
|
|
}
|
|
|
|
mockOpts := &MockReleaseOptions{
|
|
Name: releaseName,
|
|
Chart: chart,
|
|
Config: c.Opts.instReq.Values,
|
|
Namespace: ns,
|
|
Description: releaseDescription,
|
|
}
|
|
|
|
release := ReleaseMock(mockOpts)
|
|
|
|
if c.RenderManifests {
|
|
if err := RenderReleaseMock(release, false); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !c.Opts.dryRun {
|
|
c.Rels = append(c.Rels, release)
|
|
}
|
|
|
|
return &rls.InstallReleaseResponse{
|
|
Release: release,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteRelease deletes a release from the FakeClient
|
|
func (c *FakeClient) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
|
|
for i, rel := range c.Rels {
|
|
if rel.Name == rlsName {
|
|
c.Rels = append(c.Rels[:i], c.Rels[i+1:]...)
|
|
return &rls.UninstallReleaseResponse{
|
|
Release: rel,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
return nil, storageerrors.ErrReleaseNotFound(rlsName)
|
|
}
|
|
|
|
// GetVersion returns a fake version
|
|
func (c *FakeClient) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) {
|
|
return &rls.GetVersionResponse{
|
|
Version: &version.Version{
|
|
SemVer: "1.2.3-fakeclient+testonly",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists
|
|
func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
|
|
return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...)
|
|
}
|
|
|
|
// UpdateReleaseWithContext returns an UpdateReleaseResponse containing the updated release, if it exists and accepts a context
|
|
func (c *FakeClient) UpdateReleaseWithContext(ctx context.Context, rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
|
|
return c.UpdateRelease(rlsName, chStr, opts...)
|
|
}
|
|
|
|
// UpdateReleaseFromChartWithContext returns an UpdateReleaseResponse containing the updated release, if it exists and accepts a context
|
|
func (c *FakeClient) UpdateReleaseFromChartWithContext(ctx context.Context, rlsName string, newChart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
|
|
return c.UpdateReleaseFromChart(rlsName, newChart, opts...)
|
|
}
|
|
|
|
// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists
|
|
func (c *FakeClient) UpdateReleaseFromChart(rlsName string, newChart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
|
|
for _, opt := range opts {
|
|
opt(&c.Opts)
|
|
}
|
|
// Check to see if the release already exists.
|
|
rel, err := c.ReleaseContent(rlsName, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mockOpts := &MockReleaseOptions{
|
|
Name: rel.Release.Name,
|
|
Version: rel.Release.Version + 1,
|
|
Chart: newChart,
|
|
Config: c.Opts.updateReq.Values,
|
|
Namespace: rel.Release.Namespace,
|
|
Description: c.Opts.updateReq.Description,
|
|
}
|
|
|
|
newRelease := ReleaseMock(mockOpts)
|
|
|
|
if c.Opts.updateReq.ResetValues {
|
|
newRelease.Config = &chart.Config{Raw: "{}"}
|
|
} else if c.Opts.updateReq.ReuseValues {
|
|
// TODO: This should merge old and new values but does not.
|
|
}
|
|
|
|
if c.RenderManifests {
|
|
if err := RenderReleaseMock(newRelease, true); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !c.Opts.dryRun {
|
|
*rel.Release = *newRelease
|
|
}
|
|
|
|
return &rls.UpdateReleaseResponse{Release: newRelease}, nil
|
|
}
|
|
|
|
// RollbackRelease returns nil, nil
|
|
func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// ReleaseStatus returns a release status response with info from the matching release name.
|
|
func (c *FakeClient) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
|
|
for _, rel := range c.Rels {
|
|
if rel.Name == rlsName {
|
|
return &rls.GetReleaseStatusResponse{
|
|
Name: rel.Name,
|
|
Info: rel.Info,
|
|
Namespace: rel.Namespace,
|
|
}, nil
|
|
}
|
|
}
|
|
return nil, storageerrors.ErrReleaseNotFound(rlsName)
|
|
}
|
|
|
|
// ReleaseContent returns the configuration for the matching release name in the fake release client.
|
|
func (c *FakeClient) ReleaseContent(rlsName string, opts ...ContentOption) (resp *rls.GetReleaseContentResponse, err error) {
|
|
for _, rel := range c.Rels {
|
|
if rel.Name == rlsName {
|
|
return &rls.GetReleaseContentResponse{
|
|
Release: rel,
|
|
}, nil
|
|
}
|
|
}
|
|
return resp, storageerrors.ErrReleaseNotFound(rlsName)
|
|
}
|
|
|
|
// ReleaseHistory returns a release's revision history.
|
|
func (c *FakeClient) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) {
|
|
reqOpts := c.Opts
|
|
for _, opt := range opts {
|
|
opt(&reqOpts)
|
|
}
|
|
maxLen := int(reqOpts.histReq.Max)
|
|
|
|
var resp rls.GetHistoryResponse
|
|
for _, rel := range c.Rels {
|
|
if maxLen > 0 && len(resp.Releases) >= maxLen {
|
|
return &resp, nil
|
|
}
|
|
if rel.Name == rlsName {
|
|
resp.Releases = append(resp.Releases, rel)
|
|
}
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// RunReleaseTest executes a pre-defined tests on a release
|
|
func (c *FakeClient) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
|
|
|
|
results := make(chan *rls.TestReleaseResponse)
|
|
errc := make(chan error, 1)
|
|
|
|
go func() {
|
|
var wg sync.WaitGroup
|
|
for m, s := range c.Responses {
|
|
wg.Add(1)
|
|
|
|
go func(msg string, status release.TestRun_Status) {
|
|
defer wg.Done()
|
|
results <- &rls.TestReleaseResponse{Msg: msg, Status: status}
|
|
}(m, s)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(results)
|
|
close(errc)
|
|
}()
|
|
|
|
return results, errc
|
|
}
|
|
|
|
// PingTiller pings the Tiller pod and ensures that it is up and running
|
|
func (c *FakeClient) PingTiller() error {
|
|
return nil
|
|
}
|
|
|
|
// MockHookTemplate is the hook template used for all mock release objects.
|
|
var MockHookTemplate = `apiVersion: v1
|
|
kind: Job
|
|
metadata:
|
|
annotations:
|
|
"helm.sh/hook": pre-install
|
|
`
|
|
|
|
// MockManifest is the manifest used for all mock release objects.
|
|
var MockManifest = `apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: fixture
|
|
`
|
|
|
|
// MockReleaseOptions allows for user-configurable options on mock release objects.
|
|
type MockReleaseOptions struct {
|
|
Name string
|
|
Version int32
|
|
Chart *chart.Chart
|
|
Config *chart.Config
|
|
StatusCode release.Status_Code
|
|
Namespace string
|
|
Description string
|
|
}
|
|
|
|
// ReleaseMock creates a mock release object based on options set by
|
|
// MockReleaseOptions. This function should typically not be used outside of
|
|
// testing.
|
|
func ReleaseMock(opts *MockReleaseOptions) *release.Release {
|
|
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
|
|
|
|
name := opts.Name
|
|
if name == "" {
|
|
name = fmt.Sprintf("testrelease-%d", rand.Intn(100))
|
|
}
|
|
|
|
var version int32 = 1
|
|
if opts.Version != 0 {
|
|
version = opts.Version
|
|
}
|
|
|
|
namespace := opts.Namespace
|
|
if namespace == "" {
|
|
namespace = "default"
|
|
}
|
|
|
|
description := opts.Description
|
|
if description == "" {
|
|
description = "Release mock"
|
|
}
|
|
|
|
ch := opts.Chart
|
|
if opts.Chart == nil {
|
|
ch = &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "foo",
|
|
Version: "0.1.0-beta.1",
|
|
},
|
|
Templates: []*chart.Template{
|
|
{Name: "templates/foo.tpl", Data: []byte(MockManifest)},
|
|
},
|
|
}
|
|
}
|
|
|
|
config := opts.Config
|
|
if config == nil {
|
|
config = &chart.Config{Raw: `name: "value"`}
|
|
}
|
|
|
|
scode := release.Status_DEPLOYED
|
|
if opts.StatusCode > 0 {
|
|
scode = opts.StatusCode
|
|
}
|
|
|
|
return &release.Release{
|
|
Name: name,
|
|
Info: &release.Info{
|
|
FirstDeployed: &date,
|
|
LastDeployed: &date,
|
|
Status: &release.Status{Code: scode},
|
|
Description: description,
|
|
},
|
|
Chart: ch,
|
|
Config: config,
|
|
Version: version,
|
|
Namespace: namespace,
|
|
Hooks: []*release.Hook{
|
|
{
|
|
Name: "pre-install-hook",
|
|
Kind: "Job",
|
|
Path: "pre-install-hook.yaml",
|
|
Manifest: MockHookTemplate,
|
|
LastRun: &date,
|
|
Events: []release.Hook_Event{release.Hook_PRE_INSTALL},
|
|
},
|
|
},
|
|
Manifest: MockManifest,
|
|
}
|
|
}
|
|
|
|
// RenderReleaseMock will take a release (usually produced by helm.ReleaseMock)
|
|
// and will render the Manifest inside using the local mechanism (no tiller).
|
|
// (Compare to renderResources in pkg/tiller)
|
|
func RenderReleaseMock(r *release.Release, asUpgrade bool) error {
|
|
if r == nil || r.Chart == nil || r.Chart.Metadata == nil {
|
|
return errors.New("a release with a chart with metadata must be provided to render the manifests")
|
|
}
|
|
|
|
renderOpts := renderutil.Options{
|
|
ReleaseOptions: chartutil.ReleaseOptions{
|
|
Name: r.Name,
|
|
Namespace: r.Namespace,
|
|
Time: r.Info.LastDeployed,
|
|
Revision: int(r.Version),
|
|
IsUpgrade: asUpgrade,
|
|
IsInstall: !asUpgrade,
|
|
},
|
|
}
|
|
rendered, err := renderutil.Render(r.Chart, r.Config, renderOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
for _, m := range manifest.SplitManifests(rendered) {
|
|
// Remove empty manifests
|
|
if len(strings.TrimSpace(m.Content)) == 0 {
|
|
continue
|
|
}
|
|
b.WriteString("\n---\n# Source: " + m.Name + "\n")
|
|
b.WriteString(m.Content)
|
|
}
|
|
r.Manifest = b.String()
|
|
return nil
|
|
}
|