diff --git a/app/cli/internal/action/referrer_discover.go b/app/cli/internal/action/referrer_discover.go index 392f720fe..22d722f98 100644 --- a/app/cli/internal/action/referrer_discover.go +++ b/app/cli/internal/action/referrer_discover.go @@ -30,6 +30,7 @@ type ReferrerItem struct { Digest string `json:"digest"` Kind string `json:"kind"` Downloadable bool `json:"downloadable"` + Public bool `json:"public"` CreatedAt *time.Time `json:"createdAt"` References []*ReferrerItem `json:"references"` } @@ -58,6 +59,7 @@ func pbReferrerItemToAction(in *pb.ReferrerItem) *ReferrerItem { out := &ReferrerItem{ Digest: in.GetDigest(), Downloadable: in.GetDownloadable(), + Public: in.GetPublic(), Kind: in.GetKind(), CreatedAt: toTimePtr(in.GetCreatedAt().AsTime()), References: make([]*ReferrerItem, 0, len(in.GetReferences())), diff --git a/app/controlplane/api/controlplane/v1/referrer.pb.go b/app/controlplane/api/controlplane/v1/referrer.pb.go index 8cc83511d..0e5faea84 100644 --- a/app/controlplane/api/controlplane/v1/referrer.pb.go +++ b/app/controlplane/api/controlplane/v1/referrer.pb.go @@ -152,9 +152,11 @@ type ReferrerItem struct { // Kind of referrer, i.e CONTAINER_IMAGE, GIT_HEAD, ... Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"` // Whether the referrer is downloadable or not from CAS - Downloadable bool `protobuf:"varint,3,opt,name=downloadable,proto3" json:"downloadable,omitempty"` - References []*ReferrerItem `protobuf:"bytes,4,rep,name=references,proto3" json:"references,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + Downloadable bool `protobuf:"varint,3,opt,name=downloadable,proto3" json:"downloadable,omitempty"` + // Whether the referrer is public since it belongs to a public workflow + Public bool `protobuf:"varint,6,opt,name=public,proto3" json:"public,omitempty"` + References []*ReferrerItem `protobuf:"bytes,4,rep,name=references,proto3" json:"references,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` } func (x *ReferrerItem) Reset() { @@ -210,6 +212,13 @@ func (x *ReferrerItem) GetDownloadable() bool { return false } +func (x *ReferrerItem) GetPublic() bool { + if x != nil { + return x.Public + } + return false +} + func (x *ReferrerItem) GetReferences() []*ReferrerItem { if x != nil { return x.References @@ -246,36 +255,38 @@ var file_controlplane_v1_referrer_proto_rawDesc = []byte{ 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x49, 0x74, - 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xd8, 0x01, 0x0a, 0x0c, 0x52, + 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xf0, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0a, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x32, 0x9d, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, - 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x08, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, - 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x12, 0x3d, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, - 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x14, 0x12, 0x12, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x2f, 0x7b, 0x64, 0x69, - 0x67, 0x65, 0x73, 0x74, 0x7d, 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, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x32, 0x9d, 0x01, + 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x89, 0x01, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x2f, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 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, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x2f, 0x7b, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x7d, 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 ( diff --git a/app/controlplane/api/controlplane/v1/referrer.pb.validate.go b/app/controlplane/api/controlplane/v1/referrer.pb.validate.go index 23a65e8c7..230ddf543 100644 --- a/app/controlplane/api/controlplane/v1/referrer.pb.validate.go +++ b/app/controlplane/api/controlplane/v1/referrer.pb.validate.go @@ -311,6 +311,8 @@ func (m *ReferrerItem) validate(all bool) error { // no validation rules for Downloadable + // no validation rules for Public + for idx, item := range m.GetReferences() { _, _ = idx, item diff --git a/app/controlplane/api/controlplane/v1/referrer.proto b/app/controlplane/api/controlplane/v1/referrer.proto index 3698e3fac..7b1e230f6 100644 --- a/app/controlplane/api/controlplane/v1/referrer.proto +++ b/app/controlplane/api/controlplane/v1/referrer.proto @@ -47,6 +47,8 @@ message ReferrerItem { string kind = 2; // Whether the referrer is downloadable or not from CAS bool downloadable = 3; + // Whether the referrer is public since it belongs to a public workflow + bool public = 6; repeated ReferrerItem references = 4; google.protobuf.Timestamp created_at = 5; } diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts b/app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts index 4edf1ccf0..36a16bea3 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts @@ -26,6 +26,8 @@ export interface ReferrerItem { kind: string; /** Whether the referrer is downloadable or not from CAS */ downloadable: boolean; + /** Whether the referrer is public since it belongs to a public workflow */ + public: boolean; references: ReferrerItem[]; createdAt?: Date; } @@ -164,7 +166,7 @@ export const ReferrerServiceDiscoverResponse = { }; function createBaseReferrerItem(): ReferrerItem { - return { digest: "", kind: "", downloadable: false, references: [], createdAt: undefined }; + return { digest: "", kind: "", downloadable: false, public: false, references: [], createdAt: undefined }; } export const ReferrerItem = { @@ -178,6 +180,9 @@ export const ReferrerItem = { if (message.downloadable === true) { writer.uint32(24).bool(message.downloadable); } + if (message.public === true) { + writer.uint32(48).bool(message.public); + } for (const v of message.references) { ReferrerItem.encode(v!, writer.uint32(34).fork()).ldelim(); } @@ -215,6 +220,13 @@ export const ReferrerItem = { message.downloadable = reader.bool(); continue; + case 6: + if (tag !== 48) { + break; + } + + message.public = reader.bool(); + continue; case 4: if (tag !== 34) { break; @@ -243,6 +255,7 @@ export const ReferrerItem = { digest: isSet(object.digest) ? String(object.digest) : "", kind: isSet(object.kind) ? String(object.kind) : "", downloadable: isSet(object.downloadable) ? Boolean(object.downloadable) : false, + public: isSet(object.public) ? Boolean(object.public) : false, references: Array.isArray(object?.references) ? object.references.map((e: any) => ReferrerItem.fromJSON(e)) : [], createdAt: isSet(object.createdAt) ? fromJsonTimestamp(object.createdAt) : undefined, }; @@ -253,6 +266,7 @@ export const ReferrerItem = { message.digest !== undefined && (obj.digest = message.digest); message.kind !== undefined && (obj.kind = message.kind); message.downloadable !== undefined && (obj.downloadable = message.downloadable); + message.public !== undefined && (obj.public = message.public); if (message.references) { obj.references = message.references.map((e) => e ? ReferrerItem.toJSON(e) : undefined); } else { @@ -271,6 +285,7 @@ export const ReferrerItem = { message.digest = object.digest ?? ""; message.kind = object.kind ?? ""; message.downloadable = object.downloadable ?? false; + message.public = object.public ?? false; message.references = object.references?.map((e) => ReferrerItem.fromPartial(e)) || []; message.createdAt = object.createdAt ?? undefined; return message; diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index dab66af11..3c55b6554 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -64,8 +64,8 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l bootstrap_CASServer := bootstrap.CasServer v := _wireValue casClientUseCase := biz.NewCASClientUseCase(casCredentialsUseCase, bootstrap_CASServer, logger, v...) - referrerRepo := data.NewReferrerRepo(dataData, logger) - referrerUseCase := biz.NewReferrerUseCase(referrerRepo, organizationRepo, membershipRepo, logger) + referrerRepo := data.NewReferrerRepo(dataData, workflowRepo, logger) + referrerUseCase := biz.NewReferrerUseCase(referrerRepo, workflowRepo, membershipRepo, logger) workflowContractRepo := data.NewWorkflowContractRepo(dataData, logger) workflowContractUseCase := biz.NewWorkflowContractUseCase(workflowContractRepo, logger) workflowUseCase := biz.NewWorkflowUsecase(workflowRepo, workflowContractUseCase, logger) diff --git a/app/controlplane/internal/biz/referrer.go b/app/controlplane/internal/biz/referrer.go index 05582d234..eac2fedae 100644 --- a/app/controlplane/internal/biz/referrer.go +++ b/app/controlplane/internal/biz/referrer.go @@ -39,7 +39,9 @@ type Referrer struct { Kind string // Wether the item is downloadable from CAS or not Downloadable bool - References []*Referrer + // If this referrer is part of a public workflow + InPublicWorkflow bool + References []*Referrer } // Actual referrer stored in the DB which includes a nested list of storedReferences @@ -48,45 +50,67 @@ type StoredReferrer struct { ID uuid.UUID CreatedAt *time.Time // Fully expanded list of 1-level off references - References []*StoredReferrer - OrgIDs []uuid.UUID + References []*StoredReferrer + OrgIDs, WorkflowIDs []uuid.UUID } type ReferrerRepo interface { - Save(ctx context.Context, input []*Referrer, orgID uuid.UUID) error + Save(ctx context.Context, input []*Referrer, workflowID uuid.UUID) error // GetFromRoot returns the referrer identified by the provided content digest, including its first-level references // For example if sha:deadbeef represents an attestation, the result will contain the attestation + materials associated to it // OrgIDs represent an allowList of organizations where the referrers should be looked for - GetFromRoot(ctx context.Context, digest, kind string, orgIDS []uuid.UUID) (*StoredReferrer, error) + GetFromRoot(ctx context.Context, digest string, orgIDS []uuid.UUID, filters ...GetFromRootFilter) (*StoredReferrer, error) +} + +type GetFromRootFilters struct { + // RootKind is the kind of the root referrer, i.e ATTESTATION + RootKind *string + // Wether to filter by visibility or not + Public *bool +} + +type GetFromRootFilter func(*GetFromRootFilters) + +func WithKind(kind string) func(*GetFromRootFilters) { + return func(o *GetFromRootFilters) { + o.RootKind = &kind + } +} + +func WithPublicVisibility(public bool) func(*GetFromRootFilters) { + return func(o *GetFromRootFilters) { + o.Public = &public + } } type ReferrerUseCase struct { repo ReferrerRepo - orgRepo OrganizationRepo membershipRepo MembershipRepo + workflowRepo WorkflowRepo logger *log.Helper } -func NewReferrerUseCase(repo ReferrerRepo, orgRepo OrganizationRepo, mRepo MembershipRepo, l log.Logger) *ReferrerUseCase { +func NewReferrerUseCase(repo ReferrerRepo, wfRepo WorkflowRepo, mRepo MembershipRepo, l log.Logger) *ReferrerUseCase { if l == nil { l = log.NewStdLogger(io.Discard) } - return &ReferrerUseCase{repo, orgRepo, mRepo, servicelogger.ScopedHelper(l, "biz/Referrer")} + return &ReferrerUseCase{repo, mRepo, wfRepo, servicelogger.ScopedHelper(l, "biz/Referrer")} } // ExtractAndPersist extracts the referrers (subject + materials) from the given attestation // and store it as part of the referrers index table -func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envelope, orgID string) error { - orgUUID, err := uuid.Parse(orgID) +func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envelope, workflowID string) error { + workflowUUID, err := uuid.Parse(workflowID) if err != nil { return NewErrInvalidUUID(err) } - if org, err := s.orgRepo.FindByID(ctx, orgUUID); err != nil { - return fmt.Errorf("finding organization: %w", err) - } else if org == nil { - return NewErrNotFound("organization") + // Check that the workflow belongs to the organization + if wf, err := s.workflowRepo.FindByID(ctx, workflowUUID); err != nil { + return fmt.Errorf("finding workflow: %w", err) + } else if wf == nil { + return NewErrNotFound("workflow") } m, err := extractReferrers(att) @@ -94,7 +118,7 @@ func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envel return fmt.Errorf("extracting referrers: %w", err) } - if err := s.repo.Save(ctx, m, orgUUID); err != nil { + if err := s.repo.Save(ctx, m, workflowUUID); err != nil { return fmt.Errorf("saving referrers: %w", err) } @@ -110,6 +134,10 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, rootKi return nil, NewErrInvalidUUID(err) } + if _, err = cr_v1.NewHash(digest); err != nil { + return nil, NewErrValidation(fmt.Errorf("invalid digest format: %w", err)) + } + // We pass the list of organizationsIDs from where to look for the referrer // For now we just pass the list of organizations the user is member of // in the future we will expand this to publicly available orgs and so on. @@ -123,7 +151,12 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, rootKi orgIDs = append(orgIDs, m.OrganizationID) } - ref, err := s.repo.GetFromRoot(ctx, digest, rootKind, orgIDs) + filters := make([]GetFromRootFilter, 0) + if rootKind != "" { + filters = append(filters, WithKind(rootKind)) + } + + ref, err := s.repo.GetFromRoot(ctx, digest, orgIDs, filters...) if err != nil { if errors.As(err, &ErrAmbiguousReferrer{}) { return nil, NewErrValidation(fmt.Errorf("please provide the referrer kind: %w", err)) diff --git a/app/controlplane/internal/biz/referrer_integration_test.go b/app/controlplane/internal/biz/referrer_integration_test.go index 65063bd68..57207864b 100644 --- a/app/controlplane/internal/biz/referrer_integration_test.go +++ b/app/controlplane/internal/biz/referrer_integration_test.go @@ -77,21 +77,21 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { } ctx := context.Background() - s.T().Run("creation fails if the org doesn't exist", func(t *testing.T) { + s.T().Run("creation fails if the workflow doesn't exist", func(t *testing.T) { err := s.Referrer.ExtractAndPersist(ctx, envelope, uuid.NewString()) s.True(biz.IsNotFound(err)) }) var prevStoredRef *biz.StoredReferrer s.T().Run("it can store properly the first time", func(t *testing.T) { - err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID) + err := s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow1.ID.String()) s.NoError(err) prevStoredRef, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) s.NoError(err) }) s.T().Run("and it's idempotent", func(t *testing.T) { - err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID) + err := s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow1.ID.String()) s.NoError(err) ref, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) s.NoError(err) @@ -115,6 +115,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { s.Equal(want, gotR.Referrer) } s.Equal([]uuid.UUID{s.org1UUID}, got.OrgIDs) + s.Equal([]uuid.UUID{s.workflow1.ID}, got.WorkflowIDs) }) s.T().Run("can't be accessed by a second user in another org", func(t *testing.T) { @@ -124,8 +125,8 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { s.Nil(got) }) - s.T().Run("but another org can be attached", func(t *testing.T) { - err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID) + s.T().Run("but another workflow can be attached", func(t *testing.T) { + err = s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow2.ID.String()) s.NoError(err) got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) s.NoError(err) @@ -134,15 +135,17 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { s.Contains(got.OrgIDs, s.org2UUID) // and it's idempotent (no new orgs added) - err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID) + err = s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow2.ID.String()) s.NoError(err) got, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) s.NoError(err) require.Len(t, got.OrgIDs, 2) + s.Equal([]uuid.UUID{s.org1UUID, s.org2UUID}, got.OrgIDs) + s.Equal([]uuid.UUID{s.workflow1.ID, s.workflow2.ID}, got.WorkflowIDs) }) - s.T().Run("and now user2 has access to it since it has access to org2", func(t *testing.T) { - err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID) + s.T().Run("and now user2 has access to it since it has access to workflow2 in org2", func(t *testing.T) { + err = s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow2.ID.String()) s.NoError(err) got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user2.ID) s.NoError(err) @@ -173,8 +176,15 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { require.Len(t, got.References, 0) }) - s.T().Run("or not to exist", func(t *testing.T) { + s.T().Run("or it's an invalid digest", func(t *testing.T) { got, err := s.Referrer.GetFromRoot(ctx, "sha256:deadbeef", "", s.user.ID) + s.True(biz.IsErrValidation(err)) + s.ErrorContains(err, "invalid digest format") + s.Nil(got) + }) + + s.T().Run("or it does not exist", func(t *testing.T) { + got, err := s.Referrer.GetFromRoot(ctx, "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "", s.user.ID) s.True(biz.IsNotFound(err)) s.Nil(got) }) @@ -184,7 +194,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { require.NoError(s.T(), err) require.NoError(s.T(), json.Unmarshal(attJSON, &envelope)) - err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID) + err := s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow1.ID.String()) s.NoError(err) }) @@ -195,7 +205,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { require.NoError(s.T(), json.Unmarshal(attJSON, &envelope)) // storing will not fail since it's the a different artifact type - err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID) + err := s.Referrer.ExtractAndPersist(ctx, envelope, s.workflow1.ID.String()) s.NoError(err) // but retrieval should fail. In the future we will ask the user to provide the artifact type in these cases of ambiguity @@ -230,13 +240,37 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { s.Equal("ATTESTATION", got.References[1].Kind) s.Equal("sha256:c90ccaab0b2cfda9980836aef407f62d747680ea9793ddc6ad2e2d7ab615933d", got.References[1].Digest) }) + + s.T().Run("if all associated workflows are private, the referrer is private", func(t *testing.T) { + got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) + s.NoError(err) + s.False(got.InPublicWorkflow) + s.Equal([]uuid.UUID{s.workflow1.ID, s.workflow2.ID}, got.WorkflowIDs) + for _, r := range got.References { + s.False(r.InPublicWorkflow) + } + }) + + s.T().Run("the referrer will be public if one associated workflow is public", func(t *testing.T) { + // Make workflow1 public + _, err := s.Workflow.Update(ctx, s.org1.ID, s.workflow1.ID.String(), &biz.WorkflowUpdateOpts{Public: toPtrBool(true)}) + require.NoError(t, err) + + got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID) + s.NoError(err) + s.True(got.InPublicWorkflow) + for _, r := range got.References { + s.True(r.InPublicWorkflow) + } + }) } type referrerIntegrationTestSuite struct { testhelpers.UseCasesEachTestSuite - org1, org2 *biz.Organization - org1UUID, org2UUID uuid.UUID - user, user2 *biz.User + org1, org2 *biz.Organization + workflow1, workflow2 *biz.Workflow + org1UUID, org2UUID uuid.UUID + user, user2 *biz.User } func (s *referrerIntegrationTestSuite) SetupTest() { @@ -254,6 +288,11 @@ func (s *referrerIntegrationTestSuite) SetupTest() { s.org2UUID, err = uuid.Parse(s.org2.ID) require.NoError(s.T(), err) + s.workflow1, err = s.Workflow.Create(ctx, &biz.WorkflowCreateOpts{Name: "wf", Team: "team", OrgID: s.org1.ID}) + require.NoError(s.T(), err) + s.workflow2, err = s.Workflow.Create(ctx, &biz.WorkflowCreateOpts{Name: "wf from org 2", Team: "team", OrgID: s.org2.ID}) + require.NoError(s.T(), err) + // user 1 has access to org 1 and 2 s.user, err = s.User.FindOrCreateByEmail(ctx, "user-1@test.com") require.NoError(s.T(), err) diff --git a/app/controlplane/internal/biz/testhelpers/wire_gen.go b/app/controlplane/internal/biz/testhelpers/wire_gen.go index 4a1fcd7dc..246f77834 100644 --- a/app/controlplane/internal/biz/testhelpers/wire_gen.go +++ b/app/controlplane/internal/biz/testhelpers/wire_gen.go @@ -75,8 +75,8 @@ func WireTestData(testDatabase *TestDatabase, t *testing.T, logger log.Logger, r cleanup() return nil, nil, err } - referrerRepo := data.NewReferrerRepo(dataData, logger) - referrerUseCase := biz.NewReferrerUseCase(referrerRepo, organizationRepo, membershipRepo, logger) + referrerRepo := data.NewReferrerRepo(dataData, workflowRepo, logger) + referrerUseCase := biz.NewReferrerUseCase(referrerRepo, workflowRepo, membershipRepo, logger) testingUseCases := &TestingUseCases{ DB: testDatabase, Data: dataData, diff --git a/app/controlplane/internal/data/ent/client.go b/app/controlplane/internal/data/ent/client.go index 18a140c60..45dd4225d 100644 --- a/app/controlplane/internal/data/ent/client.go +++ b/app/controlplane/internal/data/ent/client.go @@ -1578,6 +1578,22 @@ func (c *ReferrerClient) QueryOrganizations(r *Referrer) *OrganizationQuery { return query } +// QueryWorkflows queries the workflows edge of a Referrer. +func (c *ReferrerClient) QueryWorkflows(r *Referrer) *WorkflowQuery { + query := (&WorkflowClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := r.ID + step := sqlgraph.NewStep( + sqlgraph.From(referrer.Table, referrer.FieldID, id), + sqlgraph.To(workflow.Table, workflow.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, referrer.WorkflowsTable, referrer.WorkflowsPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(r.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *ReferrerClient) Hooks() []Hook { return c.hooks.Referrer @@ -2060,6 +2076,22 @@ func (c *WorkflowClient) QueryIntegrationAttachments(w *Workflow) *IntegrationAt return query } +// QueryReferrers queries the referrers edge of a Workflow. +func (c *WorkflowClient) QueryReferrers(w *Workflow) *ReferrerQuery { + query := (&ReferrerClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := w.ID + step := sqlgraph.NewStep( + sqlgraph.From(workflow.Table, workflow.FieldID, id), + sqlgraph.To(referrer.Table, referrer.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, workflow.ReferrersTable, workflow.ReferrersPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(w.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *WorkflowClient) Hooks() []Hook { return c.hooks.Workflow diff --git a/app/controlplane/internal/data/ent/migrate/migrations/20231114215539.sql b/app/controlplane/internal/data/ent/migrate/migrations/20231114215539.sql new file mode 100644 index 000000000..a7c593752 --- /dev/null +++ b/app/controlplane/internal/data/ent/migrate/migrations/20231114215539.sql @@ -0,0 +1,2 @@ +-- Create "referrer_workflows" table +CREATE TABLE "referrer_workflows" ("referrer_id" uuid NOT NULL, "workflow_id" uuid NOT NULL, PRIMARY KEY ("referrer_id", "workflow_id"), CONSTRAINT "referrer_workflows_referrer_id" FOREIGN KEY ("referrer_id") REFERENCES "referrers" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "referrer_workflows_workflow_id" FOREIGN KEY ("workflow_id") REFERENCES "workflows" ("id") ON UPDATE NO ACTION ON DELETE CASCADE); diff --git a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum index 2382893af..d4a1be410 100644 --- a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:pQXA70URwiXjD6q1YKdew9Xg21EGCWOzRVoQgztCdjA= +h1:3NTOF1/OGPWxCCBP9WFNUhXL1nFJqAlfAqZTDZgR5Yo= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -13,3 +13,4 @@ h1:pQXA70URwiXjD6q1YKdew9Xg21EGCWOzRVoQgztCdjA= 20231107121730.sql h1:3Rd522oNuNVnDwbScdWKo7yxISYlN/JoDi6y6QF417c= 20231108214833.sql h1:qojrntcFArOidQQLDo2qns+zr3Y5BoJpfr2BvjCpRD0= 20231109101843.sql h1:lSRWGmd08/RoQSBWVUNb01f9tLDDS8YwFLcXq8WjDHs= +20231114215539.sql h1:eh74G4oPOP2sEDMgEk8DcDNgZFDyvI2SXYJM75F9mQM= diff --git a/app/controlplane/internal/data/ent/migrate/schema.go b/app/controlplane/internal/data/ent/migrate/schema.go index e01dcbfde..88ac94fd9 100644 --- a/app/controlplane/internal/data/ent/migrate/schema.go +++ b/app/controlplane/internal/data/ent/migrate/schema.go @@ -451,6 +451,31 @@ var ( }, }, } + // ReferrerWorkflowsColumns holds the columns for the "referrer_workflows" table. + ReferrerWorkflowsColumns = []*schema.Column{ + {Name: "referrer_id", Type: field.TypeUUID}, + {Name: "workflow_id", Type: field.TypeUUID}, + } + // ReferrerWorkflowsTable holds the schema information for the "referrer_workflows" table. + ReferrerWorkflowsTable = &schema.Table{ + Name: "referrer_workflows", + Columns: ReferrerWorkflowsColumns, + PrimaryKey: []*schema.Column{ReferrerWorkflowsColumns[0], ReferrerWorkflowsColumns[1]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "referrer_workflows_referrer_id", + Columns: []*schema.Column{ReferrerWorkflowsColumns[0]}, + RefColumns: []*schema.Column{ReferrersColumns[0]}, + OnDelete: schema.Cascade, + }, + { + Symbol: "referrer_workflows_workflow_id", + Columns: []*schema.Column{ReferrerWorkflowsColumns[1]}, + RefColumns: []*schema.Column{WorkflowsColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + } // WorkflowRunCasBackendsColumns holds the columns for the "workflow_run_cas_backends" table. WorkflowRunCasBackendsColumns = []*schema.Column{ {Name: "workflow_run_id", Type: field.TypeUUID}, @@ -494,6 +519,7 @@ var ( WorkflowRunsTable, ReferrerReferencesTable, ReferrerOrganizationsTable, + ReferrerWorkflowsTable, WorkflowRunCasBackendsTable, } ) @@ -522,6 +548,8 @@ func init() { ReferrerReferencesTable.ForeignKeys[1].RefTable = ReferrersTable ReferrerOrganizationsTable.ForeignKeys[0].RefTable = ReferrersTable ReferrerOrganizationsTable.ForeignKeys[1].RefTable = OrganizationsTable + ReferrerWorkflowsTable.ForeignKeys[0].RefTable = ReferrersTable + ReferrerWorkflowsTable.ForeignKeys[1].RefTable = WorkflowsTable WorkflowRunCasBackendsTable.ForeignKeys[0].RefTable = WorkflowRunsTable WorkflowRunCasBackendsTable.ForeignKeys[1].RefTable = CasBackendsTable } diff --git a/app/controlplane/internal/data/ent/mutation.go b/app/controlplane/internal/data/ent/mutation.go index e9e9a2312..a6499d27f 100644 --- a/app/controlplane/internal/data/ent/mutation.go +++ b/app/controlplane/internal/data/ent/mutation.go @@ -5261,6 +5261,9 @@ type ReferrerMutation struct { organizations map[uuid.UUID]struct{} removedorganizations map[uuid.UUID]struct{} clearedorganizations bool + workflows map[uuid.UUID]struct{} + removedworkflows map[uuid.UUID]struct{} + clearedworkflows bool done bool oldValue func(context.Context) (*Referrer, error) predicates []predicate.Referrer @@ -5676,6 +5679,60 @@ func (m *ReferrerMutation) ResetOrganizations() { m.removedorganizations = nil } +// AddWorkflowIDs adds the "workflows" edge to the Workflow entity by ids. +func (m *ReferrerMutation) AddWorkflowIDs(ids ...uuid.UUID) { + if m.workflows == nil { + m.workflows = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.workflows[ids[i]] = struct{}{} + } +} + +// ClearWorkflows clears the "workflows" edge to the Workflow entity. +func (m *ReferrerMutation) ClearWorkflows() { + m.clearedworkflows = true +} + +// WorkflowsCleared reports if the "workflows" edge to the Workflow entity was cleared. +func (m *ReferrerMutation) WorkflowsCleared() bool { + return m.clearedworkflows +} + +// RemoveWorkflowIDs removes the "workflows" edge to the Workflow entity by IDs. +func (m *ReferrerMutation) RemoveWorkflowIDs(ids ...uuid.UUID) { + if m.removedworkflows == nil { + m.removedworkflows = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.workflows, ids[i]) + m.removedworkflows[ids[i]] = struct{}{} + } +} + +// RemovedWorkflows returns the removed IDs of the "workflows" edge to the Workflow entity. +func (m *ReferrerMutation) RemovedWorkflowsIDs() (ids []uuid.UUID) { + for id := range m.removedworkflows { + ids = append(ids, id) + } + return +} + +// WorkflowsIDs returns the "workflows" edge IDs in the mutation. +func (m *ReferrerMutation) WorkflowsIDs() (ids []uuid.UUID) { + for id := range m.workflows { + ids = append(ids, id) + } + return +} + +// ResetWorkflows resets all changes to the "workflows" edge. +func (m *ReferrerMutation) ResetWorkflows() { + m.workflows = nil + m.clearedworkflows = false + m.removedworkflows = nil +} + // Where appends a list predicates to the ReferrerMutation builder. func (m *ReferrerMutation) Where(ps ...predicate.Referrer) { m.predicates = append(m.predicates, ps...) @@ -5860,7 +5917,7 @@ func (m *ReferrerMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *ReferrerMutation) AddedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.referred_by != nil { edges = append(edges, referrer.EdgeReferredBy) } @@ -5870,6 +5927,9 @@ func (m *ReferrerMutation) AddedEdges() []string { if m.organizations != nil { edges = append(edges, referrer.EdgeOrganizations) } + if m.workflows != nil { + edges = append(edges, referrer.EdgeWorkflows) + } return edges } @@ -5895,13 +5955,19 @@ func (m *ReferrerMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case referrer.EdgeWorkflows: + ids := make([]ent.Value, 0, len(m.workflows)) + for id := range m.workflows { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *ReferrerMutation) RemovedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.removedreferred_by != nil { edges = append(edges, referrer.EdgeReferredBy) } @@ -5911,6 +5977,9 @@ func (m *ReferrerMutation) RemovedEdges() []string { if m.removedorganizations != nil { edges = append(edges, referrer.EdgeOrganizations) } + if m.removedworkflows != nil { + edges = append(edges, referrer.EdgeWorkflows) + } return edges } @@ -5936,13 +6005,19 @@ func (m *ReferrerMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case referrer.EdgeWorkflows: + ids := make([]ent.Value, 0, len(m.removedworkflows)) + for id := range m.removedworkflows { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *ReferrerMutation) ClearedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.clearedreferred_by { edges = append(edges, referrer.EdgeReferredBy) } @@ -5952,6 +6027,9 @@ func (m *ReferrerMutation) ClearedEdges() []string { if m.clearedorganizations { edges = append(edges, referrer.EdgeOrganizations) } + if m.clearedworkflows { + edges = append(edges, referrer.EdgeWorkflows) + } return edges } @@ -5965,6 +6043,8 @@ func (m *ReferrerMutation) EdgeCleared(name string) bool { return m.clearedreferences case referrer.EdgeOrganizations: return m.clearedorganizations + case referrer.EdgeWorkflows: + return m.clearedworkflows } return false } @@ -5990,6 +6070,9 @@ func (m *ReferrerMutation) ResetEdge(name string) error { case referrer.EdgeOrganizations: m.ResetOrganizations() return nil + case referrer.EdgeWorkflows: + m.ResetWorkflows() + return nil } return fmt.Errorf("unknown Referrer edge %s", name) } @@ -7115,6 +7198,9 @@ type WorkflowMutation struct { integration_attachments map[uuid.UUID]struct{} removedintegration_attachments map[uuid.UUID]struct{} clearedintegration_attachments bool + referrers map[uuid.UUID]struct{} + removedreferrers map[uuid.UUID]struct{} + clearedreferrers bool done bool oldValue func(context.Context) (*Workflow, error) predicates []predicate.Workflow @@ -7775,6 +7861,60 @@ func (m *WorkflowMutation) ResetIntegrationAttachments() { m.removedintegration_attachments = nil } +// AddReferrerIDs adds the "referrers" edge to the Referrer entity by ids. +func (m *WorkflowMutation) AddReferrerIDs(ids ...uuid.UUID) { + if m.referrers == nil { + m.referrers = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.referrers[ids[i]] = struct{}{} + } +} + +// ClearReferrers clears the "referrers" edge to the Referrer entity. +func (m *WorkflowMutation) ClearReferrers() { + m.clearedreferrers = true +} + +// ReferrersCleared reports if the "referrers" edge to the Referrer entity was cleared. +func (m *WorkflowMutation) ReferrersCleared() bool { + return m.clearedreferrers +} + +// RemoveReferrerIDs removes the "referrers" edge to the Referrer entity by IDs. +func (m *WorkflowMutation) RemoveReferrerIDs(ids ...uuid.UUID) { + if m.removedreferrers == nil { + m.removedreferrers = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.referrers, ids[i]) + m.removedreferrers[ids[i]] = struct{}{} + } +} + +// RemovedReferrers returns the removed IDs of the "referrers" edge to the Referrer entity. +func (m *WorkflowMutation) RemovedReferrersIDs() (ids []uuid.UUID) { + for id := range m.removedreferrers { + ids = append(ids, id) + } + return +} + +// ReferrersIDs returns the "referrers" edge IDs in the mutation. +func (m *WorkflowMutation) ReferrersIDs() (ids []uuid.UUID) { + for id := range m.referrers { + ids = append(ids, id) + } + return +} + +// ResetReferrers resets all changes to the "referrers" edge. +func (m *WorkflowMutation) ResetReferrers() { + m.referrers = nil + m.clearedreferrers = false + m.removedreferrers = nil +} + // Where appends a list predicates to the WorkflowMutation builder. func (m *WorkflowMutation) Where(ps ...predicate.Workflow) { m.predicates = append(m.predicates, ps...) @@ -8046,7 +8186,7 @@ func (m *WorkflowMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *WorkflowMutation) AddedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.robotaccounts != nil { edges = append(edges, workflow.EdgeRobotaccounts) } @@ -8062,6 +8202,9 @@ func (m *WorkflowMutation) AddedEdges() []string { if m.integration_attachments != nil { edges = append(edges, workflow.EdgeIntegrationAttachments) } + if m.referrers != nil { + edges = append(edges, workflow.EdgeReferrers) + } return edges } @@ -8095,13 +8238,19 @@ func (m *WorkflowMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case workflow.EdgeReferrers: + ids := make([]ent.Value, 0, len(m.referrers)) + for id := range m.referrers { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *WorkflowMutation) RemovedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.removedrobotaccounts != nil { edges = append(edges, workflow.EdgeRobotaccounts) } @@ -8111,6 +8260,9 @@ func (m *WorkflowMutation) RemovedEdges() []string { if m.removedintegration_attachments != nil { edges = append(edges, workflow.EdgeIntegrationAttachments) } + if m.removedreferrers != nil { + edges = append(edges, workflow.EdgeReferrers) + } return edges } @@ -8136,13 +8288,19 @@ func (m *WorkflowMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case workflow.EdgeReferrers: + ids := make([]ent.Value, 0, len(m.removedreferrers)) + for id := range m.removedreferrers { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *WorkflowMutation) ClearedEdges() []string { - edges := make([]string, 0, 5) + edges := make([]string, 0, 6) if m.clearedrobotaccounts { edges = append(edges, workflow.EdgeRobotaccounts) } @@ -8158,6 +8316,9 @@ func (m *WorkflowMutation) ClearedEdges() []string { if m.clearedintegration_attachments { edges = append(edges, workflow.EdgeIntegrationAttachments) } + if m.clearedreferrers { + edges = append(edges, workflow.EdgeReferrers) + } return edges } @@ -8175,6 +8336,8 @@ func (m *WorkflowMutation) EdgeCleared(name string) bool { return m.clearedcontract case workflow.EdgeIntegrationAttachments: return m.clearedintegration_attachments + case workflow.EdgeReferrers: + return m.clearedreferrers } return false } @@ -8212,6 +8375,9 @@ func (m *WorkflowMutation) ResetEdge(name string) error { case workflow.EdgeIntegrationAttachments: m.ResetIntegrationAttachments() return nil + case workflow.EdgeReferrers: + m.ResetReferrers() + return nil } return fmt.Errorf("unknown Workflow edge %s", name) } diff --git a/app/controlplane/internal/data/ent/referrer.go b/app/controlplane/internal/data/ent/referrer.go index f03efb8d6..1540e6952 100644 --- a/app/controlplane/internal/data/ent/referrer.go +++ b/app/controlplane/internal/data/ent/referrer.go @@ -40,9 +40,11 @@ type ReferrerEdges struct { References []*Referrer `json:"references,omitempty"` // Organizations holds the value of the organizations edge. Organizations []*Organization `json:"organizations,omitempty"` + // Workflows holds the value of the workflows edge. + Workflows []*Workflow `json:"workflows,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [3]bool + loadedTypes [4]bool } // ReferredByOrErr returns the ReferredBy value or an error if the edge @@ -72,6 +74,15 @@ func (e ReferrerEdges) OrganizationsOrErr() ([]*Organization, error) { return nil, &NotLoadedError{edge: "organizations"} } +// WorkflowsOrErr returns the Workflows value or an error if the edge +// was not loaded in eager-loading. +func (e ReferrerEdges) WorkflowsOrErr() ([]*Workflow, error) { + if e.loadedTypes[3] { + return e.Workflows, nil + } + return nil, &NotLoadedError{edge: "workflows"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Referrer) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -158,6 +169,11 @@ func (r *Referrer) QueryOrganizations() *OrganizationQuery { return NewReferrerClient(r.config).QueryOrganizations(r) } +// QueryWorkflows queries the "workflows" edge of the Referrer entity. +func (r *Referrer) QueryWorkflows() *WorkflowQuery { + return NewReferrerClient(r.config).QueryWorkflows(r) +} + // Update returns a builder for updating this Referrer. // Note that you need to call Referrer.Unwrap() before calling this method if this Referrer // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/app/controlplane/internal/data/ent/referrer/referrer.go b/app/controlplane/internal/data/ent/referrer/referrer.go index 4ebbd9e2a..3991c090a 100644 --- a/app/controlplane/internal/data/ent/referrer/referrer.go +++ b/app/controlplane/internal/data/ent/referrer/referrer.go @@ -29,6 +29,8 @@ const ( EdgeReferences = "references" // EdgeOrganizations holds the string denoting the organizations edge name in mutations. EdgeOrganizations = "organizations" + // EdgeWorkflows holds the string denoting the workflows edge name in mutations. + EdgeWorkflows = "workflows" // Table holds the table name of the referrer in the database. Table = "referrers" // ReferredByTable is the table that holds the referred_by relation/edge. The primary key declared below. @@ -40,6 +42,11 @@ const ( // OrganizationsInverseTable is the table name for the Organization entity. // It exists in this package in order to avoid circular dependency with the "organization" package. OrganizationsInverseTable = "organizations" + // WorkflowsTable is the table that holds the workflows relation/edge. The primary key declared below. + WorkflowsTable = "referrer_workflows" + // WorkflowsInverseTable is the table name for the Workflow entity. + // It exists in this package in order to avoid circular dependency with the "workflow" package. + WorkflowsInverseTable = "workflows" ) // Columns holds all SQL columns for referrer fields. @@ -61,6 +68,9 @@ var ( // OrganizationsPrimaryKey and OrganizationsColumn2 are the table columns denoting the // primary key for the organizations relation (M2M). OrganizationsPrimaryKey = []string{"referrer_id", "organization_id"} + // WorkflowsPrimaryKey and WorkflowsColumn2 are the table columns denoting the + // primary key for the workflows relation (M2M). + WorkflowsPrimaryKey = []string{"referrer_id", "workflow_id"} ) // ValidColumn reports if the column name is valid (part of the table columns). @@ -149,6 +159,20 @@ func ByOrganizations(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newOrganizationsStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByWorkflowsCount orders the results by workflows count. +func ByWorkflowsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newWorkflowsStep(), opts...) + } +} + +// ByWorkflows orders the results by workflows terms. +func ByWorkflows(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newWorkflowsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newReferredByStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -170,3 +194,10 @@ func newOrganizationsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.M2M, false, OrganizationsTable, OrganizationsPrimaryKey...), ) } +func newWorkflowsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(WorkflowsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, WorkflowsTable, WorkflowsPrimaryKey...), + ) +} diff --git a/app/controlplane/internal/data/ent/referrer/where.go b/app/controlplane/internal/data/ent/referrer/where.go index 1ef4ea9f2..aeabce292 100644 --- a/app/controlplane/internal/data/ent/referrer/where.go +++ b/app/controlplane/internal/data/ent/referrer/where.go @@ -325,6 +325,29 @@ func HasOrganizationsWith(preds ...predicate.Organization) predicate.Referrer { }) } +// HasWorkflows applies the HasEdge predicate on the "workflows" edge. +func HasWorkflows() predicate.Referrer { + return predicate.Referrer(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, WorkflowsTable, WorkflowsPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasWorkflowsWith applies the HasEdge predicate on the "workflows" edge with a given conditions (other predicates). +func HasWorkflowsWith(preds ...predicate.Workflow) predicate.Referrer { + return predicate.Referrer(func(s *sql.Selector) { + step := newWorkflowsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Referrer) predicate.Referrer { return predicate.Referrer(func(s *sql.Selector) { diff --git a/app/controlplane/internal/data/ent/referrer_create.go b/app/controlplane/internal/data/ent/referrer_create.go index 374419166..fd52ff838 100644 --- a/app/controlplane/internal/data/ent/referrer_create.go +++ b/app/controlplane/internal/data/ent/referrer_create.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/schema/field" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/google/uuid" ) @@ -113,6 +114,21 @@ func (rc *ReferrerCreate) AddOrganizations(o ...*Organization) *ReferrerCreate { return rc.AddOrganizationIDs(ids...) } +// AddWorkflowIDs adds the "workflows" edge to the Workflow entity by IDs. +func (rc *ReferrerCreate) AddWorkflowIDs(ids ...uuid.UUID) *ReferrerCreate { + rc.mutation.AddWorkflowIDs(ids...) + return rc +} + +// AddWorkflows adds the "workflows" edges to the Workflow entity. +func (rc *ReferrerCreate) AddWorkflows(w ...*Workflow) *ReferrerCreate { + ids := make([]uuid.UUID, len(w)) + for i := range w { + ids[i] = w[i].ID + } + return rc.AddWorkflowIDs(ids...) +} + // Mutation returns the ReferrerMutation object of the builder. func (rc *ReferrerCreate) Mutation() *ReferrerMutation { return rc.mutation @@ -271,6 +287,22 @@ func (rc *ReferrerCreate) createSpec() (*Referrer, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := rc.mutation.WorkflowsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/app/controlplane/internal/data/ent/referrer_query.go b/app/controlplane/internal/data/ent/referrer_query.go index 11a2d49d4..87fdde5f7 100644 --- a/app/controlplane/internal/data/ent/referrer_query.go +++ b/app/controlplane/internal/data/ent/referrer_query.go @@ -14,6 +14,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/predicate" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/google/uuid" ) @@ -27,6 +28,7 @@ type ReferrerQuery struct { withReferredBy *ReferrerQuery withReferences *ReferrerQuery withOrganizations *OrganizationQuery + withWorkflows *WorkflowQuery // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -129,6 +131,28 @@ func (rq *ReferrerQuery) QueryOrganizations() *OrganizationQuery { return query } +// QueryWorkflows chains the current query on the "workflows" edge. +func (rq *ReferrerQuery) QueryWorkflows() *WorkflowQuery { + query := (&WorkflowClient{config: rq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := rq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := rq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(referrer.Table, referrer.FieldID, selector), + sqlgraph.To(workflow.Table, workflow.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, referrer.WorkflowsTable, referrer.WorkflowsPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(rq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Referrer entity from the query. // Returns a *NotFoundError when no Referrer was found. func (rq *ReferrerQuery) First(ctx context.Context) (*Referrer, error) { @@ -324,6 +348,7 @@ func (rq *ReferrerQuery) Clone() *ReferrerQuery { withReferredBy: rq.withReferredBy.Clone(), withReferences: rq.withReferences.Clone(), withOrganizations: rq.withOrganizations.Clone(), + withWorkflows: rq.withWorkflows.Clone(), // clone intermediate query. sql: rq.sql.Clone(), path: rq.path, @@ -363,6 +388,17 @@ func (rq *ReferrerQuery) WithOrganizations(opts ...func(*OrganizationQuery)) *Re return rq } +// WithWorkflows tells the query-builder to eager-load the nodes that are connected to +// the "workflows" edge. The optional arguments are used to configure the query builder of the edge. +func (rq *ReferrerQuery) WithWorkflows(opts ...func(*WorkflowQuery)) *ReferrerQuery { + query := (&WorkflowClient{config: rq.config}).Query() + for _, opt := range opts { + opt(query) + } + rq.withWorkflows = query + return rq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -441,10 +477,11 @@ func (rq *ReferrerQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Ref var ( nodes = []*Referrer{} _spec = rq.querySpec() - loadedTypes = [3]bool{ + loadedTypes = [4]bool{ rq.withReferredBy != nil, rq.withReferences != nil, rq.withOrganizations != nil, + rq.withWorkflows != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -486,6 +523,13 @@ func (rq *ReferrerQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Ref return nil, err } } + if query := rq.withWorkflows; query != nil { + if err := rq.loadWorkflows(ctx, query, nodes, + func(n *Referrer) { n.Edges.Workflows = []*Workflow{} }, + func(n *Referrer, e *Workflow) { n.Edges.Workflows = append(n.Edges.Workflows, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -672,6 +716,67 @@ func (rq *ReferrerQuery) loadOrganizations(ctx context.Context, query *Organizat } return nil } +func (rq *ReferrerQuery) loadWorkflows(ctx context.Context, query *WorkflowQuery, nodes []*Referrer, init func(*Referrer), assign func(*Referrer, *Workflow)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*Referrer) + nids := make(map[uuid.UUID]map[*Referrer]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(referrer.WorkflowsTable) + s.Join(joinT).On(s.C(workflow.FieldID), joinT.C(referrer.WorkflowsPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(referrer.WorkflowsPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(referrer.WorkflowsPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*Referrer]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Workflow](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "workflows" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} func (rq *ReferrerQuery) sqlCount(ctx context.Context) (int, error) { _spec := rq.querySpec() diff --git a/app/controlplane/internal/data/ent/referrer_update.go b/app/controlplane/internal/data/ent/referrer_update.go index 4cce6273b..46f410fe8 100644 --- a/app/controlplane/internal/data/ent/referrer_update.go +++ b/app/controlplane/internal/data/ent/referrer_update.go @@ -13,6 +13,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/predicate" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/google/uuid" ) @@ -59,6 +60,21 @@ func (ru *ReferrerUpdate) AddOrganizations(o ...*Organization) *ReferrerUpdate { return ru.AddOrganizationIDs(ids...) } +// AddWorkflowIDs adds the "workflows" edge to the Workflow entity by IDs. +func (ru *ReferrerUpdate) AddWorkflowIDs(ids ...uuid.UUID) *ReferrerUpdate { + ru.mutation.AddWorkflowIDs(ids...) + return ru +} + +// AddWorkflows adds the "workflows" edges to the Workflow entity. +func (ru *ReferrerUpdate) AddWorkflows(w ...*Workflow) *ReferrerUpdate { + ids := make([]uuid.UUID, len(w)) + for i := range w { + ids[i] = w[i].ID + } + return ru.AddWorkflowIDs(ids...) +} + // Mutation returns the ReferrerMutation object of the builder. func (ru *ReferrerUpdate) Mutation() *ReferrerMutation { return ru.mutation @@ -106,6 +122,27 @@ func (ru *ReferrerUpdate) RemoveOrganizations(o ...*Organization) *ReferrerUpdat return ru.RemoveOrganizationIDs(ids...) } +// ClearWorkflows clears all "workflows" edges to the Workflow entity. +func (ru *ReferrerUpdate) ClearWorkflows() *ReferrerUpdate { + ru.mutation.ClearWorkflows() + return ru +} + +// RemoveWorkflowIDs removes the "workflows" edge to Workflow entities by IDs. +func (ru *ReferrerUpdate) RemoveWorkflowIDs(ids ...uuid.UUID) *ReferrerUpdate { + ru.mutation.RemoveWorkflowIDs(ids...) + return ru +} + +// RemoveWorkflows removes "workflows" edges to Workflow entities. +func (ru *ReferrerUpdate) RemoveWorkflows(w ...*Workflow) *ReferrerUpdate { + ids := make([]uuid.UUID, len(w)) + for i := range w { + ids[i] = w[i].ID + } + return ru.RemoveWorkflowIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (ru *ReferrerUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, ru.sqlSave, ru.mutation, ru.hooks) @@ -232,6 +269,51 @@ func (ru *ReferrerUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if ru.mutation.WorkflowsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ru.mutation.RemovedWorkflowsIDs(); len(nodes) > 0 && !ru.mutation.WorkflowsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ru.mutation.WorkflowsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if n, err = sqlgraph.UpdateNodes(ctx, ru.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{referrer.Label} @@ -282,6 +364,21 @@ func (ruo *ReferrerUpdateOne) AddOrganizations(o ...*Organization) *ReferrerUpda return ruo.AddOrganizationIDs(ids...) } +// AddWorkflowIDs adds the "workflows" edge to the Workflow entity by IDs. +func (ruo *ReferrerUpdateOne) AddWorkflowIDs(ids ...uuid.UUID) *ReferrerUpdateOne { + ruo.mutation.AddWorkflowIDs(ids...) + return ruo +} + +// AddWorkflows adds the "workflows" edges to the Workflow entity. +func (ruo *ReferrerUpdateOne) AddWorkflows(w ...*Workflow) *ReferrerUpdateOne { + ids := make([]uuid.UUID, len(w)) + for i := range w { + ids[i] = w[i].ID + } + return ruo.AddWorkflowIDs(ids...) +} + // Mutation returns the ReferrerMutation object of the builder. func (ruo *ReferrerUpdateOne) Mutation() *ReferrerMutation { return ruo.mutation @@ -329,6 +426,27 @@ func (ruo *ReferrerUpdateOne) RemoveOrganizations(o ...*Organization) *ReferrerU return ruo.RemoveOrganizationIDs(ids...) } +// ClearWorkflows clears all "workflows" edges to the Workflow entity. +func (ruo *ReferrerUpdateOne) ClearWorkflows() *ReferrerUpdateOne { + ruo.mutation.ClearWorkflows() + return ruo +} + +// RemoveWorkflowIDs removes the "workflows" edge to Workflow entities by IDs. +func (ruo *ReferrerUpdateOne) RemoveWorkflowIDs(ids ...uuid.UUID) *ReferrerUpdateOne { + ruo.mutation.RemoveWorkflowIDs(ids...) + return ruo +} + +// RemoveWorkflows removes "workflows" edges to Workflow entities. +func (ruo *ReferrerUpdateOne) RemoveWorkflows(w ...*Workflow) *ReferrerUpdateOne { + ids := make([]uuid.UUID, len(w)) + for i := range w { + ids[i] = w[i].ID + } + return ruo.RemoveWorkflowIDs(ids...) +} + // Where appends a list predicates to the ReferrerUpdate builder. func (ruo *ReferrerUpdateOne) Where(ps ...predicate.Referrer) *ReferrerUpdateOne { ruo.mutation.Where(ps...) @@ -485,6 +603,51 @@ func (ruo *ReferrerUpdateOne) sqlSave(ctx context.Context) (_node *Referrer, err } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if ruo.mutation.WorkflowsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ruo.mutation.RemovedWorkflowsIDs(); len(nodes) > 0 && !ruo.mutation.WorkflowsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ruo.mutation.WorkflowsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: referrer.WorkflowsTable, + Columns: referrer.WorkflowsPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(workflow.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &Referrer{config: ruo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/app/controlplane/internal/data/ent/schema-viz.html b/app/controlplane/internal/data/ent/schema-viz.html index 3f27706a2..cb5ef545a 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\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"fallback\",\"type\":\"bool\"}]},{\"id\":\"CASMapping\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"created_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\":\"OrgInvitation\",\"fields\":[{\"name\":\"receiver_email\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"biz.OrgInvitationStatus\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"sender_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Referrer\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"downloadable\",\"type\":\"bool\"},{\"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\"},{\"name\":\"public\",\"type\":\"bool\"}]},{\"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\"},{\"name\":\"attestation_digest\",\"type\":\"string\"}]}],\"edges\":[{\"from\":\"CASMapping\",\"to\":\"CASBackend\",\"label\":\"cas_backend\"},{\"from\":\"CASMapping\",\"to\":\"WorkflowRun\",\"label\":\"workflow_run\"},{\"from\":\"CASMapping\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"OrgInvitation\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"OrgInvitation\",\"to\":\"User\",\"label\":\"sender\"},{\"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\":\"Referrer\",\"to\":\"Referrer\",\"label\":\"references\"},{\"from\":\"Referrer\",\"to\":\"Organization\",\"label\":\"organizations\"},{\"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\"},{\"name\":\"fallback\",\"type\":\"bool\"}]},{\"id\":\"CASMapping\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"created_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\":\"OrgInvitation\",\"fields\":[{\"name\":\"receiver_email\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"biz.OrgInvitationStatus\"},{\"name\":\"created_at\",\"type\":\"time.Time\"},{\"name\":\"deleted_at\",\"type\":\"time.Time\"},{\"name\":\"organization_id\",\"type\":\"uuid.UUID\"},{\"name\":\"sender_id\",\"type\":\"uuid.UUID\"}]},{\"id\":\"Organization\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"created_at\",\"type\":\"time.Time\"}]},{\"id\":\"Referrer\",\"fields\":[{\"name\":\"digest\",\"type\":\"string\"},{\"name\":\"kind\",\"type\":\"string\"},{\"name\":\"downloadable\",\"type\":\"bool\"},{\"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\"},{\"name\":\"public\",\"type\":\"bool\"}]},{\"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\"},{\"name\":\"attestation_digest\",\"type\":\"string\"}]}],\"edges\":[{\"from\":\"CASMapping\",\"to\":\"CASBackend\",\"label\":\"cas_backend\"},{\"from\":\"CASMapping\",\"to\":\"WorkflowRun\",\"label\":\"workflow_run\"},{\"from\":\"CASMapping\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Integration\",\"label\":\"integration\"},{\"from\":\"IntegrationAttachment\",\"to\":\"Workflow\",\"label\":\"workflow\"},{\"from\":\"OrgInvitation\",\"to\":\"Organization\",\"label\":\"organization\"},{\"from\":\"OrgInvitation\",\"to\":\"User\",\"label\":\"sender\"},{\"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\":\"Referrer\",\"to\":\"Referrer\",\"label\":\"references\"},{\"from\":\"Referrer\",\"to\":\"Organization\",\"label\":\"organizations\"},{\"from\":\"Referrer\",\"to\":\"Workflow\",\"label\":\"workflows\"},{\"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/referrer.go b/app/controlplane/internal/data/ent/schema/referrer.go index b5e9c142f..2e4267c85 100644 --- a/app/controlplane/internal/data/ent/schema/referrer.go +++ b/app/controlplane/internal/data/ent/schema/referrer.go @@ -50,7 +50,9 @@ func (Referrer) Edges() []ent.Edge { // M2M referrer can refer to itself via references edge.To("references", Referrer.Type).From("referred_by").Immutable(), // M2M. referrer can be part of multiple organizations - edge.To("organizations", Organization.Type), + edge.To("organizations", Organization.Type).Annotations(entsql.Annotation{OnDelete: entsql.Cascade}), + // M2M. referrer can be part of multiple workflows + edge.To("workflows", Workflow.Type).Annotations(entsql.Annotation{OnDelete: entsql.Cascade}), } } diff --git a/app/controlplane/internal/data/ent/schema/workflow.go b/app/controlplane/internal/data/ent/schema/workflow.go index 2237e8515..17a03fbc0 100644 --- a/app/controlplane/internal/data/ent/schema/workflow.go +++ b/app/controlplane/internal/data/ent/schema/workflow.go @@ -59,5 +59,8 @@ func (Workflow) Edges() []ent.Edge { edge.To("contract", WorkflowContract.Type).Unique().Required(), edge.From("integration_attachments", IntegrationAttachment.Type). Ref("workflow"), + + // M2M. referrer can be part of multiple workflows + edge.From("referrers", Referrer.Type).Ref("workflows"), } } diff --git a/app/controlplane/internal/data/ent/workflow.go b/app/controlplane/internal/data/ent/workflow.go index 17027a260..88c8e485f 100644 --- a/app/controlplane/internal/data/ent/workflow.go +++ b/app/controlplane/internal/data/ent/workflow.go @@ -54,9 +54,11 @@ type WorkflowEdges struct { Contract *WorkflowContract `json:"contract,omitempty"` // IntegrationAttachments holds the value of the integration_attachments edge. IntegrationAttachments []*IntegrationAttachment `json:"integration_attachments,omitempty"` + // Referrers holds the value of the referrers edge. + Referrers []*Referrer `json:"referrers,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [5]bool + loadedTypes [6]bool } // RobotaccountsOrErr returns the Robotaccounts value or an error if the edge @@ -112,6 +114,15 @@ func (e WorkflowEdges) IntegrationAttachmentsOrErr() ([]*IntegrationAttachment, return nil, &NotLoadedError{edge: "integration_attachments"} } +// ReferrersOrErr returns the Referrers value or an error if the edge +// was not loaded in eager-loading. +func (e WorkflowEdges) ReferrersOrErr() ([]*Referrer, error) { + if e.loadedTypes[5] { + return e.Referrers, nil + } + return nil, &NotLoadedError{edge: "referrers"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Workflow) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -246,6 +257,11 @@ func (w *Workflow) QueryIntegrationAttachments() *IntegrationAttachmentQuery { return NewWorkflowClient(w.config).QueryIntegrationAttachments(w) } +// QueryReferrers queries the "referrers" edge of the Workflow entity. +func (w *Workflow) QueryReferrers() *ReferrerQuery { + return NewWorkflowClient(w.config).QueryReferrers(w) +} + // Update returns a builder for updating this Workflow. // Note that you need to call Workflow.Unwrap() before calling this method if this Workflow // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/app/controlplane/internal/data/ent/workflow/where.go b/app/controlplane/internal/data/ent/workflow/where.go index d6c81175c..13e007d07 100644 --- a/app/controlplane/internal/data/ent/workflow/where.go +++ b/app/controlplane/internal/data/ent/workflow/where.go @@ -561,6 +561,29 @@ func HasIntegrationAttachmentsWith(preds ...predicate.IntegrationAttachment) pre }) } +// HasReferrers applies the HasEdge predicate on the "referrers" edge. +func HasReferrers() predicate.Workflow { + return predicate.Workflow(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ReferrersTable, ReferrersPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasReferrersWith applies the HasEdge predicate on the "referrers" edge with a given conditions (other predicates). +func HasReferrersWith(preds ...predicate.Referrer) predicate.Workflow { + return predicate.Workflow(func(s *sql.Selector) { + step := newReferrersStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Workflow) predicate.Workflow { return predicate.Workflow(func(s *sql.Selector) { diff --git a/app/controlplane/internal/data/ent/workflow/workflow.go b/app/controlplane/internal/data/ent/workflow/workflow.go index 1433cd58a..4fd0aede9 100644 --- a/app/controlplane/internal/data/ent/workflow/workflow.go +++ b/app/controlplane/internal/data/ent/workflow/workflow.go @@ -39,6 +39,8 @@ const ( EdgeContract = "contract" // EdgeIntegrationAttachments holds the string denoting the integration_attachments edge name in mutations. EdgeIntegrationAttachments = "integration_attachments" + // EdgeReferrers holds the string denoting the referrers edge name in mutations. + EdgeReferrers = "referrers" // Table holds the table name of the workflow in the database. Table = "workflows" // RobotaccountsTable is the table that holds the robotaccounts relation/edge. @@ -76,6 +78,11 @@ const ( IntegrationAttachmentsInverseTable = "integration_attachments" // IntegrationAttachmentsColumn is the table column denoting the integration_attachments relation/edge. IntegrationAttachmentsColumn = "integration_attachment_workflow" + // ReferrersTable is the table that holds the referrers relation/edge. The primary key declared below. + ReferrersTable = "referrer_workflows" + // ReferrersInverseTable is the table name for the Referrer entity. + // It exists in this package in order to avoid circular dependency with the "referrer" package. + ReferrersInverseTable = "referrers" ) // Columns holds all SQL columns for workflow fields. @@ -97,6 +104,12 @@ var ForeignKeys = []string{ "workflow_contract", } +var ( + // ReferrersPrimaryKey and ReferrersColumn2 are the table columns denoting the + // primary key for the referrers relation (M2M). + ReferrersPrimaryKey = []string{"referrer_id", "workflow_id"} +) + // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { @@ -221,6 +234,20 @@ func ByIntegrationAttachments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderO sqlgraph.OrderByNeighborTerms(s, newIntegrationAttachmentsStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByReferrersCount orders the results by referrers count. +func ByReferrersCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newReferrersStep(), opts...) + } +} + +// ByReferrers orders the results by referrers terms. +func ByReferrers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newReferrersStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newRobotaccountsStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -256,3 +283,10 @@ func newIntegrationAttachmentsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, true, IntegrationAttachmentsTable, IntegrationAttachmentsColumn), ) } +func newReferrersStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ReferrersInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ReferrersTable, ReferrersPrimaryKey...), + ) +} diff --git a/app/controlplane/internal/data/ent/workflow_create.go b/app/controlplane/internal/data/ent/workflow_create.go index f9627bf29..ac6f76161 100644 --- a/app/controlplane/internal/data/ent/workflow_create.go +++ b/app/controlplane/internal/data/ent/workflow_create.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/schema/field" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/integrationattachment" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/robotaccount" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontract" @@ -197,6 +198,21 @@ func (wc *WorkflowCreate) AddIntegrationAttachments(i ...*IntegrationAttachment) return wc.AddIntegrationAttachmentIDs(ids...) } +// AddReferrerIDs adds the "referrers" edge to the Referrer entity by IDs. +func (wc *WorkflowCreate) AddReferrerIDs(ids ...uuid.UUID) *WorkflowCreate { + wc.mutation.AddReferrerIDs(ids...) + return wc +} + +// AddReferrers adds the "referrers" edges to the Referrer entity. +func (wc *WorkflowCreate) AddReferrers(r ...*Referrer) *WorkflowCreate { + ids := make([]uuid.UUID, len(r)) + for i := range r { + ids[i] = r[i].ID + } + return wc.AddReferrerIDs(ids...) +} + // Mutation returns the WorkflowMutation object of the builder. func (wc *WorkflowCreate) Mutation() *WorkflowMutation { return wc.mutation @@ -415,6 +431,22 @@ func (wc *WorkflowCreate) createSpec() (*Workflow, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := wc.mutation.ReferrersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/app/controlplane/internal/data/ent/workflow_query.go b/app/controlplane/internal/data/ent/workflow_query.go index e1eb966ac..fa4cbf2be 100644 --- a/app/controlplane/internal/data/ent/workflow_query.go +++ b/app/controlplane/internal/data/ent/workflow_query.go @@ -14,6 +14,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/integrationattachment" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/predicate" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/robotaccount" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontract" @@ -33,6 +34,7 @@ type WorkflowQuery struct { withOrganization *OrganizationQuery withContract *WorkflowContractQuery withIntegrationAttachments *IntegrationAttachmentQuery + withReferrers *ReferrerQuery withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector @@ -180,6 +182,28 @@ func (wq *WorkflowQuery) QueryIntegrationAttachments() *IntegrationAttachmentQue return query } +// QueryReferrers chains the current query on the "referrers" edge. +func (wq *WorkflowQuery) QueryReferrers() *ReferrerQuery { + query := (&ReferrerClient{config: wq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := wq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := wq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(workflow.Table, workflow.FieldID, selector), + sqlgraph.To(referrer.Table, referrer.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, workflow.ReferrersTable, workflow.ReferrersPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(wq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Workflow entity from the query. // Returns a *NotFoundError when no Workflow was found. func (wq *WorkflowQuery) First(ctx context.Context) (*Workflow, error) { @@ -377,6 +401,7 @@ func (wq *WorkflowQuery) Clone() *WorkflowQuery { withOrganization: wq.withOrganization.Clone(), withContract: wq.withContract.Clone(), withIntegrationAttachments: wq.withIntegrationAttachments.Clone(), + withReferrers: wq.withReferrers.Clone(), // clone intermediate query. sql: wq.sql.Clone(), path: wq.path, @@ -438,6 +463,17 @@ func (wq *WorkflowQuery) WithIntegrationAttachments(opts ...func(*IntegrationAtt return wq } +// WithReferrers tells the query-builder to eager-load the nodes that are connected to +// the "referrers" edge. The optional arguments are used to configure the query builder of the edge. +func (wq *WorkflowQuery) WithReferrers(opts ...func(*ReferrerQuery)) *WorkflowQuery { + query := (&ReferrerClient{config: wq.config}).Query() + for _, opt := range opts { + opt(query) + } + wq.withReferrers = query + return wq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -517,12 +553,13 @@ func (wq *WorkflowQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Wor nodes = []*Workflow{} withFKs = wq.withFKs _spec = wq.querySpec() - loadedTypes = [5]bool{ + loadedTypes = [6]bool{ wq.withRobotaccounts != nil, wq.withWorkflowruns != nil, wq.withOrganization != nil, wq.withContract != nil, wq.withIntegrationAttachments != nil, + wq.withReferrers != nil, } ) if wq.withOrganization != nil || wq.withContract != nil { @@ -584,6 +621,13 @@ func (wq *WorkflowQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Wor return nil, err } } + if query := wq.withReferrers; query != nil { + if err := wq.loadReferrers(ctx, query, nodes, + func(n *Workflow) { n.Edges.Referrers = []*Referrer{} }, + func(n *Workflow, e *Referrer) { n.Edges.Referrers = append(n.Edges.Referrers, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -744,6 +788,67 @@ func (wq *WorkflowQuery) loadIntegrationAttachments(ctx context.Context, query * } return nil } +func (wq *WorkflowQuery) loadReferrers(ctx context.Context, query *ReferrerQuery, nodes []*Workflow, init func(*Workflow), assign func(*Workflow, *Referrer)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*Workflow) + nids := make(map[uuid.UUID]map[*Workflow]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(workflow.ReferrersTable) + s.Join(joinT).On(s.C(referrer.FieldID), joinT.C(workflow.ReferrersPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(workflow.ReferrersPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(workflow.ReferrersPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*Workflow]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Referrer](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "referrers" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} func (wq *WorkflowQuery) sqlCount(ctx context.Context) (int, error) { _spec := wq.querySpec() diff --git a/app/controlplane/internal/data/ent/workflow_update.go b/app/controlplane/internal/data/ent/workflow_update.go index 8048fdaf1..67f041bc5 100644 --- a/app/controlplane/internal/data/ent/workflow_update.go +++ b/app/controlplane/internal/data/ent/workflow_update.go @@ -14,6 +14,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/integrationattachment" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/predicate" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/robotaccount" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflowcontract" @@ -202,6 +203,21 @@ func (wu *WorkflowUpdate) AddIntegrationAttachments(i ...*IntegrationAttachment) return wu.AddIntegrationAttachmentIDs(ids...) } +// AddReferrerIDs adds the "referrers" edge to the Referrer entity by IDs. +func (wu *WorkflowUpdate) AddReferrerIDs(ids ...uuid.UUID) *WorkflowUpdate { + wu.mutation.AddReferrerIDs(ids...) + return wu +} + +// AddReferrers adds the "referrers" edges to the Referrer entity. +func (wu *WorkflowUpdate) AddReferrers(r ...*Referrer) *WorkflowUpdate { + ids := make([]uuid.UUID, len(r)) + for i := range r { + ids[i] = r[i].ID + } + return wu.AddReferrerIDs(ids...) +} + // Mutation returns the WorkflowMutation object of the builder. func (wu *WorkflowUpdate) Mutation() *WorkflowMutation { return wu.mutation @@ -282,6 +298,27 @@ func (wu *WorkflowUpdate) RemoveIntegrationAttachments(i ...*IntegrationAttachme return wu.RemoveIntegrationAttachmentIDs(ids...) } +// ClearReferrers clears all "referrers" edges to the Referrer entity. +func (wu *WorkflowUpdate) ClearReferrers() *WorkflowUpdate { + wu.mutation.ClearReferrers() + return wu +} + +// RemoveReferrerIDs removes the "referrers" edge to Referrer entities by IDs. +func (wu *WorkflowUpdate) RemoveReferrerIDs(ids ...uuid.UUID) *WorkflowUpdate { + wu.mutation.RemoveReferrerIDs(ids...) + return wu +} + +// RemoveReferrers removes "referrers" edges to Referrer entities. +func (wu *WorkflowUpdate) RemoveReferrers(r ...*Referrer) *WorkflowUpdate { + ids := make([]uuid.UUID, len(r)) + for i := range r { + ids[i] = r[i].ID + } + return wu.RemoveReferrerIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (wu *WorkflowUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, wu.sqlSave, wu.mutation, wu.hooks) @@ -555,6 +592,51 @@ func (wu *WorkflowUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if wu.mutation.ReferrersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := wu.mutation.RemovedReferrersIDs(); len(nodes) > 0 && !wu.mutation.ReferrersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := wu.mutation.ReferrersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if n, err = sqlgraph.UpdateNodes(ctx, wu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{workflow.Label} @@ -743,6 +825,21 @@ func (wuo *WorkflowUpdateOne) AddIntegrationAttachments(i ...*IntegrationAttachm return wuo.AddIntegrationAttachmentIDs(ids...) } +// AddReferrerIDs adds the "referrers" edge to the Referrer entity by IDs. +func (wuo *WorkflowUpdateOne) AddReferrerIDs(ids ...uuid.UUID) *WorkflowUpdateOne { + wuo.mutation.AddReferrerIDs(ids...) + return wuo +} + +// AddReferrers adds the "referrers" edges to the Referrer entity. +func (wuo *WorkflowUpdateOne) AddReferrers(r ...*Referrer) *WorkflowUpdateOne { + ids := make([]uuid.UUID, len(r)) + for i := range r { + ids[i] = r[i].ID + } + return wuo.AddReferrerIDs(ids...) +} + // Mutation returns the WorkflowMutation object of the builder. func (wuo *WorkflowUpdateOne) Mutation() *WorkflowMutation { return wuo.mutation @@ -823,6 +920,27 @@ func (wuo *WorkflowUpdateOne) RemoveIntegrationAttachments(i ...*IntegrationAtta return wuo.RemoveIntegrationAttachmentIDs(ids...) } +// ClearReferrers clears all "referrers" edges to the Referrer entity. +func (wuo *WorkflowUpdateOne) ClearReferrers() *WorkflowUpdateOne { + wuo.mutation.ClearReferrers() + return wuo +} + +// RemoveReferrerIDs removes the "referrers" edge to Referrer entities by IDs. +func (wuo *WorkflowUpdateOne) RemoveReferrerIDs(ids ...uuid.UUID) *WorkflowUpdateOne { + wuo.mutation.RemoveReferrerIDs(ids...) + return wuo +} + +// RemoveReferrers removes "referrers" edges to Referrer entities. +func (wuo *WorkflowUpdateOne) RemoveReferrers(r ...*Referrer) *WorkflowUpdateOne { + ids := make([]uuid.UUID, len(r)) + for i := range r { + ids[i] = r[i].ID + } + return wuo.RemoveReferrerIDs(ids...) +} + // Where appends a list predicates to the WorkflowUpdate builder. func (wuo *WorkflowUpdateOne) Where(ps ...predicate.Workflow) *WorkflowUpdateOne { wuo.mutation.Where(ps...) @@ -1126,6 +1244,51 @@ func (wuo *WorkflowUpdateOne) sqlSave(ctx context.Context) (_node *Workflow, err } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if wuo.mutation.ReferrersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := wuo.mutation.RemovedReferrersIDs(); len(nodes) > 0 && !wuo.mutation.ReferrersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := wuo.mutation.ReferrersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: workflow.ReferrersTable, + Columns: workflow.ReferrersPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(referrer.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &Workflow{config: wuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/app/controlplane/internal/data/referrer.go b/app/controlplane/internal/data/referrer.go index ec71a6b36..ae4a17ffe 100644 --- a/app/controlplane/internal/data/referrer.go +++ b/app/controlplane/internal/data/referrer.go @@ -23,31 +23,42 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/organization" "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/referrer" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/data/ent/workflow" "github.com/go-kratos/kratos/v2/log" "github.com/google/uuid" ) type ReferrerRepo struct { - data *Data - log *log.Helper + data *Data + log *log.Helper + workflowRepo biz.WorkflowRepo } -func NewReferrerRepo(data *Data, logger log.Logger) biz.ReferrerRepo { +func NewReferrerRepo(data *Data, wfRepo biz.WorkflowRepo, logger log.Logger) biz.ReferrerRepo { return &ReferrerRepo{ - data: data, - log: log.NewHelper(logger), + data: data, + log: log.NewHelper(logger), + workflowRepo: wfRepo, } } type storedReferrerMap map[string]*ent.Referrer -func (r *ReferrerRepo) Save(ctx context.Context, referrers []*biz.Referrer, orgID uuid.UUID) error { +func (r *ReferrerRepo) Save(ctx context.Context, referrers []*biz.Referrer, workflowID uuid.UUID) error { // Start transaction tx, err := r.data.db.Tx(ctx) if err != nil { return fmt.Errorf("failed to create transaction: %w", err) } + // find the workflow + wf, err := r.workflowRepo.FindByID(ctx, workflowID) + if err != nil { + return fmt.Errorf("failed to find workflow: %w", err) + } else if wf == nil { + return biz.NewErrNotFound("workflow") + } + storedMap := make(storedReferrerMap) // 1 - Find or create each referrer for _, r := range referrers { @@ -59,14 +70,20 @@ func (r *ReferrerRepo) Save(ctx context.Context, referrers []*biz.Referrer, orgI } storedRef, err = tx.Referrer.Create(). - SetDigest(r.Digest).SetKind(r.Kind).SetDownloadable(r.Downloadable).AddOrganizationIDs(orgID).Save(ctx) + SetDigest(r.Digest).SetKind(r.Kind).SetDownloadable(r.Downloadable). + AddOrganizationIDs(wf.OrgID). + AddWorkflowIDs(workflowID). + Save(ctx) if err != nil { return fmt.Errorf("failed to create referrer: %w", err) } } - // associate it with the organization - storedRef, err = storedRef.Update().AddOrganizationIDs(orgID).Save(ctx) + // associate it with the possibly new organization and workflow + storedRef, err = storedRef.Update(). + AddOrganizationIDs(wf.OrgID). + AddWorkflowIDs(workflowID). + Save(ctx) if err != nil { return fmt.Errorf("failed to add organization to referrer: %w", err) } @@ -102,7 +119,12 @@ func (r *ReferrerRepo) Save(ctx context.Context, referrers []*biz.Referrer, orgI return nil } -func (r *ReferrerRepo) GetFromRoot(ctx context.Context, digest, rootKind string, orgIDs []uuid.UUID) (*biz.StoredReferrer, error) { +func (r *ReferrerRepo) GetFromRoot(ctx context.Context, digest string, orgIDs []uuid.UUID, filters ...biz.GetFromRootFilter) (*biz.StoredReferrer, error) { + opts := &biz.GetFromRootFilters{} + for _, f := range filters { + f(opts) + } + // Find the referrer from its digest + artifactType // if there is more than 1 item we return ReferrerAmbiguous error query := r.data.db.Referrer.Query(). @@ -110,11 +132,16 @@ func (r *ReferrerRepo) GetFromRoot(ctx context.Context, digest, rootKind string, Where(referrer.HasOrganizationsWith(organization.IDIn(orgIDs...))) // We might be filtering by the rootKind, this will prevent ambiguity - if rootKind != "" { - query = query.Where(referrer.Kind(rootKind)) + if opts.RootKind != nil { + query = query.Where(referrer.Kind(*opts.RootKind)) } - refs, err := query.All(ctx) + // And by visibility + if opts.Public != nil { + query = query.Where(referrer.HasWorkflowsWith(workflow.Public(*opts.Public))) + } + + refs, err := query.WithWorkflows().WithOrganizations().All(ctx) if err != nil { return nil, fmt.Errorf("failed to query referrer: %w", err) } @@ -132,7 +159,7 @@ func (r *ReferrerRepo) GetFromRoot(ctx context.Context, digest, rootKind string, } // Find the referrer recursively starting from the root - res, err := r.doGet(ctx, refs[0], orgIDs, 0) + res, err := r.doGet(ctx, refs[0], orgIDs, opts.Public, 0) if err != nil { return nil, fmt.Errorf("failed to get referrer: %w", err) } @@ -145,23 +172,37 @@ func (r *ReferrerRepo) GetFromRoot(ctx context.Context, digest, rootKind string, // we also need to limit this because there might be cycles const maxTraverseLevels = 1 -func (r *ReferrerRepo) doGet(ctx context.Context, root *ent.Referrer, orgIDs []uuid.UUID, level int) (*biz.StoredReferrer, error) { +func (r *ReferrerRepo) doGet(ctx context.Context, root *ent.Referrer, allowedOrgs []uuid.UUID, public *bool, level int) (*biz.StoredReferrer, error) { + // Find if it has a public workflow associated + // The list of associated workflows and organizations + // that come preloaded in the edges already + isPublic := false + workflowIDs := make([]uuid.UUID, 0, len(root.Edges.Workflows)) + for _, wf := range root.Edges.Workflows { + if wf.Public { + isPublic = true + } + workflowIDs = append(workflowIDs, wf.ID) + } + + // Organization associated to the root referrer + rootOrgsIDs := make([]uuid.UUID, 0, len(root.Edges.Organizations)) + for _, org := range root.Edges.Organizations { + rootOrgsIDs = append(rootOrgsIDs, org.ID) + } + // Assemble the referrer to return res := &biz.StoredReferrer{ ID: root.ID, CreatedAt: toTimePtr(root.CreatedAt), Referrer: &biz.Referrer{ - Digest: root.Digest, - Kind: root.Kind, - Downloadable: root.Downloadable, + Digest: root.Digest, + Kind: root.Kind, + Downloadable: root.Downloadable, + InPublicWorkflow: isPublic, }, - } - - var err error - // with all the organizationIDs attached - res.OrgIDs, err = root.QueryOrganizations().IDs(ctx) - if err != nil { - return nil, fmt.Errorf("failed to query organizations: %w", err) + WorkflowIDs: workflowIDs, + OrgIDs: rootOrgsIDs, } // Next: We'll find the references recursively up to a max of maxTraverseLevels levels @@ -169,10 +210,14 @@ func (r *ReferrerRepo) doGet(ctx context.Context, root *ent.Referrer, orgIDs []u return res, nil } - // Find the references and call recursively - refs, err := root.QueryReferences(). - Where(referrer.HasOrganizationsWith(organization.IDIn(orgIDs...))). - Order(referrer.ByDigest()).All(ctx) + // Find the references and call recursively filtered out by the allowed organizations + // and by the visibility if needed + query := root.QueryReferences().Where(referrer.HasOrganizationsWith(organization.IDIn(allowedOrgs...))) + if public != nil { + query = query.Where(referrer.HasWorkflowsWith(workflow.Public(*public))) + } + + refs, err := query.WithWorkflows().WithOrganizations().Order(referrer.ByDigest()).All(ctx) if err != nil { return nil, fmt.Errorf("failed to query references: %w", err) } @@ -181,7 +226,7 @@ func (r *ReferrerRepo) doGet(ctx context.Context, root *ent.Referrer, orgIDs []u for _, reference := range refs { // Call recursively the function // we return all the references - ref, err := r.doGet(ctx, reference, orgIDs, level+1) + ref, err := r.doGet(ctx, reference, allowedOrgs, public, level+1) if err != nil { return nil, fmt.Errorf("failed to get referrer: %w", err) } diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index c262be41f..9bf90b8b3 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -221,7 +221,7 @@ func (s *AttestationService) Store(ctx context.Context, req *cpAPI.AttestationSe } // Store the exploded attestation referrer information in the DB - if err := s.referrerUseCase.ExtractAndPersist(ctx, envelope, robotAccount.OrgID); err != nil { + if err := s.referrerUseCase.ExtractAndPersist(ctx, envelope, robotAccount.WorkflowID); err != nil { return nil, sl.LogAndMaskErr(err, s.log) } diff --git a/app/controlplane/internal/service/referrer.go b/app/controlplane/internal/service/referrer.go index 59b9fabac..cbbdc75b3 100644 --- a/app/controlplane/internal/service/referrer.go +++ b/app/controlplane/internal/service/referrer.go @@ -57,6 +57,7 @@ func bizReferrerToPb(r *biz.StoredReferrer) *pb.ReferrerItem { item := &pb.ReferrerItem{ Digest: r.Digest, Downloadable: r.Downloadable, + Public: r.InPublicWorkflow, Kind: r.Kind, CreatedAt: timestamppb.New(*r.CreatedAt), }