From ae77e613770afa49686dddae0ea67c43de6bdd34 Mon Sep 17 00:00:00 2001 From: Harsha Narayana Date: Thu, 23 Jun 2022 23:44:09 +0530 Subject: [PATCH] GIT-4850: create new errors package and update extensions/docker-desktop --- errors/errors.go | 95 +++++++++++++++++++ errors/errors_test.go | 59 ++++++++++++ errors/go.mod | 3 + .../docker-desktop/apps/clustermgr/go.mod | 14 +-- .../docker-desktop/apps/clustermgr/go.sum | 6 +- .../kubeconfig/internal/kubeconfig/encode.go | 7 +- .../kubeconfig/internal/kubeconfig/helpers.go | 10 +- .../kubeconfig/internal/kubeconfig/merge.go | 8 +- .../kubeconfig/internal/kubeconfig/read.go | 9 +- .../kubeconfig/internal/kubeconfig/remove.go | 6 +- .../kubeconfig/internal/kubeconfig/write.go | 6 +- .../apps/clustermgr/pkg/utils/lock.go | 5 +- 12 files changed, 194 insertions(+), 34 deletions(-) create mode 100644 errors/errors.go create mode 100644 errors/errors_test.go create mode 100644 errors/go.mod diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000000..65118b7729 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,95 @@ +package errors + +import ( + "fmt" +) + +type Reason string + +const ( + ReasonUnknown Reason = "Unknown" + ReasonLockFailed Reason = "LockFailed" + ReasonMergeFailed Reason = "MergeFailed" + ReasonReadFailed Reason = "ReadFailed" + ReasonIOFailed Reason = "IOFailed" + ReasonMarshallingFailed Reason = "MarshallingFailed" + ReasonEncodeFailed Reason = "EncodeFailed" + ReasonNormalizationFailed Reason = "NormalizationFailed" + ReasonValidationFailed Reason = "ValidationFailed" + ReasonTimeout Reason = "Timeout" + ReasonGeneric Reason = "GenericFailure" +) + +// TODO: implement the errors.Unwrap interface to support errors.Unwrap cleanly +type TanzuError struct { + ErrorDetails ErrorDetails `json:"errorDetails"` +} + +type ErrorDetails struct { + Message string `json:"message,omitempty"` + Reason Reason `json:"reason,omitempty"` + Err error `json:"err"` +} + +func (t TanzuError) Error() string { + return fmt.Sprintf("%s: %s (%s)", t.ErrorDetails.Reason, t.ErrorDetails.Message, t.ErrorDetails.Err) +} + +func generateError(reason Reason, err error, args ...interface{}) TanzuError { + var message string + switch len(args) { + case 0: + message = "" + case 1: + message = args[0].(string) + default: + message = fmt.Sprintf(args[0].(string), args[1:]...) + } + return TanzuError{ + ErrorDetails: ErrorDetails{ + Reason: reason, + Message: message, + Err: err, + }, + } +} + +func NewLockFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonLockFailed, err, args...) +} + +func NewMergeFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonMergeFailed, err, args...) +} + +func NewReadFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonReadFailed, err, args...) +} + +func NewIOFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonIOFailed, err, args...) +} + +func NewMarshallingFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonMarshallingFailed, err, args...) +} + +func NewNormalizationFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonNormalizationFailed, err, args...) +} + +func NewValidationFailed(err error, args ...interface{}) TanzuError { + return generateError(ReasonValidationFailed, err, args...) +} + +func NewTimeout(err error, args ...interface{}) TanzuError { + return generateError(ReasonTimeout, err, args...) +} + +func NewGenericFail(err error, args ...interface{}) TanzuError { + return generateError(ReasonGeneric, err, args...) +} + +func NewEncodeFail(err error, args ...interface{}) TanzuError { + return generateError(ReasonEncodeFailed, err, args...) +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000000..6af7baf2b9 --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,59 @@ +package errors + +import ( + "errors" + "fmt" + "reflect" + "runtime" + "testing" +) + +func TestErrors(t *testing.T) { + tests := []struct { + errorFunc func(err error, args ...interface{}) TanzuError + Reason Reason + }{ + { + NewLockFailed, ReasonLockFailed, + }, + { + NewMergeFailed, ReasonMergeFailed, + }, + { + NewReadFailed, ReasonReadFailed, + }, + { + NewIOFailed, ReasonIOFailed, + }, + { + NewMarshallingFailed, ReasonMarshallingFailed, + }, + { + NewNormalizationFailed, ReasonNormalizationFailed, + }, + { + NewValidationFailed, ReasonValidationFailed, + }, + { + NewTimeout, ReasonTimeout, + }, + { + NewGenericFail, ReasonGeneric, + }, + } + + for _, tc := range tests { + tc := tc + name := runtime.FuncForPC(reflect.ValueOf(tc.errorFunc).Pointer()).Name() + t.Run(name, func(t *testing.T) { + err := tc.errorFunc(errors.New("fail"), "some failure. Arg %s", "test") + if err.ErrorDetails.Reason != tc.Reason { + t.Errorf("%s, Expected: %v, got: %v", name, tc.Reason, err.ErrorDetails.Reason) + } + expected := fmt.Sprintf("%s: %s (%s)", tc.Reason, "some failure. Arg test", "fail") + if err.Error() != expected { + t.Errorf("%s, Expected: %s, got: %s", name, expected, err.Error()) + } + }) + } +} diff --git a/errors/go.mod b/errors/go.mod new file mode 100644 index 0000000000..b1cd991b47 --- /dev/null +++ b/errors/go.mod @@ -0,0 +1,3 @@ +module github.com/vmware-tanzu/community-edition/errors + +go 1.17 diff --git a/extensions/docker-desktop/apps/clustermgr/go.mod b/extensions/docker-desktop/apps/clustermgr/go.mod index 1589adf745..f9b250b1c2 100644 --- a/extensions/docker-desktop/apps/clustermgr/go.mod +++ b/extensions/docker-desktop/apps/clustermgr/go.mod @@ -5,11 +5,11 @@ go 1.17 require ( github.com/docker/docker v20.10.16+incompatible github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b - github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.7.1 github.com/vmware-tanzu/community-edition/cli/cmd/plugin/unmanaged-cluster v0.0.0-20220607164142-9853e16163cb - gopkg.in/yaml.v3 v3.0.1 - sigs.k8s.io/kind v0.14.0 + github.com/vmware-tanzu/community-edition/errors v0.0.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b sigs.k8s.io/yaml v1.3.0 ) @@ -62,6 +62,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -72,6 +73,7 @@ require ( github.com/vmware-tanzu/carvel-kapp-controller v0.37.0 // indirect github.com/vmware-tanzu/carvel-kbld v0.32.1-0.20220207174123-dd5e71b95085 // indirect github.com/vmware-tanzu/carvel-vendir v0.27.0 // indirect + github.com/vmware-tanzu/community-edition v0.12.1 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect @@ -93,10 +95,8 @@ require ( k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/kind v0.14.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect ) -require ( - github.com/stretchr/testify v1.7.2 - github.com/vmware-tanzu/community-edition v0.12.1 // indirect -) +replace github.com/vmware-tanzu/community-edition/errors => ../../../../errors diff --git a/extensions/docker-desktop/apps/clustermgr/go.sum b/extensions/docker-desktop/apps/clustermgr/go.sum index d5bd544761..e128015f01 100644 --- a/extensions/docker-desktop/apps/clustermgr/go.sum +++ b/extensions/docker-desktop/apps/clustermgr/go.sum @@ -1179,9 +1179,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2021,9 +2020,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/encode.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/encode.go index fd669f3cc1..3598748f52 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/encode.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/encode.go @@ -7,8 +7,9 @@ import ( "bytes" yaml "gopkg.in/yaml.v3" - "sigs.k8s.io/kind/pkg/errors" kubeyaml "sigs.k8s.io/yaml" + + "github.com/vmware-tanzu/community-edition/errors" ) // Encode encodes the cfg to yaml @@ -17,7 +18,7 @@ func Encode(cfg *Config) ([]byte, error) { // so we're not using that to marshal encoded, err := yaml.Marshal(cfg) if err != nil { - return nil, errors.Wrap(err, "failed to encode KUBECONFIG") + return nil, errors.NewEncodeFail(err, "failed to encode KUBECONFIG file") } // normalize with kubernetes's yaml library @@ -25,7 +26,7 @@ func Encode(cfg *Config) ([]byte, error) { // modifying kubeconfig files, which is nice to have encoded, err = normYaml(encoded) if err != nil { - return nil, errors.Wrap(err, "failed to normalize KUBECONFIG encoding") + return nil, errors.NewNormalizationFailed(err, "failed to normalize KUBECONFIG encoding") } return encoded, nil diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/helpers.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/helpers.go index f6b44fd202..6635829765 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/helpers.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/helpers.go @@ -4,7 +4,9 @@ package kubeconfig import ( - "sigs.k8s.io/kind/pkg/errors" + "fmt" + + "github.com/vmware-tanzu/community-edition/errors" ) // KINDClusterKey identifies kind clusters in kubeconfig files @@ -16,13 +18,13 @@ func KINDClusterKey(clusterName string) string { // our expectations, namely on the number of entries func checkKubeadmExpectations(cfg *Config) error { if len(cfg.Clusters) != 1 { - return errors.Errorf("kubeadm KUBECONFIG should have one cluster, but read %d", len(cfg.Clusters)) + return errors.NewValidationFailed(fmt.Errorf("kubeadm KUBECONFIG should have one cluster, but read %d", len(cfg.Clusters))) } if len(cfg.Users) != 1 { - return errors.Errorf("kubeadm KUBECONFIG should have one user, but read %d", len(cfg.Users)) + return errors.NewValidationFailed(fmt.Errorf("kubeadm KUBECONFIG should have one user, but read %d", len(cfg.Users))) } if len(cfg.Contexts) != 1 { - return errors.Errorf("kubeadm KUBECONFIG should have one context, but read %d", len(cfg.Contexts)) + return errors.NewValidationFailed(fmt.Errorf("kubeadm KUBECONFIG should have one context, but read %d", len(cfg.Contexts))) } return nil } diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/merge.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/merge.go index 81c1af0e5c..4e1c119043 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/merge.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/merge.go @@ -6,7 +6,7 @@ package kubeconfig import ( "os" - "sigs.k8s.io/kind/pkg/errors" + "github.com/vmware-tanzu/community-edition/errors" ) // WriteMerged writes a kind kubeconfig (see KINDFromRawKubeadm) into configPath @@ -18,7 +18,7 @@ func WriteMerged(kindConfig *Config, explicitConfigPath string) error { // lock config file the same as client-go if err := lockFile(configPath); err != nil { - return errors.Wrap(err, "failed to lock config file") + return errors.NewLockFailed(err, "failed to lock config file %s", configPath) } defer func() { _ = unlockFile(configPath) @@ -27,12 +27,12 @@ func WriteMerged(kindConfig *Config, explicitConfigPath string) error { // read in existing existing, err := read(configPath) if err != nil { - return errors.Wrap(err, "failed to get kubeconfig to merge") + return errors.NewReadFailed(err, "failed to read kubeconfig file %s", configPath) } // merge with kind kubeconfig if err := merge(existing, kindConfig); err != nil { - return err + return errors.NewMergeFailed(err, "failed to merge kubeconfig file %s", configPath) } // write back out diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/read.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/read.go index 2b8b9d2783..edb6747284 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/read.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/read.go @@ -8,7 +8,8 @@ import ( "os" yaml "gopkg.in/yaml.v3" - "sigs.k8s.io/kind/pkg/errors" + + "github.com/vmware-tanzu/community-edition/errors" ) // KINDFromRawKubeadm returns a kind kubeconfig derived from the raw kubeadm kubeconfig, @@ -56,17 +57,17 @@ func read(configPath string) (*Config, error) { if os.IsNotExist(err) { return &Config{}, nil } else if err != nil { - return nil, errors.WithStack(err) + return nil, errors.NewReadFailed(err, "failed to read config file %s", configPath) } // otherwise read in and deserialize cfg := &Config{} rawExisting, err := io.ReadAll(f) if err != nil { - return nil, errors.WithStack(err) + return nil, errors.NewIOFailed(err, "failed to read config file data") } if err := yaml.Unmarshal(rawExisting, cfg); err != nil { - return nil, errors.WithStack(err) + return nil, errors.NewMarshallingFailed(err, "faled to marshall kubeconfig file %s", configPath) } return cfg, nil diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/remove.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/remove.go index 06adea10e8..6562c8920e 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/remove.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/remove.go @@ -6,7 +6,7 @@ package kubeconfig import ( "os" - "sigs.k8s.io/kind/pkg/errors" + "github.com/vmware-tanzu/community-edition/errors" ) // RemoveKIND removes the kind cluster kindClusterName from the KUBECONFIG @@ -17,7 +17,7 @@ func RemoveKIND(kindClusterName, explicitPath string) error { if err := func(configPath string) error { // lock before modifying if err := lockFile(configPath); err != nil { - return errors.Wrap(err, "failed to lock config file") + return errors.NewLockFailed(err, "failed to lock config file %s", configPath) } defer func(configPath string) { _ = unlockFile(configPath) @@ -26,7 +26,7 @@ func RemoveKIND(kindClusterName, explicitPath string) error { // read in existing existing, err := read(configPath) if err != nil { - return errors.Wrap(err, "failed to read kubeconfig to remove KIND entry") + return errors.NewReadFailed(err, "failed to read kubeconfig to remove KIND entry") } // remove the kind cluster from the config diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/write.go b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/write.go index 7f62cad950..6391cc889f 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/write.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/kubeconfig/internal/kubeconfig/write.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "sigs.k8s.io/kind/pkg/errors" + "github.com/vmware-tanzu/community-edition/errors" ) // write writes cfg to configPath @@ -21,11 +21,11 @@ func write(cfg *Config, configPath string) error { dir := filepath.Dir(configPath) if _, err := os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0755); err != nil { - return errors.Wrap(err, "failed to create directory for KUBECONFIG") + return errors.NewIOFailed(err, "failed to create directory for KUBECONFIG") } } if err := os.WriteFile(configPath, encoded, 0600); err != nil { - return errors.Wrap(err, "failed to write KUBECONFIG") + return errors.NewIOFailed(err, "failed to write KUBECONFIG") } return nil } diff --git a/extensions/docker-desktop/apps/clustermgr/pkg/utils/lock.go b/extensions/docker-desktop/apps/clustermgr/pkg/utils/lock.go index 569c8ae0ac..08359421a2 100644 --- a/extensions/docker-desktop/apps/clustermgr/pkg/utils/lock.go +++ b/extensions/docker-desktop/apps/clustermgr/pkg/utils/lock.go @@ -9,7 +9,8 @@ import ( "time" "github.com/juju/fslock" - "github.com/pkg/errors" + + "github.com/vmware-tanzu/community-edition/errors" ) const ( @@ -41,7 +42,7 @@ func GetFileLockWithTimeOut(lockPath string, lockDuration time.Duration) (*fsloc } if err := lock.LockWithTimeout(lockDuration); err != nil { - return &fslock.Lock{}, errors.Wrap(err, "failed to acquire a lock with timeout") + return &fslock.Lock{}, errors.NewTimeout(err, "failed to acquire a lock with timeout") } return lock, nil }