diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75ba699b8..b83c27676 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: # see https://entgo.io/docs/ci/ - uses: ent/contrib/ci@e38dfb6484dfbe64b8bd060fe6a219a1aa5da770 # master name: "Check all ent generated code is checked in" - if: ${{ matrix.app != 'main-module' && matrix.app != 'cli' }} + if: ${{ matrix.app != 'main-module' }} with: working-directory: app/${{ matrix.app }} tidy: true diff --git a/app/cli/cmd/attestation_init.go b/app/cli/cmd/attestation_init.go index 0d9835ce6..87b015f03 100644 --- a/app/cli/cmd/attestation_init.go +++ b/app/cli/cmd/attestation_init.go @@ -147,7 +147,7 @@ func newAttestationInitCmd() *cobra.Command { cmd.Flags().StringVar(&projectName, "project", "", "name of the project of this workflow") cobra.CheckErr(cmd.MarkFlagRequired("project")) - cmd.Flags().StringVar(&newWorkflowcontract, "contract", "", "name of an existing contract or the path/URL to a contract file to be used in the attestation. It will update any existing contract for the workflow") + cmd.Flags().StringVar(&newWorkflowcontract, "contract", "", "name of an existing contract or the path/URL to a contract file, to attach it to the auto-created workflow (it doesn't update an existing one)") cmd.Flags().StringVar(&projectVersion, "version", "", "project version, i.e 0.1.0") cmd.Flags().BoolVar(&projectVersionRelease, "release", false, "promote the provided version as a release") diff --git a/app/cli/internal/action/attestation_init.go b/app/cli/internal/action/attestation_init.go index 4a46f62e1..16e47148e 100644 --- a/app/cli/internal/action/attestation_init.go +++ b/app/cli/internal/action/attestation_init.go @@ -29,7 +29,6 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "k8s.io/apimachinery/pkg/util/validation" ) type AttestationInitOpts struct { @@ -100,13 +99,38 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun } action.Logger.Debug().Msg("Retrieving attestation definition") - client := pb.NewAttestationServiceClient(action.CPConnection) + client := pb.NewAttestationServiceClient(action.ActionsOpts.CPConnection) - // 1 - Create the workflow (and contract) if it doesn't exist - wf, err := action.createWorkflow(ctx, opts) + // 0 - find or create the contract if we are creating the workflow (if any) + contractRef := opts.NewWorkflowContractRef + _, err := NewWorkflowDescribe(action.ActionsOpts).Run(ctx, opts.WorkflowName, opts.ProjectName) + if err != nil && status.Code(err) == codes.NotFound { + // Not found, let's see if we need to create the contract + if contractRef != "" { + // Try to find it by name + _, err := NewWorkflowContractDescribe(action.ActionsOpts).Run(contractRef, 0) + // An invalid argument might be raised if we use a file or URL in the "name" field, which must be DNS-1123 + // TODO: validate locally before doing the query + if err != nil && (status.Code(err) == codes.NotFound || status.Code(err) == codes.InvalidArgument) { + createResp, err := NewWorkflowContractCreate(action.ActionsOpts).Run(fmt.Sprintf("%s-%s", opts.ProjectName, opts.WorkflowName), nil, contractRef) + if err != nil { + return "", err + } + contractRef = createResp.Name + } + } + } + + // 1 - Find or create the workflow + workflowsResp, err := client.FindOrCreateWorkflow(ctx, &pb.FindOrCreateWorkflowRequest{ + ProjectName: opts.ProjectName, + WorkflowName: opts.WorkflowName, + ContractName: contractRef, + }) if err != nil { - return "", fmt.Errorf("error creating workflow: %w", err) + return "", err } + workflow := workflowsResp.GetResult() // 2 - Get contract contractResp, err := client.GetContract(ctx, &pb.AttestationServiceGetContractRequest{ @@ -120,12 +144,12 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun contractVersion := contractResp.Result.GetContract() workflowMeta := &clientAPI.WorkflowMetadata{ - WorkflowId: wf.ID, - Name: wf.Name, - Project: wf.Project, - Team: wf.Team, + WorkflowId: workflow.GetId(), + Name: workflow.GetName(), + Project: workflow.GetProject(), + Team: workflow.GetTeam(), SchemaRevision: strconv.Itoa(int(contractVersion.GetRevision())), - ContractName: wf.ContractName, + ContractName: workflow.ContractName, } if opts.ProjectVersion != "" { @@ -222,87 +246,6 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun return attestationID, nil } -// createWorkflow creates a new workflow if it doesn't exist, as well as its contract -func (action *AttestationInit) createWorkflow(ctx context.Context, opts *AttestationInitRunOpts) (*WorkflowItem, error) { - // 1. find workflow if exists - wf, err := NewWorkflowDescribe(action.ActionsOpts).Run(ctx, opts.WorkflowName, opts.ProjectName) - if err != nil && status.Code(err) != codes.NotFound { - return nil, fmt.Errorf("error looking for workflow %q: %w", opts.WorkflowName, err) - } - - // if no new contract is provided, there's nothing to update - if wf != nil && opts.NewWorkflowContractRef == "" { - return wf, nil - } - - contractRef := opts.NewWorkflowContractRef - contractName := contractRef - if contractRef != "" { - if isContractReference(contractRef) { - _, err := NewWorkflowContractDescribe(action.ActionsOpts).Run(contractRef, 0) - if err != nil { - return nil, fmt.Errorf("contract %q could not be found", contractRef) - } - // the contract exists - } else { - // use a default name for the contract - contractName = defaultContractName(opts.ProjectName, opts.WorkflowName) - - // if the workflow exists, we just update its contract with the new contents - if wf != nil { - _, err = NewWorkflowContractUpdate(action.ActionsOpts).Run(wf.ContractName, nil, contractRef) - if err != nil { - return nil, fmt.Errorf("error updating contract %q: %w", wf.ContractName, err) - } - contractName = wf.ContractName - } else { - // if the workflow doesn't exist, let's create or update the contract - cont, err := NewWorkflowContractDescribe(action.ActionsOpts).Run(contractName, 0) - switch { - case err != nil && status.Code(err) == codes.NotFound: - // Contract not found, let's create it - _, err = NewWorkflowContractCreate(action.ActionsOpts).Run(contractName, nil, contractRef) - if err != nil { - return nil, err - } - case err != nil: - return nil, err - default: - // contract found, let's update it (chainloop will validate that there is an actual change in the contract file) - _, err := NewWorkflowContractUpdate(action.ActionsOpts).Run(cont.Contract.Name, &cont.Contract.Description, contractRef) - if err != nil { - return nil, err - } - } - } - } - } - - // if workflow doesn't exist, let's create it with the contract - if wf == nil { - wf, err = NewWorkflowCreate(action.ActionsOpts).Run(&NewWorkflowCreateOpts{ - Name: opts.WorkflowName, - Project: opts.ProjectName, - ContractName: contractName, - }) - if err != nil { - return nil, fmt.Errorf("error creating workflow %q: %w", opts.WorkflowName, err) - } - } - - return wf, nil -} - -// isContractReference checks if the reference points to an existent contract name (DNS-1123) -func isContractReference(ref string) bool { - err := validation.IsDNS1123Label(ref) - return len(err) == 0 -} - -func defaultContractName(project, workflow string) string { - return fmt.Sprintf("%s-%s", project, workflow) -} - func enrichContractMaterials(ctx context.Context, schema *v1.CraftingSchema, client pb.AttestationServiceClient, logger *zerolog.Logger) error { contractMaterials := schema.GetMaterials() for _, pgAtt := range schema.GetPolicyGroups() {