diff --git a/CHANGELOG.md b/CHANGELOG.md index e5597fc..bd4b46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This document outlines major changes between releases. - Use correct keys in `list-multipart-uploads` response (#185) ### Added +- Add basic error types and exit codes to `frostfs-s3-authmate` (#152) - Add a metric with addresses of nodes of the same and highest priority that are currently healthy (#51) - Support dump metrics descriptions (#80) - Add `copies_numbers` section to `placement_policy` in config file and support vectors of copies numbers (#70, #101) diff --git a/cmd/s3-authmate/main.go b/cmd/s3-authmate/main.go index 5465c80..86a2a22 100644 --- a/cmd/s3-authmate/main.go +++ b/cmd/s3-authmate/main.go @@ -15,6 +15,6 @@ func main() { if cmd, err := modules.Execute(ctx); err != nil { cmd.PrintErrln("Error:", err.Error()) cmd.PrintErrf("Run '%v --help' for usage.\n", cmd.CommandPath()) - os.Exit(1) + os.Exit(modules.ExitCode(err)) } } diff --git a/cmd/s3-authmate/modules/errors.go b/cmd/s3-authmate/modules/errors.go new file mode 100644 index 0000000..647071c --- /dev/null +++ b/cmd/s3-authmate/modules/errors.go @@ -0,0 +1,53 @@ +package modules + +type ( + preparationError struct { + err error + } + + frostFSInitError struct { + err error + } + + businessLogicError struct { + err error + } +) + +func wrapPreparationError(e error) error { + return preparationError{e} +} + +func (e preparationError) Error() string { + return e.err.Error() +} + +func wrapFrostFSInitError(e error) error { + return frostFSInitError{e} +} + +func (e frostFSInitError) Error() string { + return e.err.Error() +} + +func wrapBusinessLogicError(e error) error { + return businessLogicError{e} +} + +func (e businessLogicError) Error() string { + return e.err.Error() +} + +// ExitCode picks corresponding error code depending on the type of error provided. +// Returns 1 if error type is unknown. +func ExitCode(e error) int { + switch e.(type) { + case preparationError: + return 2 + case frostFSInitError: + return 3 + case businessLogicError: + return 4 + } + return 1 +} diff --git a/cmd/s3-authmate/modules/generate-presigned-url.go b/cmd/s3-authmate/modules/generate-presigned-url.go index 5c67737..d25a01c 100644 --- a/cmd/s3-authmate/modules/generate-presigned-url.go +++ b/cmd/s3-authmate/modules/generate-presigned-url.go @@ -76,7 +76,7 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error { SharedConfigState: session.SharedConfigEnable, }) if err != nil { - return fmt.Errorf("couldn't get aws credentials: %w", err) + return wrapPreparationError(fmt.Errorf("couldn't get aws credentials: %w", err)) } reqData := auth.RequestData{ @@ -94,7 +94,7 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error { req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData) if err != nil { - return err + return wrapBusinessLogicError(err) } res := &struct{ URL string }{ @@ -104,5 +104,9 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") enc.SetEscapeHTML(false) - return enc.Encode(res) + err = enc.Encode(res) + if err != nil { + return wrapBusinessLogicError(err) + } + return nil } diff --git a/cmd/s3-authmate/modules/issue-secret.go b/cmd/s3-authmate/modules/issue-secret.go index 6665ac8..3d62d0e 100644 --- a/cmd/s3-authmate/modules/issue-secret.go +++ b/cmd/s3-authmate/modules/issue-secret.go @@ -92,14 +92,14 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error { password := wallet.GetPassword(viper.GetViper(), walletPassphraseCfg) key, err := wallet.GetKeyFromPath(viper.GetString(walletFlag), viper.GetString(addressFlag), password) if err != nil { - return fmt.Errorf("failed to load frostfs private key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load frostfs private key: %s", err)) } var cnrID cid.ID containerID := viper.GetString(containerIDFlag) if len(containerID) > 0 { if err = cnrID.DecodeString(containerID); err != nil { - return fmt.Errorf("failed to parse auth container id: %s", err) + return wrapPreparationError(fmt.Errorf("failed to parse auth container id: %s", err)) } } @@ -107,35 +107,35 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error { for _, keyStr := range viper.GetStringSlice(gatePublicKeyFlag) { gpk, err := keys.NewPublicKeyFromString(keyStr) if err != nil { - return fmt.Errorf("failed to load gate's public key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load gate's public key: %s", err)) } gatesPublicKeys = append(gatesPublicKeys, gpk) } lifetime := viper.GetDuration(lifetimeFlag) if lifetime <= 0 { - return fmt.Errorf("lifetime must be greater 0, current value: %d", lifetime) + return wrapPreparationError(fmt.Errorf("lifetime must be greater 0, current value: %d", lifetime)) } policies, err := parsePolicies(viper.GetString(containerPolicyFlag)) if err != nil { - return fmt.Errorf("couldn't parse container policy: %s", err.Error()) + return wrapPreparationError(fmt.Errorf("couldn't parse container policy: %s", err.Error())) } disableImpersonate := viper.GetBool(disableImpersonateFlag) eaclRules := viper.GetString(bearerRulesFlag) if !disableImpersonate && eaclRules != "" { - return errors.New("--bearer-rules flag can be used only with --disable-impersonate") + return wrapPreparationError(errors.New("--bearer-rules flag can be used only with --disable-impersonate")) } bearerRules, err := getJSONRules(eaclRules) if err != nil { - return fmt.Errorf("couldn't parse 'bearer-rules' flag: %s", err.Error()) + return wrapPreparationError(fmt.Errorf("couldn't parse 'bearer-rules' flag: %s", err.Error())) } sessionRules, skipSessionRules, err := getSessionRules(viper.GetString(sessionTokensFlag)) if err != nil { - return fmt.Errorf("couldn't parse 'session-tokens' flag: %s", err.Error()) + return wrapPreparationError(fmt.Errorf("couldn't parse 'session-tokens' flag: %s", err.Error())) } poolCfg := PoolConfig{ @@ -149,7 +149,7 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error { frostFS, err := createFrostFS(ctx, log, poolCfg) if err != nil { - return fmt.Errorf("failed to create FrostFS component: %s", err) + return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err)) } issueSecretOptions := &authmate.IssueSecretOptions{ @@ -170,7 +170,7 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error { } if err = authmate.New(log, frostFS).IssueSecret(ctx, os.Stdout, issueSecretOptions); err != nil { - return fmt.Errorf("failed to issue secret: %s", err) + return wrapBusinessLogicError(fmt.Errorf("failed to issue secret: %s", err)) } return nil } diff --git a/cmd/s3-authmate/modules/obtain-secret.go b/cmd/s3-authmate/modules/obtain-secret.go index 27915d4..b7d5130 100644 --- a/cmd/s3-authmate/modules/obtain-secret.go +++ b/cmd/s3-authmate/modules/obtain-secret.go @@ -58,13 +58,13 @@ func runObtainSecretCmd(cmd *cobra.Command, _ []string) error { password := wallet.GetPassword(viper.GetViper(), walletPassphraseCfg) key, err := wallet.GetKeyFromPath(viper.GetString(walletFlag), viper.GetString(addressFlag), password) if err != nil { - return fmt.Errorf("failed to load frostfs private key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load frostfs private key: %s", err)) } gatePassword := wallet.GetPassword(viper.GetViper(), walletGatePassphraseCfg) gateKey, err := wallet.GetKeyFromPath(viper.GetString(gateWalletFlag), viper.GetString(gateAddressFlag), gatePassword) if err != nil { - return fmt.Errorf("failed to load s3 gate private key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load s3 gate private key: %s", err)) } poolCfg := PoolConfig{ @@ -78,7 +78,7 @@ func runObtainSecretCmd(cmd *cobra.Command, _ []string) error { frostFS, err := createFrostFS(ctx, log, poolCfg) if err != nil { - return cli.Exit(fmt.Sprintf("failed to create FrostFS component: %s", err), 2) + return wrapFrostFSInitError(cli.Exit(fmt.Sprintf("failed to create FrostFS component: %s", err), 2)) } obtainSecretOptions := &authmate.ObtainSecretOptions{ @@ -87,7 +87,7 @@ func runObtainSecretCmd(cmd *cobra.Command, _ []string) error { } if err = authmate.New(log, frostFS).ObtainSecret(ctx, os.Stdout, obtainSecretOptions); err != nil { - return fmt.Errorf("failed to obtain secret: %s", err) + return wrapBusinessLogicError(fmt.Errorf("failed to obtain secret: %s", err)) } return nil diff --git a/cmd/s3-authmate/modules/update-secret.go b/cmd/s3-authmate/modules/update-secret.go index 15ed8a0..e79cd02 100644 --- a/cmd/s3-authmate/modules/update-secret.go +++ b/cmd/s3-authmate/modules/update-secret.go @@ -56,26 +56,26 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error { password := wallet.GetPassword(viper.GetViper(), walletPassphraseCfg) key, err := wallet.GetKeyFromPath(viper.GetString(walletFlag), viper.GetString(addressFlag), password) if err != nil { - return fmt.Errorf("failed to load frostfs private key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load frostfs private key: %s", err)) } gatePassword := wallet.GetPassword(viper.GetViper(), walletGatePassphraseCfg) gateKey, err := wallet.GetKeyFromPath(viper.GetString(gateWalletFlag), viper.GetString(gateAddressFlag), gatePassword) if err != nil { - return fmt.Errorf("failed to load s3 gate private key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load s3 gate private key: %s", err)) } var accessBoxAddress oid.Address credAddr := strings.Replace(viper.GetString(accessKeyIDFlag), "0", "/", 1) if err = accessBoxAddress.DecodeString(credAddr); err != nil { - return fmt.Errorf("failed to parse creds address: %w", err) + return wrapPreparationError(fmt.Errorf("failed to parse creds address: %w", err)) } var gatesPublicKeys []*keys.PublicKey for _, keyStr := range viper.GetStringSlice(gatePublicKeyFlag) { gpk, err := keys.NewPublicKeyFromString(keyStr) if err != nil { - return fmt.Errorf("failed to load gate's public key: %s", err) + return wrapPreparationError(fmt.Errorf("failed to load gate's public key: %s", err)) } gatesPublicKeys = append(gatesPublicKeys, gpk) } @@ -91,7 +91,7 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error { frostFS, err := createFrostFS(ctx, log, poolCfg) if err != nil { - return fmt.Errorf("failed to create FrostFS component: %s", err) + return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err)) } updateSecretOptions := &authmate.UpdateSecretOptions{ @@ -102,7 +102,7 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error { } if err = authmate.New(log, frostFS).UpdateSecret(ctx, os.Stdout, updateSecretOptions); err != nil { - return fmt.Errorf("failed to update secret: %s", err) + return wrapBusinessLogicError(fmt.Errorf("failed to update secret: %s", err)) } return nil } diff --git a/docs/authmate.md b/docs/authmate.md index 69874a4..be9a99c 100644 --- a/docs/authmate.md +++ b/docs/authmate.md @@ -27,6 +27,7 @@ potentially). 3. [Obtainment of a secret](#obtaining-credential-secrets) 4. [Generate presigned url](#generate-presigned-url) 5. [Update secrets](#update-secret) +6. [Exit codes](#exit-codes) ## Generation of wallet @@ -371,3 +372,14 @@ Enter password for s3-wallet.json > "container_id": "HwrdXgetdGcEWAQwi68r1PMvw4iSm1Y5Z1fsFNSD6sQP" } ``` + +## Exit codes + +There are several non-zero exit codes added at the moment. + +| Code | Description | +|-------|--------------------------------------------------------------------------------------------| +| 1 | Any unknown errors, or errors generated by the parser of command line parameters. | +| 2 | Preparation errors: malformed configuration, issues with input data parsing. | +| 3 | FrostFS errors: connectivity problems, misconfiguration. | +| 4 | Business logic errors: `authmate` could not execute its task because of some restrictions. |