diff --git a/api/action_warning.gen.go b/api/action_warning.gen.go new file mode 100644 index 0000000000..4358e355bb --- /dev/null +++ b/api/action_warning.gen.go @@ -0,0 +1,10 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +type ActionWarning struct { + Name string `json:"name,omitempty"` + Warning string `json:"warning,omitempty"` +} diff --git a/api/behavior_warning.gen.go b/api/behavior_warning.gen.go new file mode 100644 index 0000000000..bc58610104 --- /dev/null +++ b/api/behavior_warning.gen.go @@ -0,0 +1,9 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +type BehaviorWarning struct { + Warning string `json:"warning,omitempty"` +} diff --git a/api/field_warning.gen.go b/api/field_warning.gen.go new file mode 100644 index 0000000000..85863e5cce --- /dev/null +++ b/api/field_warning.gen.go @@ -0,0 +1,10 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +type FieldWarning struct { + Name string `json:"name,omitempty"` + Warning string `json:"warning,omitempty"` +} diff --git a/api/response.go b/api/response.go index b17287c5d2..b5aca90b2e 100644 --- a/api/response.go +++ b/api/response.go @@ -30,6 +30,20 @@ func (r *Response) StatusCode() int { return r.resp.StatusCode } +func (r *Response) Warnings() ([]*Warning, error) { + ws := r.resp.Header.Get("X-Boundary-Warnings") + if ws == "" { + return nil, nil + } + + var ret WarningResponse + if err := json.Unmarshal([]byte(ws), &ret); err != nil { + return nil, fmt.Errorf("when unmarshaling warning %q: %w", ws, err) + } + + return ret.Warnings, nil +} + func (r *Response) Decode(inStruct any) (*Error, error) { if r == nil || r.resp == nil { return nil, fmt.Errorf("nil response, cannot decode") diff --git a/api/warning.gen.go b/api/warning.gen.go new file mode 100644 index 0000000000..628166d9cf --- /dev/null +++ b/api/warning.gen.go @@ -0,0 +1,12 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +type Warning struct { + Code uint32 `json:"code,omitempty"` + RequestField *FieldWarning `json:"request_field,omitempty"` + Action *ActionWarning `json:"action,omitempty"` + Behavior *BehaviorWarning `json:"behavior,omitempty"` +} diff --git a/api/warning_response.gen.go b/api/warning_response.gen.go new file mode 100644 index 0000000000..bf9deb480d --- /dev/null +++ b/api/warning_response.gen.go @@ -0,0 +1,9 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +type WarningResponse struct { + Warnings []*Warning `json:"warnings,omitempty"` +} diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index 151a156ef5..97511aa416 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -140,6 +140,31 @@ type structInfo struct { } var inputStructs = []*structInfo{ + { + inProto: &api.WarningResponse{}, + outFile: "warning_response.gen.go", + skipOptions: true, + }, + { + inProto: &api.Warning{}, + outFile: "warning.gen.go", + skipOptions: true, + }, + { + inProto: &api.FieldWarning{}, + outFile: "field_warning.gen.go", + skipOptions: true, + }, + { + inProto: &api.BehaviorWarning{}, + outFile: "behavior_warning.gen.go", + skipOptions: true, + }, + { + inProto: &api.ActionWarning{}, + outFile: "action_warning.gen.go", + skipOptions: true, + }, { inProto: &api.Error{}, outFile: "error.gen.go", diff --git a/internal/cmd/base/format.go b/internal/cmd/base/format.go index 5028a8aec8..6ee6dae679 100644 --- a/internal/cmd/base/format.go +++ b/internal/cmd/base/format.go @@ -280,6 +280,25 @@ func (c *Command) PrintApiError(in *api.Error, contextStr string, opt ...Option) } } +func (c *Command) PrintWarning(w *api.Warning) { + if w == nil { + return + } + switch Format(c.UI) { + case "table": + var descStr string + switch { + case w.Action != nil: + descStr = fmt.Sprintf("%s: %s", w.Action.Name, w.Action.Warning) + case w.Behavior != nil: + descStr = w.Behavior.Warning + case w.RequestField != nil: + descStr = fmt.Sprintf("%s: %s", w.RequestField.Name, w.RequestField.Warning) + } + c.UI.Warn(fmt.Sprintf("Code %d: %s", w.Code, descStr)) + } +} + // PrintCliError prints the given CLI error to the UI in the appropriate format func (c *Command) PrintCliError(err error) { switch Format(c.UI) { @@ -302,6 +321,14 @@ func (c *Command) PrintJsonItem(resp *api.Response, opt ...Option) bool { c.PrintCliError(errors.New("Error formatting as JSON: no response given to item formatter")) return false } + ws, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + if len(ws) > 0 { + opt = append(opt, WithApiWarning(ws)) + } + if r := resp.HttpResponse(); r != nil { opt = append(opt, WithStatusCode(r.StatusCode)) } @@ -312,11 +339,13 @@ func (c *Command) PrintJsonItem(resp *api.Response, opt ...Option) bool { func (c *Command) PrintJson(input json.RawMessage, opt ...Option) bool { opts := getOpts(opt...) output := struct { - StatusCode int `json:"status_code,omitempty"` - Item json.RawMessage `json:"item,omitempty"` + StatusCode int `json:"status_code,omitempty"` + Item json.RawMessage `json:"item,omitempty"` + ApiWarnings []*api.Warning `json:"api_warnings,omitempty"` }{ - StatusCode: opts.withStatusCode, - Item: input, + StatusCode: opts.withStatusCode, + ApiWarnings: opts.withApiWarnings, + Item: input, } b, err := JsonFormatter{}.Format(output) if err != nil { @@ -347,12 +376,19 @@ func (c *Command) PrintJsonItems(resp *api.Response) bool { return false } } + ws, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + return false + } output := struct { - StatusCode int `json:"status_code"` - Items json.RawMessage `json:"items"` + StatusCode int `json:"status_code"` + Items json.RawMessage `json:"items"` + ApiWarnings []*api.Warning `json:"api_warnings"` }{ - StatusCode: resp.HttpResponse().StatusCode, - Items: input.Items, + StatusCode: resp.HttpResponse().StatusCode, + Items: input.Items, + ApiWarnings: ws, } b, err := JsonFormatter{}.Format(output) if err != nil { diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index 68f69df0b5..c98321351c 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -4,6 +4,7 @@ package base import ( + "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/internal/event" "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping/v2" @@ -42,6 +43,7 @@ type Options struct { withEventWrapper wrapping.Wrapper withAttributeFieldPrefix string withStatusCode int + withApiWarnings []*api.Warning withHostPlugin func() (string, plugin.HostPluginServiceClient) withEventGating bool } @@ -169,6 +171,13 @@ func WithAttributeFieldPrefix(p string) Option { } } +// WithApiWarning allows passing api warnings to functions +func WithApiWarning(warnings []*api.Warning) Option { + return func(o *Options) { + o.withApiWarnings = warnings + } +} + // WithStatusCode allows passing status codes to functions func WithStatusCode(statusCode int) Option { return func(o *Options) { diff --git a/internal/cmd/commands/accountscmd/accounts.gen.go b/internal/cmd/commands/accountscmd/accounts.gen.go index 939fe706e8..e777542f00 100644 --- a/internal/cmd/commands/accountscmd/accounts.gen.go +++ b/internal/cmd/commands/accountscmd/accounts.gen.go @@ -282,6 +282,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -295,6 +302,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -304,6 +318,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/accountscmd/ldap_accounts.gen.go b/internal/cmd/commands/accountscmd/ldap_accounts.gen.go index 0e8aa8e6e0..d0623ebaa5 100644 --- a/internal/cmd/commands/accountscmd/ldap_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/ldap_accounts.gen.go @@ -243,6 +243,13 @@ func (c *LdapCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go index ddd370acae..41ef0fd2f3 100644 --- a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go @@ -243,6 +243,13 @@ func (c *OidcCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/accountscmd/password_accounts.gen.go b/internal/cmd/commands/accountscmd/password_accounts.gen.go index 97c2de51ae..141533dfd4 100644 --- a/internal/cmd/commands/accountscmd/password_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/password_accounts.gen.go @@ -243,6 +243,13 @@ func (c *PasswordCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/authenticate/funcs.go b/internal/cmd/commands/authenticate/funcs.go index cb89678b4c..5f09467ec3 100644 --- a/internal/cmd/commands/authenticate/funcs.go +++ b/internal/cmd/commands/authenticate/funcs.go @@ -27,6 +27,14 @@ func saveAndOrPrintToken(c *base.Command, result *authmethods.AuthenticateResult switch base.Format(c.UI) { case "table": + warnings, err := result.GetResponse().Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } + c.UI.Output(base.WrapForHelpText([]string{ "", "Authentication information:", diff --git a/internal/cmd/commands/authmethodscmd/authmethods.gen.go b/internal/cmd/commands/authmethodscmd/authmethods.gen.go index 56a0735021..1807f9bb81 100644 --- a/internal/cmd/commands/authmethodscmd/authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/authmethods.gen.go @@ -249,6 +249,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -262,6 +269,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -271,6 +285,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/authmethodscmd/ldap_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/ldap_authmethods.gen.go index 1a86659b99..36d1cc1380 100644 --- a/internal/cmd/commands/authmethodscmd/ldap_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/ldap_authmethods.gen.go @@ -248,6 +248,13 @@ func (c *LdapCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go index 39e7bee1a6..a2d249fb54 100644 --- a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go @@ -256,6 +256,13 @@ func (c *OidcCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go index 46ab08fbd4..c306f94984 100644 --- a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go @@ -248,6 +248,13 @@ func (c *PasswordCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/authtokenscmd/authtokens.gen.go b/internal/cmd/commands/authtokenscmd/authtokens.gen.go index 9bc9f7d09c..3c0648bb8a 100644 --- a/internal/cmd/commands/authtokenscmd/authtokens.gen.go +++ b/internal/cmd/commands/authtokenscmd/authtokens.gen.go @@ -244,6 +244,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -257,6 +264,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -266,6 +280,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go index bb204ecdce..88a54e2975 100644 --- a/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go +++ b/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go @@ -244,6 +244,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -257,6 +264,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -266,6 +280,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentiallibrariescmd/vault-generic_credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/vault-generic_credentiallibraries.gen.go index e154cd52e6..85c364b8cc 100644 --- a/internal/cmd/commands/credentiallibrariescmd/vault-generic_credentiallibraries.gen.go +++ b/internal/cmd/commands/credentiallibrariescmd/vault-generic_credentiallibraries.gen.go @@ -243,6 +243,13 @@ func (c *VaultGenericCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentiallibrariescmd/vault-ssh-certificate_credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/vault-ssh-certificate_credentiallibraries.gen.go index 862c60d2d4..e49d42c576 100644 --- a/internal/cmd/commands/credentiallibrariescmd/vault-ssh-certificate_credentiallibraries.gen.go +++ b/internal/cmd/commands/credentiallibrariescmd/vault-ssh-certificate_credentiallibraries.gen.go @@ -243,6 +243,13 @@ func (c *VaultSshCertificateCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go index b2e76a154e..de6d37753c 100644 --- a/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go +++ b/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go @@ -243,6 +243,13 @@ func (c *VaultCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialscmd/credentials.gen.go b/internal/cmd/commands/credentialscmd/credentials.gen.go index 0db3a0e212..cdad0bb4f7 100644 --- a/internal/cmd/commands/credentialscmd/credentials.gen.go +++ b/internal/cmd/commands/credentialscmd/credentials.gen.go @@ -244,6 +244,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -257,6 +264,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -266,6 +280,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialscmd/json_credentials.gen.go b/internal/cmd/commands/credentialscmd/json_credentials.gen.go index ad2b166bdb..564b201454 100644 --- a/internal/cmd/commands/credentialscmd/json_credentials.gen.go +++ b/internal/cmd/commands/credentialscmd/json_credentials.gen.go @@ -270,6 +270,13 @@ func (c *JsonCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialscmd/ssh-private-key_credentials.gen.go b/internal/cmd/commands/credentialscmd/ssh-private-key_credentials.gen.go index 4916261dfb..5e2c1789fe 100644 --- a/internal/cmd/commands/credentialscmd/ssh-private-key_credentials.gen.go +++ b/internal/cmd/commands/credentialscmd/ssh-private-key_credentials.gen.go @@ -243,6 +243,13 @@ func (c *SshPrivateKeyCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialscmd/username-password_credentials.gen.go b/internal/cmd/commands/credentialscmd/username-password_credentials.gen.go index a4f9ab3425..53bd1de376 100644 --- a/internal/cmd/commands/credentialscmd/username-password_credentials.gen.go +++ b/internal/cmd/commands/credentialscmd/username-password_credentials.gen.go @@ -243,6 +243,13 @@ func (c *UsernamePasswordCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go index a6555f23ac..e75a81da92 100644 --- a/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go +++ b/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go @@ -249,6 +249,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -262,6 +269,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -271,6 +285,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go index 8736c5e2e6..7a53daf915 100644 --- a/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go +++ b/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go @@ -246,6 +246,13 @@ func (c *StaticCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go index 8c4eb8cb6f..723d06cc8a 100644 --- a/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go +++ b/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go @@ -248,6 +248,13 @@ func (c *VaultCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/groupscmd/groups.gen.go b/internal/cmd/commands/groupscmd/groups.gen.go index d9f0a04d31..c39eaf0386 100644 --- a/internal/cmd/commands/groupscmd/groups.gen.go +++ b/internal/cmd/commands/groupscmd/groups.gen.go @@ -333,6 +333,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -346,6 +353,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -355,6 +369,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go index 78c40e6dac..f6ed14b5a6 100644 --- a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go @@ -249,6 +249,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -262,6 +269,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -271,6 +285,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go index 9f67aec608..21283456b6 100644 --- a/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go @@ -319,6 +319,13 @@ func (c *PluginCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go index 9fba63abd0..f61fafaa59 100644 --- a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go @@ -246,6 +246,13 @@ func (c *StaticCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostscmd/hosts.gen.go b/internal/cmd/commands/hostscmd/hosts.gen.go index 82a8d9d3b5..e648fffe67 100644 --- a/internal/cmd/commands/hostscmd/hosts.gen.go +++ b/internal/cmd/commands/hostscmd/hosts.gen.go @@ -260,6 +260,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -273,6 +280,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -282,6 +296,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostscmd/static_hosts.gen.go b/internal/cmd/commands/hostscmd/static_hosts.gen.go index d7de3952d0..887f9649b0 100644 --- a/internal/cmd/commands/hostscmd/static_hosts.gen.go +++ b/internal/cmd/commands/hostscmd/static_hosts.gen.go @@ -243,6 +243,13 @@ func (c *StaticCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostsetscmd/hostsets.gen.go b/internal/cmd/commands/hostsetscmd/hostsets.gen.go index 539ce9e58e..b92fb1fe57 100644 --- a/internal/cmd/commands/hostsetscmd/hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/hostsets.gen.go @@ -290,6 +290,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -303,6 +310,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -312,6 +326,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go index 3bd6f0268c..9694fb7582 100644 --- a/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go @@ -274,6 +274,13 @@ func (c *PluginCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go index fddec896b8..89dff88ac5 100644 --- a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go @@ -241,6 +241,13 @@ func (c *StaticCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/managedgroupscmd/ldap_managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/ldap_managedgroups.gen.go index 4ae135b958..7d7e1fe11f 100644 --- a/internal/cmd/commands/managedgroupscmd/ldap_managedgroups.gen.go +++ b/internal/cmd/commands/managedgroupscmd/ldap_managedgroups.gen.go @@ -243,6 +243,13 @@ func (c *LdapCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go index ca3a3918ba..6d9fcda130 100644 --- a/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go +++ b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go @@ -260,6 +260,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -273,6 +280,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -282,6 +296,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go index 5b7ed8d3e4..7ee2104ee8 100644 --- a/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go +++ b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go @@ -243,6 +243,13 @@ func (c *OidcCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/rolescmd/roles.gen.go b/internal/cmd/commands/rolescmd/roles.gen.go index 334f4a9bb3..a2cc0daa43 100644 --- a/internal/cmd/commands/rolescmd/roles.gen.go +++ b/internal/cmd/commands/rolescmd/roles.gen.go @@ -357,6 +357,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -370,6 +377,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -379,6 +393,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/scopescmd/destroy_key_version.go b/internal/cmd/commands/scopescmd/destroy_key_version.go index ba80accd0a..708244d2ea 100644 --- a/internal/cmd/commands/scopescmd/destroy_key_version.go +++ b/internal/cmd/commands/scopescmd/destroy_key_version.go @@ -116,6 +116,13 @@ func (c *DestroyKeyVersionCommand) Run(args []string) int { return base.CommandCliError } default: + warnings, err := result.GetResponse().Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } switch result.State { case "completed": c.UI.Output("The key version was successfully destroyed.") diff --git a/internal/cmd/commands/scopescmd/rotate_keys.go b/internal/cmd/commands/scopescmd/rotate_keys.go index 9fa0d2cb68..c6afc569a9 100644 --- a/internal/cmd/commands/scopescmd/rotate_keys.go +++ b/internal/cmd/commands/scopescmd/rotate_keys.go @@ -113,6 +113,13 @@ func (c *RotateKeysCommand) Run(args []string) int { } default: + warnings, err := result.GetResponse().Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The rotate operation completed successfully.") } diff --git a/internal/cmd/commands/scopescmd/scopes.gen.go b/internal/cmd/commands/scopescmd/scopes.gen.go index 6f6e652c62..b9ba387739 100644 --- a/internal/cmd/commands/scopescmd/scopes.gen.go +++ b/internal/cmd/commands/scopescmd/scopes.gen.go @@ -309,6 +309,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -322,6 +329,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -331,6 +345,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go b/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go index d35fbce707..0cde3e0368 100644 --- a/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go +++ b/internal/cmd/commands/sessionrecordingscmd/sessionrecordings.gen.go @@ -233,6 +233,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -242,6 +249,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/sessionscmd/sessions.gen.go b/internal/cmd/commands/sessionscmd/sessions.gen.go index e236946824..a7d5bcd9de 100644 --- a/internal/cmd/commands/sessionscmd/sessions.gen.go +++ b/internal/cmd/commands/sessionscmd/sessions.gen.go @@ -249,6 +249,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -258,6 +265,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/storagebucketscmd/storagebuckets.gen.go b/internal/cmd/commands/storagebucketscmd/storagebuckets.gen.go index 5dc0abda1a..cb5a08ff2e 100644 --- a/internal/cmd/commands/storagebucketscmd/storagebuckets.gen.go +++ b/internal/cmd/commands/storagebucketscmd/storagebuckets.gen.go @@ -382,6 +382,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -395,6 +402,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -404,6 +418,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/targetscmd/ssh_targets.gen.go b/internal/cmd/commands/targetscmd/ssh_targets.gen.go index b2ca443fa7..5f54463440 100644 --- a/internal/cmd/commands/targetscmd/ssh_targets.gen.go +++ b/internal/cmd/commands/targetscmd/ssh_targets.gen.go @@ -248,6 +248,13 @@ func (c *SshCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/targetscmd/targets.gen.go b/internal/cmd/commands/targetscmd/targets.gen.go index 4aa222e48a..8f0afaa9c6 100644 --- a/internal/cmd/commands/targetscmd/targets.gen.go +++ b/internal/cmd/commands/targetscmd/targets.gen.go @@ -320,6 +320,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -333,6 +340,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -342,6 +356,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/targetscmd/tcp_targets.gen.go b/internal/cmd/commands/targetscmd/tcp_targets.gen.go index ff068c5911..c181bf166c 100644 --- a/internal/cmd/commands/targetscmd/tcp_targets.gen.go +++ b/internal/cmd/commands/targetscmd/tcp_targets.gen.go @@ -248,6 +248,13 @@ func (c *TcpCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/userscmd/users.gen.go b/internal/cmd/commands/userscmd/users.gen.go index 177be405dd..e7f3fb13f4 100644 --- a/internal/cmd/commands/userscmd/users.gen.go +++ b/internal/cmd/commands/userscmd/users.gen.go @@ -333,6 +333,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -346,6 +353,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -355,6 +369,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/workerscmd/certificate-authority.go b/internal/cmd/commands/workerscmd/certificate-authority.go index abaa1542d1..29014d703f 100644 --- a/internal/cmd/commands/workerscmd/certificate-authority.go +++ b/internal/cmd/commands/workerscmd/certificate-authority.go @@ -214,6 +214,13 @@ func (c *WorkerCACommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(item)) case "json": diff --git a/internal/cmd/commands/workerscmd/controller-led_workers.gen.go b/internal/cmd/commands/workerscmd/controller-led_workers.gen.go index c1db00255d..7abab082b5 100644 --- a/internal/cmd/commands/workerscmd/controller-led_workers.gen.go +++ b/internal/cmd/commands/workerscmd/controller-led_workers.gen.go @@ -215,6 +215,13 @@ func (c *ControllerLedCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/workerscmd/worker-led_workers.gen.go b/internal/cmd/commands/workerscmd/worker-led_workers.gen.go index 95b4c10060..4404d8a038 100644 --- a/internal/cmd/commands/workerscmd/worker-led_workers.gen.go +++ b/internal/cmd/commands/workerscmd/worker-led_workers.gen.go @@ -217,6 +217,13 @@ func (c *WorkerLedCommand) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/commands/workerscmd/workers.gen.go b/internal/cmd/commands/workerscmd/workers.gen.go index 71bcfe2aeb..179cbcf437 100644 --- a/internal/cmd/commands/workerscmd/workers.gen.go +++ b/internal/cmd/commands/workerscmd/workers.gen.go @@ -313,6 +313,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -326,6 +333,13 @@ func (c *Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -335,6 +349,13 @@ func (c *Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/cmd/gencli/templates.go b/internal/cmd/gencli/templates.go index 853184ff2a..1886cf8dc7 100644 --- a/internal/cmd/gencli/templates.go +++ b/internal/cmd/gencli/templates.go @@ -564,6 +564,13 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output("The delete operation completed successfully.") } @@ -578,6 +585,13 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int { } case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(c.printListTable(items)) } @@ -588,6 +602,13 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int { switch base.Format(c.UI) { case "table": + warnings, err := resp.Warnings() + if err != nil { + c.PrintCliError(fmt.Errorf("Error getting warnings: %w", err)) + } + for _, w := range warnings { + c.PrintWarning(w) + } c.UI.Output(printItemTable(item, resp)) case "json": diff --git a/internal/daemon/controller/gateway.go b/internal/daemon/controller/gateway.go index a832184b05..0574502512 100644 --- a/internal/daemon/controller/gateway.go +++ b/internal/daemon/controller/gateway.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/boundary/internal/event" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/subtypes" + "github.com/hashicorp/boundary/internal/warning" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" ) @@ -58,6 +59,7 @@ func newGrpcGatewayMux() *runtime.ServeMux { }), runtime.WithErrorHandler(handlers.ErrorHandler()), runtime.WithForwardResponseOption(handlers.OutgoingResponseFilter), + runtime.WithOutgoingHeaderMatcher(warning.OutgoingHeaderMatcher()), ) } @@ -114,6 +116,7 @@ func newGrpcServer( grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( unaryCtxInterceptor, // populated requestInfo from headers into the request ctx + warning.GrpcInterceptor(ctx), // add warning handling to the ctx and convert them to an http header errorInterceptor(ctx), // convert domain and api errors into headers for the http proxy subtypes.AttributeTransformerInterceptor(ctx), // convert to/from generic attributes from/to subtype specific attributes eventsRequestInterceptor(ctx), // before we get started, send the required events with the request diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index b461c3cb3d..1320b3ac32 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -459,6 +459,10 @@ func wrapHandlerWithCors(h http.Handler, props HandlerProperties) http.Handler { "Authorization", }, props.ListenerConfig.CorsAllowedHeaders...) + exposedHeaders := []string{ + "X-Boundary-Warnings", + } + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if props.ListenerConfig.CorsEnabled == nil || !*props.ListenerConfig.CorsEnabled { h.ServeHTTP(w, req) @@ -513,6 +517,7 @@ func wrapHandlerWithCors(h http.Handler, props HandlerProperties) http.Handler { if req.Method == http.MethodOptions { w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", ")) w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", ")) + w.Header().Set("Access-Control-Expose-Headers", strings.Join(exposedHeaders, ", ")) w.Header().Set("Access-Control-Max-Age", "300") w.WriteHeader(http.StatusNoContent) return diff --git a/internal/gen/controller/api/warning.pb.go b/internal/gen/controller/api/warning.pb.go new file mode 100644 index 0000000000..c06e526bde --- /dev/null +++ b/internal/gen/controller/api/warning.pb.go @@ -0,0 +1,511 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: controller/api/v1/warning.proto + +package api + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// FieldWarning contains warning information on a per field basis. +type FieldWarning struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the field. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The warning regarding the provided field name. + Warning string `protobuf:"bytes,2,opt,name=warning,proto3" json:"warning,omitempty"` +} + +func (x *FieldWarning) Reset() { + *x = FieldWarning{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_warning_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FieldWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldWarning) ProtoMessage() {} + +func (x *FieldWarning) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_warning_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldWarning.ProtoReflect.Descriptor instead. +func (*FieldWarning) Descriptor() ([]byte, []int) { + return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{0} +} + +func (x *FieldWarning) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FieldWarning) GetWarning() string { + if x != nil { + return x.Warning + } + return "" +} + +// ActionWarning contains warning information regarding a specific action. +type ActionWarning struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the action + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The warning regarding the provided action + Warning string `protobuf:"bytes,2,opt,name=warning,proto3" json:"warning,omitempty"` +} + +func (x *ActionWarning) Reset() { + *x = ActionWarning{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_warning_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActionWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActionWarning) ProtoMessage() {} + +func (x *ActionWarning) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_warning_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActionWarning.ProtoReflect.Descriptor instead. +func (*ActionWarning) Descriptor() ([]byte, []int) { + return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{1} +} + +func (x *ActionWarning) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ActionWarning) GetWarning() string { + if x != nil { + return x.Warning + } + return "" +} + +// BehaviorWarning contains a warning about Boundary behavior which may be +// surprising or not intuitive. +type BehaviorWarning struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The warning text regarding the behavior + Warning string `protobuf:"bytes,1,opt,name=warning,proto3" json:"warning,omitempty"` +} + +func (x *BehaviorWarning) Reset() { + *x = BehaviorWarning{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_warning_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BehaviorWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BehaviorWarning) ProtoMessage() {} + +func (x *BehaviorWarning) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_warning_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BehaviorWarning.ProtoReflect.Descriptor instead. +func (*BehaviorWarning) Descriptor() ([]byte, []int) { + return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{2} +} + +func (x *BehaviorWarning) GetWarning() string { + if x != nil { + return x.Warning + } + return "" +} + +// A warning in the Boundary system. +type Warning struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + // Types that are assignable to Warning: + // + // *Warning_RequestField + // *Warning_Action + // *Warning_Behavior + Warning isWarning_Warning `protobuf_oneof:"warning"` +} + +func (x *Warning) Reset() { + *x = Warning{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_warning_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Warning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Warning) ProtoMessage() {} + +func (x *Warning) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_warning_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Warning.ProtoReflect.Descriptor instead. +func (*Warning) Descriptor() ([]byte, []int) { + return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{3} +} + +func (x *Warning) GetCode() uint32 { + if x != nil { + return x.Code + } + return 0 +} + +func (m *Warning) GetWarning() isWarning_Warning { + if m != nil { + return m.Warning + } + return nil +} + +func (x *Warning) GetRequestField() *FieldWarning { + if x, ok := x.GetWarning().(*Warning_RequestField); ok { + return x.RequestField + } + return nil +} + +func (x *Warning) GetAction() *ActionWarning { + if x, ok := x.GetWarning().(*Warning_Action); ok { + return x.Action + } + return nil +} + +func (x *Warning) GetBehavior() *BehaviorWarning { + if x, ok := x.GetWarning().(*Warning_Behavior); ok { + return x.Behavior + } + return nil +} + +type isWarning_Warning interface { + isWarning_Warning() +} + +type Warning_RequestField struct { + RequestField *FieldWarning `protobuf:"bytes,2,opt,name=request_field,json=request_fields,proto3,oneof"` +} + +type Warning_Action struct { + Action *ActionWarning `protobuf:"bytes,3,opt,name=action,proto3,oneof"` +} + +type Warning_Behavior struct { + Behavior *BehaviorWarning `protobuf:"bytes,4,opt,name=behavior,proto3,oneof"` +} + +func (*Warning_RequestField) isWarning_Warning() {} + +func (*Warning_Action) isWarning_Warning() {} + +func (*Warning_Behavior) isWarning_Warning() {} + +// Warning is returned by the JSON API when a warning occurs. +type WarningResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Request-field-specific warning details. + Warnings []*Warning `protobuf:"bytes,1,rep,name=warnings,proto3" json:"warnings,omitempty"` +} + +func (x *WarningResponse) Reset() { + *x = WarningResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_warning_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WarningResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WarningResponse) ProtoMessage() {} + +func (x *WarningResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_warning_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WarningResponse.ProtoReflect.Descriptor instead. +func (*WarningResponse) Descriptor() ([]byte, []int) { + return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{4} +} + +func (x *WarningResponse) GetWarnings() []*Warning { + if x != nil { + return x.Warnings + } + return nil +} + +var File_controller_api_v1_warning_proto protoreflect.FileDescriptor + +var file_controller_api_v1_warning_proto_rawDesc = []byte{ + 0x0a, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x22, 0x3c, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x57, 0x61, 0x72, + 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, + 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x22, 0x3d, 0x0a, 0x0d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x72, 0x6e, + 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, + 0x67, 0x22, 0x2b, 0x0a, 0x0f, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x57, 0x61, 0x72, + 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0xf0, + 0x01, 0x0a, 0x07, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x48, + 0x0a, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x57, + 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x68, 0x61, 0x76, + 0x69, 0x6f, 0x72, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x08, 0x62, 0x65, + 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, + 0x67, 0x22, 0x49, 0x0a, 0x0f, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x3f, 0x5a, 0x3d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_v1_warning_proto_rawDescOnce sync.Once + file_controller_api_v1_warning_proto_rawDescData = file_controller_api_v1_warning_proto_rawDesc +) + +func file_controller_api_v1_warning_proto_rawDescGZIP() []byte { + file_controller_api_v1_warning_proto_rawDescOnce.Do(func() { + file_controller_api_v1_warning_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_v1_warning_proto_rawDescData) + }) + return file_controller_api_v1_warning_proto_rawDescData +} + +var file_controller_api_v1_warning_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_controller_api_v1_warning_proto_goTypes = []interface{}{ + (*FieldWarning)(nil), // 0: controller.api.v1.FieldWarning + (*ActionWarning)(nil), // 1: controller.api.v1.ActionWarning + (*BehaviorWarning)(nil), // 2: controller.api.v1.BehaviorWarning + (*Warning)(nil), // 3: controller.api.v1.Warning + (*WarningResponse)(nil), // 4: controller.api.v1.WarningResponse +} +var file_controller_api_v1_warning_proto_depIdxs = []int32{ + 0, // 0: controller.api.v1.Warning.request_field:type_name -> controller.api.v1.FieldWarning + 1, // 1: controller.api.v1.Warning.action:type_name -> controller.api.v1.ActionWarning + 2, // 2: controller.api.v1.Warning.behavior:type_name -> controller.api.v1.BehaviorWarning + 3, // 3: controller.api.v1.WarningResponse.warnings:type_name -> controller.api.v1.Warning + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_controller_api_v1_warning_proto_init() } +func file_controller_api_v1_warning_proto_init() { + if File_controller_api_v1_warning_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_v1_warning_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FieldWarning); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_v1_warning_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActionWarning); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_v1_warning_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BehaviorWarning); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_v1_warning_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Warning); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_v1_warning_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WarningResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_controller_api_v1_warning_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*Warning_RequestField)(nil), + (*Warning_Action)(nil), + (*Warning_Behavior)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_v1_warning_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_v1_warning_proto_goTypes, + DependencyIndexes: file_controller_api_v1_warning_proto_depIdxs, + MessageInfos: file_controller_api_v1_warning_proto_msgTypes, + }.Build() + File_controller_api_v1_warning_proto = out.File + file_controller_api_v1_warning_proto_rawDesc = nil + file_controller_api_v1_warning_proto_goTypes = nil + file_controller_api_v1_warning_proto_depIdxs = nil +} diff --git a/internal/proto/controller/api/v1/warning.proto b/internal/proto/controller/api/v1/warning.proto new file mode 100644 index 0000000000..443bdd82a9 --- /dev/null +++ b/internal/proto/controller/api/v1/warning.proto @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +syntax = "proto3"; + +package controller.api.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api;api"; + +// FieldWarning contains warning information on a per field basis. +message FieldWarning { + // The name of the field. + string name = 1; + + // The warning regarding the provided field name. + string warning = 2; +} + +// ActionWarning contains warning information regarding a specific action. +message ActionWarning { + // The name of the action + string name = 1; + + // The warning regarding the provided action + string warning = 2; +} + +// BehaviorWarning contains a warning about Boundary behavior which may be +// surprising or not intuitive. +message BehaviorWarning { + // The warning text regarding the behavior + string warning = 1; +} + +// A warning in the Boundary system. +message Warning { + uint32 code = 1; + oneof warning { + FieldWarning request_field = 2 [json_name = "request_fields"]; + ActionWarning action = 3; + BehaviorWarning behavior = 4; + } +} + +// Warning is returned by the JSON API when a warning occurs. +message WarningResponse { + // Request-field-specific warning details. + repeated Warning warnings = 1; +} diff --git a/internal/server/repository_worker.go b/internal/server/repository_worker.go index 9612b18b16..f7cc6c772d 100644 --- a/internal/server/repository_worker.go +++ b/internal/server/repository_worker.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/server/store" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/warning" "github.com/hashicorp/go-dbw" "github.com/hashicorp/nodeenrollment" "github.com/hashicorp/nodeenrollment/registration" @@ -31,11 +32,28 @@ func (r *Repository) DeleteWorker(ctx context.Context, publicId string, _ ...Opt if publicId == "" { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id") } + + // If it's a KMS-PKI worker we should warn the user that deleting the + // worker may not persist as the worker can be auto recreated. If the + // public ID is predictably generated in the KMS fashion, it's a KMS-PKI + // worker. + wAgg := &workerAggregate{PublicId: publicId} + if err := r.reader.LookupById(ctx, wAgg); err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + workerId, err := NewWorkerIdFromScopeAndName(ctx, wAgg.ScopeId, wAgg.Name) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error generating worker id in kms-pki name check case")) + } + if workerId == publicId { + warning.Warn(ctx, warning.DeletingKmsLedWorkersMayNotBePermanent) + } + worker := allocWorker() worker.Worker.PublicId = publicId var rowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, diff --git a/internal/target/repository.go b/internal/target/repository.go index f0fd1197a6..568ff6315b 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/warning" "github.com/hashicorp/go-dbw" ) @@ -566,6 +567,8 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui switch { case strings.EqualFold("Address", f): updateAddress = true + case strings.EqualFold("WorkerFilter", f): + warning.Warn(ctx, warning.FieldDeprecatedTargetWorkerFilters) default: filteredDbMask = append(filteredDbMask, f) } diff --git a/internal/tests/api/workers/worker_test.go b/internal/tests/api/workers/worker_test.go index 855c77a699..fa33172bbb 100644 --- a/internal/tests/api/workers/worker_test.go +++ b/internal/tests/api/workers/worker_test.go @@ -10,10 +10,12 @@ import ( "github.com/hashicorp/boundary/api/workers" "github.com/hashicorp/boundary/internal/daemon/controller" + "github.com/hashicorp/boundary/internal/daemon/worker" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/warning" "github.com/hashicorp/nodeenrollment/rotation" "github.com/hashicorp/nodeenrollment/storage/file" "github.com/hashicorp/nodeenrollment/types" @@ -27,7 +29,6 @@ func TestWorkerTagsASD(t *testing.T) { ctx := context.Background() assert, require := assert.New(t), require.New(t) tc := controller.NewTestController(t, nil) - defer tc.Shutdown() client := tc.Client() token := tc.Token() @@ -132,3 +133,70 @@ func TestWorkerTagsASD(t *testing.T) { require.Error(err) require.Nil(ewcr) } + +func TestWorkerTagsUpdateDelete(t *testing.T) { + ctx := context.Background() + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + + tw := worker.NewTestWorker(t, &worker.TestWorkerOpts{ + Name: "testworker", + InitialUpstreams: tc.ClusterAddrs(), + WorkerAuthKms: tc.Config().WorkerAuthKms, + }) + require.NoError(tc.WaitForNextWorkerStatusUpdate(tw.Name())) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + // Ensures the global scope contains a valid root key + err := kmsCache.CreateKeys(ctx, scope.Global.String(), kms.WithRandomReader(rand.Reader)) + require.NoError(err) + wrapper, err = kmsCache.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase) + require.NoError(err) + require.NotNil(wrapper) + + // Set up certs on the controller + rootStorage, err := server.NewRepositoryStorage(ctx, rw, rw, kmsCache) + require.NoError(err) + _, err = rotation.RotateRootCertificates(ctx, rootStorage) + require.NoError(err) + + workerClient := workers.NewClient(client) + + // Controller led workers can be created, updated, and deleted + clW, err := workerClient.CreateControllerLed(ctx, "global") + require.NoError(err) + require.NotNil(clW) + assert.Empty(clW.GetResponse().Warnings()) + + r, err := workerClient.Update(ctx, clW.Item.Id, 0, workers.WithName("updated"), workers.WithAutomaticVersioning(true)) + require.NoError(err) + assert.Equal("updated", r.GetItem().Name) + + delResp, err := workerClient.Delete(ctx, clW.Item.Id) + assert.NoError(err) + assert.Empty(delResp.GetResponse().Warnings()) + + // KMS led PKI workers cannot have name updated and warns when deleted + res, err := workerClient.List(ctx, "global") + require.NoError(err) + assert.Len(res.GetItems(), 1) + klW := res.GetItems()[0] + + r, err = workerClient.Update(ctx, klW.Id, 0, workers.WithName("cant update"), workers.WithAutomaticVersioning(true)) + require.Error(err) + assert.Nil(r) + + delResp, err = workerClient.Delete(ctx, klW.Id) + assert.NoError(err) + war, err := delResp.GetResponse().Warnings() + assert.NoError(err) + assert.Len(war, 1) + assert.EqualValues(warning.DeletingKmsLedWorkersMayNotBePermanent, war[0].Code) +} diff --git a/internal/warning/enumerated.go b/internal/warning/enumerated.go new file mode 100644 index 0000000000..5ff1815dfe --- /dev/null +++ b/internal/warning/enumerated.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package warning + +import pb "github.com/hashicorp/boundary/internal/gen/controller/api" + +type apiWarning uint32 + +// The set of warnings that boundary ever returns as a result of API requests. +// Besides zzzKeepThisLastSentinel, the warnings should keep the numbers they +// are initially released with because the enumerated number is used to uniquely +// identify them and potentially provide additional information in documentation. +const ( + Unknown apiWarning = 0 + FieldDeprecatedTargetWorkerFilters apiWarning = 1 + OidcAuthMethodInactiveCannotBeUsed apiWarning = 2 + DeletingKmsLedWorkersMayNotBePermanent apiWarning = 3 + + // This is a sentinel value that captures the largest apiWarning id currently + // known. Add all warnings above this line. + zzzKeepThisLastSentinel +) + +func (a apiWarning) toProto() *pb.Warning { + nw := &pb.Warning{ + Code: uint32(a), + } + switch a { + case FieldDeprecatedTargetWorkerFilters: + nw.Warning = &pb.Warning_RequestField{RequestField: &pb.FieldWarning{ + Name: "worker_filter", + Warning: "This field is deprecated. Please use ingress_worker_filter and/or egress_worker_filter", + }} + case OidcAuthMethodInactiveCannotBeUsed: + nw.Warning = &pb.Warning_Behavior{Behavior: &pb.BehaviorWarning{ + Warning: "OIDC Auth Methods cannot be authenticated until they have been made active.", + }} + case DeletingKmsLedWorkersMayNotBePermanent: + nw.Warning = &pb.Warning_Behavior{Behavior: &pb.BehaviorWarning{ + Warning: "A KMS worker may be automatically recreated after deletion if it is still running.", + }} + default: + // don't add any unknown warning to the warner + return nil + } + return nw +} diff --git a/internal/warning/warning.go b/internal/warning/warning.go new file mode 100644 index 0000000000..9457d73ec5 --- /dev/null +++ b/internal/warning/warning.go @@ -0,0 +1,104 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package warning + +import ( + "context" + "sync" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/encoding/protojson" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/event" + pbwarnings "github.com/hashicorp/boundary/internal/gen/controller/api" +) + +type warningKey int + +var ( + warnerContextKey warningKey + warningHeader = "x-boundary-warnings" +) + +// warner holds all the warnings generated for a given user facing API request. +// It is thread safe. +type warner struct { + warnings []*pbwarnings.Warning + + // l protects each of the slices in this struct. + l sync.Mutex +} + +func Warn(ctx context.Context, d apiWarning) error { + const op = "apiWarning.ForField" + w, ok := ctx.Value(warnerContextKey).(*warner) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain apiWarning functionality") + } + w.l.Lock() + defer w.l.Unlock() + if wp := d.toProto(); wp != nil { + w.warnings = append(w.warnings, wp) + } + return nil +} + +func newContext(ctx context.Context) context.Context { + return context.WithValue(ctx, warnerContextKey, &warner{}) +} + +func convertToGrpcHeaders(ctx context.Context) error { + const op = "apiWarning.convertToGrpcHeaders" + w, ok := ctx.Value(warnerContextKey).(*warner) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't have warner") + } + w.l.Lock() + defer w.l.Unlock() + + if len(w.warnings) == 0 { + return nil + } + pbWar := &pbwarnings.WarningResponse{ + Warnings: w.warnings, + } + var buf []byte + var err error + if buf, err = protojson.Marshal(pbWar); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal warnings")) + } + if err := grpc.SetHeader(ctx, metadata.Pairs(warningHeader, string(buf))); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to set apiWarning grpc header")) + } + return nil +} + +// OutgoingHeaderMatcher provides a runtime.HeaderMatcherFunc that can be used +// as an option when creating a new grpc gateway server muxer, and specifies +// the boundary apiWarning headers which can be forwarded on to the requesting client. +func OutgoingHeaderMatcher() runtime.HeaderMatcherFunc { + return func(s string) (string, bool) { + if s == warningHeader { + return warningHeader, true + } + return "", false + } +} + +// GrpcInterceptor intercepts warnings as reported by the handlers and populates +// them in a specific header. +func GrpcInterceptor(outerCtx context.Context) grpc.UnaryServerInterceptor { + const op = "controller.warningInterceptor" + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + ctx = newContext(ctx) + h, handlerErr := handler(ctx, req) + if err := convertToGrpcHeaders(ctx); err != nil { + event.WriteError(outerCtx, op, err) + } + return h, handlerErr + } +} diff --git a/internal/warning/warning_test.go b/internal/warning/warning_test.go new file mode 100644 index 0000000000..9d97fa5937 --- /dev/null +++ b/internal/warning/warning_test.go @@ -0,0 +1,158 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package warning + +import ( + "context" + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/hashicorp/boundary/globals" + pbwarnings "github.com/hashicorp/boundary/internal/gen/controller/api" + opsservices "github.com/hashicorp/boundary/internal/gen/ops/services" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/encoding/protojson" +) + +func TestApiWarnings(t *testing.T) { + for i := 1; i <= int(zzzKeepThisLastSentinel); i++ { + assert.NotNil(t, apiWarning(i).toProto(), "Api warning with code: %d", i) + } +} + +func TestContext(t *testing.T) { + t.Run("no apiWarning on context", func(t *testing.T) { + ctx := context.Background() + assert.Error(t, Warn(ctx, FieldDeprecatedTargetWorkerFilters)) + }) + + t.Run("empty apiWarning on context", func(t *testing.T) { + ctx := newContext(context.Background()) + newW, ok := ctx.Value(warnerContextKey).(*warner) + assert.True(t, ok) + assert.Empty(t, newW) + }) +} + +func TestWarn(t *testing.T) { + ctx := newContext(context.Background()) + assert.NoError(t, Warn(ctx, FieldDeprecatedTargetWorkerFilters)) + + newW, ok := ctx.Value(warnerContextKey).(*warner) + assert.True(t, ok) + assert.Equal(t, &warner{warnings: []*pbwarnings.Warning{ + FieldDeprecatedTargetWorkerFilters.toProto(), + }}, newW) +} + +func TestGrpcGatwayWiring(t *testing.T) { + ctx := context.Background() + + service := &fakeService{addWarnFunc: func(ctx context.Context) {}} + grpcSrv := grpc.NewServer(grpc.UnaryInterceptor(GrpcInterceptor(ctx))) + opsservices.RegisterHealthServiceServer(grpcSrv, service) + + l := bufconn.Listen(int(globals.DefaultMaxRequestSize)) + go grpcSrv.Serve(l) + t.Cleanup(func() { + grpcSrv.GracefulStop() + }) + + gwMux := runtime.NewServeMux( + runtime.WithOutgoingHeaderMatcher(OutgoingHeaderMatcher()), + ) + require.NoError(t, opsservices.RegisterHealthServiceHandlerFromEndpoint(ctx, gwMux, "", []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return l.Dial() + }), + })) + + mux := http.NewServeMux() + mux.Handle("/health", gwMux) + + httpSrv := &http.Server{ + Handler: mux, + ReadHeaderTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + IdleTimeout: 5 * time.Minute, + } + + lis, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + go httpSrv.Serve(lis) + t.Cleanup(func() { + assert.NoError(t, httpSrv.Shutdown(ctx)) + }) + + t.Run("field apiWarning only", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + Warn(ctx, FieldDeprecatedTargetWorkerFilters) + } + resp, err := http.Get(fmt.Sprintf("http://%s/health", lis.Addr().String())) + require.NoError(t, err) + got := resp.Header.Get(warningHeader) + require.NoError(t, err) + + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{FieldDeprecatedTargetWorkerFilters.toProto()}, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) + + t.Run("behavior apiWarning only", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + assert.NoError(t, Warn(ctx, OidcAuthMethodInactiveCannotBeUsed)) + } + resp, err := http.Get(fmt.Sprintf("http://%s/health", lis.Addr().String())) + require.NoError(t, err) + got := resp.Header.Get(warningHeader) + require.NoError(t, err) + + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{OidcAuthMethodInactiveCannotBeUsed.toProto()}, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) + t.Run("all apiWarning types", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + assert.NoError(t, Warn(ctx, FieldDeprecatedTargetWorkerFilters)) + assert.NoError(t, Warn(ctx, OidcAuthMethodInactiveCannotBeUsed)) + } + resp, err := http.Get(fmt.Sprintf("http://%s/health", lis.Addr().String())) + require.NoError(t, err) + got := resp.Header.Get(warningHeader) + require.NoError(t, err) + + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{ + FieldDeprecatedTargetWorkerFilters.toProto(), + OidcAuthMethodInactiveCannotBeUsed.toProto(), + }, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) +} + +// fakeService is made to +type fakeService struct { + opsservices.UnimplementedHealthServiceServer + addWarnFunc func(context.Context) +} + +func (f fakeService) GetHealth(ctx context.Context, request *opsservices.GetHealthRequest) (*opsservices.GetHealthResponse, error) { + f.addWarnFunc(ctx) + return &opsservices.GetHealthResponse{}, nil +}