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