github.com/Beeketing/helm@v2.12.1+incompatible/pkg/helm/fake.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package helm // import "k8s.io/helm/pkg/helm"
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"math/rand"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/golang/protobuf/ptypes/timestamp"
    27  	"k8s.io/helm/pkg/chartutil"
    28  	"k8s.io/helm/pkg/manifest"
    29  	"k8s.io/helm/pkg/proto/hapi/chart"
    30  	"k8s.io/helm/pkg/proto/hapi/release"
    31  	rls "k8s.io/helm/pkg/proto/hapi/services"
    32  	"k8s.io/helm/pkg/proto/hapi/version"
    33  	"k8s.io/helm/pkg/renderutil"
    34  	storageerrors "k8s.io/helm/pkg/storage/errors"
    35  )
    36  
    37  // FakeClient implements Interface
    38  type FakeClient struct {
    39  	Rels            []*release.Release
    40  	Responses       map[string]release.TestRun_Status
    41  	Opts            options
    42  	RenderManifests bool
    43  }
    44  
    45  // Option returns the fake release client
    46  func (c *FakeClient) Option(opts ...Option) Interface {
    47  	for _, opt := range opts {
    48  		opt(&c.Opts)
    49  	}
    50  	return c
    51  }
    52  
    53  var _ Interface = &FakeClient{}
    54  var _ Interface = (*FakeClient)(nil)
    55  
    56  // ListReleases lists the current releases
    57  func (c *FakeClient) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
    58  	reqOpts := c.Opts
    59  	for _, opt := range opts {
    60  		opt(&reqOpts)
    61  	}
    62  	req := &reqOpts.listReq
    63  	rels := c.Rels
    64  	count := int64(len(c.Rels))
    65  	var next string
    66  	limit := req.GetLimit()
    67  	// TODO: Handle all other options.
    68  	if limit != 0 && limit < count {
    69  		rels = rels[:limit]
    70  		count = limit
    71  		next = c.Rels[limit].GetName()
    72  	}
    73  
    74  	resp := &rls.ListReleasesResponse{
    75  		Count:    count,
    76  		Releases: rels,
    77  	}
    78  	if next != "" {
    79  		resp.Next = next
    80  	}
    81  	return resp, nil
    82  }
    83  
    84  // InstallRelease creates a new release and returns a InstallReleaseResponse containing that release
    85  func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
    86  	chart := &chart.Chart{}
    87  	return c.InstallReleaseFromChart(chart, ns, opts...)
    88  }
    89  
    90  // InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release
    91  func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
    92  	for _, opt := range opts {
    93  		opt(&c.Opts)
    94  	}
    95  
    96  	releaseName := c.Opts.instReq.Name
    97  	releaseDescription := c.Opts.instReq.Description
    98  
    99  	// Check to see if the release already exists.
   100  	rel, err := c.ReleaseStatus(releaseName, nil)
   101  	if err == nil && rel != nil {
   102  		return nil, errors.New("cannot re-use a name that is still in use")
   103  	}
   104  
   105  	mockOpts := &MockReleaseOptions{
   106  		Name:        releaseName,
   107  		Chart:       chart,
   108  		Config:      c.Opts.instReq.Values,
   109  		Namespace:   ns,
   110  		Description: releaseDescription,
   111  	}
   112  
   113  	release := ReleaseMock(mockOpts)
   114  
   115  	if c.RenderManifests {
   116  		if err := RenderReleaseMock(release, false); err != nil {
   117  			return nil, err
   118  		}
   119  	}
   120  
   121  	if !c.Opts.dryRun {
   122  		c.Rels = append(c.Rels, release)
   123  	}
   124  
   125  	return &rls.InstallReleaseResponse{
   126  		Release: release,
   127  	}, nil
   128  }
   129  
   130  // DeleteRelease deletes a release from the FakeClient
   131  func (c *FakeClient) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
   132  	for i, rel := range c.Rels {
   133  		if rel.Name == rlsName {
   134  			c.Rels = append(c.Rels[:i], c.Rels[i+1:]...)
   135  			return &rls.UninstallReleaseResponse{
   136  				Release: rel,
   137  			}, nil
   138  		}
   139  	}
   140  
   141  	return nil, storageerrors.ErrReleaseNotFound(rlsName)
   142  }
   143  
   144  // GetVersion returns a fake version
   145  func (c *FakeClient) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) {
   146  	return &rls.GetVersionResponse{
   147  		Version: &version.Version{
   148  			SemVer: "1.2.3-fakeclient+testonly",
   149  		},
   150  	}, nil
   151  }
   152  
   153  // UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists
   154  func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
   155  	return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...)
   156  }
   157  
   158  // UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists
   159  func (c *FakeClient) UpdateReleaseFromChart(rlsName string, newChart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
   160  	for _, opt := range opts {
   161  		opt(&c.Opts)
   162  	}
   163  	// Check to see if the release already exists.
   164  	rel, err := c.ReleaseContent(rlsName, nil)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	mockOpts := &MockReleaseOptions{
   170  		Name:        rel.Release.Name,
   171  		Version:     rel.Release.Version + 1,
   172  		Chart:       newChart,
   173  		Config:      c.Opts.updateReq.Values,
   174  		Namespace:   rel.Release.Namespace,
   175  		Description: c.Opts.updateReq.Description,
   176  	}
   177  
   178  	newRelease := ReleaseMock(mockOpts)
   179  
   180  	if c.Opts.updateReq.ResetValues {
   181  		newRelease.Config = &chart.Config{Raw: "{}"}
   182  	} else if c.Opts.updateReq.ReuseValues {
   183  		// TODO: This should merge old and new values but does not.
   184  	}
   185  
   186  	if c.RenderManifests {
   187  		if err := RenderReleaseMock(newRelease, true); err != nil {
   188  			return nil, err
   189  		}
   190  	}
   191  
   192  	if !c.Opts.dryRun {
   193  		*rel.Release = *newRelease
   194  	}
   195  
   196  	return &rls.UpdateReleaseResponse{Release: newRelease}, nil
   197  }
   198  
   199  // RollbackRelease returns nil, nil
   200  func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
   201  	return nil, nil
   202  }
   203  
   204  // ReleaseStatus returns a release status response with info from the matching release name.
   205  func (c *FakeClient) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
   206  	for _, rel := range c.Rels {
   207  		if rel.Name == rlsName {
   208  			return &rls.GetReleaseStatusResponse{
   209  				Name:      rel.Name,
   210  				Info:      rel.Info,
   211  				Namespace: rel.Namespace,
   212  			}, nil
   213  		}
   214  	}
   215  	return nil, storageerrors.ErrReleaseNotFound(rlsName)
   216  }
   217  
   218  // ReleaseContent returns the configuration for the matching release name in the fake release client.
   219  func (c *FakeClient) ReleaseContent(rlsName string, opts ...ContentOption) (resp *rls.GetReleaseContentResponse, err error) {
   220  	for _, rel := range c.Rels {
   221  		if rel.Name == rlsName {
   222  			return &rls.GetReleaseContentResponse{
   223  				Release: rel,
   224  			}, nil
   225  		}
   226  	}
   227  	return resp, storageerrors.ErrReleaseNotFound(rlsName)
   228  }
   229  
   230  // ReleaseHistory returns a release's revision history.
   231  func (c *FakeClient) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) {
   232  	return &rls.GetHistoryResponse{Releases: c.Rels}, nil
   233  }
   234  
   235  // RunReleaseTest executes a pre-defined tests on a release
   236  func (c *FakeClient) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
   237  
   238  	results := make(chan *rls.TestReleaseResponse)
   239  	errc := make(chan error, 1)
   240  
   241  	go func() {
   242  		var wg sync.WaitGroup
   243  		for m, s := range c.Responses {
   244  			wg.Add(1)
   245  
   246  			go func(msg string, status release.TestRun_Status) {
   247  				defer wg.Done()
   248  				results <- &rls.TestReleaseResponse{Msg: msg, Status: status}
   249  			}(m, s)
   250  		}
   251  
   252  		wg.Wait()
   253  		close(results)
   254  		close(errc)
   255  	}()
   256  
   257  	return results, errc
   258  }
   259  
   260  // PingTiller pings the Tiller pod and ensure's that it is up and running
   261  func (c *FakeClient) PingTiller() error {
   262  	return nil
   263  }
   264  
   265  // MockHookTemplate is the hook template used for all mock release objects.
   266  var MockHookTemplate = `apiVersion: v1
   267  kind: Job
   268  metadata:
   269    annotations:
   270      "helm.sh/hook": pre-install
   271  `
   272  
   273  // MockManifest is the manifest used for all mock release objects.
   274  var MockManifest = `apiVersion: v1
   275  kind: Secret
   276  metadata:
   277    name: fixture
   278  `
   279  
   280  // MockReleaseOptions allows for user-configurable options on mock release objects.
   281  type MockReleaseOptions struct {
   282  	Name        string
   283  	Version     int32
   284  	Chart       *chart.Chart
   285  	Config      *chart.Config
   286  	StatusCode  release.Status_Code
   287  	Namespace   string
   288  	Description string
   289  }
   290  
   291  // ReleaseMock creates a mock release object based on options set by
   292  // MockReleaseOptions. This function should typically not be used outside of
   293  // testing.
   294  func ReleaseMock(opts *MockReleaseOptions) *release.Release {
   295  	date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
   296  
   297  	name := opts.Name
   298  	if name == "" {
   299  		name = "testrelease-" + string(rand.Intn(100))
   300  	}
   301  
   302  	var version int32 = 1
   303  	if opts.Version != 0 {
   304  		version = opts.Version
   305  	}
   306  
   307  	namespace := opts.Namespace
   308  	if namespace == "" {
   309  		namespace = "default"
   310  	}
   311  
   312  	description := opts.Description
   313  	if description == "" {
   314  		description = "Release mock"
   315  	}
   316  
   317  	ch := opts.Chart
   318  	if opts.Chart == nil {
   319  		ch = &chart.Chart{
   320  			Metadata: &chart.Metadata{
   321  				Name:    "foo",
   322  				Version: "0.1.0-beta.1",
   323  			},
   324  			Templates: []*chart.Template{
   325  				{Name: "templates/foo.tpl", Data: []byte(MockManifest)},
   326  			},
   327  		}
   328  	}
   329  
   330  	config := opts.Config
   331  	if config == nil {
   332  		config = &chart.Config{Raw: `name: "value"`}
   333  	}
   334  
   335  	scode := release.Status_DEPLOYED
   336  	if opts.StatusCode > 0 {
   337  		scode = opts.StatusCode
   338  	}
   339  
   340  	return &release.Release{
   341  		Name: name,
   342  		Info: &release.Info{
   343  			FirstDeployed: &date,
   344  			LastDeployed:  &date,
   345  			Status:        &release.Status{Code: scode},
   346  			Description:   description,
   347  		},
   348  		Chart:     ch,
   349  		Config:    config,
   350  		Version:   version,
   351  		Namespace: namespace,
   352  		Hooks: []*release.Hook{
   353  			{
   354  				Name:     "pre-install-hook",
   355  				Kind:     "Job",
   356  				Path:     "pre-install-hook.yaml",
   357  				Manifest: MockHookTemplate,
   358  				LastRun:  &date,
   359  				Events:   []release.Hook_Event{release.Hook_PRE_INSTALL},
   360  			},
   361  		},
   362  		Manifest: MockManifest,
   363  	}
   364  }
   365  
   366  // RenderReleaseMock will take a release (usually produced by helm.ReleaseMock)
   367  // and will render the Manifest inside using the local mechanism (no tiller).
   368  // (Compare to renderResources in pkg/tiller)
   369  func RenderReleaseMock(r *release.Release, asUpgrade bool) error {
   370  	if r == nil || r.Chart == nil || r.Chart.Metadata == nil {
   371  		return errors.New("a release with a chart with metadata must be provided to render the manifests")
   372  	}
   373  
   374  	renderOpts := renderutil.Options{
   375  		ReleaseOptions: chartutil.ReleaseOptions{
   376  			Name:      r.Name,
   377  			Namespace: r.Namespace,
   378  			Time:      r.Info.LastDeployed,
   379  			Revision:  int(r.Version),
   380  			IsUpgrade: asUpgrade,
   381  			IsInstall: !asUpgrade,
   382  		},
   383  	}
   384  	rendered, err := renderutil.Render(r.Chart, r.Config, renderOpts)
   385  	if err != nil {
   386  		return err
   387  	}
   388  
   389  	b := bytes.NewBuffer(nil)
   390  	for _, m := range manifest.SplitManifests(rendered) {
   391  		// Remove empty manifests
   392  		if len(strings.TrimSpace(m.Content)) == 0 {
   393  			continue
   394  		}
   395  		b.WriteString("\n---\n# Source: " + m.Name + "\n")
   396  		b.WriteString(m.Content)
   397  	}
   398  	r.Manifest = b.String()
   399  	return nil
   400  }