diff --git a/app/cli/cmd/casbackend.go b/app/cli/cmd/casbackend.go index 033855ee4..a282bec0b 100644 --- a/app/cli/cmd/casbackend.go +++ b/app/cli/cmd/casbackend.go @@ -16,6 +16,9 @@ package cmd import ( + "fmt" + + "github.com/chainloop-dev/chainloop/app/cli/internal/action" "github.com/spf13/cobra" ) @@ -25,7 +28,7 @@ func newCASBackendCmd() *cobra.Command { Short: "Operations on Artifact CAS backends", } - cmd.AddCommand(newCASBackendListCmd(), newCASBackendAddCmd(), newCASBackendUpdateCmd()) + cmd.AddCommand(newCASBackendListCmd(), newCASBackendAddCmd(), newCASBackendUpdateCmd(), newCASBackendDeleteCmd()) return cmd } @@ -54,3 +57,61 @@ func newCASBackendUpdateCmd() *cobra.Command { cmd.AddCommand(newCASBackendUpdateOCICmd()) return cmd } + +// confirmDefaultCASBackendOverride asks the user to confirm the override of the default CAS backend +// in the event that there is one already set and its not the same as the one we are setting +func confirmDefaultCASBackendOverride(actionOpts *action.ActionsOpts, id string) (bool, error) { + // get existing backends + backends, err := action.NewCASBackendList(actionOpts).Run() + if err != nil { + return false, fmt.Errorf("failed to list existing CAS backends: %w", err) + } + + // Find the default + var defaultB *action.CASBackendItem + for _, b := range backends { + if b.Default { + defaultB = b + break + } + } + + // If there is none or there is but it's the same as the one we are setting, we are ok + if defaultB == nil || (id != "" && id == defaultB.ID) { + return true, nil + } + + // Ask the user to confirm the override + return confirmationPrompt("You are changing the default CAS backend in your organization"), nil +} + +// If we are removing the default we confirm too +func confirmDefaultCASBackendRemoval(actionOpts *action.ActionsOpts, id string) (bool, error) { + return confirmDefaultCASBackendUnset(id, "You are deleting the default CAS backend.", actionOpts) +} + +func confirmDefaultCASBackendUnset(id, msg string, actionOpts *action.ActionsOpts) (bool, error) { + // get existing backends + backends, err := action.NewCASBackendList(actionOpts).Run() + if err != nil { + return false, fmt.Errorf("failed to list existing CAS backends: %w", err) + } + + for _, b := range backends { + // We are removing ourselves as the default, ask the user to confirm + if b.Default && b.ID == id { + return confirmationPrompt(msg), nil + } + } + + return true, nil +} + +// y/n confirmation prompt +func confirmationPrompt(msg string) bool { + fmt.Printf("%s\nPlease confirm to continue y/N\n", msg) + var gotChallenge string + fmt.Scanln(&gotChallenge) + + return gotChallenge == "y" || gotChallenge == "Y" +} diff --git a/app/cli/cmd/casbackend_add_oci.go b/app/cli/cmd/casbackend_add_oci.go index f77c628a0..5e8be7be8 100644 --- a/app/cli/cmd/casbackend_add_oci.go +++ b/app/cli/cmd/casbackend_add_oci.go @@ -16,8 +16,6 @@ package cmd import ( - "fmt" - "github.com/chainloop-dev/chainloop/app/cli/internal/action" "github.com/go-kratos/kratos/v2/log" "github.com/spf13/cobra" @@ -80,35 +78,3 @@ func newCASBackendAddOCICmd() *cobra.Command { cobra.CheckErr(err) return cmd } - -// confirmDefaultCASBackendOverride asks the user to confirm the override of the default CAS backend -// in the event that there is one already set and its not the same as the one we are setting -func confirmDefaultCASBackendOverride(actionOpts *action.ActionsOpts, id string) (bool, error) { - // get existing backends - backends, err := action.NewCASBackendList(actionOpts).Run() - if err != nil { - return false, fmt.Errorf("failed to list existing CAS backends: %w", err) - } - - // Find the default - var defaultB *action.CASBackendItem - for _, b := range backends { - if b.Default { - defaultB = b - break - } - } - - // If there is none or there is but it's the same as the one we are setting, we are ok - if defaultB == nil || (id != "" && id == defaultB.ID) { - return true, nil - } - - // Ask the user to confirm the override - fmt.Println("There is already a default CAS backend in your organization.\nPlease confirm to override y/N: ") - var gotChallenge string - fmt.Scanln(&gotChallenge) - - // If the user does not confirm, we are done - return gotChallenge == "y" || gotChallenge == "Y", nil -} diff --git a/app/cli/cmd/casbackend_delete.go b/app/cli/cmd/casbackend_delete.go new file mode 100644 index 000000000..828fa3200 --- /dev/null +++ b/app/cli/cmd/casbackend_delete.go @@ -0,0 +1,52 @@ +// +// Copyright 2023 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/chainloop-dev/chainloop/app/cli/internal/action" + "github.com/go-kratos/kratos/v2/log" + "github.com/spf13/cobra" +) + +func newCASBackendDeleteCmd() *cobra.Command { + var backendID string + + cmd := &cobra.Command{ + Use: "delete", + Aliases: []string{"rm"}, + Short: "Delete a CAS Backend from your organization", + RunE: func(cmd *cobra.Command, args []string) error { + if confirmed, err := confirmDefaultCASBackendRemoval(actionOpts, backendID); err != nil { + return err + } else if !confirmed { + log.Info("Aborting...") + return nil + } + + if err := action.NewCASBackendDelete(actionOpts).Run(backendID); err != nil { + return err + } + + logger.Info().Msg("Backend deleted") + + return nil + }, + } + + cmd.Flags().StringVar(&backendID, "id", "", "CAS Backend ID") + cobra.CheckErr(cmd.MarkFlagRequired("id")) + return cmd +} diff --git a/app/cli/cmd/casbackend_update_oci.go b/app/cli/cmd/casbackend_update_oci.go index aeb5dfc92..d8f65734e 100644 --- a/app/cli/cmd/casbackend_update_oci.go +++ b/app/cli/cmd/casbackend_update_oci.go @@ -16,8 +16,6 @@ package cmd import ( - "fmt" - "github.com/chainloop-dev/chainloop/app/cli/internal/action" "github.com/go-kratos/kratos/v2/log" "github.com/spf13/cobra" @@ -45,8 +43,9 @@ func newCASBackendUpdateOCICmd() *cobra.Command { log.Info("Aborting...") return nil } - } else { // If we are removing the default we ask for confirmation too - if confirmed, err := confirmDefaultCASBackendRemoval(actionOpts, backendID); err != nil { + } else { + // If we are removing the default we ask for confirmation too + if confirmed, err := confirmDefaultCASBackendUnset(backendID, "You are setting the default CAS backend to false", actionOpts); err != nil { return err } else if !confirmed { log.Info("Aborting...") @@ -88,29 +87,3 @@ func newCASBackendUpdateOCICmd() *cobra.Command { cmd.Flags().StringVarP(&password, "password", "p", "", "registry password") return cmd } - -// If we are removing the default we confirm too -func confirmDefaultCASBackendRemoval(actionOpts *action.ActionsOpts, id string) (bool, error) { - // get existing backends - backends, err := action.NewCASBackendList(actionOpts).Run() - if err != nil { - return false, fmt.Errorf("failed to list existing CAS backends: %w", err) - } - - for _, b := range backends { - // We are removing ourselves as the default, ask the user to confirm - if b.Default && b.ID == id { - // Ask the user to confirm the override - fmt.Println("This is the default CAS backend and your are setting default=false.\nPlease confirm to continue y/N: ") - var gotChallenge string - fmt.Scanln(&gotChallenge) - - // If the user does not confirm, we are done - if gotChallenge != "y" && gotChallenge != "Y" { - return false, nil - } - } - } - - return true, nil -} diff --git a/app/cli/internal/action/casbackend_delete.go b/app/cli/internal/action/casbackend_delete.go new file mode 100644 index 000000000..249700a92 --- /dev/null +++ b/app/cli/internal/action/casbackend_delete.go @@ -0,0 +1,39 @@ +// +// Copyright 2023 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package action + +import ( + "context" + + pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" +) + +type CASBackendDelete struct { + cfg *ActionsOpts +} + +func NewCASBackendDelete(cfg *ActionsOpts) *CASBackendDelete { + return &CASBackendDelete{cfg} +} + +func (action *CASBackendDelete) Run(id string) error { + client := pb.NewCASBackendServiceClient(action.cfg.CPConnection) + _, err := client.Delete(context.Background(), &pb.CASBackendServiceDeleteRequest{ + Id: id, + }) + + return err +} diff --git a/app/controlplane/api/controlplane/v1/cas_backends.pb.go b/app/controlplane/api/controlplane/v1/cas_backends.pb.go index aad3e6d71..8f628d569 100644 --- a/app/controlplane/api/controlplane/v1/cas_backends.pb.go +++ b/app/controlplane/api/controlplane/v1/cas_backends.pb.go @@ -379,6 +379,91 @@ func (x *CASBackendServiceUpdateResponse) GetResult() *CASBackendItem { return nil } +type CASBackendServiceDeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CASBackendServiceDeleteRequest) Reset() { + *x = CASBackendServiceDeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controlplane_v1_cas_backends_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CASBackendServiceDeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CASBackendServiceDeleteRequest) ProtoMessage() {} + +func (x *CASBackendServiceDeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_cas_backends_proto_msgTypes[6] + 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 CASBackendServiceDeleteRequest.ProtoReflect.Descriptor instead. +func (*CASBackendServiceDeleteRequest) Descriptor() ([]byte, []int) { + return file_controlplane_v1_cas_backends_proto_rawDescGZIP(), []int{6} +} + +func (x *CASBackendServiceDeleteRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type CASBackendServiceDeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CASBackendServiceDeleteResponse) Reset() { + *x = CASBackendServiceDeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controlplane_v1_cas_backends_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CASBackendServiceDeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CASBackendServiceDeleteResponse) ProtoMessage() {} + +func (x *CASBackendServiceDeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_cas_backends_proto_msgTypes[7] + 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 CASBackendServiceDeleteResponse.ProtoReflect.Descriptor instead. +func (*CASBackendServiceDeleteResponse) Descriptor() ([]byte, []int) { + return file_controlplane_v1_cas_backends_proto_rawDescGZIP(), []int{7} +} + var File_controlplane_v1_cas_backends_proto protoreflect.FileDescriptor var file_controlplane_v1_cas_backends_proto_rawDesc = []byte{ @@ -436,34 +521,47 @@ var file_controlplane_v1_cas_backends_proto_rawDesc = []byte{ 0x12, 0x37, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x74, 0x65, - 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xd4, 0x02, 0x0a, 0x11, 0x43, 0x41, - 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x65, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, - 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x0a, 0x1e, 0x43, 0x41, 0x53, + 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, + 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x21, 0x0a, 0x1f, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, + 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc1, 0x03, 0x0a, 0x11, 0x43, 0x41, 0x53, + 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x65, + 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, + 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, + 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x2e, + 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x41, 0x53, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x6b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, 0x42, + 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x53, + 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4c, 0x5a, 0x4a, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, + 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -478,7 +576,7 @@ func file_controlplane_v1_cas_backends_proto_rawDescGZIP() []byte { return file_controlplane_v1_cas_backends_proto_rawDescData } -var file_controlplane_v1_cas_backends_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_controlplane_v1_cas_backends_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_controlplane_v1_cas_backends_proto_goTypes = []interface{}{ (*CASBackendServiceListRequest)(nil), // 0: controlplane.v1.CASBackendServiceListRequest (*CASBackendServiceListResponse)(nil), // 1: controlplane.v1.CASBackendServiceListResponse @@ -486,23 +584,27 @@ var file_controlplane_v1_cas_backends_proto_goTypes = []interface{}{ (*CASBackendServiceCreateResponse)(nil), // 3: controlplane.v1.CASBackendServiceCreateResponse (*CASBackendServiceUpdateRequest)(nil), // 4: controlplane.v1.CASBackendServiceUpdateRequest (*CASBackendServiceUpdateResponse)(nil), // 5: controlplane.v1.CASBackendServiceUpdateResponse - (*CASBackendItem)(nil), // 6: controlplane.v1.CASBackendItem - (*structpb.Struct)(nil), // 7: google.protobuf.Struct + (*CASBackendServiceDeleteRequest)(nil), // 6: controlplane.v1.CASBackendServiceDeleteRequest + (*CASBackendServiceDeleteResponse)(nil), // 7: controlplane.v1.CASBackendServiceDeleteResponse + (*CASBackendItem)(nil), // 8: controlplane.v1.CASBackendItem + (*structpb.Struct)(nil), // 9: google.protobuf.Struct } var file_controlplane_v1_cas_backends_proto_depIdxs = []int32{ - 6, // 0: controlplane.v1.CASBackendServiceListResponse.result:type_name -> controlplane.v1.CASBackendItem - 7, // 1: controlplane.v1.CASBackendServiceCreateRequest.credentials:type_name -> google.protobuf.Struct - 6, // 2: controlplane.v1.CASBackendServiceCreateResponse.result:type_name -> controlplane.v1.CASBackendItem - 7, // 3: controlplane.v1.CASBackendServiceUpdateRequest.credentials:type_name -> google.protobuf.Struct - 6, // 4: controlplane.v1.CASBackendServiceUpdateResponse.result:type_name -> controlplane.v1.CASBackendItem + 8, // 0: controlplane.v1.CASBackendServiceListResponse.result:type_name -> controlplane.v1.CASBackendItem + 9, // 1: controlplane.v1.CASBackendServiceCreateRequest.credentials:type_name -> google.protobuf.Struct + 8, // 2: controlplane.v1.CASBackendServiceCreateResponse.result:type_name -> controlplane.v1.CASBackendItem + 9, // 3: controlplane.v1.CASBackendServiceUpdateRequest.credentials:type_name -> google.protobuf.Struct + 8, // 4: controlplane.v1.CASBackendServiceUpdateResponse.result:type_name -> controlplane.v1.CASBackendItem 0, // 5: controlplane.v1.CASBackendService.List:input_type -> controlplane.v1.CASBackendServiceListRequest 2, // 6: controlplane.v1.CASBackendService.Create:input_type -> controlplane.v1.CASBackendServiceCreateRequest 4, // 7: controlplane.v1.CASBackendService.Update:input_type -> controlplane.v1.CASBackendServiceUpdateRequest - 1, // 8: controlplane.v1.CASBackendService.List:output_type -> controlplane.v1.CASBackendServiceListResponse - 3, // 9: controlplane.v1.CASBackendService.Create:output_type -> controlplane.v1.CASBackendServiceCreateResponse - 5, // 10: controlplane.v1.CASBackendService.Update:output_type -> controlplane.v1.CASBackendServiceUpdateResponse - 8, // [8:11] is the sub-list for method output_type - 5, // [5:8] is the sub-list for method input_type + 6, // 8: controlplane.v1.CASBackendService.Delete:input_type -> controlplane.v1.CASBackendServiceDeleteRequest + 1, // 9: controlplane.v1.CASBackendService.List:output_type -> controlplane.v1.CASBackendServiceListResponse + 3, // 10: controlplane.v1.CASBackendService.Create:output_type -> controlplane.v1.CASBackendServiceCreateResponse + 5, // 11: controlplane.v1.CASBackendService.Update:output_type -> controlplane.v1.CASBackendServiceUpdateResponse + 7, // 12: controlplane.v1.CASBackendService.Delete:output_type -> controlplane.v1.CASBackendServiceDeleteResponse + 9, // [9:13] is the sub-list for method output_type + 5, // [5:9] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name @@ -587,6 +689,30 @@ func file_controlplane_v1_cas_backends_proto_init() { return nil } } + file_controlplane_v1_cas_backends_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CASBackendServiceDeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controlplane_v1_cas_backends_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CASBackendServiceDeleteResponse); 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{ @@ -594,7 +720,7 @@ func file_controlplane_v1_cas_backends_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controlplane_v1_cas_backends_proto_rawDesc, NumEnums: 0, - NumMessages: 6, + NumMessages: 8, NumExtensions: 0, NumServices: 1, }, diff --git a/app/controlplane/api/controlplane/v1/cas_backends.pb.validate.go b/app/controlplane/api/controlplane/v1/cas_backends.pb.validate.go index 691e02a5f..713695726 100644 --- a/app/controlplane/api/controlplane/v1/cas_backends.pb.validate.go +++ b/app/controlplane/api/controlplane/v1/cas_backends.pb.validate.go @@ -866,3 +866,229 @@ var _ interface { Cause() error ErrorName() string } = CASBackendServiceUpdateResponseValidationError{} + +// Validate checks the field values on CASBackendServiceDeleteRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CASBackendServiceDeleteRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CASBackendServiceDeleteRequest with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// CASBackendServiceDeleteRequestMultiError, or nil if none found. +func (m *CASBackendServiceDeleteRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *CASBackendServiceDeleteRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if err := m._validateUuid(m.GetId()); err != nil { + err = CASBackendServiceDeleteRequestValidationError{ + field: "Id", + reason: "value must be a valid UUID", + cause: err, + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return CASBackendServiceDeleteRequestMultiError(errors) + } + + return nil +} + +func (m *CASBackendServiceDeleteRequest) _validateUuid(uuid string) error { + if matched := _cas_backends_uuidPattern.MatchString(uuid); !matched { + return errors.New("invalid uuid format") + } + + return nil +} + +// CASBackendServiceDeleteRequestMultiError is an error wrapping multiple +// validation errors returned by CASBackendServiceDeleteRequest.ValidateAll() +// if the designated constraints aren't met. +type CASBackendServiceDeleteRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CASBackendServiceDeleteRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CASBackendServiceDeleteRequestMultiError) AllErrors() []error { return m } + +// CASBackendServiceDeleteRequestValidationError is the validation error +// returned by CASBackendServiceDeleteRequest.Validate if the designated +// constraints aren't met. +type CASBackendServiceDeleteRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CASBackendServiceDeleteRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CASBackendServiceDeleteRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CASBackendServiceDeleteRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CASBackendServiceDeleteRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CASBackendServiceDeleteRequestValidationError) ErrorName() string { + return "CASBackendServiceDeleteRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e CASBackendServiceDeleteRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCASBackendServiceDeleteRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = CASBackendServiceDeleteRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CASBackendServiceDeleteRequestValidationError{} + +// Validate checks the field values on CASBackendServiceDeleteResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CASBackendServiceDeleteResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CASBackendServiceDeleteResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// CASBackendServiceDeleteResponseMultiError, or nil if none found. +func (m *CASBackendServiceDeleteResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *CASBackendServiceDeleteResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return CASBackendServiceDeleteResponseMultiError(errors) + } + + return nil +} + +// CASBackendServiceDeleteResponseMultiError is an error wrapping multiple +// validation errors returned by CASBackendServiceDeleteResponse.ValidateAll() +// if the designated constraints aren't met. +type CASBackendServiceDeleteResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CASBackendServiceDeleteResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CASBackendServiceDeleteResponseMultiError) AllErrors() []error { return m } + +// CASBackendServiceDeleteResponseValidationError is the validation error +// returned by CASBackendServiceDeleteResponse.Validate if the designated +// constraints aren't met. +type CASBackendServiceDeleteResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CASBackendServiceDeleteResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CASBackendServiceDeleteResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CASBackendServiceDeleteResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CASBackendServiceDeleteResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CASBackendServiceDeleteResponseValidationError) ErrorName() string { + return "CASBackendServiceDeleteResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e CASBackendServiceDeleteResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCASBackendServiceDeleteResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = CASBackendServiceDeleteResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CASBackendServiceDeleteResponseValidationError{} diff --git a/app/controlplane/api/controlplane/v1/cas_backends.proto b/app/controlplane/api/controlplane/v1/cas_backends.proto index e8cf96bde..d8240593b 100644 --- a/app/controlplane/api/controlplane/v1/cas_backends.proto +++ b/app/controlplane/api/controlplane/v1/cas_backends.proto @@ -27,6 +27,7 @@ service CASBackendService { rpc List (CASBackendServiceListRequest) returns (CASBackendServiceListResponse); rpc Create (CASBackendServiceCreateRequest) returns (CASBackendServiceCreateResponse); rpc Update (CASBackendServiceUpdateRequest) returns (CASBackendServiceUpdateResponse); + rpc Delete (CASBackendServiceDeleteRequest) returns (CASBackendServiceDeleteResponse); } message CASBackendServiceListRequest {} @@ -69,4 +70,10 @@ message CASBackendServiceUpdateRequest { message CASBackendServiceUpdateResponse { CASBackendItem result = 1; -} \ No newline at end of file +} + +message CASBackendServiceDeleteRequest { + string id = 1 [(validate.rules).string.uuid = true]; +} + +message CASBackendServiceDeleteResponse {} \ No newline at end of file diff --git a/app/controlplane/api/controlplane/v1/cas_backends_grpc.pb.go b/app/controlplane/api/controlplane/v1/cas_backends_grpc.pb.go index 8745b7bf8..e9ecffe88 100644 --- a/app/controlplane/api/controlplane/v1/cas_backends_grpc.pb.go +++ b/app/controlplane/api/controlplane/v1/cas_backends_grpc.pb.go @@ -37,6 +37,7 @@ const ( CASBackendService_List_FullMethodName = "/controlplane.v1.CASBackendService/List" CASBackendService_Create_FullMethodName = "/controlplane.v1.CASBackendService/Create" CASBackendService_Update_FullMethodName = "/controlplane.v1.CASBackendService/Update" + CASBackendService_Delete_FullMethodName = "/controlplane.v1.CASBackendService/Delete" ) // CASBackendServiceClient is the client API for CASBackendService service. @@ -46,6 +47,7 @@ type CASBackendServiceClient interface { List(ctx context.Context, in *CASBackendServiceListRequest, opts ...grpc.CallOption) (*CASBackendServiceListResponse, error) Create(ctx context.Context, in *CASBackendServiceCreateRequest, opts ...grpc.CallOption) (*CASBackendServiceCreateResponse, error) Update(ctx context.Context, in *CASBackendServiceUpdateRequest, opts ...grpc.CallOption) (*CASBackendServiceUpdateResponse, error) + Delete(ctx context.Context, in *CASBackendServiceDeleteRequest, opts ...grpc.CallOption) (*CASBackendServiceDeleteResponse, error) } type cASBackendServiceClient struct { @@ -83,6 +85,15 @@ func (c *cASBackendServiceClient) Update(ctx context.Context, in *CASBackendServ return out, nil } +func (c *cASBackendServiceClient) Delete(ctx context.Context, in *CASBackendServiceDeleteRequest, opts ...grpc.CallOption) (*CASBackendServiceDeleteResponse, error) { + out := new(CASBackendServiceDeleteResponse) + err := c.cc.Invoke(ctx, CASBackendService_Delete_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // CASBackendServiceServer is the server API for CASBackendService service. // All implementations must embed UnimplementedCASBackendServiceServer // for forward compatibility @@ -90,6 +101,7 @@ type CASBackendServiceServer interface { List(context.Context, *CASBackendServiceListRequest) (*CASBackendServiceListResponse, error) Create(context.Context, *CASBackendServiceCreateRequest) (*CASBackendServiceCreateResponse, error) Update(context.Context, *CASBackendServiceUpdateRequest) (*CASBackendServiceUpdateResponse, error) + Delete(context.Context, *CASBackendServiceDeleteRequest) (*CASBackendServiceDeleteResponse, error) mustEmbedUnimplementedCASBackendServiceServer() } @@ -106,6 +118,9 @@ func (UnimplementedCASBackendServiceServer) Create(context.Context, *CASBackendS func (UnimplementedCASBackendServiceServer) Update(context.Context, *CASBackendServiceUpdateRequest) (*CASBackendServiceUpdateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") } +func (UnimplementedCASBackendServiceServer) Delete(context.Context, *CASBackendServiceDeleteRequest) (*CASBackendServiceDeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} func (UnimplementedCASBackendServiceServer) mustEmbedUnimplementedCASBackendServiceServer() {} // UnsafeCASBackendServiceServer may be embedded to opt out of forward compatibility for this service. @@ -173,6 +188,24 @@ func _CASBackendService_Update_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _CASBackendService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CASBackendServiceDeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CASBackendServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CASBackendService_Delete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CASBackendServiceServer).Delete(ctx, req.(*CASBackendServiceDeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + // CASBackendService_ServiceDesc is the grpc.ServiceDesc for CASBackendService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -192,6 +225,10 @@ var CASBackendService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Update", Handler: _CASBackendService_Update_Handler, }, + { + MethodName: "Delete", + Handler: _CASBackendService_Delete_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "controlplane/v1/cas_backends.proto", diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/cas_backends.ts b/app/controlplane/api/gen/frontend/controlplane/v1/cas_backends.ts index 5d712a902..12cf0751d 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/cas_backends.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/cas_backends.ts @@ -52,6 +52,13 @@ export interface CASBackendServiceUpdateResponse { result?: CASBackendItem; } +export interface CASBackendServiceDeleteRequest { + id: string; +} + +export interface CASBackendServiceDeleteResponse { +} + function createBaseCASBackendServiceListRequest(): CASBackendServiceListRequest { return {}; } @@ -489,6 +496,108 @@ export const CASBackendServiceUpdateResponse = { }, }; +function createBaseCASBackendServiceDeleteRequest(): CASBackendServiceDeleteRequest { + return { id: "" }; +} + +export const CASBackendServiceDeleteRequest = { + encode(message: CASBackendServiceDeleteRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== "") { + writer.uint32(10).string(message.id); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CASBackendServiceDeleteRequest { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCASBackendServiceDeleteRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): CASBackendServiceDeleteRequest { + return { id: isSet(object.id) ? String(object.id) : "" }; + }, + + toJSON(message: CASBackendServiceDeleteRequest): unknown { + const obj: any = {}; + message.id !== undefined && (obj.id = message.id); + return obj; + }, + + create, I>>(base?: I): CASBackendServiceDeleteRequest { + return CASBackendServiceDeleteRequest.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I, + ): CASBackendServiceDeleteRequest { + const message = createBaseCASBackendServiceDeleteRequest(); + message.id = object.id ?? ""; + return message; + }, +}; + +function createBaseCASBackendServiceDeleteResponse(): CASBackendServiceDeleteResponse { + return {}; +} + +export const CASBackendServiceDeleteResponse = { + encode(_: CASBackendServiceDeleteResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CASBackendServiceDeleteResponse { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCASBackendServiceDeleteResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(_: any): CASBackendServiceDeleteResponse { + return {}; + }, + + toJSON(_: CASBackendServiceDeleteResponse): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): CASBackendServiceDeleteResponse { + return CASBackendServiceDeleteResponse.fromPartial(base ?? {}); + }, + + fromPartial, I>>(_: I): CASBackendServiceDeleteResponse { + const message = createBaseCASBackendServiceDeleteResponse(); + return message; + }, +}; + export interface CASBackendService { List( request: DeepPartial, @@ -502,6 +611,10 @@ export interface CASBackendService { request: DeepPartial, metadata?: grpc.Metadata, ): Promise; + Delete( + request: DeepPartial, + metadata?: grpc.Metadata, + ): Promise; } export class CASBackendServiceClientImpl implements CASBackendService { @@ -512,6 +625,7 @@ export class CASBackendServiceClientImpl implements CASBackendService { this.List = this.List.bind(this); this.Create = this.Create.bind(this); this.Update = this.Update.bind(this); + this.Delete = this.Delete.bind(this); } List( @@ -534,6 +648,13 @@ export class CASBackendServiceClientImpl implements CASBackendService { ): Promise { return this.rpc.unary(CASBackendServiceUpdateDesc, CASBackendServiceUpdateRequest.fromPartial(request), metadata); } + + Delete( + request: DeepPartial, + metadata?: grpc.Metadata, + ): Promise { + return this.rpc.unary(CASBackendServiceDeleteDesc, CASBackendServiceDeleteRequest.fromPartial(request), metadata); + } } export const CASBackendServiceDesc = { serviceName: "controlplane.v1.CASBackendService" }; @@ -607,6 +728,29 @@ export const CASBackendServiceUpdateDesc: UnaryMethodDefinitionish = { } as any, }; +export const CASBackendServiceDeleteDesc: UnaryMethodDefinitionish = { + methodName: "Delete", + service: CASBackendServiceDesc, + requestStream: false, + responseStream: false, + requestType: { + serializeBinary() { + return CASBackendServiceDeleteRequest.encode(this).finish(); + }, + } as any, + responseType: { + deserializeBinary(data: Uint8Array) { + const value = CASBackendServiceDeleteResponse.decode(data); + return { + ...value, + toObject() { + return value; + }, + }; + }, + } as any, +}; + interface UnaryMethodDefinitionishR extends grpc.UnaryMethodDefinition { requestStream: any; responseStream: any; diff --git a/app/controlplane/internal/biz/casbackend.go b/app/controlplane/internal/biz/casbackend.go index 3f3b0138d..ef47113e8 100644 --- a/app/controlplane/internal/biz/casbackend.go +++ b/app/controlplane/internal/biz/casbackend.go @@ -80,6 +80,7 @@ type CASBackendRepo interface { Create(context.Context, *CASBackendCreateOpts) (*CASBackend, error) Update(context.Context, *CASBackendUpdateOpts) (*CASBackend, error) Delete(ctx context.Context, ID uuid.UUID) error + SoftDelete(ctx context.Context, ID uuid.UUID) error } type CASBackendReader interface { @@ -291,7 +292,32 @@ func (uc *CASBackendUseCase) CreateOrUpdate(ctx context.Context, orgID, name, us }) } +// SoftDelete will mark the cas backend as deleted but will not delete the secret in the external secrets manager +// We keep it so it can be restored or referenced in the future while trying to download an asset +func (uc *CASBackendUseCase) SoftDelete(ctx context.Context, orgID, id string) error { + orgUUID, err := uuid.Parse(orgID) + if err != nil { + return NewErrInvalidUUID(err) + } + + backendUUID, err := uuid.Parse(id) + if err != nil { + return NewErrInvalidUUID(err) + } + + // Make sure the repo exists in the organization + repo, err := uc.repo.FindByIDInOrg(ctx, orgUUID, backendUUID) + if err != nil { + return err + } else if repo == nil { + return NewErrNotFound("CAS Backend") + } + + return uc.repo.SoftDelete(ctx, backendUUID) +} + // Delete will delete the secret in the external secrets manager and the CAS backend from the database +// This method is used during user off-boarding func (uc *CASBackendUseCase) Delete(ctx context.Context, id string) error { uc.logger.Infow("msg", "deleting CAS Backend", "ID", id) diff --git a/app/controlplane/internal/biz/mocks/CASBackendRepo.go b/app/controlplane/internal/biz/mocks/CASBackendRepo.go index b15c64829..2f27d7edb 100644 --- a/app/controlplane/internal/biz/mocks/CASBackendRepo.go +++ b/app/controlplane/internal/biz/mocks/CASBackendRepo.go @@ -161,6 +161,20 @@ func (_m *CASBackendRepo) List(ctx context.Context, orgID uuid.UUID) ([]*biz.CAS return r0, r1 } +// SoftDelete provides a mock function with given fields: ctx, ID +func (_m *CASBackendRepo) SoftDelete(ctx context.Context, ID uuid.UUID) error { + ret := _m.Called(ctx, ID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = rf(ctx, ID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Update provides a mock function with given fields: _a0, _a1 func (_m *CASBackendRepo) Update(_a0 context.Context, _a1 *biz.CASBackendUpdateOpts) (*biz.CASBackend, error) { ret := _m.Called(_a0, _a1) diff --git a/app/controlplane/internal/data/casbackend.go b/app/controlplane/internal/data/casbackend.go index 5f13ba99a..110fd5da1 100644 --- a/app/controlplane/internal/data/casbackend.go +++ b/app/controlplane/internal/data/casbackend.go @@ -42,6 +42,7 @@ func NewCASBackendRepo(data *Data, logger log.Logger) biz.CASBackendRepo { func (r *CASBackendRepo) List(ctx context.Context, orgID uuid.UUID) ([]*biz.CASBackend, error) { backends, err := orgScopedQuery(r.data.db, orgID).QueryCasBackends().WithOrganization(). + Where(casbackend.DeletedAtIsNil()). Order(ent.Desc(casbackend.FieldCreatedAt)). All(ctx) if err != nil { @@ -57,7 +58,9 @@ func (r *CASBackendRepo) List(ctx context.Context, orgID uuid.UUID) ([]*biz.CASB } func (r *CASBackendRepo) FindDefaultBackend(ctx context.Context, orgID uuid.UUID) (*biz.CASBackend, error) { - backend, err := orgScopedQuery(r.data.db, orgID).QueryCasBackends().Where(casbackend.Default(true)).Only(ctx) + backend, err := orgScopedQuery(r.data.db, orgID).QueryCasBackends(). + Where(casbackend.Default(true), casbackend.DeletedAtIsNil()). + Only(ctx) if err != nil && !ent.IsNotFound(err) { return nil, err } @@ -129,7 +132,8 @@ func (r *CASBackendRepo) Update(ctx context.Context, opts *biz.CASBackendUpdateO // FindByID finds a CAS backend by ID // If not found, returns nil and no error func (r *CASBackendRepo) FindByID(ctx context.Context, id uuid.UUID) (*biz.CASBackend, error) { - backend, err := r.data.db.CASBackend.Query().WithOrganization().Where(casbackend.ID(id)).Only(ctx) + backend, err := r.data.db.CASBackend.Query().WithOrganization(). + Where(casbackend.ID(id), casbackend.DeletedAtIsNil()).Only(ctx) if err != nil && !ent.IsNotFound(err) { return nil, err } else if backend == nil { @@ -142,7 +146,8 @@ func (r *CASBackendRepo) FindByID(ctx context.Context, id uuid.UUID) (*biz.CASBa // FindByIDInOrg finds a CAS backend by ID in the given organization. // If not found, returns nil and no error func (r *CASBackendRepo) FindByIDInOrg(ctx context.Context, orgID, id uuid.UUID) (*biz.CASBackend, error) { - backend, err := orgScopedQuery(r.data.db, orgID).QueryCasBackends().WithOrganization().Where(casbackend.ID(id)).Only(ctx) + backend, err := orgScopedQuery(r.data.db, orgID).QueryCasBackends().WithOrganization(). + Where(casbackend.ID(id), casbackend.DeletedAtIsNil()).Only(ctx) if err != nil && !ent.IsNotFound(err) { return nil, err } else if backend == nil { @@ -152,6 +157,12 @@ func (r *CASBackendRepo) FindByIDInOrg(ctx context.Context, orgID, id uuid.UUID) return entCASBackendToBiz(backend), nil } +// Set deleted at instead of actually deleting the backend +func (r *CASBackendRepo) SoftDelete(ctx context.Context, id uuid.UUID) error { + return r.data.db.CASBackend.UpdateOneID(id).SetDeletedAt(time.Now()).Exec(ctx) +} + +// Delete deletes a CAS backend from the DB func (r *CASBackendRepo) Delete(ctx context.Context, id uuid.UUID) error { return r.data.db.CASBackend.DeleteOneID(id).Exec(ctx) } diff --git a/app/controlplane/internal/data/ent/casbackend.go b/app/controlplane/internal/data/ent/casbackend.go index 122c3c8a1..ff5fb6de2 100644 --- a/app/controlplane/internal/data/ent/casbackend.go +++ b/app/controlplane/internal/data/ent/casbackend.go @@ -36,6 +36,8 @@ type CASBackend struct { ValidatedAt time.Time `json:"validated_at,omitempty"` // Default holds the value of the "default" field. Default bool `json:"default,omitempty"` + // DeletedAt holds the value of the "deleted_at" field. + DeletedAt time.Time `json:"deleted_at,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the CASBackendQuery when eager-loading is set. Edges CASBackendEdges `json:"edges"` @@ -85,7 +87,7 @@ func (*CASBackend) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullBool) case casbackend.FieldLocation, casbackend.FieldProvider, casbackend.FieldDescription, casbackend.FieldSecretName, casbackend.FieldValidationStatus: values[i] = new(sql.NullString) - case casbackend.FieldCreatedAt, casbackend.FieldValidatedAt: + case casbackend.FieldCreatedAt, casbackend.FieldValidatedAt, casbackend.FieldDeletedAt: values[i] = new(sql.NullTime) case casbackend.FieldID: values[i] = new(uuid.UUID) @@ -160,6 +162,12 @@ func (cb *CASBackend) assignValues(columns []string, values []any) error { } else if value.Valid { cb.Default = value.Bool } + case casbackend.FieldDeletedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field deleted_at", values[i]) + } else if value.Valid { + cb.DeletedAt = value.Time + } case casbackend.ForeignKeys[0]: if value, ok := values[i].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field organization_cas_backends", values[i]) @@ -236,6 +244,9 @@ func (cb *CASBackend) String() string { builder.WriteString(", ") builder.WriteString("default=") builder.WriteString(fmt.Sprintf("%v", cb.Default)) + builder.WriteString(", ") + builder.WriteString("deleted_at=") + builder.WriteString(cb.DeletedAt.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/internal/data/ent/casbackend/casbackend.go b/app/controlplane/internal/data/ent/casbackend/casbackend.go index 61eaf17f7..2d4a35dc2 100644 --- a/app/controlplane/internal/data/ent/casbackend/casbackend.go +++ b/app/controlplane/internal/data/ent/casbackend/casbackend.go @@ -33,6 +33,8 @@ const ( FieldValidatedAt = "validated_at" // FieldDefault holds the string denoting the default field in the database. FieldDefault = "default" + // FieldDeletedAt holds the string denoting the deleted_at field in the database. + FieldDeletedAt = "deleted_at" // EdgeOrganization holds the string denoting the organization edge name in mutations. EdgeOrganization = "organization" // EdgeWorkflowRun holds the string denoting the workflow_run edge name in mutations. @@ -64,6 +66,7 @@ var Columns = []string{ FieldValidationStatus, FieldValidatedAt, FieldDefault, + FieldDeletedAt, } // ForeignKeys holds the SQL foreign-keys that are owned by the "cas_backends" @@ -174,6 +177,11 @@ func ByDefault(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDefault, opts...).ToFunc() } +// ByDeletedAt orders the results by the deleted_at field. +func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDeletedAt, opts...).ToFunc() +} + // ByOrganizationField orders the results by organization field. func ByOrganizationField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/app/controlplane/internal/data/ent/casbackend/where.go b/app/controlplane/internal/data/ent/casbackend/where.go index cb985ea2e..a0693c1f2 100644 --- a/app/controlplane/internal/data/ent/casbackend/where.go +++ b/app/controlplane/internal/data/ent/casbackend/where.go @@ -87,6 +87,11 @@ func Default(v bool) predicate.CASBackend { return predicate.CASBackend(sql.FieldEQ(FieldDefault, v)) } +// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ. +func DeletedAt(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldEQ(FieldDeletedAt, v)) +} + // LocationEQ applies the EQ predicate on the "location" field. func LocationEQ(v string) predicate.CASBackend { return predicate.CASBackend(sql.FieldEQ(FieldLocation, v)) @@ -442,6 +447,56 @@ func DefaultNEQ(v bool) predicate.CASBackend { return predicate.CASBackend(sql.FieldNEQ(FieldDefault, v)) } +// DeletedAtEQ applies the EQ predicate on the "deleted_at" field. +func DeletedAtEQ(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldEQ(FieldDeletedAt, v)) +} + +// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field. +func DeletedAtNEQ(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldNEQ(FieldDeletedAt, v)) +} + +// DeletedAtIn applies the In predicate on the "deleted_at" field. +func DeletedAtIn(vs ...time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldIn(FieldDeletedAt, vs...)) +} + +// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field. +func DeletedAtNotIn(vs ...time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldNotIn(FieldDeletedAt, vs...)) +} + +// DeletedAtGT applies the GT predicate on the "deleted_at" field. +func DeletedAtGT(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldGT(FieldDeletedAt, v)) +} + +// DeletedAtGTE applies the GTE predicate on the "deleted_at" field. +func DeletedAtGTE(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldGTE(FieldDeletedAt, v)) +} + +// DeletedAtLT applies the LT predicate on the "deleted_at" field. +func DeletedAtLT(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldLT(FieldDeletedAt, v)) +} + +// DeletedAtLTE applies the LTE predicate on the "deleted_at" field. +func DeletedAtLTE(v time.Time) predicate.CASBackend { + return predicate.CASBackend(sql.FieldLTE(FieldDeletedAt, v)) +} + +// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field. +func DeletedAtIsNil() predicate.CASBackend { + return predicate.CASBackend(sql.FieldIsNull(FieldDeletedAt)) +} + +// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field. +func DeletedAtNotNil() predicate.CASBackend { + return predicate.CASBackend(sql.FieldNotNull(FieldDeletedAt)) +} + // HasOrganization applies the HasEdge predicate on the "organization" edge. func HasOrganization() predicate.CASBackend { return predicate.CASBackend(func(s *sql.Selector) { diff --git a/app/controlplane/internal/data/ent/casbackend_create.go b/app/controlplane/internal/data/ent/casbackend_create.go index da4a58a2c..b9c01b241 100644 --- a/app/controlplane/internal/data/ent/casbackend_create.go +++ b/app/controlplane/internal/data/ent/casbackend_create.go @@ -112,6 +112,20 @@ func (cbc *CASBackendCreate) SetNillableDefault(b *bool) *CASBackendCreate { return cbc } +// SetDeletedAt sets the "deleted_at" field. +func (cbc *CASBackendCreate) SetDeletedAt(t time.Time) *CASBackendCreate { + cbc.mutation.SetDeletedAt(t) + return cbc +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (cbc *CASBackendCreate) SetNillableDeletedAt(t *time.Time) *CASBackendCreate { + if t != nil { + cbc.SetDeletedAt(*t) + } + return cbc +} + // SetID sets the "id" field. func (cbc *CASBackendCreate) SetID(u uuid.UUID) *CASBackendCreate { cbc.mutation.SetID(u) @@ -312,6 +326,10 @@ func (cbc *CASBackendCreate) createSpec() (*CASBackend, *sqlgraph.CreateSpec) { _spec.SetField(casbackend.FieldDefault, field.TypeBool, value) _node.Default = value } + if value, ok := cbc.mutation.DeletedAt(); ok { + _spec.SetField(casbackend.FieldDeletedAt, field.TypeTime, value) + _node.DeletedAt = value + } if nodes := cbc.mutation.OrganizationIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/app/controlplane/internal/data/ent/casbackend_update.go b/app/controlplane/internal/data/ent/casbackend_update.go index 38dbcfdb8..9d5732ddc 100644 --- a/app/controlplane/internal/data/ent/casbackend_update.go +++ b/app/controlplane/internal/data/ent/casbackend_update.go @@ -100,6 +100,26 @@ func (cbu *CASBackendUpdate) SetNillableDefault(b *bool) *CASBackendUpdate { return cbu } +// SetDeletedAt sets the "deleted_at" field. +func (cbu *CASBackendUpdate) SetDeletedAt(t time.Time) *CASBackendUpdate { + cbu.mutation.SetDeletedAt(t) + return cbu +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (cbu *CASBackendUpdate) SetNillableDeletedAt(t *time.Time) *CASBackendUpdate { + if t != nil { + cbu.SetDeletedAt(*t) + } + return cbu +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (cbu *CASBackendUpdate) ClearDeletedAt() *CASBackendUpdate { + cbu.mutation.ClearDeletedAt() + return cbu +} + // SetOrganizationID sets the "organization" edge to the Organization entity by ID. func (cbu *CASBackendUpdate) SetOrganizationID(id uuid.UUID) *CASBackendUpdate { cbu.mutation.SetOrganizationID(id) @@ -228,6 +248,12 @@ func (cbu *CASBackendUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := cbu.mutation.Default(); ok { _spec.SetField(casbackend.FieldDefault, field.TypeBool, value) } + if value, ok := cbu.mutation.DeletedAt(); ok { + _spec.SetField(casbackend.FieldDeletedAt, field.TypeTime, value) + } + if cbu.mutation.DeletedAtCleared() { + _spec.ClearField(casbackend.FieldDeletedAt, field.TypeTime) + } if cbu.mutation.OrganizationCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -390,6 +416,26 @@ func (cbuo *CASBackendUpdateOne) SetNillableDefault(b *bool) *CASBackendUpdateOn return cbuo } +// SetDeletedAt sets the "deleted_at" field. +func (cbuo *CASBackendUpdateOne) SetDeletedAt(t time.Time) *CASBackendUpdateOne { + cbuo.mutation.SetDeletedAt(t) + return cbuo +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (cbuo *CASBackendUpdateOne) SetNillableDeletedAt(t *time.Time) *CASBackendUpdateOne { + if t != nil { + cbuo.SetDeletedAt(*t) + } + return cbuo +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (cbuo *CASBackendUpdateOne) ClearDeletedAt() *CASBackendUpdateOne { + cbuo.mutation.ClearDeletedAt() + return cbuo +} + // SetOrganizationID sets the "organization" edge to the Organization entity by ID. func (cbuo *CASBackendUpdateOne) SetOrganizationID(id uuid.UUID) *CASBackendUpdateOne { cbuo.mutation.SetOrganizationID(id) @@ -548,6 +594,12 @@ func (cbuo *CASBackendUpdateOne) sqlSave(ctx context.Context) (_node *CASBackend if value, ok := cbuo.mutation.Default(); ok { _spec.SetField(casbackend.FieldDefault, field.TypeBool, value) } + if value, ok := cbuo.mutation.DeletedAt(); ok { + _spec.SetField(casbackend.FieldDeletedAt, field.TypeTime, value) + } + if cbuo.mutation.DeletedAtCleared() { + _spec.ClearField(casbackend.FieldDeletedAt, field.TypeTime) + } if cbuo.mutation.OrganizationCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/app/controlplane/internal/data/ent/migrate/migrations/20230714085240-deleted-at.sql b/app/controlplane/internal/data/ent/migrate/migrations/20230714085240-deleted-at.sql new file mode 100644 index 000000000..6afc0f75a --- /dev/null +++ b/app/controlplane/internal/data/ent/migrate/migrations/20230714085240-deleted-at.sql @@ -0,0 +1,2 @@ +-- Modify "cas_backends" table +ALTER TABLE "cas_backends" ADD COLUMN "deleted_at" timestamptz NULL; diff --git a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum index 7674bf6c1..053a9f6e9 100644 --- a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum @@ -1,5 +1,6 @@ -h1:Hu0VtkIyLR0K/3CiXJ5L/DhWEu5gOW0n4mDQNoVsJ7Y= +h1:zq4TfwTmB8n7oI6/PMR5X7ZasM/KZdgBb+KgG32PR40= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= 20230713112216-description.sql h1:jTxi1Gnp5oiqu3qAf4N7RaNfV0NbjupN2fIld+HcTTQ= +20230714085240-deleted-at.sql h1:uw5atDlH7wF4sxh9gkJ8NU01RNhDHMiZQcd7jY+ABvk= diff --git a/app/controlplane/internal/data/ent/migrate/schema.go b/app/controlplane/internal/data/ent/migrate/schema.go index efd713d94..d9c62463d 100644 --- a/app/controlplane/internal/data/ent/migrate/schema.go +++ b/app/controlplane/internal/data/ent/migrate/schema.go @@ -19,6 +19,7 @@ var ( {Name: "validation_status", Type: field.TypeEnum, Enums: []string{"OK", "Invalid"}, Default: "OK"}, {Name: "validated_at", Type: field.TypeTime, Default: "CURRENT_TIMESTAMP"}, {Name: "default", Type: field.TypeBool, Default: false}, + {Name: "deleted_at", Type: field.TypeTime, Nullable: true}, {Name: "organization_cas_backends", Type: field.TypeUUID}, } // CasBackendsTable holds the schema information for the "cas_backends" table. @@ -29,7 +30,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "cas_backends_organizations_cas_backends", - Columns: []*schema.Column{CasBackendsColumns[9]}, + Columns: []*schema.Column{CasBackendsColumns[10]}, RefColumns: []*schema.Column{OrganizationsColumns[0]}, OnDelete: schema.Cascade, }, diff --git a/app/controlplane/internal/data/ent/mutation.go b/app/controlplane/internal/data/ent/mutation.go index ee040bc54..a2d91d95a 100644 --- a/app/controlplane/internal/data/ent/mutation.go +++ b/app/controlplane/internal/data/ent/mutation.go @@ -64,6 +64,7 @@ type CASBackendMutation struct { validation_status *biz.CASBackendValidationStatus validated_at *time.Time _default *bool + deleted_at *time.Time clearedFields map[string]struct{} organization *uuid.UUID clearedorganization bool @@ -480,6 +481,55 @@ func (m *CASBackendMutation) ResetDefault() { m._default = nil } +// SetDeletedAt sets the "deleted_at" field. +func (m *CASBackendMutation) SetDeletedAt(t time.Time) { + m.deleted_at = &t +} + +// DeletedAt returns the value of the "deleted_at" field in the mutation. +func (m *CASBackendMutation) DeletedAt() (r time.Time, exists bool) { + v := m.deleted_at + if v == nil { + return + } + return *v, true +} + +// OldDeletedAt returns the old "deleted_at" field's value of the CASBackend entity. +// If the CASBackend object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *CASBackendMutation) OldDeletedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDeletedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err) + } + return oldValue.DeletedAt, nil +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (m *CASBackendMutation) ClearDeletedAt() { + m.deleted_at = nil + m.clearedFields[casbackend.FieldDeletedAt] = struct{}{} +} + +// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation. +func (m *CASBackendMutation) DeletedAtCleared() bool { + _, ok := m.clearedFields[casbackend.FieldDeletedAt] + return ok +} + +// ResetDeletedAt resets all changes to the "deleted_at" field. +func (m *CASBackendMutation) ResetDeletedAt() { + m.deleted_at = nil + delete(m.clearedFields, casbackend.FieldDeletedAt) +} + // SetOrganizationID sets the "organization" edge to the Organization entity by id. func (m *CASBackendMutation) SetOrganizationID(id uuid.UUID) { m.organization = &id @@ -607,7 +657,7 @@ func (m *CASBackendMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *CASBackendMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 9) if m.location != nil { fields = append(fields, casbackend.FieldLocation) } @@ -632,6 +682,9 @@ func (m *CASBackendMutation) Fields() []string { if m._default != nil { fields = append(fields, casbackend.FieldDefault) } + if m.deleted_at != nil { + fields = append(fields, casbackend.FieldDeletedAt) + } return fields } @@ -656,6 +709,8 @@ func (m *CASBackendMutation) Field(name string) (ent.Value, bool) { return m.ValidatedAt() case casbackend.FieldDefault: return m.Default() + case casbackend.FieldDeletedAt: + return m.DeletedAt() } return nil, false } @@ -681,6 +736,8 @@ func (m *CASBackendMutation) OldField(ctx context.Context, name string) (ent.Val return m.OldValidatedAt(ctx) case casbackend.FieldDefault: return m.OldDefault(ctx) + case casbackend.FieldDeletedAt: + return m.OldDeletedAt(ctx) } return nil, fmt.Errorf("unknown CASBackend field %s", name) } @@ -746,6 +803,13 @@ func (m *CASBackendMutation) SetField(name string, value ent.Value) error { } m.SetDefault(v) return nil + case casbackend.FieldDeletedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDeletedAt(v) + return nil } return fmt.Errorf("unknown CASBackend field %s", name) } @@ -779,6 +843,9 @@ func (m *CASBackendMutation) ClearedFields() []string { if m.FieldCleared(casbackend.FieldDescription) { fields = append(fields, casbackend.FieldDescription) } + if m.FieldCleared(casbackend.FieldDeletedAt) { + fields = append(fields, casbackend.FieldDeletedAt) + } return fields } @@ -796,6 +863,9 @@ func (m *CASBackendMutation) ClearField(name string) error { case casbackend.FieldDescription: m.ClearDescription() return nil + case casbackend.FieldDeletedAt: + m.ClearDeletedAt() + return nil } return fmt.Errorf("unknown CASBackend nullable field %s", name) } @@ -828,6 +898,9 @@ func (m *CASBackendMutation) ResetField(name string) error { case casbackend.FieldDefault: m.ResetDefault() return nil + case casbackend.FieldDeletedAt: + m.ResetDeletedAt() + return nil } return fmt.Errorf("unknown CASBackend field %s", name) } diff --git a/app/controlplane/internal/data/ent/schema-viz.html b/app/controlplane/internal/data/ent/schema-viz.html index 8132086af..a81ecea89 100644 --- a/app/controlplane/internal/data/ent/schema-viz.html +++ b/app/controlplane/internal/data/ent/schema-viz.html @@ -70,7 +70,7 @@ } - const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"CASBackend\",\"fields\":[{\"name\":\"location\",\"type\":\"string\"},{\"name\":\"provider\",\"type\":\"biz.CASBackendProvider\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.CASBackendValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"},{\"name\":\"default\",\"type\":\"bool\"}]},{\"id\":\"Integration\",\"fields\":[{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation\",\"type\":\"*dsse.Envelope\"}]}],\"edges\":[{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"RobotAccount\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"},{\"from\":\"WorkflowRun\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"}]}"); + const entGraph = JSON.parse("{\"nodes\":[{\"id\":\"CASBackend\",\"fields\":[{\"name\":\"location\",\"type\":\"string\"},{\"name\":\"provider\",\"type\":\"biz.CASBackendProvider\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"validation_status\",\"type\":\"biz.CASBackendValidationStatus\"},{\"name\":\"validated_at\",\"type\":\"time.Time\"},{\"name\":\"default\",\"type\":\"bool\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Integration\",\"fields\":[{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"secret_name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"IntegrationAttachment\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"configuration\",\"type\":\"[]byte\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"Membership\",\"fields\":[{\"name\":\"current\",\"type\":\"bool\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"updated_at\",\"type\":\"time.Time\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"RobotAccount\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"revoked_at\",\"type\":\"time.Time\"}]},{\"id\":\"User\",\"fields\":[{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Workflow\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"project\",\"type\":\"string\"},{\"name\":\"team\",\"type\":\"string\"},{\"name\":\"runs_count\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContract\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowContractVersion\",\"fields\":[{\"name\":\"body\",\"type\":\"[]byte\"},{\"name\":\"revision\",\"type\":\"int\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"WorkflowRun\",\"fields\":[{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"finished_at\",\"type\":\"time.Time\"},{\"name\":\"state\",\"type\":\"biz.WorkflowRunStatus\"},{\"name\":\"reason\",\"type\":\"string\"},{\"name\":\"run_url\",\"type\":\"string\"},{\"name\":\"runner_type\",\"type\":\"string\"},{\"name\":\"attestation\",\"type\":\"*dsse.Envelope\"}]}],\"edges\":[{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"Organization\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Organization\",\"to\":\"WorkflowContract\",\"label\":\"workflow_contracts\"},{\"from\":\"Organization\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"from\":\"Organization\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"},{\"from\":\"Organization\",\"to\":\"Integration\",\"label\":\"integrations\"},{\"from\":\"RobotAccount\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"User\",\"to\":\"Membership\",\"label\":\"memberships\"},{\"from\":\"Workflow\",\"to\":\"RobotAccount\",\"label\":\"robotaccounts\"},{\"from\":\"Workflow\",\"to\":\"WorkflowRun\",\"label\":\"workflowruns\"},{\"from\":\"Workflow\",\"to\":\"WorkflowContract\",\"label\":\"contract\"},{\"from\":\"WorkflowContract\",\"to\":\"WorkflowContractVersion\",\"label\":\"versions\"},{\"from\":\"WorkflowRun\",\"to\":\"WorkflowContractVersion\",\"label\":\"contract_version\"},{\"from\":\"WorkflowRun\",\"to\":\"CASBackend\",\"label\":\"cas_backends\"}]}"); const nodes = new vis.DataSet((entGraph.nodes || []).map(n => ({ id: n.id, diff --git a/app/controlplane/internal/data/ent/schema/casbackend.go b/app/controlplane/internal/data/ent/schema/casbackend.go index e0ce284d6..b10eefdcf 100644 --- a/app/controlplane/internal/data/ent/schema/casbackend.go +++ b/app/controlplane/internal/data/ent/schema/casbackend.go @@ -50,6 +50,7 @@ func (CASBackend) Fields() []ent.Field { field.Time("validated_at").Default(time.Now). Annotations(&entsql.Annotation{Default: "CURRENT_TIMESTAMP"}), field.Bool("default").Default(false), + field.Time("deleted_at").Optional(), } } diff --git a/app/controlplane/internal/service/casbackend.go b/app/controlplane/internal/service/casbackend.go index fe97738e0..516eeaf9f 100644 --- a/app/controlplane/internal/service/casbackend.go +++ b/app/controlplane/internal/service/casbackend.go @@ -109,6 +109,25 @@ func (s *CASBackendService) Update(ctx context.Context, req *pb.CASBackendServic return &pb.CASBackendServiceUpdateResponse{Result: bizOCASBackendToPb(res)}, nil } +// Delete the CAS backend +func (s *CASBackendService) Delete(ctx context.Context, req *pb.CASBackendServiceDeleteRequest) (*pb.CASBackendServiceDeleteResponse, error) { + _, currentOrg, err := loadCurrentUserAndOrg(ctx) + if err != nil { + return nil, err + } + + // In fact we soft-delete the backend instead + if err := s.uc.SoftDelete(ctx, currentOrg.ID, req.Id); err != nil { + if biz.IsNotFound(err) { + return nil, errors.NotFound("CAS backend not found", err.Error()) + } + + return nil, sl.LogAndMaskErr(err, s.log) + } + + return &pb.CASBackendServiceDeleteResponse{}, nil +} + func bizOCASBackendToPb(in *biz.CASBackend) *pb.CASBackendItem { r := &pb.CASBackendItem{ Id: in.ID.String(), Location: in.Location, Description: in.Description,