From e0de93cac6a489dc8ffa2b6e8da03ae4b98ecbb4 Mon Sep 17 00:00:00 2001 From: Todd Date: Tue, 20 Jun 2023 14:09:18 -0700 Subject: [PATCH 1/5] Add warnings to the customer facing Boundary API. Warnings are helpful for deprecation notices or when a request configures Boundary in a way that is valid but might not work as intended. A recent example of where warnings might be helpful is when a user updates a target's "filter" field which is deprecated in favor of "ingress_worker_filter" or "egress_worker_filter". --- internal/daemon/controller/gateway.go | 3 + internal/gen/controller/api/warning.pb.go | 399 ++++++++++++++++++ .../proto/controller/api/v1/warning.proto | 45 ++ internal/warning/warning.go | 140 ++++++ internal/warning/warning_test.go | 239 +++++++++++ 5 files changed, 826 insertions(+) create mode 100644 internal/gen/controller/api/warning.pb.go create mode 100644 internal/proto/controller/api/v1/warning.proto create mode 100644 internal/warning/warning.go create mode 100644 internal/warning/warning_test.go 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/gen/controller/api/warning.pb.go b/internal/gen/controller/api/warning.pb.go new file mode 100644 index 0000000000..210b2f378d --- /dev/null +++ b/internal/gen/controller/api/warning.pb.go @@ -0,0 +1,399 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.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 "" +} + +// Warning is returned by the JSON API when a warning occurs. +type Warning struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Request-field-specific warning details. + RequestFields []*FieldWarning `protobuf:"bytes,1,rep,name=request_fields,proto3" json:"request_fields,omitempty"` + // Action specific warning details. + Actions []*ActionWarning `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"` + // Boundary behavior warning details. + Behaviors []*BehaviorWarning `protobuf:"bytes,3,rep,name=behaviors,proto3" json:"behaviors,omitempty"` +} + +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) GetRequestFields() []*FieldWarning { + if x != nil { + return x.RequestFields + } + return nil +} + +func (x *Warning) GetActions() []*ActionWarning { + if x != nil { + return x.Actions + } + return nil +} + +func (x *Warning) GetBehaviors() []*BehaviorWarning { + if x != nil { + return x.Behaviors + } + 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, 0xd0, + 0x01, 0x0a, 0x07, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x47, 0x0a, 0x0e, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, + 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, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 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, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x40, 0x0a, 0x09, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, + 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, 0x52, 0x09, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, + 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, 4) +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 +} +var file_controller_api_v1_warning_proto_depIdxs = []int32{ + 0, // 0: controller.api.v1.Warning.request_fields:type_name -> controller.api.v1.FieldWarning + 1, // 1: controller.api.v1.Warning.actions:type_name -> controller.api.v1.ActionWarning + 2, // 2: controller.api.v1.Warning.behaviors:type_name -> controller.api.v1.BehaviorWarning + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] 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 + } + } + } + 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: 4, + 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..5da67d83bd --- /dev/null +++ b/internal/proto/controller/api/v1/warning.proto @@ -0,0 +1,45 @@ +// 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; +} + +// Warning is returned by the JSON API when a warning occurs. +message Warning { + // Request-field-specific warning details. + repeated FieldWarning request_fields = 1 [json_name = "request_fields"]; + + // Action specific warning details. + repeated ActionWarning actions = 2; + + // Boundary behavior warning details. + repeated BehaviorWarning behaviors = 3; +} diff --git a/internal/warning/warning.go b/internal/warning/warning.go new file mode 100644 index 0000000000..de5ae3b34f --- /dev/null +++ b/internal/warning/warning.go @@ -0,0 +1,140 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package warning + +import ( + "context" + "sync" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/hashicorp/boundary/internal/observability/event" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + + "github.com/hashicorp/boundary/internal/errors" + 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 { + fieldWarnings []*pbwarnings.FieldWarning + actionWarnings []*pbwarnings.ActionWarning + behaviorWarnings []*pbwarnings.BehaviorWarning + + // l protects each of the slices in this struct. + l sync.Mutex +} + +func ForField(ctx context.Context, field, warning string) error { + const op = "warning.ForField" + w, ok := ctx.Value(warnerContextkey).(*warner) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain warning functionality") + } + w.l.Lock() + defer w.l.Unlock() + w.fieldWarnings = append(w.fieldWarnings, &pbwarnings.FieldWarning{ + Name: field, + Warning: warning, + }) + return nil +} + +func ForAction(ctx context.Context, action, warning string) error { + const op = "warning.ForAction" + w, ok := ctx.Value(warnerContextkey).(*warner) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain warning functionality") + } + w.l.Lock() + defer w.l.Unlock() + w.actionWarnings = append(w.actionWarnings, &pbwarnings.ActionWarning{ + Name: action, + Warning: warning, + }) + return nil +} + +func ForBehavior(ctx context.Context, warning string) error { + const op = "warning.ForBehavior" + w, ok := ctx.Value(warnerContextkey).(*warner) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain warning functionality") + } + w.l.Lock() + defer w.l.Unlock() + w.behaviorWarnings = append(w.behaviorWarnings, &pbwarnings.BehaviorWarning{ + Warning: warning, + }) + return nil +} + +func newContext(ctx context.Context) context.Context { + return context.WithValue(ctx, warnerContextkey, &warner{}) +} + +func convertToGrpcHeaders(ctx context.Context) error { + const op = "warning.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() + + pbWar := &pbwarnings.Warning{ + RequestFields: w.fieldWarnings, + Actions: w.actionWarnings, + Behaviors: w.behaviorWarnings, + } + if proto.Equal(pbWar, &pbwarnings.Warning{}) { + // no warnings included + return nil + } + 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 warning 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 warning 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..4417fe3294 --- /dev/null +++ b/internal/warning/warning_test.go @@ -0,0 +1,239 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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 TestContext(t *testing.T) { + t.Run("no warning on context", func(t *testing.T) { + ctx := context.Background() + assert.Error(t, ForField(ctx, "test", "test value")) + }) + + t.Run("empty warning 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 TestForField(t *testing.T) { + ctx := newContext(context.Background()) + assert.NoError(t, ForField(ctx, "test_field", "this is a test")) + + newW, ok := ctx.Value(warnerContextkey).(*warner) + assert.True(t, ok) + assert.Equal(t, &warner{fieldWarnings: []*pbwarnings.FieldWarning{ + { + Name: "test_field", + Warning: "this is a test", + }, + }}, newW) +} + +func TestForAction(t *testing.T) { + ctx := newContext(context.Background()) + assert.NoError(t, ForAction(ctx, "test_action", "this is a test")) + + newW, ok := ctx.Value(warnerContextkey).(*warner) + assert.True(t, ok) + assert.Equal(t, &warner{actionWarnings: []*pbwarnings.ActionWarning{ + { + Name: "test_action", + Warning: "this is a test", + }, + }}, newW) +} + +func TestForBehavior(t *testing.T) { + ctx := newContext(context.Background()) + assert.NoError(t, ForBehavior(ctx, "this is a test")) + + newW, ok := ctx.Value(warnerContextkey).(*warner) + assert.True(t, ok) + assert.Equal(t, &warner{behaviorWarnings: []*pbwarnings.BehaviorWarning{ + { + Warning: "this is a test", + }, + }}, newW) +} + +func TestGrpcGatwayWiring(t *testing.T) { + ctx := context.Background() + fieldWarnings := []*pbwarnings.FieldWarning{ + { + Name: "test_field_1", + Warning: "test warning description 1", + }, + { + Name: "test_field_2", + Warning: "test warning description 2", + }, + } + actionWarnings := []*pbwarnings.ActionWarning{ + { + Name: "test_action1", + Warning: "test warning description 1", + }, + { + Name: "test_action2", + Warning: "test warning description 2", + }, + } + behaviorWarnings := []*pbwarnings.BehaviorWarning{ + { + Warning: "test warning 1", + }, + { + Warning: "test warning 2", + }, + } + + 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 warning only", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + for _, w := range fieldWarnings { + assert.NoError(t, ForField(ctx, w.GetName(), w.GetWarning())) + } + } + 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.Warning{ + RequestFields: fieldWarnings, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) + + t.Run("action warning only", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + for _, w := range actionWarnings { + assert.NoError(t, ForAction(ctx, w.GetName(), w.GetWarning())) + } + } + 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.Warning{ + Actions: actionWarnings, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) + + t.Run("behavior warning only", func(t *testing.T) { + service.addWarnFunc = func(ctx context.Context) { + for _, w := range behaviorWarnings { + assert.NoError(t, ForBehavior(ctx, w.GetWarning())) + } + } + 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.Warning{ + Behaviors: behaviorWarnings, + }) + require.NoError(t, err) + assert.Equal(t, string(want), got) + }) + t.Run("all warning types", func(t *testing.T) { + + service.addWarnFunc = func(ctx context.Context) { + for _, w := range fieldWarnings { + assert.NoError(t, ForField(ctx, w.GetName(), w.GetWarning())) + } + for _, w := range actionWarnings { + assert.NoError(t, ForAction(ctx, w.GetName(), w.GetWarning())) + } + for _, w := range behaviorWarnings { + assert.NoError(t, ForBehavior(ctx, w.GetWarning())) + } + } + 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.Warning{ + RequestFields: fieldWarnings, + Actions: actionWarnings, + Behaviors: behaviorWarnings, + }) + 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 +} From cd5c06e1d2a089b77e92ff376ce6fa66427f7f43 Mon Sep 17 00:00:00 2001 From: Todd Date: Tue, 27 Jun 2023 13:24:37 -0700 Subject: [PATCH 2/5] Warnings are predefined with a specific code and referenced when adding them to the response. --- internal/gen/controller/api/warning.pb.go | 198 ++++++++++++++---- .../proto/controller/api/v1/warning.proto | 20 +- internal/warning/enumerated.go | 48 +++++ internal/warning/warning.go | 64 ++---- internal/warning/warning_test.go | 143 +++---------- 5 files changed, 261 insertions(+), 212 deletions(-) create mode 100644 internal/warning/enumerated.go diff --git a/internal/gen/controller/api/warning.pb.go b/internal/gen/controller/api/warning.pb.go index 210b2f378d..13f192e78b 100644 --- a/internal/gen/controller/api/warning.pb.go +++ b/internal/gen/controller/api/warning.pb.go @@ -189,18 +189,19 @@ func (x *BehaviorWarning) GetWarning() string { return "" } -// Warning is returned by the JSON API when a warning occurs. +// A warning in the Boundary system. type Warning struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Request-field-specific warning details. - RequestFields []*FieldWarning `protobuf:"bytes,1,rep,name=request_fields,proto3" json:"request_fields,omitempty"` - // Action specific warning details. - Actions []*ActionWarning `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"` - // Boundary behavior warning details. - Behaviors []*BehaviorWarning `protobuf:"bytes,3,rep,name=behaviors,proto3" json:"behaviors,omitempty"` + 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() { @@ -235,23 +236,108 @@ func (*Warning) Descriptor() ([]byte, []int) { return file_controller_api_v1_warning_proto_rawDescGZIP(), []int{3} } -func (x *Warning) GetRequestFields() []*FieldWarning { +func (x *Warning) GetCode() uint32 { if x != nil { - return x.RequestFields + return x.Code + } + return 0 +} + +func (m *Warning) GetWarning() isWarning_Warning { + if m != nil { + return m.Warning } return nil } -func (x *Warning) GetActions() []*ActionWarning { - if x != nil { - return x.Actions +func (x *Warning) GetRequestField() *FieldWarning { + if x, ok := x.GetWarning().(*Warning_RequestField); ok { + return x.RequestField } return nil } -func (x *Warning) GetBehaviors() []*BehaviorWarning { +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.Behaviors + return x.Warnings } return nil } @@ -272,25 +358,32 @@ var file_controller_api_v1_warning_proto_rawDesc = []byte{ 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, 0xd0, - 0x01, 0x0a, 0x07, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x47, 0x0a, 0x0e, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, - 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, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x73, 0x12, 0x3a, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x03, 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, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x40, 0x0a, 0x09, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, - 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, 0x52, 0x09, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, - 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, + 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 ( @@ -305,22 +398,24 @@ func file_controller_api_v1_warning_proto_rawDescGZIP() []byte { return file_controller_api_v1_warning_proto_rawDescData } -var file_controller_api_v1_warning_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +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_fields:type_name -> controller.api.v1.FieldWarning - 1, // 1: controller.api.v1.Warning.actions:type_name -> controller.api.v1.ActionWarning - 2, // 2: controller.api.v1.Warning.behaviors:type_name -> controller.api.v1.BehaviorWarning - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 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() } @@ -377,6 +472,23 @@ func file_controller_api_v1_warning_proto_init() { 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{ @@ -384,7 +496,7 @@ func file_controller_api_v1_warning_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_v1_warning_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/controller/api/v1/warning.proto b/internal/proto/controller/api/v1/warning.proto index 5da67d83bd..443bdd82a9 100644 --- a/internal/proto/controller/api/v1/warning.proto +++ b/internal/proto/controller/api/v1/warning.proto @@ -32,14 +32,18 @@ message BehaviorWarning { string warning = 1; } -// Warning is returned by the JSON API when a warning occurs. +// A warning in the Boundary system. message Warning { - // Request-field-specific warning details. - repeated FieldWarning request_fields = 1 [json_name = "request_fields"]; - - // Action specific warning details. - repeated ActionWarning actions = 2; + uint32 code = 1; + oneof warning { + FieldWarning request_field = 2 [json_name = "request_fields"]; + ActionWarning action = 3; + BehaviorWarning behavior = 4; + } +} - // Boundary behavior warning details. - repeated BehaviorWarning behaviors = 3; +// 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/warning/enumerated.go b/internal/warning/enumerated.go new file mode 100644 index 0000000000..c4c771b054 --- /dev/null +++ b/internal/warning/enumerated.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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 index de5ae3b34f..9829c83158 100644 --- a/internal/warning/warning.go +++ b/internal/warning/warning.go @@ -21,82 +21,48 @@ import ( type warningKey int var ( - warnerContextkey warningKey + 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 { - fieldWarnings []*pbwarnings.FieldWarning - actionWarnings []*pbwarnings.ActionWarning - behaviorWarnings []*pbwarnings.BehaviorWarning + warnings []*pbwarnings.Warning // l protects each of the slices in this struct. l sync.Mutex } -func ForField(ctx context.Context, field, warning string) error { - const op = "warning.ForField" - w, ok := ctx.Value(warnerContextkey).(*warner) +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 warning functionality") + return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain apiWarning functionality") } w.l.Lock() defer w.l.Unlock() - w.fieldWarnings = append(w.fieldWarnings, &pbwarnings.FieldWarning{ - Name: field, - Warning: warning, - }) - return nil -} - -func ForAction(ctx context.Context, action, warning string) error { - const op = "warning.ForAction" - w, ok := ctx.Value(warnerContextkey).(*warner) - if !ok { - return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain warning functionality") + if wp := d.toProto(); wp != nil { + w.warnings = append(w.warnings, wp) } - w.l.Lock() - defer w.l.Unlock() - w.actionWarnings = append(w.actionWarnings, &pbwarnings.ActionWarning{ - Name: action, - Warning: warning, - }) - return nil -} - -func ForBehavior(ctx context.Context, warning string) error { - const op = "warning.ForBehavior" - w, ok := ctx.Value(warnerContextkey).(*warner) - if !ok { - return errors.New(ctx, errors.InvalidParameter, op, "context doesn't contain warning functionality") - } - w.l.Lock() - defer w.l.Unlock() - w.behaviorWarnings = append(w.behaviorWarnings, &pbwarnings.BehaviorWarning{ - Warning: warning, - }) return nil } func newContext(ctx context.Context) context.Context { - return context.WithValue(ctx, warnerContextkey, &warner{}) + return context.WithValue(ctx, warnerContextKey, &warner{}) } func convertToGrpcHeaders(ctx context.Context) error { - const op = "warning.convertToGrpcHeaders" - w, ok := ctx.Value(warnerContextkey).(*warner) + 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() - pbWar := &pbwarnings.Warning{ - RequestFields: w.fieldWarnings, - Actions: w.actionWarnings, - Behaviors: w.behaviorWarnings, + pbWar := &pbwarnings.WarningResponse{ + Warnings: w.warnings, } if proto.Equal(pbWar, &pbwarnings.Warning{}) { // no warnings included @@ -108,14 +74,14 @@ func convertToGrpcHeaders(ctx context.Context) error { 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 warning grpc header")) + 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 warning headers which can be forwarded on to the requesting client. +// 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 { diff --git a/internal/warning/warning_test.go b/internal/warning/warning_test.go index 4417fe3294..fdd6bebd3b 100644 --- a/internal/warning/warning_test.go +++ b/internal/warning/warning_test.go @@ -23,91 +23,39 @@ import ( "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 warning on context", func(t *testing.T) { + t.Run("no apiWarning on context", func(t *testing.T) { ctx := context.Background() - assert.Error(t, ForField(ctx, "test", "test value")) + assert.Error(t, Warn(ctx, FieldDeprecatedTargetWorkerFilters)) }) - t.Run("empty warning on context", func(t *testing.T) { + t.Run("empty apiWarning on context", func(t *testing.T) { ctx := newContext(context.Background()) - newW, ok := ctx.Value(warnerContextkey).(*warner) + newW, ok := ctx.Value(warnerContextKey).(*warner) assert.True(t, ok) assert.Empty(t, newW) }) } -func TestForField(t *testing.T) { - ctx := newContext(context.Background()) - assert.NoError(t, ForField(ctx, "test_field", "this is a test")) - - newW, ok := ctx.Value(warnerContextkey).(*warner) - assert.True(t, ok) - assert.Equal(t, &warner{fieldWarnings: []*pbwarnings.FieldWarning{ - { - Name: "test_field", - Warning: "this is a test", - }, - }}, newW) -} - -func TestForAction(t *testing.T) { - ctx := newContext(context.Background()) - assert.NoError(t, ForAction(ctx, "test_action", "this is a test")) - - newW, ok := ctx.Value(warnerContextkey).(*warner) - assert.True(t, ok) - assert.Equal(t, &warner{actionWarnings: []*pbwarnings.ActionWarning{ - { - Name: "test_action", - Warning: "this is a test", - }, - }}, newW) -} - -func TestForBehavior(t *testing.T) { +func TestWarn(t *testing.T) { ctx := newContext(context.Background()) - assert.NoError(t, ForBehavior(ctx, "this is a test")) + assert.NoError(t, Warn(ctx, FieldDeprecatedTargetWorkerFilters)) - newW, ok := ctx.Value(warnerContextkey).(*warner) + newW, ok := ctx.Value(warnerContextKey).(*warner) assert.True(t, ok) - assert.Equal(t, &warner{behaviorWarnings: []*pbwarnings.BehaviorWarning{ - { - Warning: "this is a test", - }, + assert.Equal(t, &warner{warnings: []*pbwarnings.Warning{ + FieldDeprecatedTargetWorkerFilters.toProto(), }}, newW) } func TestGrpcGatwayWiring(t *testing.T) { ctx := context.Background() - fieldWarnings := []*pbwarnings.FieldWarning{ - { - Name: "test_field_1", - Warning: "test warning description 1", - }, - { - Name: "test_field_2", - Warning: "test warning description 2", - }, - } - actionWarnings := []*pbwarnings.ActionWarning{ - { - Name: "test_action1", - Warning: "test warning description 1", - }, - { - Name: "test_action2", - Warning: "test warning description 2", - }, - } - behaviorWarnings := []*pbwarnings.BehaviorWarning{ - { - Warning: "test warning 1", - }, - { - Warning: "test warning 2", - }, - } service := &fakeService{addWarnFunc: func(ctx context.Context) {}} grpcSrv := grpc.NewServer(grpc.UnaryInterceptor(GrpcInterceptor(ctx))) @@ -146,81 +94,52 @@ func TestGrpcGatwayWiring(t *testing.T) { assert.NoError(t, httpSrv.Shutdown(ctx)) }) - t.Run("field warning only", func(t *testing.T) { + t.Run("field apiWarning only", func(t *testing.T) { service.addWarnFunc = func(ctx context.Context) { - for _, w := range fieldWarnings { - assert.NoError(t, ForField(ctx, w.GetName(), w.GetWarning())) - } + 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.Warning{ - RequestFields: fieldWarnings, + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{FieldDeprecatedTargetWorkerFilters.toProto()}, }) require.NoError(t, err) assert.Equal(t, string(want), got) }) - t.Run("action warning only", func(t *testing.T) { + t.Run("behavior apiWarning only", func(t *testing.T) { service.addWarnFunc = func(ctx context.Context) { - for _, w := range actionWarnings { - assert.NoError(t, ForAction(ctx, w.GetName(), w.GetWarning())) - } + 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.Warning{ - Actions: actionWarnings, + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{OidcAuthMethodInactiveCannotBeUsed.toProto()}, }) require.NoError(t, err) assert.Equal(t, string(want), got) }) - - t.Run("behavior warning only", func(t *testing.T) { - service.addWarnFunc = func(ctx context.Context) { - for _, w := range behaviorWarnings { - assert.NoError(t, ForBehavior(ctx, w.GetWarning())) - } - } - 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.Warning{ - Behaviors: behaviorWarnings, - }) - require.NoError(t, err) - assert.Equal(t, string(want), got) - }) - t.Run("all warning types", func(t *testing.T) { - + t.Run("all apiWarning types", func(t *testing.T) { service.addWarnFunc = func(ctx context.Context) { - for _, w := range fieldWarnings { - assert.NoError(t, ForField(ctx, w.GetName(), w.GetWarning())) - } - for _, w := range actionWarnings { - assert.NoError(t, ForAction(ctx, w.GetName(), w.GetWarning())) - } - for _, w := range behaviorWarnings { - assert.NoError(t, ForBehavior(ctx, w.GetWarning())) - } + 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.Warning{ - RequestFields: fieldWarnings, - Actions: actionWarnings, - Behaviors: behaviorWarnings, + want, err := protojson.Marshal(&pbwarnings.WarningResponse{ + Warnings: []*pbwarnings.Warning{ + FieldDeprecatedTargetWorkerFilters.toProto(), + OidcAuthMethodInactiveCannotBeUsed.toProto(), + }, }) require.NoError(t, err) assert.Equal(t, string(want), got) From 9d2f41f6364cce77f8d09dfd437023f5feb5304e Mon Sep 17 00:00:00 2001 From: Todd Date: Tue, 10 Oct 2023 15:58:39 -0700 Subject: [PATCH 3/5] Add warnings for deleting kms led pki workers --- internal/daemon/controller/handler.go | 5 +++++ internal/gen/controller/api/warning.pb.go | 2 +- internal/server/repository_worker.go | 20 +++++++++++++++++++- internal/target/repository.go | 3 +++ internal/warning/enumerated.go | 2 +- internal/warning/warning.go | 12 +++++------- internal/warning/warning_test.go | 2 +- 7 files changed, 35 insertions(+), 11 deletions(-) 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 index 13f192e78b..c06e526bde 100644 --- a/internal/gen/controller/api/warning.pb.go +++ b/internal/gen/controller/api/warning.pb.go @@ -3,7 +3,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc (unknown) // source: controller/api/v1/warning.proto 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/warning/enumerated.go b/internal/warning/enumerated.go index c4c771b054..5ff1815dfe 100644 --- a/internal/warning/enumerated.go +++ b/internal/warning/enumerated.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: BUSL-1.1 package warning diff --git a/internal/warning/warning.go b/internal/warning/warning.go index 9829c83158..9457d73ec5 100644 --- a/internal/warning/warning.go +++ b/internal/warning/warning.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: BUSL-1.1 package warning @@ -8,13 +8,12 @@ import ( "sync" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "github.com/hashicorp/boundary/internal/observability/event" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/event" pbwarnings "github.com/hashicorp/boundary/internal/gen/controller/api" ) @@ -61,13 +60,12 @@ func convertToGrpcHeaders(ctx context.Context) error { w.l.Lock() defer w.l.Unlock() + if len(w.warnings) == 0 { + return nil + } pbWar := &pbwarnings.WarningResponse{ Warnings: w.warnings, } - if proto.Equal(pbWar, &pbwarnings.Warning{}) { - // no warnings included - return nil - } var buf []byte var err error if buf, err = protojson.Marshal(pbWar); err != nil { diff --git a/internal/warning/warning_test.go b/internal/warning/warning_test.go index fdd6bebd3b..9d97fa5937 100644 --- a/internal/warning/warning_test.go +++ b/internal/warning/warning_test.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: BUSL-1.1 package warning From 0eadb20eded2b5e28d75e1dfcebb1f0233d93f44 Mon Sep 17 00:00:00 2001 From: Todd Date: Wed, 11 Oct 2023 09:45:17 -0700 Subject: [PATCH 4/5] Go SDK provides warnings --- api/action_warning.gen.go | 10 ++++ api/behavior_warning.gen.go | 9 +++ api/field_warning.gen.go | 10 ++++ api/response.go | 14 +++++ api/warning.gen.go | 12 ++++ api/warning_response.gen.go | 9 +++ internal/api/genapi/input.go | 25 ++++++++ internal/tests/api/workers/worker_test.go | 70 ++++++++++++++++++++++- 8 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 api/action_warning.gen.go create mode 100644 api/behavior_warning.gen.go create mode 100644 api/field_warning.gen.go create mode 100644 api/warning.gen.go create mode 100644 api/warning_response.gen.go 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/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) +} From 90dbd44f39555fdec1ea029a3aa6638b736a7b69 Mon Sep 17 00:00:00 2001 From: Todd Date: Wed, 11 Oct 2023 14:54:07 -0700 Subject: [PATCH 5/5] CLI outputs warnings returned by controllers --- internal/cmd/base/format.go | 52 ++++++++++++++++--- internal/cmd/base/option.go | 9 ++++ .../cmd/commands/accountscmd/accounts.gen.go | 21 ++++++++ .../commands/accountscmd/ldap_accounts.gen.go | 7 +++ .../commands/accountscmd/oidc_accounts.gen.go | 7 +++ .../accountscmd/password_accounts.gen.go | 7 +++ internal/cmd/commands/authenticate/funcs.go | 8 +++ .../authmethodscmd/authmethods.gen.go | 21 ++++++++ .../authmethodscmd/ldap_authmethods.gen.go | 7 +++ .../authmethodscmd/oidc_authmethods.gen.go | 7 +++ .../password_authmethods.gen.go | 7 +++ .../commands/authtokenscmd/authtokens.gen.go | 21 ++++++++ .../credentiallibraries.gen.go | 21 ++++++++ .../vault-generic_credentiallibraries.gen.go | 7 +++ ...ssh-certificate_credentiallibraries.gen.go | 7 +++ .../vault_credentiallibraries.gen.go | 7 +++ .../credentialscmd/credentials.gen.go | 21 ++++++++ .../credentialscmd/json_credentials.gen.go | 7 +++ .../ssh-private-key_credentials.gen.go | 7 +++ .../username-password_credentials.gen.go | 7 +++ .../credentialstores.gen.go | 21 ++++++++ .../static_credentialstores.gen.go | 7 +++ .../vault_credentialstores.gen.go | 7 +++ internal/cmd/commands/groupscmd/groups.gen.go | 21 ++++++++ .../hostcatalogscmd/hostcatalogs.gen.go | 21 ++++++++ .../plugin_hostcatalogs.gen.go | 7 +++ .../static_hostcatalogs.gen.go | 7 +++ internal/cmd/commands/hostscmd/hosts.gen.go | 21 ++++++++ .../cmd/commands/hostscmd/static_hosts.gen.go | 7 +++ .../cmd/commands/hostsetscmd/hostsets.gen.go | 21 ++++++++ .../hostsetscmd/plugin_hostsets.gen.go | 7 +++ .../hostsetscmd/static_hostsets.gen.go | 7 +++ .../ldap_managedgroups.gen.go | 7 +++ .../managedgroupscmd/managedgroups.gen.go | 21 ++++++++ .../oidc_managedgroups.gen.go | 7 +++ internal/cmd/commands/rolescmd/roles.gen.go | 21 ++++++++ .../commands/scopescmd/destroy_key_version.go | 7 +++ .../cmd/commands/scopescmd/rotate_keys.go | 7 +++ internal/cmd/commands/scopescmd/scopes.gen.go | 21 ++++++++ .../sessionrecordings.gen.go | 14 +++++ .../cmd/commands/sessionscmd/sessions.gen.go | 14 +++++ .../storagebucketscmd/storagebuckets.gen.go | 21 ++++++++ .../commands/targetscmd/ssh_targets.gen.go | 7 +++ .../cmd/commands/targetscmd/targets.gen.go | 21 ++++++++ .../commands/targetscmd/tcp_targets.gen.go | 7 +++ internal/cmd/commands/userscmd/users.gen.go | 21 ++++++++ .../workerscmd/certificate-authority.go | 7 +++ .../workerscmd/controller-led_workers.gen.go | 7 +++ .../workerscmd/worker-led_workers.gen.go | 7 +++ .../cmd/commands/workerscmd/workers.gen.go | 21 ++++++++ internal/cmd/gencli/templates.go | 21 ++++++++ 51 files changed, 663 insertions(+), 8 deletions(-) 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":