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 }