diff --git a/app/controlplane/cmd/main.go b/app/controlplane/cmd/main.go index baebbba5e..80e22b2be 100644 --- a/app/controlplane/cmd/main.go +++ b/app/controlplane/cmd/main.go @@ -64,7 +64,7 @@ func init() { func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server, ms *server.HTTPMetricsServer, profilerSvc *server.HTTPProfilerServer, expirer *biz.WorkflowRunExpirerUseCase, plugins sdk.AvailablePlugins, tokenSync *biz.APITokenSyncerUseCase, - userAccessSyncer *biz.UserAccessSyncerUseCase, cfg *conf.Bootstrap) *app { + userAccessSyncer *biz.UserAccessSyncerUseCase, casBackendChecker *biz.CASBackendChecker, cfg *conf.Bootstrap) *app { servers := []transport.Server{gs, hs, ms} if cfg.EnableProfiler { servers = append(servers, profilerSvc) @@ -78,7 +78,7 @@ func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server, ms *server.HTTP kratos.Metadata(map[string]string{}), kratos.Logger(logger), kratos.Server(servers...), - ), expirer, plugins, tokenSync, userAccessSyncer} + ), expirer, plugins, tokenSync, userAccessSyncer, casBackendChecker} } func main() { @@ -167,6 +167,12 @@ func main() { } }() + // Start the background CAS Backend checker + // TODO: Make it configurable from the application config + if app.casBackendChecker != nil { + go app.casBackendChecker.Start(ctx, &biz.CASBackendCheckerOpts{CheckInterval: 30 * time.Minute, OnlyDefaults: true}) + } + // start and wait for stop signal if err := app.Run(); err != nil { panic(err) @@ -180,6 +186,8 @@ type app struct { availablePlugins sdk.AvailablePlugins tokenAuthSyncer *biz.APITokenSyncerUseCase userAccessSyncer *biz.UserAccessSyncerUseCase + // Background checker for CAS backends + casBackendChecker *biz.CASBackendChecker } // Connection to nats is optional, if not configured, pubsub will be disabled diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index b70e4edb8..15cb1d5c7 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -45,11 +45,6 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l providers := loader.LoadProviders(readerWriter) bootstrap_CASServer := bootstrap.CasServer casServerDefaultOpts := newCASServerOptions(bootstrap_CASServer) - casBackendUseCase, err := biz.NewCASBackendUseCase(casBackendRepo, readerWriter, providers, casServerDefaultOpts, logger) - if err != nil { - cleanup() - return nil, nil, err - } bootstrap_NatsServer := bootstrap.NatsServer conn, err := newNatsConnection(bootstrap_NatsServer) if err != nil { @@ -62,6 +57,11 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l return nil, nil, err } auditorUseCase := biz.NewAuditorUseCase(auditLogPublisher, logger) + casBackendUseCase, err := biz.NewCASBackendUseCase(casBackendRepo, readerWriter, providers, casServerDefaultOpts, auditorUseCase, logger) + if err != nil { + cleanup() + return nil, nil, err + } integrationRepo := data.NewIntegrationRepo(dataData, logger) integrationAttachmentRepo := data.NewIntegrationAttachmentRepo(dataData, logger) workflowRepo := data.NewWorkflowRepo(dataData, logger) @@ -302,7 +302,8 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l } workflowRunExpirerUseCase := biz.NewWorkflowRunExpirerUseCase(workflowRunRepo, prometheusUseCase, logger) apiTokenSyncerUseCase := biz.NewAPITokenSyncerUseCase(apiTokenUseCase) - mainApp := newApp(logger, grpcServer, httpServer, httpMetricsServer, httpProfilerServer, workflowRunExpirerUseCase, availablePlugins, apiTokenSyncerUseCase, userAccessSyncerUseCase, bootstrap) + casBackendChecker := biz.NewCASBackendChecker(logger, casBackendRepo, casBackendUseCase) + mainApp := newApp(logger, grpcServer, httpServer, httpMetricsServer, httpProfilerServer, workflowRunExpirerUseCase, availablePlugins, apiTokenSyncerUseCase, userAccessSyncerUseCase, casBackendChecker, bootstrap) return mainApp, func() { cleanup() }, nil diff --git a/app/controlplane/pkg/auditor/events/casbackend.go b/app/controlplane/pkg/auditor/events/casbackend.go new file mode 100644 index 000000000..892495e81 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/casbackend.go @@ -0,0 +1,215 @@ +// +// Copyright 2025 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 events + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor" + "github.com/google/uuid" +) + +var ( + _ auditor.LogEntry = (*CASBackendCreated)(nil) + _ auditor.LogEntry = (*CASBackendUpdated)(nil) + _ auditor.LogEntry = (*CASBackendDeleted)(nil) + _ auditor.LogEntry = (*CASBackendPermanentDeleted)(nil) + _ auditor.LogEntry = (*CASBackendStatusChanged)(nil) +) + +const ( + CASBackendType auditor.TargetType = "CASBackend" + CASBackendCreatedActionType string = "CASBackendCreated" + CASBackendUpdatedActionType string = "CASBackendUpdated" + CASBackendDeletedActionType string = "CASBackendSoftDeleted" + CASBackendPermanentDeletedType string = "CASBackendPermanentDeleted" + CASBackendStatusChangedAction string = "CASBackendStatusChanged" +) + +// CASBackendBase contains the common fields for all CAS backend events +type CASBackendBase struct { + CASBackendID *uuid.UUID `json:"cas_backend_id,omitempty"` + CASBackendName string `json:"cas_backend_name,omitempty"` + Provider string `json:"provider,omitempty"` + Location string `json:"location,omitempty"` + Default bool `json:"default,omitempty"` +} + +func (c *CASBackendBase) RequiresActor() bool { + return true +} + +func (c *CASBackendBase) TargetType() auditor.TargetType { + return CASBackendType +} + +func (c *CASBackendBase) TargetID() *uuid.UUID { + return c.CASBackendID +} + +func (c *CASBackendBase) ActionInfo() (json.RawMessage, error) { + if c.CASBackendID == nil || c.CASBackendName == "" { + return nil, errors.New("cas backend id and name are required") + } + + return json.Marshal(&c) +} + +// CASBackendCreated represents the creation of a CAS backend +type CASBackendCreated struct { + *CASBackendBase + CASBackendDescription string `json:"description,omitempty"` +} + +func (c *CASBackendCreated) ActionType() string { + return CASBackendCreatedActionType +} + +func (c *CASBackendCreated) ActionInfo() (json.RawMessage, error) { + if _, err := c.CASBackendBase.ActionInfo(); err != nil { + return nil, err + } + + return json.Marshal(&c) +} + +func (c *CASBackendCreated) Description() string { + defaultStatus := "non-default" + if c.Default { + defaultStatus = "default" + } + return fmt.Sprintf("%s has created CAS backend %s with provider %s (%s)", auditor.GetActorIdentifier(), c.CASBackendName, c.Provider, defaultStatus) +} + +// CASBackendUpdated represents an update to a CAS backend +type CASBackendUpdated struct { + *CASBackendBase + NewDescription *string `json:"new_description,omitempty"` + CredentialsChanged bool `json:"credentials_changed,omitempty"` + PreviousDefault bool `json:"previous_default,omitempty"` +} + +func (c *CASBackendUpdated) ActionType() string { + return CASBackendUpdatedActionType +} + +func (c *CASBackendUpdated) ActionInfo() (json.RawMessage, error) { + if _, err := c.CASBackendBase.ActionInfo(); err != nil { + return nil, err + } + + return json.Marshal(&c) +} + +func (c *CASBackendUpdated) Description() string { + var credentialsInfo string + if c.CredentialsChanged { + // nolint: gosec + credentialsInfo = " and updated credentials" + } + + if c.PreviousDefault != c.Default { + defaultStatus := "default" + if !c.Default { + defaultStatus = "non-default" + } + return fmt.Sprintf("%s has updated CAS backend %s to %s%s", + auditor.GetActorIdentifier(), c.CASBackendName, defaultStatus, credentialsInfo) + } + + return fmt.Sprintf("%s has updated CAS backend %s%s", + auditor.GetActorIdentifier(), c.CASBackendName, credentialsInfo) +} + +// CASBackendDeleted represents the deletion of a CAS backend +type CASBackendDeleted struct { + *CASBackendBase +} + +func (c *CASBackendDeleted) ActionType() string { + return CASBackendDeletedActionType +} + +func (c *CASBackendDeleted) ActionInfo() (json.RawMessage, error) { + if _, err := c.CASBackendBase.ActionInfo(); err != nil { + return nil, err + } + + return json.Marshal(&c) +} + +func (c *CASBackendDeleted) Description() string { + return fmt.Sprintf("%s has deleted CAS backend %s", auditor.GetActorIdentifier(), c.CASBackendName) +} + +// CASBackendPermanentDeleted represents the permanent deletion of a CAS backend +type CASBackendPermanentDeleted struct { + *CASBackendBase +} + +func (c *CASBackendPermanentDeleted) ActionType() string { + return CASBackendPermanentDeletedType +} + +func (c *CASBackendPermanentDeleted) ActionInfo() (json.RawMessage, error) { + if _, err := c.CASBackendBase.ActionInfo(); err != nil { + return nil, err + } + + return json.Marshal(&c) +} + +func (c *CASBackendPermanentDeleted) Description() string { + return fmt.Sprintf("%s has permanently deleted CAS backend %s", auditor.GetActorIdentifier(), c.CASBackendName) +} + +// CASBackendStatusChanged represents a change in the validation status of a CAS backend +type CASBackendStatusChanged struct { + *CASBackendBase + PreviousStatus string `json:"previous_status,omitempty"` + NewStatus string `json:"new_status,omitempty"` + IsRecovery bool `json:"is_recovery,omitempty"` +} + +func (c *CASBackendStatusChanged) ActionType() string { + return CASBackendStatusChangedAction +} + +func (c *CASBackendStatusChanged) ActionInfo() (json.RawMessage, error) { + if _, err := c.CASBackendBase.ActionInfo(); err != nil { + return nil, err + } + + return json.Marshal(&c) +} + +func (c *CASBackendStatusChanged) Description() string { + var statusInfo string + if c.IsRecovery { + statusInfo = " has recovered from invalid state" + } else { + statusInfo = fmt.Sprintf(" status changed from %s to %s", c.PreviousStatus, c.NewStatus) + } + + return fmt.Sprintf("CAS backend %s%s", c.CASBackendName, statusInfo) +} + +func (c *CASBackendStatusChanged) RequiresActor() bool { + // Status changes are system-generated, no actor required + return false +} diff --git a/app/controlplane/pkg/auditor/events/casbackend_test.go b/app/controlplane/pkg/auditor/events/casbackend_test.go new file mode 100644 index 000000000..da5cff63a --- /dev/null +++ b/app/controlplane/pkg/auditor/events/casbackend_test.go @@ -0,0 +1,367 @@ +// +// Copyright 2025 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 events_test + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor/events" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCASBackendEvents(t *testing.T) { + userUUID, err := uuid.Parse("1089bb36-e27b-428b-8009-d015c8737c54") + require.NoError(t, err) + orgUUID, err := uuid.Parse("1089bb36-e27b-428b-8009-d015c8737c54") + require.NoError(t, err) + backendUUID, err := uuid.Parse("3089bb36-e27b-428b-8009-d015c8737c56") + require.NoError(t, err) + + backendName := "test-backend" + backendDescription := "test description" + backendLocation := "test-location" + backendProvider := "OCI" + + tests := []struct { + name string + event auditor.LogEntry + expected string + actor auditor.ActorType + actorID uuid.UUID + }{ + { + name: "CAS Backend created by user", + event: &events.CASBackendCreated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + CASBackendDescription: backendDescription, + }, + expected: "testdata/casbackends/casbackend_created.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend updated by user", + event: &events.CASBackendUpdated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + NewDescription: &backendDescription, + CredentialsChanged: true, + PreviousDefault: false, + }, + expected: "testdata/casbackends/casbackend_updated.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend updated by user without credential change", + event: &events.CASBackendUpdated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: false, + }, + NewDescription: &backendDescription, + CredentialsChanged: false, + PreviousDefault: true, + }, + expected: "testdata/casbackends/casbackend_updated_default_change.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend soft deleted by user", + event: &events.CASBackendDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + }, + expected: "testdata/casbackends/casbackend_soft_deleted.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend permanently deleted by user", + event: &events.CASBackendPermanentDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + }, + expected: "testdata/casbackends/casbackend_permanent_deleted.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend status changed with recovery", + event: &events.CASBackendStatusChanged{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + PreviousStatus: "Invalid", + NewStatus: "OK", + IsRecovery: true, + }, + expected: "testdata/casbackends/casbackend_status_recovery.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend status changed without recovery", + event: &events.CASBackendStatusChanged{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + PreviousStatus: "OK", + NewStatus: "Invalid", + IsRecovery: false, + }, + expected: "testdata/casbackends/casbackend_status_change.json", + actor: auditor.ActorTypeUser, + actorID: userUUID, + }, + { + name: "CAS Backend created by system", + event: &events.CASBackendCreated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + CASBackendName: backendName, + Provider: backendProvider, + Location: backendLocation, + Default: true, + }, + CASBackendDescription: backendDescription, + }, + expected: "testdata/casbackends/casbackend_created_by_system.json", + actor: auditor.ActorTypeSystem, + actorID: uuid.Nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := []auditor.GeneratorOption{ + auditor.WithOrgID(orgUUID), + } + + if tt.actor == auditor.ActorTypeUser { + opts = append(opts, auditor.WithActor(auditor.ActorTypeUser, tt.actorID, testEmail, testName)) + } else { + opts = append(opts, auditor.WithActor(auditor.ActorTypeSystem, uuid.Nil, "", testAPITokenName)) + } + + eventPayload, err := auditor.GenerateAuditEvent(tt.event, opts...) + require.NoError(t, err) + + want, err := json.MarshalIndent(eventPayload.Data, "", " ") + require.NoError(t, err) + + if updateGolden { + err := os.MkdirAll(filepath.Dir(tt.expected), 0755) + require.NoError(t, err) + err = os.WriteFile(filepath.Clean(tt.expected), want, 0600) + require.NoError(t, err) + } + + gotRaw, err := os.ReadFile(filepath.Clean(tt.expected)) + require.NoError(t, err) + + var gotPayload auditor.AuditEventPayload + err = json.Unmarshal(gotRaw, &gotPayload) + require.NoError(t, err) + got, err := json.MarshalIndent(gotPayload, "", " ") + require.NoError(t, err) + + assert.Equal(t, string(want), string(got)) + }) + } +} + +// TestCASBackendEventsFailed tests the behavior of CAS backend events when they are expected to fail +func TestCASBackendEventsFailed(t *testing.T) { + backendUUID, err := uuid.Parse("3089bb36-e27b-428b-8009-d015c8737c56") + require.NoError(t, err) + backendDescription := "test description" + + tests := []struct { + name string + event auditor.LogEntry + expectedErr string + }{ + { + name: "CAS Backend created with missing ID", + event: &events.CASBackendCreated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendName: "test-backend", + Provider: "OCI", + Location: "test-location", + }, + CASBackendDescription: backendDescription, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend created with missing name", + event: &events.CASBackendCreated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + Provider: "OCI", + Location: "test-location", + }, + CASBackendDescription: backendDescription, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend updated with missing ID", + event: &events.CASBackendUpdated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendName: "test-backend", + Provider: "OCI", + Location: "test-location", + }, + NewDescription: &backendDescription, + CredentialsChanged: true, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend updated with missing name", + event: &events.CASBackendUpdated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + Provider: "OCI", + Location: "test-location", + }, + NewDescription: &backendDescription, + CredentialsChanged: true, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend soft deleted with missing ID", + event: &events.CASBackendDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendName: "test-backend", + Provider: "OCI", + Location: "test-location", + }, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend soft deleted with missing name", + event: &events.CASBackendDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + Provider: "OCI", + Location: "test-location", + }, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend permanently deleted with missing ID", + event: &events.CASBackendPermanentDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendName: "test-backend", + Provider: "OCI", + Location: "test-location", + }, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend permanently deleted with missing name", + event: &events.CASBackendPermanentDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + Provider: "OCI", + Location: "test-location", + }, + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend status changed with missing ID", + event: &events.CASBackendStatusChanged{ + CASBackendBase: &events.CASBackendBase{ + CASBackendName: "test-backend", + Provider: "OCI", + Location: "test-location", + }, + PreviousStatus: "Invalid", + NewStatus: "OK", + }, + expectedErr: "cas backend id and name are required", + }, + { + name: "CAS Backend status changed with missing name", + event: &events.CASBackendStatusChanged{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backendUUID, + Provider: "OCI", + Location: "test-location", + }, + PreviousStatus: "Invalid", + NewStatus: "OK", + }, + expectedErr: "cas backend id and name are required", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := tt.event.ActionInfo() + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr) + }) + } +} diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created.json new file mode 100644 index 000000000..b2e587a43 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created.json @@ -0,0 +1,20 @@ +{ + "ActionType": "CASBackendCreated", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has created CAS backend test-backend with provider OCI (default)", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true, + "description": "test description" + }, + "Digest": "sha256:e35e315f427c27772253c0eef3e455be4d35d4845e7925c7b4620fb6927946a7" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created_by_system.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created_by_system.json new file mode 100644 index 000000000..2812f7d79 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_created_by_system.json @@ -0,0 +1,20 @@ +{ + "ActionType": "CASBackendCreated", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "SYSTEM", + "ActorID": null, + "ActorEmail": "", + "ActorName": "test-token", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "test-token has created CAS backend test-backend with provider OCI (default)", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true, + "description": "test description" + }, + "Digest": "sha256:aa8f32fed1393ad0dcbb06ec7a478e14c6a02fe449233a5068786b69232d571e" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_deleted.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_deleted.json new file mode 100644 index 000000000..276db948c --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_deleted.json @@ -0,0 +1,19 @@ +{ + "ActionType": "CASBackendDeleted", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has deleted CAS backend test-backend", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true + }, + "Digest": "sha256:d7db94c5468222738b9c18b1eac65811bad6bf2701916b3b1a887ed87b783018" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_permanent_deleted.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_permanent_deleted.json new file mode 100644 index 000000000..0e2ff9d72 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_permanent_deleted.json @@ -0,0 +1,19 @@ +{ + "ActionType": "CASBackendPermanentDeleted", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has permanently deleted CAS backend test-backend", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true + }, + "Digest": "sha256:819ef0a1100e731bfb003d20e5b897294e469cb612e095f0b9bb4a1f7f59eb0f" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_soft_deleted.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_soft_deleted.json new file mode 100644 index 000000000..c56a92d4c --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_soft_deleted.json @@ -0,0 +1,19 @@ +{ + "ActionType": "CASBackendSoftDeleted", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has deleted CAS backend test-backend", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true + }, + "Digest": "sha256:1ea71357557c287db2f67687985c681c9cf4b383c726ea209df97a065afb81cd" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_change.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_change.json new file mode 100644 index 000000000..05c393ffe --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_change.json @@ -0,0 +1,21 @@ +{ + "ActionType": "CASBackendStatusChanged", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "CAS backend test-backend status changed from OK to Invalid", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true, + "previous_status": "OK", + "new_status": "Invalid" + }, + "Digest": "sha256:2112ea486d9e36c40a6869efcbf745836742c70dc97a17cffe30ae3651b0579c" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_recovery.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_recovery.json new file mode 100644 index 000000000..1038a443d --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_status_recovery.json @@ -0,0 +1,22 @@ +{ + "ActionType": "CASBackendStatusChanged", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "CAS backend test-backend has recovered from invalid state", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true, + "previous_status": "Invalid", + "new_status": "OK", + "is_recovery": true + }, + "Digest": "sha256:0838b2fbfbe4348ee5406efcd275c513107713d4843706fd8dfa5f724638178e" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated.json new file mode 100644 index 000000000..1966ef324 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated.json @@ -0,0 +1,21 @@ +{ + "ActionType": "CASBackendUpdated", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has updated CAS backend test-backend to default and updated credentials", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "default": true, + "new_description": "test description", + "credentials_changed": true + }, + "Digest": "sha256:2c738aacd42a4257f9496c270586c1edf0be13d0d10160e8c564a96fedf0a726" +} \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated_default_change.json b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated_default_change.json new file mode 100644 index 000000000..6a0b54799 --- /dev/null +++ b/app/controlplane/pkg/auditor/events/testdata/casbackends/casbackend_updated_default_change.json @@ -0,0 +1,20 @@ +{ + "ActionType": "CASBackendUpdated", + "TargetType": "CASBackend", + "TargetID": "3089bb36-e27b-428b-8009-d015c8737c56", + "ActorType": "USER", + "ActorID": "1089bb36-e27b-428b-8009-d015c8737c54", + "ActorEmail": "john@cyberdyne.io", + "ActorName": "John Connor", + "OrgID": "1089bb36-e27b-428b-8009-d015c8737c54", + "Description": "John Connor has updated CAS backend test-backend to non-default", + "Info": { + "cas_backend_id": "3089bb36-e27b-428b-8009-d015c8737c56", + "cas_backend_name": "test-backend", + "provider": "OCI", + "location": "test-location", + "new_description": "test description", + "previous_default": true + }, + "Digest": "sha256:c229b657b7f984a60a2e7c5b6eab1137f026e50eecd76bc718f6548c006d625f" +} \ No newline at end of file diff --git a/app/controlplane/pkg/biz/.mockery.yml b/app/controlplane/pkg/biz/.mockery.yml index 5bef01cbc..103d2e709 100644 --- a/app/controlplane/pkg/biz/.mockery.yml +++ b/app/controlplane/pkg/biz/.mockery.yml @@ -15,3 +15,4 @@ packages: github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz: interfaces: APITokenRepo: + CASBackendRepo: diff --git a/app/controlplane/pkg/biz/biz.go b/app/controlplane/pkg/biz/biz.go index 355478de5..ee83195e8 100644 --- a/app/controlplane/pkg/biz/biz.go +++ b/app/controlplane/pkg/biz/biz.go @@ -57,6 +57,7 @@ var ProviderSet = wire.NewSet( NewAuditorUseCase, NewUserAccessSyncerUseCase, NewGroupUseCase, + NewCASBackendChecker, wire.Bind(new(PromObservable), new(*PrometheusUseCase)), wire.Struct(new(NewIntegrationUseCaseOpts), "*"), wire.Struct(new(NewUserUseCaseParams), "*"), diff --git a/app/controlplane/pkg/biz/casbackend.go b/app/controlplane/pkg/biz/casbackend.go index 0189578db..f8a7f15bf 100644 --- a/app/controlplane/pkg/biz/casbackend.go +++ b/app/controlplane/pkg/biz/casbackend.go @@ -24,6 +24,7 @@ import ( "time" "code.cloudfoundry.org/bytefmt" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor/events" backend "github.com/chainloop-dev/chainloop/pkg/blobmanager" "github.com/chainloop-dev/chainloop/pkg/blobmanager/azureblob" "github.com/chainloop-dev/chainloop/pkg/blobmanager/oci" @@ -97,6 +98,9 @@ type CASBackendRepo interface { FindByIDInOrg(ctx context.Context, OrgID, ID uuid.UUID) (*CASBackend, error) FindByNameInOrg(ctx context.Context, OrgID uuid.UUID, name string) (*CASBackend, error) List(ctx context.Context, orgID uuid.UUID) ([]*CASBackend, error) + // ListBackends returns CAS backends across all organizations + // If onlyDefaults is true, only default backends are returned + ListBackends(ctx context.Context, onlyDefaults bool) ([]*CASBackend, error) UpdateValidationStatus(ctx context.Context, ID uuid.UUID, status CASBackendValidationStatus) error Create(context.Context, *CASBackendCreateOpts) (*CASBackend, error) Update(context.Context, *CASBackendUpdateOpts) (*CASBackend, error) @@ -116,6 +120,7 @@ type CASBackendUseCase struct { credsRW credentials.ReaderWriter providers backend.Providers MaxBytesDefault int64 + auditorUC *AuditorUseCase } // CASServerDefaultOpts holds the default options for the CAS server @@ -123,7 +128,7 @@ type CASServerDefaultOpts struct { DefaultEntryMaxSize string } -func NewCASBackendUseCase(repo CASBackendRepo, credsRW credentials.ReaderWriter, providers backend.Providers, c *CASServerDefaultOpts, l log.Logger) (*CASBackendUseCase, error) { +func NewCASBackendUseCase(repo CASBackendRepo, credsRW credentials.ReaderWriter, providers backend.Providers, c *CASServerDefaultOpts, auditorUC *AuditorUseCase, l log.Logger) (*CASBackendUseCase, error) { if l == nil { l = log.NewStdLogger(io.Discard) } @@ -137,7 +142,14 @@ func NewCASBackendUseCase(repo CASBackendRepo, credsRW credentials.ReaderWriter, } } - return &CASBackendUseCase{repo, servicelogger.ScopedHelper(l, "biz/CASBackend"), credsRW, providers, int64(maxBytesDefault)}, nil + return &CASBackendUseCase{ + repo: repo, + logger: servicelogger.ScopedHelper(l, "biz/CASBackend"), + credsRW: credsRW, + providers: providers, + MaxBytesDefault: int64(maxBytesDefault), + auditorUC: auditorUC, + }, nil } func (uc *CASBackendUseCase) List(ctx context.Context, orgID string) ([]*CASBackend, error) { @@ -284,6 +296,20 @@ func (uc *CASBackendUseCase) Create(ctx context.Context, orgID, name, location, return nil, fmt.Errorf("failed to create CAS backend: %w", err) } + // Record CAS backend creation in audit log + if uc.auditorUC != nil { + uc.auditorUC.Dispatch(ctx, &events.CASBackendCreated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backend.ID, + CASBackendName: backend.Name, + Provider: string(backend.Provider), + Location: backend.Location, + Default: backend.Default, + }, + CASBackendDescription: description, + }, &orgUUID) + } + return backend, nil } @@ -332,6 +358,22 @@ func (uc *CASBackendUseCase) Update(ctx context.Context, orgID, id, description } } + // Record CAS backend update in audit log + if uc.auditorUC != nil { + uc.auditorUC.Dispatch(ctx, &events.CASBackendUpdated{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &after.ID, + CASBackendName: after.Name, + Provider: string(after.Provider), + Location: after.Location, + Default: after.Default, + }, + NewDescription: &description, + CredentialsChanged: creds != nil, + PreviousDefault: before.Default, + }, &orgUUID) + } + return after, nil } @@ -414,6 +456,19 @@ func (uc *CASBackendUseCase) SoftDelete(ctx context.Context, orgID, id string) e } } + // Record CAS backend soft deletion in audit log + if uc.auditorUC != nil { + uc.auditorUC.Dispatch(ctx, &events.CASBackendDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backend.ID, + CASBackendName: backend.Name, + Provider: string(backend.Provider), + Location: backend.Location, + Default: backend.Default, + }, + }, &orgUUID) + } + return nil } @@ -442,8 +497,25 @@ func (uc *CASBackendUseCase) Delete(ctx context.Context, id string) error { } } + if delErr := uc.repo.Delete(ctx, backendUUID); delErr != nil { + return delErr + } uc.logger.Infow("msg", "CAS Backend deleted", "ID", id) - return uc.repo.Delete(ctx, backendUUID) + + // Record CAS backend permanent deletion in audit log + if uc.auditorUC != nil { + uc.auditorUC.Dispatch(ctx, &events.CASBackendPermanentDeleted{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backend.ID, + CASBackendName: backend.Name, + Provider: string(backend.Provider), + Location: backend.Location, + Default: backend.Default, + }, + }, &backend.OrganizationID) + } + + return nil } // Implements https://pkg.go.dev/entgo.io/ent/schema/field#EnumValues @@ -487,10 +559,39 @@ func (uc *CASBackendUseCase) PerformValidation(ctx context.Context, id string) ( return } + // Store previous status for audit logging + previousStatus := backend.ValidationStatus + // Update the validation status uc.logger.Infow("msg", "updating validation status", "ID", id, "status", validationStatus) if err := uc.repo.UpdateValidationStatus(ctx, backendUUID, validationStatus); err != nil { uc.logger.Errorw("msg", "updating validation status", "ID", id, "error", err) + return + } + + // Log status change as an audit event if status has changed and auditor is available + if uc.auditorUC != nil && previousStatus != validationStatus { + uc.logger.Debugw("msg", "status changed, dispatching audit event", + "backend", backend.ID, + "previousStatus", previousStatus, + "newStatus", validationStatus) + + // Check if this is a recovery event (going from failed to OK) + isRecovery := previousStatus == CASBackendValidationFailed && validationStatus == CASBackendValidationOK + + // Create and send event for the status change + uc.auditorUC.Dispatch(ctx, &events.CASBackendStatusChanged{ + CASBackendBase: &events.CASBackendBase{ + CASBackendID: &backend.ID, + CASBackendName: backend.Name, + Provider: string(backend.Provider), + Location: backend.Location, + Default: backend.Default, + }, + PreviousStatus: string(previousStatus), + NewStatus: string(validationStatus), + IsRecovery: isRecovery, + }, &backend.OrganizationID) } }() diff --git a/app/controlplane/pkg/biz/casbackend_checker.go b/app/controlplane/pkg/biz/casbackend_checker.go new file mode 100644 index 000000000..bbb3ab0ce --- /dev/null +++ b/app/controlplane/pkg/biz/casbackend_checker.go @@ -0,0 +1,148 @@ +// +// Copyright 2025 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 biz + +import ( + "context" + "fmt" + "time" + + "github.com/go-kratos/kratos/v2/log" +) + +// Default check interval if none is provided +const ( + defaultInterval = 30 * time.Minute + defaultValidationTimeout = 10 * time.Second +) + +type CASBackendChecker struct { + logger *log.Helper + casBackendRepo CASBackendRepo + caseBackendUseCase *CASBackendUseCase + // Validation timeout for each backend check + validationTimeout time.Duration +} + +type CASBackendCheckerOpts struct { + // Whether to check only default backends or all backends + OnlyDefaults bool + // Interval between checks, defaults to 30 minutes + CheckInterval time.Duration + // Timeout for each individual backend validation, defaults to 10 seconds + ValidationTimeout time.Duration +} + +// NewCASBackendChecker creates a new CAS backend checker that will periodically validate +// the status of CAS backends +func NewCASBackendChecker(logger log.Logger, casBackendRepo CASBackendRepo, casBackendUseCase *CASBackendUseCase) *CASBackendChecker { + return &CASBackendChecker{ + logger: log.NewHelper(log.With(logger, "component", "biz/CASBackendChecker")), + casBackendRepo: casBackendRepo, + caseBackendUseCase: casBackendUseCase, + validationTimeout: defaultValidationTimeout, + } +} + +// Start begins the periodic checking of CAS backends +func (c *CASBackendChecker) Start(ctx context.Context, opts *CASBackendCheckerOpts) { + interval := defaultInterval + if opts != nil && opts.CheckInterval > 0 { + interval = opts.CheckInterval + } + + onlyDefaults := true + if opts != nil { + onlyDefaults = opts.OnlyDefaults + } + + // Apply validation timeout from options if provided + if opts != nil && opts.ValidationTimeout > 0 { + c.validationTimeout = opts.ValidationTimeout + } + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + // Run one check immediately + if err := c.CheckAllBackends(ctx, onlyDefaults); err != nil { + c.logger.Errorf("initial CAS backend check failed: %v", err) + } + + c.logger.Infof("CAS backend checker started with interval %s, checking %s, timeout %s", + interval, + conditionalString(onlyDefaults, "only default backends", "all backends"), + c.validationTimeout) + + for { + select { + case <-ctx.Done(): + c.logger.Info("CAS backend checker stopping due to context cancellation") + return + case <-ticker.C: + if err := c.CheckAllBackends(ctx, onlyDefaults); err != nil { + c.logger.Errorf("periodic CAS backend check failed: %v", err) + } + } + } +} + +// CheckAllBackends validates all CAS backends (or just default ones based on configuration) +// using a worker pool for parallel processing with timeouts +func (c *CASBackendChecker) CheckAllBackends(ctx context.Context, onlyDefaults bool) error { + c.logger.Debug("starting CAS backend validation check") + + backends, err := c.casBackendRepo.ListBackends(ctx, onlyDefaults) + if err != nil { + return fmt.Errorf("failed to list CAS backends: %w", err) + } + + c.logger.Debugf("found %d CAS backends to validate using %s timeout per backend", + len(backends), c.validationTimeout) + + if len(backends) == 0 { + return nil + } + + for _, backend := range backends { + // Create a context with timeout for this specific backend validation + timeoutCtx, cancel := context.WithTimeout(ctx, c.validationTimeout) + + c.logger.Debugf("validating CAS backend %s (%s)", backend.ID, backend.Name) + + // Run the validation and log the result + err := c.caseBackendUseCase.PerformValidation(timeoutCtx, backend.ID.String()) + if err != nil { + c.logger.Errorf("failed to validate CAS backend %s: %v", backend.ID, err) + } else { + c.logger.Debugf("successfully validated CAS backend %s", backend.ID) + } + + // Clean up the timeout context + cancel() + } + + c.logger.Debug("all CAS backend validations completed") + return nil +} + +// Helper function to return different strings based on a condition +func conditionalString(condition bool, trueStr, falseStr string) string { + if condition { + return trueStr + } + return falseStr +} diff --git a/app/controlplane/pkg/biz/casbackend_test.go b/app/controlplane/pkg/biz/casbackend_test.go index d223b6125..ccfa97669 100644 --- a/app/controlplane/pkg/biz/casbackend_test.go +++ b/app/controlplane/pkg/biz/casbackend_test.go @@ -21,7 +21,6 @@ import ( "testing" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" - bizMocks "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz/mocks" backends "github.com/chainloop-dev/chainloop/pkg/blobmanager" blobM "github.com/chainloop-dev/chainloop/pkg/blobmanager/mocks" "github.com/chainloop-dev/chainloop/pkg/credentials" @@ -37,7 +36,7 @@ type casBackendTestSuite struct { validUUID uuid.UUID invalidUUID string useCase *biz.CASBackendUseCase - repo *bizMocks.CASBackendRepo + repo *biz.MockCASBackendRepo credsRW *credentialsM.ReaderWriter backendProvider *blobM.Provider } @@ -247,7 +246,7 @@ func (s *casBackendTestSuite) TestNewCASBackendUseCase() { useCase, err := biz.NewCASBackendUseCase(s.repo, s.credsRW, backends.Providers{ "OCI": s.backendProvider, - }, tc.config, nil) + }, tc.config, nil, nil) if tc.expectError { assert.Error(err) @@ -278,14 +277,14 @@ func (s *casBackendTestSuite) resetMock() { func (s *casBackendTestSuite) SetupTest() { s.validUUID = uuid.New() s.invalidUUID = "deadbeef" - s.repo = bizMocks.NewCASBackendRepo(s.T()) + s.repo = biz.NewMockCASBackendRepo(s.T()) s.credsRW = credentialsM.NewReaderWriter(s.T()) s.backendProvider = blobM.NewProvider(s.T()) var err error s.useCase, err = biz.NewCASBackendUseCase(s.repo, s.credsRW, backends.Providers{ "OCI": s.backendProvider, - }, nil, nil, + }, nil, nil, nil, ) s.Require().NoError(err) } diff --git a/app/controlplane/pkg/biz/mocks_test.go b/app/controlplane/pkg/biz/mocks_test.go index 1b01e8403..459f40397 100644 --- a/app/controlplane/pkg/biz/mocks_test.go +++ b/app/controlplane/pkg/biz/mocks_test.go @@ -199,9 +199,83 @@ func (_c *MockAPITokenRepo_FindByID_Call) RunAndReturn(run func(ctx context.Cont return _c } +// FindByIDInOrg provides a mock function for the type MockAPITokenRepo +func (_mock *MockAPITokenRepo) FindByIDInOrg(ctx context.Context, orgID uuid.UUID, id uuid.UUID) (*APIToken, error) { + ret := _mock.Called(ctx, orgID, id) + + if len(ret) == 0 { + panic("no return value specified for FindByIDInOrg") + } + + var r0 *APIToken + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) (*APIToken, error)); ok { + return returnFunc(ctx, orgID, id) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) *APIToken); ok { + r0 = returnFunc(ctx, orgID, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*APIToken) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, uuid.UUID) error); ok { + r1 = returnFunc(ctx, orgID, id) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockAPITokenRepo_FindByIDInOrg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByIDInOrg' +type MockAPITokenRepo_FindByIDInOrg_Call struct { + *mock.Call +} + +// FindByIDInOrg is a helper method to define mock.On call +// - ctx context.Context +// - orgID uuid.UUID +// - id uuid.UUID +func (_e *MockAPITokenRepo_Expecter) FindByIDInOrg(ctx interface{}, orgID interface{}, id interface{}) *MockAPITokenRepo_FindByIDInOrg_Call { + return &MockAPITokenRepo_FindByIDInOrg_Call{Call: _e.mock.On("FindByIDInOrg", ctx, orgID, id)} +} + +func (_c *MockAPITokenRepo_FindByIDInOrg_Call) Run(run func(ctx context.Context, orgID uuid.UUID, id uuid.UUID)) *MockAPITokenRepo_FindByIDInOrg_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockAPITokenRepo_FindByIDInOrg_Call) Return(aPIToken *APIToken, err error) *MockAPITokenRepo_FindByIDInOrg_Call { + _c.Call.Return(aPIToken, err) + return _c +} + +func (_c *MockAPITokenRepo_FindByIDInOrg_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID, id uuid.UUID) (*APIToken, error)) *MockAPITokenRepo_FindByIDInOrg_Call { + _c.Call.Return(run) + return _c +} + // FindByNameInOrg provides a mock function for the type MockAPITokenRepo -func (_mock *MockAPITokenRepo) FindByNameInOrg(ctx context.Context, orgID uuid.UUID, name string, projectID *uuid.UUID) (*APIToken, error) { - ret := _mock.Called(ctx, orgID, name, projectID) +func (_mock *MockAPITokenRepo) FindByNameInOrg(ctx context.Context, orgID uuid.UUID, name string) (*APIToken, error) { + ret := _mock.Called(ctx, orgID, name) if len(ret) == 0 { panic("no return value specified for FindByNameInOrg") @@ -209,18 +283,18 @@ func (_mock *MockAPITokenRepo) FindByNameInOrg(ctx context.Context, orgID uuid.U var r0 *APIToken var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string, *uuid.UUID) (*APIToken, error)); ok { - return returnFunc(ctx, orgID, name, projectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) (*APIToken, error)); ok { + return returnFunc(ctx, orgID, name) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string, *uuid.UUID) *APIToken); ok { - r0 = returnFunc(ctx, orgID, name, projectID) + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) *APIToken); ok { + r0 = returnFunc(ctx, orgID, name) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*APIToken) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, string, *uuid.UUID) error); ok { - r1 = returnFunc(ctx, orgID, name, projectID) + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, orgID, name) } else { r1 = ret.Error(1) } @@ -236,12 +310,11 @@ type MockAPITokenRepo_FindByNameInOrg_Call struct { // - ctx context.Context // - orgID uuid.UUID // - name string -// - projectID *uuid.UUID -func (_e *MockAPITokenRepo_Expecter) FindByNameInOrg(ctx interface{}, orgID interface{}, name interface{}, projectID interface{}) *MockAPITokenRepo_FindByNameInOrg_Call { - return &MockAPITokenRepo_FindByNameInOrg_Call{Call: _e.mock.On("FindByNameInOrg", ctx, orgID, name, projectID)} +func (_e *MockAPITokenRepo_Expecter) FindByNameInOrg(ctx interface{}, orgID interface{}, name interface{}) *MockAPITokenRepo_FindByNameInOrg_Call { + return &MockAPITokenRepo_FindByNameInOrg_Call{Call: _e.mock.On("FindByNameInOrg", ctx, orgID, name)} } -func (_c *MockAPITokenRepo_FindByNameInOrg_Call) Run(run func(ctx context.Context, orgID uuid.UUID, name string, projectID *uuid.UUID)) *MockAPITokenRepo_FindByNameInOrg_Call { +func (_c *MockAPITokenRepo_FindByNameInOrg_Call) Run(run func(ctx context.Context, orgID uuid.UUID, name string)) *MockAPITokenRepo_FindByNameInOrg_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -255,15 +328,10 @@ func (_c *MockAPITokenRepo_FindByNameInOrg_Call) Run(run func(ctx context.Contex if args[2] != nil { arg2 = args[2].(string) } - var arg3 *uuid.UUID - if args[3] != nil { - arg3 = args[3].(*uuid.UUID) - } run( arg0, arg1, arg2, - arg3, ) }) return _c @@ -274,14 +342,14 @@ func (_c *MockAPITokenRepo_FindByNameInOrg_Call) Return(aPIToken *APIToken, err return _c } -func (_c *MockAPITokenRepo_FindByNameInOrg_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID, name string, projectID *uuid.UUID) (*APIToken, error)) *MockAPITokenRepo_FindByNameInOrg_Call { +func (_c *MockAPITokenRepo_FindByNameInOrg_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID, name string) (*APIToken, error)) *MockAPITokenRepo_FindByNameInOrg_Call { _c.Call.Return(run) return _c } // List provides a mock function for the type MockAPITokenRepo -func (_mock *MockAPITokenRepo) List(ctx context.Context, orgID *uuid.UUID, projectID *uuid.UUID, includeRevoked bool, showOnlySystemTokens bool) ([]*APIToken, error) { - ret := _mock.Called(ctx, orgID, projectID, includeRevoked, showOnlySystemTokens) +func (_mock *MockAPITokenRepo) List(ctx context.Context, orgID *uuid.UUID, filters *APITokenListFilters) ([]*APIToken, error) { + ret := _mock.Called(ctx, orgID, filters) if len(ret) == 0 { panic("no return value specified for List") @@ -289,18 +357,18 @@ func (_mock *MockAPITokenRepo) List(ctx context.Context, orgID *uuid.UUID, proje var r0 []*APIToken var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *uuid.UUID, *uuid.UUID, bool, bool) ([]*APIToken, error)); ok { - return returnFunc(ctx, orgID, projectID, includeRevoked, showOnlySystemTokens) + if returnFunc, ok := ret.Get(0).(func(context.Context, *uuid.UUID, *APITokenListFilters) ([]*APIToken, error)); ok { + return returnFunc(ctx, orgID, filters) } - if returnFunc, ok := ret.Get(0).(func(context.Context, *uuid.UUID, *uuid.UUID, bool, bool) []*APIToken); ok { - r0 = returnFunc(ctx, orgID, projectID, includeRevoked, showOnlySystemTokens) + if returnFunc, ok := ret.Get(0).(func(context.Context, *uuid.UUID, *APITokenListFilters) []*APIToken); ok { + r0 = returnFunc(ctx, orgID, filters) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*APIToken) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, *uuid.UUID, *uuid.UUID, bool, bool) error); ok { - r1 = returnFunc(ctx, orgID, projectID, includeRevoked, showOnlySystemTokens) + if returnFunc, ok := ret.Get(1).(func(context.Context, *uuid.UUID, *APITokenListFilters) error); ok { + r1 = returnFunc(ctx, orgID, filters) } else { r1 = ret.Error(1) } @@ -315,14 +383,12 @@ type MockAPITokenRepo_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context // - orgID *uuid.UUID -// - projectID *uuid.UUID -// - includeRevoked bool -// - showOnlySystemTokens bool -func (_e *MockAPITokenRepo_Expecter) List(ctx interface{}, orgID interface{}, projectID interface{}, includeRevoked interface{}, showOnlySystemTokens interface{}) *MockAPITokenRepo_List_Call { - return &MockAPITokenRepo_List_Call{Call: _e.mock.On("List", ctx, orgID, projectID, includeRevoked, showOnlySystemTokens)} +// - filters *APITokenListFilters +func (_e *MockAPITokenRepo_Expecter) List(ctx interface{}, orgID interface{}, filters interface{}) *MockAPITokenRepo_List_Call { + return &MockAPITokenRepo_List_Call{Call: _e.mock.On("List", ctx, orgID, filters)} } -func (_c *MockAPITokenRepo_List_Call) Run(run func(ctx context.Context, orgID *uuid.UUID, projectID *uuid.UUID, includeRevoked bool, showOnlySystemTokens bool)) *MockAPITokenRepo_List_Call { +func (_c *MockAPITokenRepo_List_Call) Run(run func(ctx context.Context, orgID *uuid.UUID, filters *APITokenListFilters)) *MockAPITokenRepo_List_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -332,24 +398,14 @@ func (_c *MockAPITokenRepo_List_Call) Run(run func(ctx context.Context, orgID *u if args[1] != nil { arg1 = args[1].(*uuid.UUID) } - var arg2 *uuid.UUID + var arg2 *APITokenListFilters if args[2] != nil { - arg2 = args[2].(*uuid.UUID) - } - var arg3 bool - if args[3] != nil { - arg3 = args[3].(bool) - } - var arg4 bool - if args[4] != nil { - arg4 = args[4].(bool) + arg2 = args[2].(*APITokenListFilters) } run( arg0, arg1, arg2, - arg3, - arg4, ) }) return _c @@ -360,7 +416,7 @@ func (_c *MockAPITokenRepo_List_Call) Return(aPITokens []*APIToken, err error) * return _c } -func (_c *MockAPITokenRepo_List_Call) RunAndReturn(run func(ctx context.Context, orgID *uuid.UUID, projectID *uuid.UUID, includeRevoked bool, showOnlySystemTokens bool) ([]*APIToken, error)) *MockAPITokenRepo_List_Call { +func (_c *MockAPITokenRepo_List_Call) RunAndReturn(run func(ctx context.Context, orgID *uuid.UUID, filters *APITokenListFilters) ([]*APIToken, error)) *MockAPITokenRepo_List_Call { _c.Call.Return(run) return _c } @@ -553,3 +609,831 @@ func (_c *MockAPITokenRepo_UpdateLastUsedAt_Call) RunAndReturn(run func(ctx cont _c.Call.Return(run) return _c } + +// NewMockCASBackendRepo creates a new instance of MockCASBackendRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockCASBackendRepo(t interface { + mock.TestingT + Cleanup(func()) +}) *MockCASBackendRepo { + mock := &MockCASBackendRepo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockCASBackendRepo is an autogenerated mock type for the CASBackendRepo type +type MockCASBackendRepo struct { + mock.Mock +} + +type MockCASBackendRepo_Expecter struct { + mock *mock.Mock +} + +func (_m *MockCASBackendRepo) EXPECT() *MockCASBackendRepo_Expecter { + return &MockCASBackendRepo_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) Create(context1 context.Context, cASBackendCreateOpts *CASBackendCreateOpts) (*CASBackend, error) { + ret := _mock.Called(context1, cASBackendCreateOpts) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *CASBackendCreateOpts) (*CASBackend, error)); ok { + return returnFunc(context1, cASBackendCreateOpts) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *CASBackendCreateOpts) *CASBackend); ok { + r0 = returnFunc(context1, cASBackendCreateOpts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *CASBackendCreateOpts) error); ok { + r1 = returnFunc(context1, cASBackendCreateOpts) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type MockCASBackendRepo_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - context1 context.Context +// - cASBackendCreateOpts *CASBackendCreateOpts +func (_e *MockCASBackendRepo_Expecter) Create(context1 interface{}, cASBackendCreateOpts interface{}) *MockCASBackendRepo_Create_Call { + return &MockCASBackendRepo_Create_Call{Call: _e.mock.On("Create", context1, cASBackendCreateOpts)} +} + +func (_c *MockCASBackendRepo_Create_Call) Run(run func(context1 context.Context, cASBackendCreateOpts *CASBackendCreateOpts)) *MockCASBackendRepo_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *CASBackendCreateOpts + if args[1] != nil { + arg1 = args[1].(*CASBackendCreateOpts) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_Create_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_Create_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_Create_Call) RunAndReturn(run func(context1 context.Context, cASBackendCreateOpts *CASBackendCreateOpts) (*CASBackend, error)) *MockCASBackendRepo_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) Delete(ctx context.Context, ID uuid.UUID) error { + ret := _mock.Called(ctx, ID) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = returnFunc(ctx, ID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockCASBackendRepo_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type MockCASBackendRepo_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - ID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) Delete(ctx interface{}, ID interface{}) *MockCASBackendRepo_Delete_Call { + return &MockCASBackendRepo_Delete_Call{Call: _e.mock.On("Delete", ctx, ID)} +} + +func (_c *MockCASBackendRepo_Delete_Call) Run(run func(ctx context.Context, ID uuid.UUID)) *MockCASBackendRepo_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_Delete_Call) Return(err error) *MockCASBackendRepo_Delete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockCASBackendRepo_Delete_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID) error) *MockCASBackendRepo_Delete_Call { + _c.Call.Return(run) + return _c +} + +// FindByID provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) FindByID(ctx context.Context, ID uuid.UUID) (*CASBackend, error) { + ret := _mock.Called(ctx, ID) + + if len(ret) == 0 { + panic("no return value specified for FindByID") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*CASBackend, error)); ok { + return returnFunc(ctx, ID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) *CASBackend); ok { + r0 = returnFunc(ctx, ID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, ID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_FindByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByID' +type MockCASBackendRepo_FindByID_Call struct { + *mock.Call +} + +// FindByID is a helper method to define mock.On call +// - ctx context.Context +// - ID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) FindByID(ctx interface{}, ID interface{}) *MockCASBackendRepo_FindByID_Call { + return &MockCASBackendRepo_FindByID_Call{Call: _e.mock.On("FindByID", ctx, ID)} +} + +func (_c *MockCASBackendRepo_FindByID_Call) Run(run func(ctx context.Context, ID uuid.UUID)) *MockCASBackendRepo_FindByID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_FindByID_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_FindByID_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_FindByID_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID) (*CASBackend, error)) *MockCASBackendRepo_FindByID_Call { + _c.Call.Return(run) + return _c +} + +// FindByIDInOrg provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) FindByIDInOrg(ctx context.Context, OrgID uuid.UUID, ID uuid.UUID) (*CASBackend, error) { + ret := _mock.Called(ctx, OrgID, ID) + + if len(ret) == 0 { + panic("no return value specified for FindByIDInOrg") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) (*CASBackend, error)); ok { + return returnFunc(ctx, OrgID, ID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) *CASBackend); ok { + r0 = returnFunc(ctx, OrgID, ID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, uuid.UUID) error); ok { + r1 = returnFunc(ctx, OrgID, ID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_FindByIDInOrg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByIDInOrg' +type MockCASBackendRepo_FindByIDInOrg_Call struct { + *mock.Call +} + +// FindByIDInOrg is a helper method to define mock.On call +// - ctx context.Context +// - OrgID uuid.UUID +// - ID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) FindByIDInOrg(ctx interface{}, OrgID interface{}, ID interface{}) *MockCASBackendRepo_FindByIDInOrg_Call { + return &MockCASBackendRepo_FindByIDInOrg_Call{Call: _e.mock.On("FindByIDInOrg", ctx, OrgID, ID)} +} + +func (_c *MockCASBackendRepo_FindByIDInOrg_Call) Run(run func(ctx context.Context, OrgID uuid.UUID, ID uuid.UUID)) *MockCASBackendRepo_FindByIDInOrg_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_FindByIDInOrg_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_FindByIDInOrg_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_FindByIDInOrg_Call) RunAndReturn(run func(ctx context.Context, OrgID uuid.UUID, ID uuid.UUID) (*CASBackend, error)) *MockCASBackendRepo_FindByIDInOrg_Call { + _c.Call.Return(run) + return _c +} + +// FindByNameInOrg provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) FindByNameInOrg(ctx context.Context, OrgID uuid.UUID, name string) (*CASBackend, error) { + ret := _mock.Called(ctx, OrgID, name) + + if len(ret) == 0 { + panic("no return value specified for FindByNameInOrg") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) (*CASBackend, error)); ok { + return returnFunc(ctx, OrgID, name) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) *CASBackend); ok { + r0 = returnFunc(ctx, OrgID, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, OrgID, name) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_FindByNameInOrg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByNameInOrg' +type MockCASBackendRepo_FindByNameInOrg_Call struct { + *mock.Call +} + +// FindByNameInOrg is a helper method to define mock.On call +// - ctx context.Context +// - OrgID uuid.UUID +// - name string +func (_e *MockCASBackendRepo_Expecter) FindByNameInOrg(ctx interface{}, OrgID interface{}, name interface{}) *MockCASBackendRepo_FindByNameInOrg_Call { + return &MockCASBackendRepo_FindByNameInOrg_Call{Call: _e.mock.On("FindByNameInOrg", ctx, OrgID, name)} +} + +func (_c *MockCASBackendRepo_FindByNameInOrg_Call) Run(run func(ctx context.Context, OrgID uuid.UUID, name string)) *MockCASBackendRepo_FindByNameInOrg_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_FindByNameInOrg_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_FindByNameInOrg_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_FindByNameInOrg_Call) RunAndReturn(run func(ctx context.Context, OrgID uuid.UUID, name string) (*CASBackend, error)) *MockCASBackendRepo_FindByNameInOrg_Call { + _c.Call.Return(run) + return _c +} + +// FindDefaultBackend provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) FindDefaultBackend(ctx context.Context, orgID uuid.UUID) (*CASBackend, error) { + ret := _mock.Called(ctx, orgID) + + if len(ret) == 0 { + panic("no return value specified for FindDefaultBackend") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*CASBackend, error)); ok { + return returnFunc(ctx, orgID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) *CASBackend); ok { + r0 = returnFunc(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, orgID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_FindDefaultBackend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindDefaultBackend' +type MockCASBackendRepo_FindDefaultBackend_Call struct { + *mock.Call +} + +// FindDefaultBackend is a helper method to define mock.On call +// - ctx context.Context +// - orgID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) FindDefaultBackend(ctx interface{}, orgID interface{}) *MockCASBackendRepo_FindDefaultBackend_Call { + return &MockCASBackendRepo_FindDefaultBackend_Call{Call: _e.mock.On("FindDefaultBackend", ctx, orgID)} +} + +func (_c *MockCASBackendRepo_FindDefaultBackend_Call) Run(run func(ctx context.Context, orgID uuid.UUID)) *MockCASBackendRepo_FindDefaultBackend_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_FindDefaultBackend_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_FindDefaultBackend_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_FindDefaultBackend_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID) (*CASBackend, error)) *MockCASBackendRepo_FindDefaultBackend_Call { + _c.Call.Return(run) + return _c +} + +// FindFallbackBackend provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) FindFallbackBackend(ctx context.Context, orgID uuid.UUID) (*CASBackend, error) { + ret := _mock.Called(ctx, orgID) + + if len(ret) == 0 { + panic("no return value specified for FindFallbackBackend") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*CASBackend, error)); ok { + return returnFunc(ctx, orgID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) *CASBackend); ok { + r0 = returnFunc(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, orgID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_FindFallbackBackend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindFallbackBackend' +type MockCASBackendRepo_FindFallbackBackend_Call struct { + *mock.Call +} + +// FindFallbackBackend is a helper method to define mock.On call +// - ctx context.Context +// - orgID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) FindFallbackBackend(ctx interface{}, orgID interface{}) *MockCASBackendRepo_FindFallbackBackend_Call { + return &MockCASBackendRepo_FindFallbackBackend_Call{Call: _e.mock.On("FindFallbackBackend", ctx, orgID)} +} + +func (_c *MockCASBackendRepo_FindFallbackBackend_Call) Run(run func(ctx context.Context, orgID uuid.UUID)) *MockCASBackendRepo_FindFallbackBackend_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_FindFallbackBackend_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_FindFallbackBackend_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_FindFallbackBackend_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID) (*CASBackend, error)) *MockCASBackendRepo_FindFallbackBackend_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) List(ctx context.Context, orgID uuid.UUID) ([]*CASBackend, error) { + ret := _mock.Called(ctx, orgID) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []*CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) ([]*CASBackend, error)); ok { + return returnFunc(ctx, orgID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) []*CASBackend); ok { + r0 = returnFunc(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, orgID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type MockCASBackendRepo_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - orgID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) List(ctx interface{}, orgID interface{}) *MockCASBackendRepo_List_Call { + return &MockCASBackendRepo_List_Call{Call: _e.mock.On("List", ctx, orgID)} +} + +func (_c *MockCASBackendRepo_List_Call) Run(run func(ctx context.Context, orgID uuid.UUID)) *MockCASBackendRepo_List_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_List_Call) Return(cASBackends []*CASBackend, err error) *MockCASBackendRepo_List_Call { + _c.Call.Return(cASBackends, err) + return _c +} + +func (_c *MockCASBackendRepo_List_Call) RunAndReturn(run func(ctx context.Context, orgID uuid.UUID) ([]*CASBackend, error)) *MockCASBackendRepo_List_Call { + _c.Call.Return(run) + return _c +} + +// ListBackends provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) ListBackends(ctx context.Context, onlyDefaults bool) ([]*CASBackend, error) { + ret := _mock.Called(ctx, onlyDefaults) + + if len(ret) == 0 { + panic("no return value specified for ListBackends") + } + + var r0 []*CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, bool) ([]*CASBackend, error)); ok { + return returnFunc(ctx, onlyDefaults) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, bool) []*CASBackend); ok { + r0 = returnFunc(ctx, onlyDefaults) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, bool) error); ok { + r1 = returnFunc(ctx, onlyDefaults) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_ListBackends_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBackends' +type MockCASBackendRepo_ListBackends_Call struct { + *mock.Call +} + +// ListBackends is a helper method to define mock.On call +// - ctx context.Context +// - onlyDefaults bool +func (_e *MockCASBackendRepo_Expecter) ListBackends(ctx interface{}, onlyDefaults interface{}) *MockCASBackendRepo_ListBackends_Call { + return &MockCASBackendRepo_ListBackends_Call{Call: _e.mock.On("ListBackends", ctx, onlyDefaults)} +} + +func (_c *MockCASBackendRepo_ListBackends_Call) Run(run func(ctx context.Context, onlyDefaults bool)) *MockCASBackendRepo_ListBackends_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_ListBackends_Call) Return(cASBackends []*CASBackend, err error) *MockCASBackendRepo_ListBackends_Call { + _c.Call.Return(cASBackends, err) + return _c +} + +func (_c *MockCASBackendRepo_ListBackends_Call) RunAndReturn(run func(ctx context.Context, onlyDefaults bool) ([]*CASBackend, error)) *MockCASBackendRepo_ListBackends_Call { + _c.Call.Return(run) + return _c +} + +// SoftDelete provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) SoftDelete(ctx context.Context, ID uuid.UUID) error { + ret := _mock.Called(ctx, ID) + + if len(ret) == 0 { + panic("no return value specified for SoftDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = returnFunc(ctx, ID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockCASBackendRepo_SoftDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SoftDelete' +type MockCASBackendRepo_SoftDelete_Call struct { + *mock.Call +} + +// SoftDelete is a helper method to define mock.On call +// - ctx context.Context +// - ID uuid.UUID +func (_e *MockCASBackendRepo_Expecter) SoftDelete(ctx interface{}, ID interface{}) *MockCASBackendRepo_SoftDelete_Call { + return &MockCASBackendRepo_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, ID)} +} + +func (_c *MockCASBackendRepo_SoftDelete_Call) Run(run func(ctx context.Context, ID uuid.UUID)) *MockCASBackendRepo_SoftDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_SoftDelete_Call) Return(err error) *MockCASBackendRepo_SoftDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockCASBackendRepo_SoftDelete_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID) error) *MockCASBackendRepo_SoftDelete_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) Update(context1 context.Context, cASBackendUpdateOpts *CASBackendUpdateOpts) (*CASBackend, error) { + ret := _mock.Called(context1, cASBackendUpdateOpts) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 *CASBackend + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *CASBackendUpdateOpts) (*CASBackend, error)); ok { + return returnFunc(context1, cASBackendUpdateOpts) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *CASBackendUpdateOpts) *CASBackend); ok { + r0 = returnFunc(context1, cASBackendUpdateOpts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CASBackend) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *CASBackendUpdateOpts) error); ok { + r1 = returnFunc(context1, cASBackendUpdateOpts) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockCASBackendRepo_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type MockCASBackendRepo_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - context1 context.Context +// - cASBackendUpdateOpts *CASBackendUpdateOpts +func (_e *MockCASBackendRepo_Expecter) Update(context1 interface{}, cASBackendUpdateOpts interface{}) *MockCASBackendRepo_Update_Call { + return &MockCASBackendRepo_Update_Call{Call: _e.mock.On("Update", context1, cASBackendUpdateOpts)} +} + +func (_c *MockCASBackendRepo_Update_Call) Run(run func(context1 context.Context, cASBackendUpdateOpts *CASBackendUpdateOpts)) *MockCASBackendRepo_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *CASBackendUpdateOpts + if args[1] != nil { + arg1 = args[1].(*CASBackendUpdateOpts) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_Update_Call) Return(cASBackend *CASBackend, err error) *MockCASBackendRepo_Update_Call { + _c.Call.Return(cASBackend, err) + return _c +} + +func (_c *MockCASBackendRepo_Update_Call) RunAndReturn(run func(context1 context.Context, cASBackendUpdateOpts *CASBackendUpdateOpts) (*CASBackend, error)) *MockCASBackendRepo_Update_Call { + _c.Call.Return(run) + return _c +} + +// UpdateValidationStatus provides a mock function for the type MockCASBackendRepo +func (_mock *MockCASBackendRepo) UpdateValidationStatus(ctx context.Context, ID uuid.UUID, status CASBackendValidationStatus) error { + ret := _mock.Called(ctx, ID, status) + + if len(ret) == 0 { + panic("no return value specified for UpdateValidationStatus") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, CASBackendValidationStatus) error); ok { + r0 = returnFunc(ctx, ID, status) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockCASBackendRepo_UpdateValidationStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateValidationStatus' +type MockCASBackendRepo_UpdateValidationStatus_Call struct { + *mock.Call +} + +// UpdateValidationStatus is a helper method to define mock.On call +// - ctx context.Context +// - ID uuid.UUID +// - status CASBackendValidationStatus +func (_e *MockCASBackendRepo_Expecter) UpdateValidationStatus(ctx interface{}, ID interface{}, status interface{}) *MockCASBackendRepo_UpdateValidationStatus_Call { + return &MockCASBackendRepo_UpdateValidationStatus_Call{Call: _e.mock.On("UpdateValidationStatus", ctx, ID, status)} +} + +func (_c *MockCASBackendRepo_UpdateValidationStatus_Call) Run(run func(ctx context.Context, ID uuid.UUID, status CASBackendValidationStatus)) *MockCASBackendRepo_UpdateValidationStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + var arg2 CASBackendValidationStatus + if args[2] != nil { + arg2 = args[2].(CASBackendValidationStatus) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockCASBackendRepo_UpdateValidationStatus_Call) Return(err error) *MockCASBackendRepo_UpdateValidationStatus_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockCASBackendRepo_UpdateValidationStatus_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID, status CASBackendValidationStatus) error) *MockCASBackendRepo_UpdateValidationStatus_Call { + _c.Call.Return(run) + return _c +} diff --git a/app/controlplane/pkg/biz/testhelpers/wire_gen.go b/app/controlplane/pkg/biz/testhelpers/wire_gen.go index 4c54d5f7b..591373b16 100644 --- a/app/controlplane/pkg/biz/testhelpers/wire_gen.go +++ b/app/controlplane/pkg/biz/testhelpers/wire_gen.go @@ -43,22 +43,22 @@ func WireTestData(testDatabase *TestDatabase, t *testing.T, logger log.Logger, r casBackendRepo := data.NewCASBackendRepo(dataData, logger) bootstrap_CASServer := NewCASBackendConfig() casServerDefaultOpts := NewCASServerOptions(bootstrap_CASServer) - casBackendUseCase, err := biz.NewCASBackendUseCase(casBackendRepo, readerWriter, providers, casServerDefaultOpts, logger) + conn, err := newNatsConnection() if err != nil { cleanup() return nil, nil, err } - conn, err := newNatsConnection() + auditLogPublisher, err := auditor.NewAuditLogPublisher(conn, logger) if err != nil { cleanup() return nil, nil, err } - auditLogPublisher, err := auditor.NewAuditLogPublisher(conn, logger) + auditorUseCase := biz.NewAuditorUseCase(auditLogPublisher, logger) + casBackendUseCase, err := biz.NewCASBackendUseCase(casBackendRepo, readerWriter, providers, casServerDefaultOpts, auditorUseCase, logger) if err != nil { cleanup() return nil, nil, err } - auditorUseCase := biz.NewAuditorUseCase(auditLogPublisher, logger) integrationRepo := data.NewIntegrationRepo(dataData, logger) integrationAttachmentRepo := data.NewIntegrationAttachmentRepo(dataData, logger) workflowRepo := data.NewWorkflowRepo(dataData, logger) diff --git a/app/controlplane/pkg/data/casbackend.go b/app/controlplane/pkg/data/casbackend.go index 748231e2a..def0ab0c3 100644 --- a/app/controlplane/pkg/data/casbackend.go +++ b/app/controlplane/pkg/data/casbackend.go @@ -232,6 +232,35 @@ func (r *CASBackendRepo) UpdateValidationStatus(ctx context.Context, id uuid.UUI Exec(ctx) } +// ListBackends returns CAS backends across all organizations. Only not inline backends are returned +// If onlyDefaults is true, only default backends are returned +func (r *CASBackendRepo) ListBackends(ctx context.Context, onlyDefaults bool) ([]*biz.CASBackend, error) { + query := r.data.DB.CASBackend.Query(). + WithOrganization(). + Where(casbackend.DeletedAtIsNil(), + casbackend.ProviderNEQ(biz.CASBackendInline), + casbackend.HasOrganizationWith( + organization.DeletedAtIsNil(), + ), + ) + + if onlyDefaults { + query = query.Where(casbackend.Default(true)) + } + + backends, err := query.All(ctx) + if err != nil { + return nil, err + } + + result := make([]*biz.CASBackend, 0, len(backends)) + for _, b := range backends { + result = append(result, entCASBackendToBiz(b)) + } + + return result, nil +} + func entCASBackendToBiz(backend *ent.CASBackend) *biz.CASBackend { if backend == nil { return nil