github.com/koderover/helm@v2.17.0+incompatible/pkg/tiller/release_install.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 tiller 18 19 import ( 20 "fmt" 21 "strings" 22 23 ctx "golang.org/x/net/context" 24 25 "k8s.io/helm/pkg/chartutil" 26 "k8s.io/helm/pkg/hooks" 27 "k8s.io/helm/pkg/proto/hapi/release" 28 "k8s.io/helm/pkg/proto/hapi/services" 29 relutil "k8s.io/helm/pkg/releaseutil" 30 "k8s.io/helm/pkg/timeconv" 31 ) 32 33 // InstallRelease installs a release and stores the release record. 34 func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 35 s.Log("preparing install for %s", req.Name) 36 rel, err := s.prepareRelease(req) 37 if err != nil { 38 s.Log("failed install prepare step: %s", err) 39 res := &services.InstallReleaseResponse{Release: rel} 40 41 // On dry run, append the manifest contents to a failed release. This is 42 // a stop-gap until we can revisit an error backchannel post-2.0. 43 if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { 44 err = fmt.Errorf("%s\n%s", err, rel.Manifest) 45 } 46 return res, err 47 } 48 49 s.Log("performing install for %s", req.Name) 50 res, err := s.performRelease(rel, req) 51 if err != nil { 52 s.Log("failed install perform step: %s", err) 53 } 54 return res, err 55 } 56 57 // prepareRelease builds a release for an install operation. 58 func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { 59 if req.Chart == nil { 60 return nil, errMissingChart 61 } 62 63 name, err := s.uniqName(req.Name, req.ReuseName) 64 if err != nil { 65 return nil, err 66 } 67 68 caps, err := capabilities(s.clientset.Discovery()) 69 if err != nil { 70 return nil, err 71 } 72 73 revision := 1 74 ts := timeconv.Now() 75 options := chartutil.ReleaseOptions{ 76 Name: name, 77 Time: ts, 78 Namespace: req.Namespace, 79 Revision: revision, 80 IsInstall: true, 81 } 82 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 83 if err != nil { 84 return nil, err 85 } 86 87 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions) 88 if err != nil { 89 // Return a release with partial data so that client can show debugging 90 // information. 91 rel := &release.Release{ 92 Name: name, 93 Namespace: req.Namespace, 94 Chart: req.Chart, 95 Config: req.Values, 96 Info: &release.Info{ 97 FirstDeployed: ts, 98 LastDeployed: ts, 99 Status: &release.Status{Code: release.Status_UNKNOWN}, 100 Description: fmt.Sprintf("Install failed: %s", err), 101 }, 102 Version: 0, 103 } 104 if manifestDoc != nil { 105 rel.Manifest = manifestDoc.String() 106 } 107 return rel, err 108 } 109 110 // Store a release. 111 rel := &release.Release{ 112 Name: name, 113 Namespace: req.Namespace, 114 Chart: req.Chart, 115 Config: req.Values, 116 Info: &release.Info{ 117 FirstDeployed: ts, 118 LastDeployed: ts, 119 Status: &release.Status{Code: release.Status_PENDING_INSTALL}, 120 Description: "Initial install underway", // Will be overwritten. 121 }, 122 Manifest: manifestDoc.String(), 123 Hooks: hooks, 124 Version: int32(revision), 125 } 126 if len(notesTxt) > 0 { 127 rel.Info.Status.Notes = notesTxt 128 } 129 130 return rel, nil 131 } 132 133 func hasCRDHook(hs []*release.Hook) bool { 134 for _, h := range hs { 135 for _, e := range h.Events { 136 if e == events[hooks.CRDInstall] { 137 return true 138 } 139 } 140 } 141 return false 142 } 143 144 // performRelease runs a release. 145 func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 146 res := &services.InstallReleaseResponse{Release: r} 147 manifestDoc := []byte(r.Manifest) 148 149 if req.DryRun { 150 s.Log("dry run for %s", r.Name) 151 152 if !req.DisableCrdHook && hasCRDHook(r.Hooks) { 153 s.Log("validation skipped because CRD hook is present") 154 res.Release.Info.Description = "Validation skipped because CRDs are not installed" 155 return res, nil 156 } 157 158 // Here's the problem with dry runs and CRDs: We can't install a CRD 159 // during a dry run, which means it cannot be validated. 160 if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil { 161 return res, err 162 } 163 164 res.Release.Info.Description = "Dry run complete" 165 return res, nil 166 } 167 168 // crd-install hooks 169 if !req.DisableHooks && !req.DisableCrdHook { 170 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil { 171 fmt.Printf("Finished installing CRD: %s", err) 172 return res, err 173 } 174 } else { 175 s.Log("CRD install hooks disabled for %s", req.Name) 176 } 177 178 // Because the CRDs are installed, they are used for validation during this step. 179 if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil { 180 return res, fmt.Errorf("validation failed: %s", err) 181 } 182 183 // pre-install hooks 184 if !req.DisableHooks { 185 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { 186 return res, err 187 } 188 } else { 189 s.Log("install hooks disabled for %s", req.Name) 190 } 191 192 switch h, err := s.env.Releases.History(req.Name); { 193 // if this is a replace operation, append to the release history 194 case req.ReuseName && err == nil && len(h) >= 1: 195 s.Log("name reuse for %s requested, replacing release", req.Name) 196 // get latest release revision 197 relutil.Reverse(h, relutil.SortByRevision) 198 199 // old release 200 old := h[0] 201 202 // update old release status 203 old.Info.Status.Code = release.Status_SUPERSEDED 204 s.recordRelease(old, true) 205 206 // update new release with next revision number 207 // so as to append to the old release's history 208 r.Version = old.Version + 1 209 updateReq := &services.UpdateReleaseRequest{ 210 Wait: req.Wait, 211 Recreate: false, 212 Timeout: req.Timeout, 213 } 214 s.recordRelease(r, false) 215 if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { 216 msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) 217 s.Log("warning: %s", msg) 218 old.Info.Status.Code = release.Status_SUPERSEDED 219 r.Info.Status.Code = release.Status_FAILED 220 r.Info.Description = msg 221 s.recordRelease(old, true) 222 s.recordRelease(r, true) 223 return res, err 224 } 225 226 default: 227 // nothing to replace, create as normal 228 // regular manifests 229 s.recordRelease(r, false) 230 if err := s.ReleaseModule.Create(r, req, s.env); err != nil { 231 msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) 232 s.Log("warning: %s", msg) 233 r.Info.Status.Code = release.Status_FAILED 234 r.Info.Description = msg 235 s.recordRelease(r, true) 236 return res, fmt.Errorf("release %s failed: %s", r.Name, err) 237 } 238 } 239 240 // post-install hooks 241 if !req.DisableHooks { 242 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { 243 msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) 244 s.Log("warning: %s", msg) 245 r.Info.Status.Code = release.Status_FAILED 246 r.Info.Description = msg 247 s.recordRelease(r, true) 248 return res, err 249 } 250 } 251 252 r.Info.Status.Code = release.Status_DEPLOYED 253 if req.Description == "" { 254 r.Info.Description = "Install complete" 255 } else { 256 r.Info.Description = req.Description 257 } 258 // This is a tricky case. The release has been created, but the result 259 // cannot be recorded. The truest thing to tell the user is that the 260 // release was created. However, the user will not be able to do anything 261 // further with this release. 262 // 263 // One possible strategy would be to do a timed retry to see if we can get 264 // this stored in the future. 265 s.recordRelease(r, true) 266 267 return res, nil 268 }