From 12c866bcb974b323ef77c226ee32ce0f31624a4c Mon Sep 17 00:00:00 2001 From: Paul Meyer <49727155+katexochen@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:56:25 +0100 Subject: [PATCH] deps: replace multierr with native errors.Join Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com> --- cli/internal/cloudcmd/rollback.go | 13 ++++---- cli/internal/cloudcmd/validators.go | 6 +--- cli/internal/cmd/configfetchmeasurements.go | 7 +++- cli/internal/cmd/create.go | 6 +++- cli/internal/cmd/init.go | 6 +++- cli/internal/cmd/minidown.go | 3 +- cli/internal/cmd/miniup.go | 6 +++- cli/internal/cmd/recover.go | 10 ++++-- cli/internal/cmd/terminate.go | 5 ++- cli/internal/cmd/upgradeapply.go | 6 +++- cli/internal/cmd/upgradecheck.go | 6 +++- cli/internal/cmd/verify.go | 6 +++- cli/internal/terraform/terraform_test.go | 12 ++++--- debugd/internal/cdbg/cmd/deploy.go | 7 +++- debugd/internal/debugd/server/server.go | 8 +---- go.mod | 2 +- hack/go.mod | 2 +- hack/image-measurement/main.go | 12 +++---- .../attestation/idkeydigest/idkeydigest.go | 6 ++-- .../attestation/measurements/measurements.go | 10 +++--- internal/config/config.go | 11 +++---- internal/config/config_test.go | 28 +++++----------- internal/config/validation.go | 30 +++++++++-------- internal/installer/installer_test.go | 6 ++-- internal/versionsapi/cli/rm.go | 15 ++++----- internal/versionsapi/client/client.go | 7 ++-- internal/versionsapi/imageinfo.go | 29 ++++++++--------- internal/versionsapi/latest.go | 18 +++++------ internal/versionsapi/list.go | 32 +++++++++---------- internal/versionsapi/version.go | 11 +++---- operators/constellation-node-operator/go.mod | 2 +- .../internal/cloud/gcp/client/client.go | 4 +-- 32 files changed, 173 insertions(+), 159 deletions(-) diff --git a/cli/internal/cloudcmd/rollback.go b/cli/internal/cloudcmd/rollback.go index 3be6baad7..0890ada9e 100644 --- a/cli/internal/cloudcmd/rollback.go +++ b/cli/internal/cloudcmd/rollback.go @@ -8,10 +8,9 @@ package cloudcmd import ( "context" + "errors" "fmt" "io" - - "go.uber.org/multierr" ) // rollbacker does a rollback. @@ -28,7 +27,7 @@ func rollbackOnError(ctx context.Context, w io.Writer, onErr *error, roll rollba fmt.Fprintf(w, "An error occurred: %s\n", *onErr) fmt.Fprintln(w, "Attempting to roll back.") if err := roll.rollback(ctx); err != nil { - *onErr = multierr.Append(*onErr, fmt.Errorf("on rollback: %w", err)) // TODO: print the error, or return it? + *onErr = errors.Join(*onErr, fmt.Errorf("on rollback: %w", err)) // TODO: print the error, or return it? return } fmt.Fprintln(w, "Rollback succeeded.") @@ -40,9 +39,9 @@ type rollbackerTerraform struct { func (r *rollbackerTerraform) rollback(ctx context.Context) error { var err error - err = multierr.Append(err, r.client.Destroy(ctx)) + err = errors.Join(err, r.client.Destroy(ctx)) if err == nil { - err = multierr.Append(err, r.client.CleanUpWorkspace()) + err = errors.Join(err, r.client.CleanUpWorkspace()) } return err } @@ -56,9 +55,9 @@ type rollbackerQEMU struct { func (r *rollbackerQEMU) rollback(ctx context.Context) error { var err error if r.createdWorkspace { - err = multierr.Append(err, r.client.Destroy(ctx)) + err = errors.Join(err, r.client.Destroy(ctx)) } - err = multierr.Append(err, r.libvirt.Stop(ctx)) + err = errors.Join(err, r.libvirt.Stop(ctx)) if err == nil { err = r.client.CleanUpWorkspace() } diff --git a/cli/internal/cloudcmd/validators.go b/cli/internal/cloudcmd/validators.go index 04f45d76b..02ff30b74 100644 --- a/cli/internal/cloudcmd/validators.go +++ b/cli/internal/cloudcmd/validators.go @@ -24,7 +24,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/spf13/cobra" - "go.uber.org/multierr" ) // Validator validates Platform Configuration Registers (PCRs). @@ -84,10 +83,7 @@ func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error { hexErr := err decoded, err = base64.StdEncoding.DecodeString(encoded) if err != nil { - return multierr.Append( - fmt.Errorf("input [%s] is not hex encoded: %w", encoded, hexErr), - fmt.Errorf("input [%s] is not base64 encoded: %w", encoded, err), - ) + return fmt.Errorf("input [%s] could neither be hex decoded (%w) nor base64 decoded (%w)", encoded, hexErr, err) } } // new_pcr_value := hash(old_pcr_value || data_to_extend) diff --git a/cli/internal/cmd/configfetchmeasurements.go b/cli/internal/cmd/configfetchmeasurements.go index 1abf66c7f..f50443164 100644 --- a/cli/internal/cmd/configfetchmeasurements.go +++ b/cli/internal/cmd/configfetchmeasurements.go @@ -8,6 +8,7 @@ package cmd import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -80,8 +81,12 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( cfm.log.Debugf("Loading configuration file from %q", flags.configPath) conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } if !conf.IsReleaseImage() { diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index e6c5be4d1..18332961c 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -73,8 +73,12 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler c.log.Debugf("Loading configuration file from %q", flags.configPath) conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } c.log.Debugf("Checking configuration for warnings") diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index 1dcb03382..ef4ec2dd8 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -104,8 +104,12 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud i.log.Debugf("Using flags: %+v", flags) i.log.Debugf("Loading configuration file from %q", flags.configPath) conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } i.log.Debugf("Checking cluster ID file") diff --git a/cli/internal/cmd/minidown.go b/cli/internal/cmd/minidown.go index 4de3babe0..a869536ba 100644 --- a/cli/internal/cmd/minidown.go +++ b/cli/internal/cmd/minidown.go @@ -17,7 +17,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" "github.com/spf13/cobra" - "go.uber.org/multierr" ) func newMiniDownCmd() *cobra.Command { @@ -39,7 +38,7 @@ func runDown(cmd *cobra.Command, args []string) error { err := runTerminate(cmd, args) if removeErr := os.Remove(constants.MasterSecretFilename); removeErr != nil && !os.IsNotExist(removeErr) { - err = multierr.Append(err, removeErr) + err = errors.Join(err, removeErr) } return err } diff --git a/cli/internal/cmd/miniup.go b/cli/internal/cmd/miniup.go index 0635a5cde..c4b469608 100644 --- a/cli/internal/cmd/miniup.go +++ b/cli/internal/cmd/miniup.go @@ -187,8 +187,12 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler) // check for existing config if configPath != "" { conf, err := config.New(fileHandler, configPath, force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return nil, config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return nil, err } if conf.GetProvider() != cloudprovider.QEMU { return nil, errors.New("invalid provider for MiniConstellation cluster") diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go index bf7e985f7..f67df6bc0 100644 --- a/cli/internal/cmd/recover.go +++ b/cli/internal/cmd/recover.go @@ -8,6 +8,7 @@ package cmd import ( "context" + "errors" "fmt" "io" "net" @@ -80,9 +81,14 @@ func (r *recoverCmd) recover( r.log.Debugf("Loading configuration file from %q", flags.configPath) conf, err := config.New(fileHandler, flags.configPath, flags.force) - if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) } + if err != nil { + return err + } + provider := conf.GetProvider() r.log.Debugf("Got provider %s", provider.String()) if provider == cloudprovider.Azure { diff --git a/cli/internal/cmd/terminate.go b/cli/internal/cmd/terminate.go index a23650361..a49d331e3 100644 --- a/cli/internal/cmd/terminate.go +++ b/cli/internal/cmd/terminate.go @@ -13,7 +13,6 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "go.uber.org/multierr" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/internal/constants" @@ -79,11 +78,11 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. var retErr error if err := fileHandler.Remove(constants.AdminConfFilename); err != nil && !errors.Is(err, fs.ErrNotExist) { - retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.AdminConfFilename)) + retErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.AdminConfFilename)) } if err := fileHandler.Remove(constants.ClusterIDsFileName); err != nil && !errors.Is(err, fs.ErrNotExist) { - retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.ClusterIDsFileName)) + retErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.ClusterIDsFileName)) } return retErr diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index d4491e2ba..c0344f618 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -74,8 +74,12 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, imageFetcher imageFet return fmt.Errorf("parsing flags: %w", err) } conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } if err := u.handleServiceUpgrade(cmd, conf, flags); err != nil { diff --git a/cli/internal/cmd/upgradecheck.go b/cli/internal/cmd/upgradecheck.go index 70996c893..fe12a4684 100644 --- a/cli/internal/cmd/upgradecheck.go +++ b/cli/internal/cmd/upgradecheck.go @@ -127,8 +127,12 @@ type upgradeCheckCmd struct { // upgradePlan plans an upgrade of a Constellation cluster. func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Handler, flags upgradeCheckFlags) error { conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } u.log.Debugf("Read configuration from %q", flags.configPath) // get current image version of the cluster diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index 4514a506f..97af70cec 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -76,8 +76,12 @@ func (v *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC v.log.Debugf("Loading configuration file from %q", flags.configPath) conf, err := config.New(fileHandler, flags.configPath, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } provider := conf.GetProvider() diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index d8f0df20c..f6ab7580c 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/multierr" ) func TestPrepareCluster(t *testing.T) { @@ -682,10 +681,13 @@ func TestCleanupWorkspace(t *testing.T) { "files are cleaned up": { provider: cloudprovider.QEMU, prepareFS: func(f file.Handler) error { - var err error - err = multierr.Append(err, f.Write("terraform.tfvars", someContent)) - err = multierr.Append(err, f.Write("terraform.tfstate", someContent)) - return multierr.Append(err, f.Write("terraform.tfstate.backup", someContent)) + if err := f.Write("terraform.tfvars", someContent); err != nil { + return err + } + if err := f.Write("terraform.tfstate", someContent); err != nil { + return err + } + return f.Write("terraform.tfstate.backup", someContent) }, }, "no error if files do not exist": { diff --git a/debugd/internal/cdbg/cmd/deploy.go b/debugd/internal/cdbg/cmd/deploy.go index f15992f61..e8a637e94 100644 --- a/debugd/internal/cdbg/cmd/deploy.go +++ b/debugd/internal/cdbg/cmd/deploy.go @@ -8,6 +8,7 @@ package cmd import ( "context" + "errors" "fmt" "io" "net" @@ -68,8 +69,12 @@ func runDeploy(cmd *cobra.Command, args []string) error { streamer := streamer.New(fs) transfer := filetransfer.New(log, streamer, filetransfer.ShowProgress) constellationConfig, err := config.New(fileHandler, configName, force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } if err != nil { - return config.DisplayValidationErrors(cmd.ErrOrStderr(), err) + return err } return deploy(cmd, fileHandler, constellationConfig, transfer, log) diff --git a/debugd/internal/debugd/server/server.go b/debugd/internal/debugd/server/server.go index 9ec85c594..8d9968498 100644 --- a/debugd/internal/debugd/server/server.go +++ b/debugd/internal/debugd/server/server.go @@ -21,7 +21,6 @@ import ( pb "github.com/edgelesssys/constellation/v2/debugd/service" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/logger" - "go.uber.org/multierr" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" @@ -116,14 +115,9 @@ func (s *debugdServer) UploadFiles(stream pb.Debugd_UploadFilesServer) error { continue } // continue on error to allow other units to be overridden - // TODO: switch to native go multierror once 1.20 is released - // err = s.serviceManager.OverrideServiceUnitExecStart(stream.Context(), file.OverrideServiceUnit, file.TargetPath) - // if err != nil { - // overrideUnitErr = errors.Join(overrideUnitErr, err) - // } err = s.serviceManager.OverrideServiceUnitExecStart(stream.Context(), file.OverrideServiceUnit, file.TargetPath) if err != nil { - overrideUnitErr = multierr.Append(overrideUnitErr, err) + overrideUnitErr = errors.Join(overrideUnitErr, err) } } diff --git a/go.mod b/go.mod index 40e9a2c92..0b0149aa4 100644 --- a/go.mod +++ b/go.mod @@ -91,7 +91,6 @@ require ( github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 go.uber.org/goleak v1.2.1 - go.uber.org/multierr v1.9.0 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.6.0 golang.org/x/mod v0.8.0 @@ -298,6 +297,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect golang.org/x/net v0.6.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect diff --git a/hack/go.mod b/hack/go.mod index 28c5bb664..898b517ff 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -44,7 +44,6 @@ require ( github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 go.uber.org/goleak v1.2.1 - go.uber.org/multierr v1.9.0 go.uber.org/zap v1.24.0 golang.org/x/mod v0.8.0 google.golang.org/grpc v1.51.0 @@ -268,6 +267,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect golang.org/x/net v0.6.0 // indirect diff --git a/hack/image-measurement/main.go b/hack/image-measurement/main.go index 70fd6d539..a7426ae12 100644 --- a/hack/image-measurement/main.go +++ b/hack/image-measurement/main.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only package main import ( + "errors" "flag" "fmt" "io" @@ -18,7 +19,6 @@ import ( "github.com/edgelesssys/constellation/v2/hack/image-measurement/server" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/logger" - "go.uber.org/multierr" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/yaml.v3" @@ -46,7 +46,7 @@ func (l *libvirtInstance) uploadBaseImage(baseVolume *libvirt.StorageVol) (err e return fmt.Errorf("error while opening %s: %s", l.imagePath, err) } defer func() { - err = multierr.Append(err, file.Close()) + err = errors.Join(err, file.Close()) }() fi, err := file.Stat() @@ -276,9 +276,9 @@ func (l *libvirtInstance) deletePool() error { func (l *libvirtInstance) deleteLibvirtInstance() error { var err error - err = multierr.Append(err, l.deleteNetwork()) - err = multierr.Append(err, l.deleteDomain()) - err = multierr.Append(err, l.deletePool()) + err = errors.Join(err, l.deleteNetwork()) + err = errors.Join(err, l.deleteDomain()) + err = errors.Join(err, l.deletePool()) return err } @@ -295,7 +295,7 @@ func (l *libvirtInstance) obtainMeasurements() (measurements measurements.M, err } }() defer func() { - err = multierr.Append(err, l.deleteLibvirtInstance()) + err = errors.Join(err, l.deleteLibvirtInstance()) }() if err := l.createLibvirtInstance(); err != nil { return nil, err diff --git a/internal/attestation/idkeydigest/idkeydigest.go b/internal/attestation/idkeydigest/idkeydigest.go index 61014cf05..f7da37eb1 100644 --- a/internal/attestation/idkeydigest/idkeydigest.go +++ b/internal/attestation/idkeydigest/idkeydigest.go @@ -9,10 +9,10 @@ package idkeydigest import ( "encoding/hex" "encoding/json" + "errors" "fmt" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "go.uber.org/multierr" ) // IDKeyDigests is a list of trusted digest values for the ID key. @@ -60,7 +60,7 @@ func (d *IDKeyDigests) UnmarshalYAML(unmarshal func(any) error) error { // Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct. var unmarshalledString string if legacyErr := unmarshal(&unmarshalledString); legacyErr != nil { - return multierr.Append( + return errors.Join( err, fmt.Errorf("trying legacy format: %w", legacyErr), ) @@ -89,7 +89,7 @@ func (d *IDKeyDigests) UnmarshalJSON(b []byte) error { // Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct. var unmarshalledString string if legacyErr := json.Unmarshal(b, &unmarshalledString); legacyErr != nil { - return multierr.Append( + return errors.Join( err, fmt.Errorf("trying legacy format: %w", legacyErr), ) diff --git a/internal/attestation/measurements/measurements.go b/internal/attestation/measurements/measurements.go index 34c498535..4b6d9d188 100644 --- a/internal/attestation/measurements/measurements.go +++ b/internal/attestation/measurements/measurements.go @@ -20,6 +20,7 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -31,7 +32,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/google/go-tpm/tpmutil" "github.com/siderolabs/talos/pkg/machinery/config/encoder" - "go.uber.org/multierr" "gopkg.in/yaml.v3" ) @@ -91,7 +91,7 @@ func (m *M) FetchAndVerify( var mWithMetadata WithMetadata if err := json.Unmarshal(measurements, &mWithMetadata); err != nil { if yamlErr := yaml.Unmarshal(measurements, &mWithMetadata); yamlErr != nil { - return "", multierr.Append( + return "", errors.Join( err, fmt.Errorf("trying yaml format: %w", yamlErr), ) @@ -193,7 +193,7 @@ func (m *Measurement) UnmarshalJSON(b []byte) error { // Unmarshalling failed, Measurement might be a simple string instead of Measurement struct. // These values will always be enforced. if legacyErr := json.Unmarshal(b, &eM.Expected); legacyErr != nil { - return multierr.Append( + return errors.Join( err, fmt.Errorf("trying legacy format: %w", legacyErr), ) @@ -222,7 +222,7 @@ func (m *Measurement) UnmarshalYAML(unmarshal func(any) error) error { // Unmarshalling failed, Measurement might be a simple string instead of Measurement struct. // These values will always be enforced. if legacyErr := unmarshal(&eM.Expected); legacyErr != nil { - return multierr.Append( + return errors.Join( err, fmt.Errorf("trying legacy format: %w", legacyErr), ) @@ -252,7 +252,7 @@ func (m *Measurement) unmarshal(eM encodedMeasurement) error { hexErr := err expected, err = base64.StdEncoding.DecodeString(eM.Expected) if err != nil { - return multierr.Append( + return errors.Join( fmt.Errorf("invalid measurement: not a hex string %w", hexErr), fmt.Errorf("not a base64 string: %w", err), ) diff --git a/internal/config/config.go b/internal/config/config.go index 4cd99bf9d..2617366ed 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -36,7 +36,6 @@ import ( ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" en_translations "github.com/go-playground/validator/v10/translations/en" - "go.uber.org/multierr" ) // Measurements is a required alias since docgen is not able to work with @@ -586,12 +585,10 @@ func (c *Config) Validate(force bool) error { return err } - var validationErrors error + var validationErrMsgs []string for _, e := range errs { - validationErrors = multierr.Append( - validationErrors, - errors.New(e.Translate(trans)), - ) + validationErrMsgs = append(validationErrMsgs, e.Translate(trans)) } - return validationErrors + + return &ValidationError{validationErrMsgs: validationErrMsgs} } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 0ea4bb33d..ccd63ba14 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" - "go.uber.org/multierr" ) func TestMain(m *testing.M) { @@ -185,19 +184,13 @@ func TestNewWithDefaultOptions(t *testing.T) { } func TestValidate(t *testing.T) { - const defaultErrCount = 21 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default - const azErrCount = 9 - const gcpErrCount = 6 - testCases := map[string]struct { - cnf *Config - wantErr bool - wantErrCount int + cnf *Config + wantErr bool }{ "default config is not valid": { - cnf: Default(), - wantErr: true, - wantErrCount: defaultErrCount, + cnf: Default(), + wantErr: true, }, "v0 is one error": { cnf: func() *Config { @@ -205,8 +198,7 @@ func TestValidate(t *testing.T) { cnf.Version = "v0" return cnf }(), - wantErr: true, - wantErrCount: defaultErrCount + 1, + wantErr: true, }, "v0 and negative state disk are two errors": { cnf: func() *Config { @@ -215,8 +207,7 @@ func TestValidate(t *testing.T) { cnf.StateDiskSizeGB = -1 return cnf }(), - wantErr: true, - wantErrCount: defaultErrCount + 2, + wantErr: true, }, "default Azure config is not valid": { cnf: func() *Config { @@ -226,8 +217,7 @@ func TestValidate(t *testing.T) { cnf.Provider.Azure = az return cnf }(), - wantErr: true, - wantErrCount: azErrCount, + wantErr: true, }, "Azure config with all required fields is valid": { cnf: func() *Config { @@ -255,8 +245,7 @@ func TestValidate(t *testing.T) { cnf.Provider.GCP = gcp return cnf }(), - wantErr: true, - wantErrCount: gcpErrCount, + wantErr: true, }, "GCP config with all required fields is valid": { cnf: func() *Config { @@ -302,7 +291,6 @@ func TestValidate(t *testing.T) { err := tc.cnf.Validate(false) if tc.wantErr { assert.Error(err) - assert.Len(multierr.Errors(err), tc.wantErrCount) return } assert.NoError(err) diff --git a/internal/config/validation.go b/internal/config/validation.go index fb863a1d5..0be597544 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -10,7 +10,6 @@ import ( "bytes" "errors" "fmt" - "io" "os" "sort" "strconv" @@ -25,22 +24,27 @@ import ( "github.com/edgelesssys/constellation/v2/internal/versionsapi" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) -// DisplayValidationErrors shows all validation errors inside configError as one formatted string. -func DisplayValidationErrors(errWriter io.Writer, configError error) error { - errs := multierr.Errors(configError) - if errs != nil { - fmt.Fprintln(errWriter, "Problems validating config file:") - for _, err := range errs { - fmt.Fprintln(errWriter, "\t"+err.Error()) - } - fmt.Fprintln(errWriter, "Fix the invalid entries or generate a new configuration using `constellation config generate`") - return errors.New("invalid configuration") +// ValidationError occurs when the validation of a config fails. +// It contains a list of errors that occurred during validation. +type ValidationError struct { + validationErrMsgs []string +} + +func (e *ValidationError) Error() string { + return "invalid configuration" +} + +// LongMessage prints the errors that occurred during validation in a verbose and user friendly way. +func (e *ValidationError) LongMessage() string { + msg := "Problems validating config file:\n" + for _, ve := range e.validationErrMsgs { + msg += fmt.Sprintf("\t%s\n", ve) } - return nil + msg += "Fix the invalid entries or generate a new configuration using `constellation config generate`" + return msg } func registerInvalidK8sVersionError(ut ut.Translator) error { diff --git a/internal/installer/installer_test.go b/internal/installer/installer_test.go index ebd2d305e..7490f1dce 100644 --- a/internal/installer/installer_test.go +++ b/internal/installer/installer_test.go @@ -12,6 +12,7 @@ import ( "bytes" "compress/gzip" "context" + "errors" "io" "io/fs" "net" @@ -26,7 +27,6 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/multierr" "google.golang.org/grpc/test/bufconn" testclock "k8s.io/utils/clock/testing" ) @@ -737,10 +737,10 @@ func (w *tarGzWriter) Bytes() []byte { func (w *tarGzWriter) Close() (result error) { if err := w.tarWriter.Close(); err != nil { - result = multierr.Append(result, err) + result = errors.Join(result, err) } if err := w.gzWriter.Close(); err != nil { - result = multierr.Append(result, err) + result = errors.Join(result, err) } return result } diff --git a/internal/versionsapi/cli/rm.go b/internal/versionsapi/cli/rm.go index 811d9aa2a..ffb4527be 100644 --- a/internal/versionsapi/cli/rm.go +++ b/internal/versionsapi/cli/rm.go @@ -29,7 +29,6 @@ import ( verclient "github.com/edgelesssys/constellation/v2/internal/versionsapi/client" gaxv2 "github.com/googleapis/gax-go/v2" "github.com/spf13/cobra" - "go.uber.org/multierr" "go.uber.org/zap/zapcore" ) @@ -141,12 +140,12 @@ func deleteSingleVersion(ctx context.Context, clients rmImageClients, ver versio log.Debugf("Deleting images for %s", ver.Version) if err := deleteImage(ctx, clients, ver, dryrun, log); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting images: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting images: %w", err)) } log.Debugf("Deleting version %s from versions API", ver.Version) if err := clients.version.DeleteVersion(ctx, ver); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting version from versions API: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting version from versions API: %w", err)) } return retErr @@ -182,13 +181,13 @@ func deleteRef(ctx context.Context, clients rmImageClients, ref string, dryrun b for _, ver := range vers { if err := deleteImage(ctx, clients, ver, dryrun, log); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting images for version %s: %w", ver.Version, err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting images for version %s: %w", ver.Version, err)) } } log.Infof("Deleting ref %s from versions API", ref) if err := clients.version.DeleteRef(ctx, ref); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting ref from versions API: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting ref from versions API: %w", err)) } return retErr @@ -215,21 +214,21 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve log.Infof("Deleting AWS images from %s", imageInfo.JSONPath()) for awsRegion, awsImage := range imageInfo.AWS { if err := clients.aws.deleteImage(ctx, awsImage, awsRegion, dryrun, log); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting AWS image %s: %w", awsImage, err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting AWS image %s: %w", awsImage, err)) } } log.Infof("Deleting GCP images from %s", imageInfo.JSONPath()) for _, gcpImage := range imageInfo.GCP { if err := clients.gcp.deleteImage(ctx, gcpImage, dryrun, log); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting GCP image %s: %w", gcpImage, err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting GCP image %s: %w", gcpImage, err)) } } log.Infof("Deleting Azure images from %s", imageInfo.JSONPath()) for _, azImage := range imageInfo.Azure { if err := clients.az.deleteImage(ctx, azImage, dryrun, log); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting Azure image %s: %w", azImage, err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting Azure image %s: %w", azImage, err)) } } diff --git a/internal/versionsapi/client/client.go b/internal/versionsapi/client/client.go index 46a6c873e..e56217e67 100644 --- a/internal/versionsapi/client/client.go +++ b/internal/versionsapi/client/client.go @@ -43,7 +43,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/versionsapi" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) @@ -167,17 +166,17 @@ func (c *Client) DeleteVersion(ctx context.Context, ver versionsapi.Version) err c.log.Debugf("Deleting version %s from minor version list", ver.Version) possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver) if err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("removing from minor version list: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err)) } c.log.Debugf("Checking latest version for %s", ver.Version) if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("updating latest version: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err)) } c.log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(), ver.Version) if err := c.deletePath(ctx, ver.ArtifactPath()); err != nil { - retErr = multierr.Append(retErr, fmt.Errorf("deleting artifact path: %w", err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err)) } return retErr diff --git a/internal/versionsapi/imageinfo.go b/internal/versionsapi/imageinfo.go index 185faff76..9c8e74adf 100644 --- a/internal/versionsapi/imageinfo.go +++ b/internal/versionsapi/imageinfo.go @@ -13,7 +13,6 @@ import ( "path" "github.com/edgelesssys/constellation/v2/internal/constants" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) @@ -62,25 +61,25 @@ func (i ImageInfo) URL() (string, error) { func (i ImageInfo) ValidateRequest() error { var retErr error if err := ValidateRef(i.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(i.Ref, i.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if !semver.IsValid(i.Version) { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) } if len(i.AWS) != 0 { - retErr = multierr.Append(retErr, errors.New("AWS map must be empty for request")) + retErr = errors.Join(retErr, errors.New("AWS map must be empty for request")) } if len(i.Azure) != 0 { - retErr = multierr.Append(retErr, errors.New("Azure map must be empty for request")) + retErr = errors.Join(retErr, errors.New("Azure map must be empty for request")) } if len(i.GCP) != 0 { - retErr = multierr.Append(retErr, errors.New("GCP map must be empty for request")) + retErr = errors.Join(retErr, errors.New("GCP map must be empty for request")) } if len(i.QEMU) != 0 { - retErr = multierr.Append(retErr, errors.New("QEMU map must be empty for request")) + retErr = errors.Join(retErr, errors.New("QEMU map must be empty for request")) } return retErr @@ -90,25 +89,25 @@ func (i ImageInfo) ValidateRequest() error { func (i ImageInfo) Validate() error { var retErr error if err := ValidateRef(i.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(i.Ref, i.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if !semver.IsValid(i.Version) { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) } if len(i.AWS) == 0 { - retErr = multierr.Append(retErr, errors.New("AWS map must not be empty")) + retErr = errors.Join(retErr, errors.New("AWS map must not be empty")) } if len(i.Azure) == 0 { - retErr = multierr.Append(retErr, errors.New("Azure map must not be empty")) + retErr = errors.Join(retErr, errors.New("Azure map must not be empty")) } if len(i.GCP) == 0 { - retErr = multierr.Append(retErr, errors.New("GCP map must not be empty")) + retErr = errors.Join(retErr, errors.New("GCP map must not be empty")) } if len(i.QEMU) == 0 { - retErr = multierr.Append(retErr, errors.New("QEMU map must not be empty")) + retErr = errors.Join(retErr, errors.New("QEMU map must not be empty")) } return retErr diff --git a/internal/versionsapi/latest.go b/internal/versionsapi/latest.go index 58905b6f2..2c9f2a20b 100644 --- a/internal/versionsapi/latest.go +++ b/internal/versionsapi/latest.go @@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only package versionsapi import ( + "errors" "fmt" "net/url" "path" "github.com/edgelesssys/constellation/v2/internal/constants" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) @@ -54,16 +54,16 @@ func (l Latest) URL() (string, error) { func (l Latest) Validate() error { var retErr error if err := ValidateRef(l.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(l.Ref, l.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if l.Kind == VersionKindUnknown { - retErr = multierr.Append(retErr, fmt.Errorf("version of kind %q is not supported", l.Kind)) + retErr = errors.Join(retErr, fmt.Errorf("version of kind %q is not supported", l.Kind)) } if !semver.IsValid(l.Version) { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a valid semver", l.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", l.Version)) } return retErr @@ -73,16 +73,16 @@ func (l Latest) Validate() error { func (l Latest) ValidateRequest() error { var retErr error if err := ValidateRef(l.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(l.Ref, l.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if l.Kind == VersionKindUnknown { - retErr = multierr.Append(retErr, fmt.Errorf("version of kind %q is not supported", l.Kind)) + retErr = errors.Join(retErr, fmt.Errorf("version of kind %q is not supported", l.Kind)) } if l.Version != "" { - retErr = multierr.Append(retErr, fmt.Errorf("version %q must be empty for request", l.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q must be empty for request", l.Version)) } return retErr } diff --git a/internal/versionsapi/list.go b/internal/versionsapi/list.go index 1012cb6c5..9a4745502 100644 --- a/internal/versionsapi/list.go +++ b/internal/versionsapi/list.go @@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only package versionsapi import ( + "errors" "fmt" "net/url" "path" "github.com/edgelesssys/constellation/v2/internal/constants" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) @@ -66,19 +66,19 @@ func (l List) URL() (string, error) { func (l List) ValidateRequest() error { var retErr error if err := ValidateRef(l.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(l.Ref, l.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if l.Granularity != GranularityMajor && l.Granularity != GranularityMinor { - retErr = multierr.Append(retErr, fmt.Errorf("granularity %q is not supported", l.Granularity)) + retErr = errors.Join(retErr, fmt.Errorf("granularity %q is not supported", l.Granularity)) } if l.Kind != VersionKindImage { - retErr = multierr.Append(retErr, fmt.Errorf("kind %q is not supported", l.Kind)) + retErr = errors.Join(retErr, fmt.Errorf("kind %q is not supported", l.Kind)) } if !semver.IsValid(l.Base) { - retErr = multierr.Append(retErr, fmt.Errorf("base version %q is not a valid semantic version", l.Base)) + retErr = errors.Join(retErr, fmt.Errorf("base version %q is not a valid semantic version", l.Base)) } var normalizeFunc func(string) string switch l.Granularity { @@ -90,10 +90,10 @@ func (l List) ValidateRequest() error { normalizeFunc = func(s string) string { return s } } if normalizeFunc(l.Base) != l.Base { - retErr = multierr.Append(retErr, fmt.Errorf("base version %q does not match granularity %q", l.Base, l.Granularity)) + retErr = errors.Join(retErr, fmt.Errorf("base version %q does not match granularity %q", l.Base, l.Granularity)) } if len(l.Versions) != 0 { - retErr = multierr.Append(retErr, fmt.Errorf("versions must be empty for request")) + retErr = errors.Join(retErr, fmt.Errorf("versions must be empty for request")) } return retErr } @@ -109,19 +109,19 @@ func (l List) ValidateRequest() error { func (l List) Validate() error { var retErr error if err := ValidateRef(l.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(l.Ref, l.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if l.Granularity != GranularityMajor && l.Granularity != GranularityMinor { - retErr = multierr.Append(retErr, fmt.Errorf("granularity %q is not supported", l.Granularity)) + retErr = errors.Join(retErr, fmt.Errorf("granularity %q is not supported", l.Granularity)) } if l.Kind != VersionKindImage { - retErr = multierr.Append(retErr, fmt.Errorf("kind %q is not supported", l.Kind)) + retErr = errors.Join(retErr, fmt.Errorf("kind %q is not supported", l.Kind)) } if !semver.IsValid(l.Base) { - retErr = multierr.Append(retErr, fmt.Errorf("base version %q is not a valid semantic version", l.Base)) + retErr = errors.Join(retErr, fmt.Errorf("base version %q is not a valid semantic version", l.Base)) } var normalizeFunc func(string) string switch l.Granularity { @@ -133,14 +133,14 @@ func (l List) Validate() error { normalizeFunc = func(s string) string { return s } } if normalizeFunc(l.Base) != l.Base { - retErr = multierr.Append(retErr, fmt.Errorf("base version %q does not match granularity %q", l.Base, l.Granularity)) + retErr = errors.Join(retErr, fmt.Errorf("base version %q does not match granularity %q", l.Base, l.Granularity)) } for _, ver := range l.Versions { if !semver.IsValid(ver) { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a valid semantic version", ver)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semantic version", ver)) } if normalizeFunc(ver) != l.Base || normalizeFunc(ver) == ver { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not finer-grained than base version %q", ver, l.Base)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not finer-grained than base version %q", ver, l.Base)) } } diff --git a/internal/versionsapi/version.go b/internal/versionsapi/version.go index 2e0c782ac..c1d7a7a1c 100644 --- a/internal/versionsapi/version.go +++ b/internal/versionsapi/version.go @@ -15,7 +15,6 @@ import ( "strings" "github.com/edgelesssys/constellation/v2/internal/constants" - "go.uber.org/multierr" "golang.org/x/mod/semver" ) @@ -64,19 +63,19 @@ func (v Version) ShortPath() string { func (v Version) Validate() error { var retErr error if err := ValidateRef(v.Ref); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if err := ValidateStream(v.Ref, v.Stream); err != nil { - retErr = multierr.Append(retErr, err) + retErr = errors.Join(retErr, err) } if !semver.IsValid(v.Version) { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a valid semantic version", v.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semantic version", v.Version)) } if semver.Canonical(v.Version) != v.Version { - retErr = multierr.Append(retErr, fmt.Errorf("version %q is not a canonical semantic version", v.Version)) + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a canonical semantic version", v.Version)) } if v.Kind == VersionKindUnknown { - retErr = multierr.Append(retErr, errors.New("version kind is unknown")) + retErr = errors.Join(retErr, errors.New("version kind is unknown")) } return retErr } diff --git a/operators/constellation-node-operator/go.mod b/operators/constellation-node-operator/go.mod index 60fd55af1..d2f690272 100644 --- a/operators/constellation-node-operator/go.mod +++ b/operators/constellation-node-operator/go.mod @@ -24,7 +24,6 @@ require ( go.etcd.io/etcd/api/v3 v3.5.7 go.etcd.io/etcd/client/pkg/v3 v3.5.7 go.etcd.io/etcd/client/v3 v3.5.7 - go.uber.org/multierr v1.9.0 golang.org/x/mod v0.8.0 google.golang.org/api v0.107.0 google.golang.org/protobuf v1.28.1 @@ -83,6 +82,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/net v0.6.0 // indirect diff --git a/operators/constellation-node-operator/internal/cloud/gcp/client/client.go b/operators/constellation-node-operator/internal/cloud/gcp/client/client.go index f995feb14..aa0a46ae7 100644 --- a/operators/constellation-node-operator/internal/cloud/gcp/client/client.go +++ b/operators/constellation-node-operator/internal/cloud/gcp/client/client.go @@ -8,12 +8,12 @@ package client import ( "context" + "errors" "math/rand" "time" compute "cloud.google.com/go/compute/apiv1" "github.com/spf13/afero" - "go.uber.org/multierr" ) // Client is a client for the Google Compute Engine. @@ -100,7 +100,7 @@ func closeAll(closers []closer) error { // close operations, even if a previous operation failed. var errs error for _, closer := range closers { - errs = multierr.Append(errs, closer.Close()) + errs = errors.Join(errs, closer.Close()) } return errs }