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 }