Skip to content

Commit f9c6b26

Browse files
authored
feat(cli): extend workflow contract with apply (#2469)
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
1 parent 970dc11 commit f9c6b26

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed

app/cli/cmd/workflow_contract.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func newWorkflowContractCmd() *cobra.Command {
2828
cmd.AddCommand(
2929
newWorkflowContractListCmd(),
3030
newWorkflowContractCreateCmd(),
31+
newWorkflowContractApplyCmd(),
3132
newWorkflowContractDescribeCmd(),
3233
newWorkflowContractUpdateCmd(),
3334
newWorkflowContractDeleteCmd(),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Copyright 2025 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"github.com/chainloop-dev/chainloop/app/cli/cmd/output"
20+
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
21+
"github.com/spf13/cobra"
22+
)
23+
24+
func newWorkflowContractApplyCmd() *cobra.Command {
25+
var contractPath, name, description, projectName string
26+
27+
cmd := &cobra.Command{
28+
Use: "apply",
29+
Short: "Apply a contract (create or update)",
30+
Long: `Apply a contract from a file. This command will create the contract if it doesn't exist,
31+
or update it if it already exists.`,
32+
Example: ` # Apply a contract from file
33+
chainloop workflow contract apply --contract my-contract.yaml --name my-contract --project my-project`,
34+
RunE: func(cmd *cobra.Command, _ []string) error {
35+
var desc *string
36+
if cmd.Flags().Changed("description") {
37+
desc = &description
38+
}
39+
40+
res, err := action.NewWorkflowContractApply(ActionOpts).Run(cmd.Context(), name, contractPath, desc, projectName)
41+
if err != nil {
42+
return err
43+
}
44+
45+
logger.Info().Msg("Contract applied!")
46+
return output.EncodeOutput(flagOutputFormat, res, contractItemTableOutput)
47+
},
48+
}
49+
50+
cmd.Flags().StringVar(&name, "name", "", "contract name")
51+
err := cmd.MarkFlagRequired("name")
52+
cobra.CheckErr(err)
53+
54+
cmd.Flags().StringVarP(&contractPath, "contract", "f", "", "path or URL to the contract schema")
55+
cmd.Flags().StringVar(&description, "description", "", "description of the contract")
56+
cmd.Flags().StringVar(&projectName, "project", "", "project name used to scope the contract, if not set the contract will be created in the organization")
57+
58+
return cmd
59+
}

app/cli/documentation/cli-reference.mdx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3299,6 +3299,52 @@ Options inherited from parent commands
32993299
-y, --yes Skip confirmation
33003300
```
33013301

3302+
#### chainloop workflow contract apply
3303+
3304+
Apply a contract (create or update)
3305+
3306+
Synopsis
3307+
3308+
Apply a contract from a file. This command will create the contract if it doesn't exist,
3309+
or update it if it already exists.
3310+
3311+
```
3312+
chainloop workflow contract apply [flags]
3313+
```
3314+
3315+
Examples
3316+
3317+
```
3318+
Apply a contract from file
3319+
chainloop workflow contract apply --contract my-contract.yaml --name my-contract --project my-project
3320+
```
3321+
3322+
Options
3323+
3324+
```
3325+
-f, --contract string path or URL to the contract schema
3326+
--description string description of the contract
3327+
-h, --help help for apply
3328+
--name string contract name
3329+
--project string project name used to scope the contract, if not set the contract will be created in the organization
3330+
```
3331+
3332+
Options inherited from parent commands
3333+
3334+
```
3335+
--artifact-cas string URL for the Artifacts Content Addressable Storage API ($CHAINLOOP_ARTIFACT_CAS_API) (default "api.cas.chainloop.dev:443")
3336+
--artifact-cas-ca string CUSTOM CA file for the Artifacts CAS API (optional) ($CHAINLOOP_ARTIFACT_CAS_API_CA)
3337+
-c, --config string Path to an existing config file (default is $HOME/.config/chainloop/config.toml)
3338+
--control-plane string URL for the Control Plane API ($CHAINLOOP_CONTROL_PLANE_API) (default "api.cp.chainloop.dev:443")
3339+
--control-plane-ca string CUSTOM CA file for the Control Plane API (optional) ($CHAINLOOP_CONTROL_PLANE_API_CA)
3340+
--debug Enable debug/verbose logging mode
3341+
-i, --insecure Skip TLS transport during connection to the control plane ($CHAINLOOP_API_INSECURE)
3342+
-n, --org string organization name
3343+
-o, --output string Output format, valid options are json and table (default "table")
3344+
-t, --token string API token. NOTE: Alternatively use the env variable CHAINLOOP_TOKEN
3345+
-y, --yes Skip confirmation
3346+
```
3347+
33023348
#### chainloop workflow contract create
33033349

33043350
Create a new contract
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// Copyright 2025 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package action
17+
18+
import (
19+
"context"
20+
"fmt"
21+
22+
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
23+
)
24+
25+
type WorkflowContractApply struct {
26+
cfg *ActionsOpts
27+
}
28+
29+
func NewWorkflowContractApply(cfg *ActionsOpts) *WorkflowContractApply {
30+
return &WorkflowContractApply{cfg}
31+
}
32+
33+
func (action *WorkflowContractApply) Run(ctx context.Context, contractName string, contractPath string, description *string, projectName string) (*WorkflowContractItem, error) {
34+
client := pb.NewWorkflowContractServiceClient(action.cfg.CPConnection)
35+
36+
// Try to describe the specific contract first to determine if we should create or update
37+
describeReq := &pb.WorkflowContractServiceDescribeRequest{
38+
Name: contractName,
39+
}
40+
41+
var rawContract []byte
42+
if contractPath != "" {
43+
raw, err := LoadFileOrURL(contractPath)
44+
if err != nil {
45+
action.cfg.Logger.Debug().Err(err).Msg("loading the contract")
46+
return nil, err
47+
}
48+
rawContract = raw
49+
}
50+
51+
_, err := client.Describe(ctx, describeReq)
52+
if err == nil {
53+
// Contract exists, perform update
54+
updateReq := &pb.WorkflowContractServiceUpdateRequest{
55+
Name: contractName,
56+
Description: description,
57+
RawContract: rawContract,
58+
}
59+
60+
res, err := client.Update(ctx, updateReq)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to update existing contract '%s': %w", contractName, err)
63+
}
64+
65+
return pbWorkflowContractItemToAction(res.Result.Contract), nil
66+
}
67+
68+
// Contract doesn't exist, perform create
69+
createReq := &pb.WorkflowContractServiceCreateRequest{
70+
Name: contractName,
71+
Description: description,
72+
RawContract: rawContract,
73+
}
74+
75+
if projectName != "" {
76+
createReq.ProjectReference = &pb.IdentityReference{
77+
Name: &projectName,
78+
}
79+
}
80+
81+
res, err := client.Create(ctx, createReq)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to create new contract '%s': %w", contractName, err)
84+
}
85+
86+
return pbWorkflowContractItemToAction(res.Result), nil
87+
}

0 commit comments

Comments
 (0)