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