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 }