diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 7a7dfb0f6..3c9eeb75b 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -19,13 +19,12 @@ package main import ( "fmt" "io" - "strings" "github.com/spf13/cobra" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/helm" - "k8s.io/helm/pkg/storage/driver" ) const upgradeDesc = ` @@ -152,15 +151,13 @@ func (u *upgradeCmd) run() error { // So we're stuck doing string matching against the wrapped error, which is nested somewhere // inside of the grpc.rpcError message. releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1)) - if err == nil { previousReleaseNamespace := releaseHistory.Releases[0].Namespace if previousReleaseNamespace != u.namespace { fmt.Fprintf(u.out, "WARNING: Namespace doesn't match with previous. Release will be deployed to %s\n", previousReleaseNamespace) } } - - if err != nil && strings.Contains(err.Error(), driver.ErrReleaseNotFound(u.release).Error()) { + if err != nil && errors.IsNotFound(err) { fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release) ic := &installCmd{ chartPath: chartPath, diff --git a/pkg/errors/error.go b/pkg/errors/error.go new file mode 100644 index 000000000..404af28ff --- /dev/null +++ b/pkg/errors/error.go @@ -0,0 +1,75 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package errors + +import ( + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ErrInvalidArgument create a new grpc coded error with codes.InvalidArgument +// indicates user request contains invalid field(missing chart, invalid release name, version, etc) +func ErrInvalidArgument(format string, arg ...interface{}) error { + return status.Errorf(codes.InvalidArgument, format, arg...) +} + +// ErrNotFound create a new grpc coded error with codes.NotFound +func ErrNotFound(format string, arg ...interface{}) error { + return status.Errorf(codes.NotFound, format, arg...) +} + +// ErrConflict create a new grpc coded error with codes.AlreadyExists +func ErrConflict(format string, arg ...interface{}) error { + return status.Errorf(codes.AlreadyExists, format, arg...) +} + +// ErrUnavailable create a new grpc coded error with codes.Unavailable +// client see this kind of error can retry the failed request after some +// backoff periods +func ErrUnavailable(format string, arg ...interface{}) error { + return status.Errorf(codes.Unavailable, format, arg...) +} + +// ErrInternal create a new grpc coded error with codes.Internal +// indicates tiller may encounter some connection issues with k8s apiserver +// or backend metadata storage +func ErrInternal(format string, arg ...interface{}) error { + return status.Errorf(codes.Internal, format, arg...) +} + +// ErrUnknown create a new grpc coded error with codes.Unknown +// indicates tiller may encounter render/marshal/unmarshal issues +func ErrUnknown(format string, arg ...interface{}) error { + return status.Errorf(codes.Unknown, format, arg...) +} + +// IsNotFound is use to check if a error is a grpc coded error with code NotFound +func IsNotFound(err error) bool { + if e, ok := status.FromError(err); ok { + return e.Code() == codes.NotFound + } + return false +} + +// IsInvalidArgument is use to check if a error is a grpc coded error with code +// InvalidArgument +func IsInvalidArgument(err error) bool { + if e, ok := status.FromError(err); ok { + return e.Code() == codes.InvalidArgument + } + return false +} diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 63c03a1d2..7b635cf40 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -17,7 +17,6 @@ limitations under the License. package driver // import "k8s.io/helm/pkg/storage/driver" import ( - "fmt" "strconv" "strings" "time" @@ -29,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + codederrors "k8s.io/helm/pkg/errors" rspb "k8s.io/helm/pkg/proto/hapi/release" ) @@ -65,17 +65,17 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { obj, err := cfgmaps.impl.Get(key, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound(key) + return nil, codederrors.ErrNotFound("release %s not found", key) } cfgmaps.Log("get: failed to get %q: %s", key, err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } // found the configmap, decode the base64 data string r, err := decodeRelease(obj.Data["release"]) if err != nil { cfgmaps.Log("get: failed to decode data %q: %s", key, err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } // return the release object return r, nil @@ -91,7 +91,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas list, err := cfgmaps.impl.List(opts) if err != nil { cfgmaps.Log("list: failed to list: %s", err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } var results []*rspb.Release @@ -117,7 +117,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err ls := kblabels.Set{} for k, v := range labels { if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + return nil, codederrors.ErrInvalidArgument("invalid label value: %q: %s", v, strings.Join(errs, "; ")) } ls[k] = v } @@ -127,11 +127,11 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err list, err := cfgmaps.impl.List(opts) if err != nil { cfgmaps.Log("query: failed to query with labels: %s", err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } if len(list.Items) == 0 { - return nil, ErrReleaseNotFound(labels["NAME"]) + return nil, codederrors.ErrNotFound("release %s not found", labels["NAME"]) } var results []*rspb.Release @@ -159,16 +159,16 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) - return err + return codederrors.ErrInternal(err.Error()) } // push the configmap object out into the kubiverse if _, err := cfgmaps.impl.Create(obj); err != nil { if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists(key) + return codederrors.ErrConflict("release %s already exist", key) } cfgmaps.Log("create: failed to create: %s", err) - return err + return codederrors.ErrInternal(err.Error()) } return nil } @@ -186,13 +186,13 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) - return err + return codederrors.ErrInternal(err.Error()) } // push the configmap object out into the kubiverse _, err = cfgmaps.impl.Update(obj) if err != nil { cfgmaps.Log("update: failed to update: %s", err) - return err + return codederrors.ErrInternal(err.Error()) } return nil } @@ -202,15 +202,14 @@ func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { // fetch the release to check existence if rls, err = cfgmaps.Get(key); err != nil { if apierrors.IsNotFound(err) { - return nil, ErrReleaseExists(rls.Name) + return nil, codederrors.ErrNotFound("release %s not found", rls.Name) } - cfgmaps.Log("delete: failed to get release %q: %s", key, err) return nil, err } // delete the release if err = cfgmaps.impl.Delete(key, &metav1.DeleteOptions{}); err != nil { - return rls, err + return rls, codederrors.ErrInternal(err.Error()) } return rls, nil } diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go index e01d35d64..869134a98 100644 --- a/pkg/storage/driver/driver.go +++ b/pkg/storage/driver/driver.go @@ -17,20 +17,9 @@ limitations under the License. package driver // import "k8s.io/helm/pkg/storage/driver" import ( - "fmt" - rspb "k8s.io/helm/pkg/proto/hapi/release" ) -var ( - // ErrReleaseNotFound indicates that a release is not found. - ErrReleaseNotFound = func(release string) error { return fmt.Errorf("release: %q not found", release) } - // ErrReleaseExists indicates that a release already exists. - ErrReleaseExists = func(release string) error { return fmt.Errorf("release: %q already exists", release) } - // ErrInvalidKey indicates that a release key could not be parsed. - ErrInvalidKey = func(release string) error { return fmt.Errorf("release: %q invalid key", release) } -) - // Creator is the interface that wraps the Create method. // // Create stores the release or returns ErrReleaseExists diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index ceb0d67dd..af5a3e7fa 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -21,6 +21,7 @@ import ( "strings" "sync" + errors "k8s.io/helm/pkg/errors" rspb "k8s.io/helm/pkg/proto/hapi/release" ) @@ -53,16 +54,16 @@ func (mem *Memory) Get(key string) (*rspb.Release, error) { case 2: name, ver := elems[0], elems[1] if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey(key) + return nil, errors.ErrInvalidArgument(key) } if recs, ok := mem.cache[name]; ok { if r := recs.Get(key); r != nil { return r.rls, nil } } - return nil, ErrReleaseNotFound(key) + return nil, errors.ErrNotFound(key) default: - return nil, ErrInvalidKey(key) + return nil, errors.ErrInvalidArgument(key) } } @@ -131,7 +132,7 @@ func (mem *Memory) Update(key string, rls *rspb.Release) error { rs.Replace(key, newRecord(key, rls)) return nil } - return ErrReleaseNotFound(rls.Name) + return errors.ErrNotFound(rls.Name) } // Delete deletes a release or returns ErrReleaseNotFound. @@ -141,12 +142,12 @@ func (mem *Memory) Delete(key string) (*rspb.Release, error) { elems := strings.Split(key, ".v") if len(elems) != 2 { - return nil, ErrInvalidKey(key) + return nil, errors.ErrInvalidArgument(key) } name, ver := elems[0], elems[1] if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey(key) + return nil, errors.ErrInvalidArgument(key) } if recs, ok := mem.cache[name]; ok { if r := recs.Remove(key); r != nil { @@ -155,7 +156,7 @@ func (mem *Memory) Delete(key string) (*rspb.Release, error) { return r.rls, nil } } - return nil, ErrReleaseNotFound(key) + return nil, errors.ErrNotFound(key) } // wlock locks mem for writing diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go index ce72308a8..a54c6c952 100644 --- a/pkg/storage/driver/records.go +++ b/pkg/storage/driver/records.go @@ -21,7 +21,7 @@ import ( "strconv" "github.com/golang/protobuf/proto" - + "k8s.io/helm/pkg/errors" rspb "k8s.io/helm/pkg/proto/hapi/release" ) @@ -38,7 +38,7 @@ func (rs *records) Add(r *record) error { } if rs.Exists(r.key) { - return ErrReleaseExists(r.key) + return errors.ErrConflict("release %s already exist", r.key) } *rs = append(*rs, r) diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index f81b475c0..167f0a556 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -17,7 +17,6 @@ limitations under the License. package driver // import "k8s.io/helm/pkg/storage/driver" import ( - "fmt" "strconv" "strings" "time" @@ -29,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + codederrors "k8s.io/helm/pkg/errors" rspb "k8s.io/helm/pkg/proto/hapi/release" ) @@ -65,7 +65,7 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) { obj, err := secrets.impl.Get(key, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound(key) + return nil, codederrors.ErrNotFound("release %s not found", key) } secrets.Log("get: failed to get %q: %s", key, err) @@ -75,7 +75,7 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) { r, err := decodeRelease(string(obj.Data["release"])) if err != nil { secrets.Log("get: failed to decode data %q: %s", key, err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } // return the release object return r, nil @@ -91,7 +91,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, list, err := secrets.impl.List(opts) if err != nil { secrets.Log("list: failed to list: %s", err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } var results []*rspb.Release @@ -117,7 +117,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) ls := kblabels.Set{} for k, v := range labels { if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + return nil, codederrors.ErrInvalidArgument("invalid label value: %q: %s", v, strings.Join(errs, "; ")) } ls[k] = v } @@ -127,11 +127,11 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) list, err := secrets.impl.List(opts) if err != nil { secrets.Log("query: failed to query with labels: %s", err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } if len(list.Items) == 0 { - return nil, ErrReleaseNotFound(labels["NAME"]) + return nil, codederrors.ErrNotFound("release %s not found", labels["NAME"]) } var results []*rspb.Release @@ -159,16 +159,16 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error { obj, err := newSecretsObject(key, rls, lbs) if err != nil { secrets.Log("create: failed to encode release %q: %s", rls.Name, err) - return err + return codederrors.ErrInternal(err.Error()) } // push the secret object out into the kubiverse if _, err := secrets.impl.Create(obj); err != nil { if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists(rls.Name) + return codederrors.ErrConflict("release %s already exist", rls.Name) } secrets.Log("create: failed to create: %s", err) - return err + return codederrors.ErrInternal(err.Error()) } return nil } @@ -186,13 +186,13 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error { obj, err := newSecretsObject(key, rls, lbs) if err != nil { secrets.Log("update: failed to encode release %q: %s", rls.Name, err) - return err + return codederrors.ErrInternal(err.Error()) } // push the secret object out into the kubiverse _, err = secrets.impl.Update(obj) if err != nil { secrets.Log("update: failed to update: %s", err) - return err + return codederrors.ErrInternal(err.Error()) } return nil } @@ -202,15 +202,15 @@ func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) { // fetch the release to check existence if rls, err = secrets.Get(key); err != nil { if apierrors.IsNotFound(err) { - return nil, ErrReleaseExists(rls.Name) + return nil, codederrors.ErrNotFound("release %s not found", rls.Name) } secrets.Log("delete: failed to get release %q: %s", key, err) - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } // delete the release if err = secrets.impl.Delete(key, &metav1.DeleteOptions{}); err != nil { - return rls, err + return rls, codederrors.ErrInternal(err.Error()) } return rls, nil } @@ -234,7 +234,7 @@ func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*api.Secret, e // encode the release s, err := encodeRelease(rls) if err != nil { - return nil, err + return nil, codederrors.ErrInternal(err.Error()) } if lbs == nil { diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 05abb66e7..b11ac8f95 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -26,6 +26,7 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" + codederrors "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" util "k8s.io/helm/pkg/releaseutil" @@ -100,7 +101,7 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort } if err := manifestFile.sort(result); err != nil { - return result.hooks, result.generic, err + return result.hooks, result.generic, codederrors.ErrUnknown(err.Error()) } } diff --git a/pkg/tiller/release_install.go b/pkg/tiller/release_install.go index 8e7fd3acd..b73ef72c1 100644 --- a/pkg/tiller/release_install.go +++ b/pkg/tiller/release_install.go @@ -23,6 +23,7 @@ import ( ctx "golang.org/x/net/context" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" @@ -57,7 +58,7 @@ func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea // prepareRelease builds a release for an install operation. func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { if req.Chart == nil { - return nil, errMissingChart + return nil, errors.ErrInvalidArgument("missing chart") } name, err := s.uniqName(req.Name, req.ReuseName) @@ -81,7 +82,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re } valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) if err != nil { - return nil, err + return nil, errors.ErrUnknown(err.Error()) } hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) @@ -181,7 +182,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install r.Info.Description = msg s.recordRelease(old, true) s.recordRelease(r, true) - return res, err + return res, errors.ErrUnknown(msg) } default: @@ -194,7 +195,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install r.Info.Status.Code = release.Status_FAILED r.Info.Description = msg s.recordRelease(r, true) - return res, fmt.Errorf("release %s failed: %s", r.Name, err) + return res, errors.ErrUnknown(msg) } } diff --git a/pkg/tiller/release_list.go b/pkg/tiller/release_list.go index 9ccc8a686..408785107 100644 --- a/pkg/tiller/release_list.go +++ b/pkg/tiller/release_list.go @@ -17,9 +17,9 @@ limitations under the License. package tiller import ( - "fmt" "regexp" + codederrors "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" relutil "k8s.io/helm/pkg/releaseutil" @@ -45,10 +45,7 @@ func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream s } if req.Namespace != "" { - rels, err = filterByNamespace(req.Namespace, rels) - if err != nil { - return err - } + rels = filterByNamespace(req.Namespace, rels) } if len(req.Filter) != 0 { @@ -86,11 +83,11 @@ func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream s } } if i == -1 { - return fmt.Errorf("offset %q not found", req.Offset) + return codederrors.ErrInvalidArgument("offset %q not found", req.Offset) } if len(rels) < i { - return fmt.Errorf("no items after %q", req.Offset) + return codederrors.ErrInvalidArgument("no items after %q", req.Offset) } rels = rels[i:] @@ -117,20 +114,20 @@ func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream s return stream.Send(res) } -func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { +func filterByNamespace(namespace string, rels []*release.Release) []*release.Release { matches := []*release.Release{} for _, r := range rels { if namespace == r.Namespace { matches = append(matches, r) } } - return matches, nil + return matches } func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { preg, err := regexp.Compile(filter) if err != nil { - return rels, err + return rels, codederrors.ErrInvalidArgument(err.Error()) } matches := []*release.Release{} for _, r := range rels { diff --git a/pkg/tiller/release_rollback.go b/pkg/tiller/release_rollback.go index e8b6435b5..26925dc0a 100644 --- a/pkg/tiller/release_rollback.go +++ b/pkg/tiller/release_rollback.go @@ -21,6 +21,7 @@ import ( ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" @@ -66,7 +67,7 @@ func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (* } if req.Version < 0 { - return nil, nil, errInvalidRevision + return nil, nil, errors.ErrInvalidArgument("invalid release version %d, must >= 0", req.Version) } crls, err := s.env.Releases.Last(req.Name) @@ -136,7 +137,7 @@ func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.R targetRelease.Info.Description = msg s.recordRelease(currentRelease, true) s.recordRelease(targetRelease, false) - return res, err + return res, errors.ErrUnknown(msg) } // post-rollback hooks diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 44d5d847a..8939a4f80 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -18,7 +18,6 @@ package tiller import ( "bytes" - "errors" "fmt" "path" "regexp" @@ -30,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" @@ -53,17 +53,6 @@ const releaseNameMaxLen = 53 // since there can be filepath in front of it. const notesFileSuffix = "NOTES.txt" -var ( - // errMissingChart indicates that a chart was not provided. - errMissingChart = errors.New("no chart provided") - // errMissingRelease indicates that a release (name) was not provided. - errMissingRelease = errors.New("no release provided") - // errInvalidRevision indicates that an invalid release revision number was provided. - errInvalidRevision = errors.New("invalid release revision") - //errInvalidName indicates that an invalid release name was provided - errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") -) - // ListDefaultLimit is the default limit for number of items returned in a list. var ListDefaultLimit int64 = 512 @@ -127,13 +116,13 @@ func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current // We have to regenerate the old coalesced values: oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) if err != nil { - err := fmt.Errorf("failed to rebuild old values: %s", err) - s.Log("%s", err) - return err + msg := fmt.Sprintf("failed to rebuild old values: %s", err) + s.Log(msg) + return errors.ErrUnknown(msg) } nv, err := oldVals.YAML() if err != nil { - return err + return errors.ErrUnknown(err.Error()) } req.Chart.Values = &chart.Config{Raw: nv} return nil @@ -159,11 +148,15 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { if start != "" { if len(start) > releaseNameMaxLen { - return "", fmt.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen) + return "", errors.ErrInvalidArgument("release %s exceeds max length of %d", start, releaseNameMaxLen) } h, err := s.env.Releases.History(start) - if err != nil || len(h) < 1 { + if err != nil && errors.IsNotFound(err) { + return start, nil + } else if err != nil { + return start, err + } else if len(h) < 1 { return start, nil } relutil.Reverse(h, relutil.SortByRevision) @@ -174,10 +167,10 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { s.Log("name %s exists but is not in use, reusing name", start) return start, nil } else if reuse { - return "", errors.New("cannot re-use a name that is still in use") + return "", errors.ErrConflict("release %s already exist", start) } - return "", fmt.Errorf("a release named %s already exists.\nRun: helm ls --all %s; to check the status of the release\nOr run: helm del --purge %s; to delete it", start, start, start) + return "", errors.ErrConflict("release %s already exist", start) } maxTries := 5 @@ -187,13 +180,15 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { if len(name) > releaseNameMaxLen { name = name[:releaseNameMaxLen] } - if _, err := s.env.Releases.Get(name, 1); strings.Contains(err.Error(), "not found") { - return name, nil + if _, err := s.env.Releases.Get(name, 1); err != nil { + if errors.IsNotFound(err) { + return name, nil + } } s.Log("info: generated name %s is taken. Searching again.", name) } s.Log("warning: No available release names found after %d tries", maxTries) - return "ERROR", errors.New("no available release name found") + return "ERROR", errors.ErrUnavailable("no available release name found") } func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { @@ -212,11 +207,11 @@ func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { sv, err := disc.ServerVersion() if err != nil { - return nil, err + return nil, errors.ErrUnknown(err.Error()) } vs, err := GetVersionSet(disc) if err != nil { - return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) + return nil, err } return &chartutil.Capabilities{ APIVersions: vs, @@ -229,7 +224,8 @@ func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, e func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { groups, err := client.ServerGroups() if err != nil { - return chartutil.DefaultVersionSet, err + //return chartutil.DefaultVersionSet, err + return chartutil.DefaultVersionSet, errors.ErrUnknown(err.Error()) } // FIXME: The Kubernetes test fixture for cli appears to always return nil @@ -249,14 +245,14 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values sver := version.GetVersion() if ch.Metadata.TillerVersion != "" && !version.IsCompatibleRange(ch.Metadata.TillerVersion, sver) { - return nil, nil, "", fmt.Errorf("Chart incompatible with Tiller %s", sver) + return nil, nil, "", errors.ErrInvalidArgument("Chart incompatible with Tiller %s", sver) } s.Log("rendering %s chart using values", ch.GetMetadata().Name) renderer := s.engine(ch) files, err := renderer.Render(ch, values) if err != nil { - return nil, nil, "", err + return nil, nil, "", errors.ErrUnknown(err.Error()) } // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, @@ -321,7 +317,8 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin kubeCli := s.env.KubeClient code, ok := events[hook] if !ok { - return fmt.Errorf("unknown hook %s", hook) + // this shouldn't happen, since all the execHook call use Const in pkg hook + return errors.ErrUnknown("unknown hook %s", hook) } s.Log("executing %d %s hooks for %s", len(hs), hook, name) @@ -341,7 +338,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin b := bytes.NewBufferString(h.Manifest) if err := kubeCli.Create(namespace, b, timeout, false); err != nil { s.Log("warning: Release %s %s %s failed: %s", name, hook, h.Path, err) - return err + return errors.ErrInternal(err.Error()) } // No way to rewind a bytes.Buffer()? b.Reset() @@ -356,10 +353,10 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, hooks.HookFailed) if errHookDelete := kubeCli.Delete(namespace, b); errHookDelete != nil { s.Log("warning: Release %s %s %S could not be deleted: %s", name, hook, h.Path, errHookDelete) - return errHookDelete + return errors.ErrInternal(errHookDelete.Error()) } } - return err + return errors.ErrInternal(err.Error()) } } @@ -372,7 +369,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, hooks.HookSucceeded) if errHookDelete := kubeCli.Delete(namespace, b); errHookDelete != nil { s.Log("warning: Release %s %s %S could not be deleted: %s", name, hook, h.Path, errHookDelete) - return errHookDelete + return errors.ErrInternal(errHookDelete.Error()) } } h.LastRun = timeconv.Now() @@ -384,16 +381,19 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { r := bytes.NewReader(manifest) _, err := c.BuildUnstructured(ns, r) + if err != nil { + err = errors.ErrInternal(err.Error()) + } return err } func validateReleaseName(releaseName string) error { if releaseName == "" { - return errMissingRelease + return errors.ErrInvalidArgument("release name is empty") } if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) { - return errInvalidName + return errors.ErrInvalidArgument("release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") } return nil diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index b7b14a4f1..9e0db03ba 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -29,6 +29,7 @@ import ( "google.golang.org/grpc/metadata" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + codederrors "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" @@ -183,24 +184,29 @@ func upgradeReleaseVersion(rel *release.Release) *release.Release { } func TestValidName(t *testing.T) { - for name, valid := range map[string]error{ - "nina pinta santa-maria": errInvalidName, - "nina-pinta-santa-maria": nil, - "-nina": errInvalidName, - "pinta-": errInvalidName, - "santa-maria": nil, - "niña": errInvalidName, - "...": errInvalidName, - "pinta...": errInvalidName, - "santa...maria": nil, - "": errMissingRelease, - " ": errInvalidName, - ".nina.": errInvalidName, - "nina.pinta": nil, - "abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcd": errInvalidName, + type isSomeError func(err error) bool + isNilError := func(err error) bool { + return err == nil + } + isInvalidArgumentError := codederrors.IsInvalidArgument + for name, valid := range map[string]isSomeError{ + "nina pinta santa-maria": isInvalidArgumentError, + "nina-pinta-santa-maria": isNilError, + "-nina": isInvalidArgumentError, + "pinta-": isInvalidArgumentError, + "santa-maria": isNilError, + "niña": isInvalidArgumentError, + "...": isInvalidArgumentError, + "pinta...": isInvalidArgumentError, + "santa...maria": isNilError, + "": isInvalidArgumentError, + " ": isInvalidArgumentError, + ".nina.": isInvalidArgumentError, + "nina.pinta": isNilError, + "abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcd": isInvalidArgumentError, } { - if valid != validateReleaseName(name) { - t.Errorf("Expected %q to be %t", name, valid) + if !valid(validateReleaseName(name)) { + t.Errorf("%s error type missmatch", name) } } } diff --git a/pkg/tiller/release_status.go b/pkg/tiller/release_status.go index e0d75877d..80d264171 100644 --- a/pkg/tiller/release_status.go +++ b/pkg/tiller/release_status.go @@ -17,11 +17,9 @@ limitations under the License. package tiller import ( - "errors" - "fmt" - ctx "golang.org/x/net/context" + codederrors "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" ) @@ -39,20 +37,20 @@ func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease var err error rel, err = s.env.Releases.Last(req.Name) if err != nil { - return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err) + return nil, err } } else { var err error if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { - return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err) + return nil, err } } if rel.Info == nil { - return nil, errors.New("release info is missing") + return nil, codederrors.ErrInternal("release info is missing") } if rel.Chart == nil { - return nil, errors.New("release chart is missing") + return nil, codederrors.ErrInternal("release chart is missing") } sc := rel.Info.Status.Code diff --git a/pkg/tiller/release_uninstall.go b/pkg/tiller/release_uninstall.go index 423b6e7ef..698c79e5e 100644 --- a/pkg/tiller/release_uninstall.go +++ b/pkg/tiller/release_uninstall.go @@ -17,11 +17,11 @@ limitations under the License. package tiller import ( - "fmt" "strings" ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" @@ -42,7 +42,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR return nil, err } if len(rels) < 1 { - return nil, errMissingRelease + return nil, errors.ErrNotFound("release %s not exist", req.Name) } relutil.SortByRevision(rels) @@ -58,7 +58,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR } return &services.UninstallReleaseResponse{Release: rel}, nil } - return nil, fmt.Errorf("the release named %q is already deleted", req.Name) + return nil, errors.ErrConflict("the release named %q is already deleted", req.Name) } s.Log("uninstall: Deleting %s", req.Name) @@ -113,7 +113,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR } if len(es) > 0 { - return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) + return res, errors.ErrUnknown("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) } return res, nil } diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index d251db753..dbe272c86 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -22,6 +22,7 @@ import ( ctx "golang.org/x/net/context" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/errors" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" @@ -66,7 +67,7 @@ func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease // prepareUpdate builds an updated release for an update operation. func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { if req.Chart == nil { - return nil, nil, errMissingChart + return nil, nil, errors.ErrNotFound("missing chart") } // finds the deployed release with the given name @@ -105,7 +106,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele } valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) if err != nil { - return nil, nil, err + return nil, nil, errors.ErrUnknown(err.Error()) } hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) @@ -161,7 +162,7 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R updatedRelease.Info.Description = msg s.recordRelease(originalRelease, true) s.recordRelease(updatedRelease, true) - return res, err + return res, errors.ErrUnknown(msg) } // post-upgrade hooks