-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
952 add commands for Flows with template and Trails (#60)
* add hidden create flow v2 command * add create trail command * Start adding 'attest artifact' command * rename create trail to beging trail and tidy up * Use 'begin trail' in example [ci skip] * Update logger info message when trail is created [ci skip] * start adding attest generic command * tidy up attest generic command * update trail and flow creation/update output * fix test setup and add flow with template creation tests * add begin trail tests * start adding tests for attest artifact * add tests for attest generic * refactor attest generic and add attest snyk * add attest junit command * add attest jira command * add attest gitlab pr command * add attest bitbucket and azure pr commands * bump k8s dependency * fix pr tests --------- Co-authored-by: JonJagger <[email protected]>
- Loading branch information
1 parent
a72d2b5
commit 47f9735
Showing
41 changed files
with
3,624 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
const attestDesc = `All Kosli attest commands.` | ||
|
||
func newAttestCmd(out io.Writer) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "attest", | ||
Short: attestDesc, | ||
Long: attestDesc, | ||
Hidden: true, | ||
} | ||
|
||
// Add subcommands | ||
cmd.AddCommand( | ||
newAttestArtifactCmd(out), | ||
newAttestGenericCmd(out), | ||
newAttestSnykCmd(out), | ||
newAttestJunitCmd(out), | ||
newAttestJiraCmd(out), | ||
newAttestPRCmd(out), | ||
) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"path/filepath" | ||
|
||
"github.com/kosli-dev/cli/internal/gitview" | ||
"github.com/kosli-dev/cli/internal/requests" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type attestArtifactOptions struct { | ||
fingerprintOptions *fingerprintOptions | ||
flowName string | ||
gitReference string | ||
srcRepoRoot string | ||
displayName string | ||
payload AttestArtifactPayload | ||
} | ||
|
||
type AttestArtifactPayload struct { | ||
Fingerprint string `json:"fingerprint"` | ||
Filename string `json:"filename"` | ||
GitCommit string `json:"git_commit"` | ||
BuildUrl string `json:"build_url"` | ||
CommitUrl string `json:"commit_url"` | ||
RepoUrl string `json:"repo_url"` | ||
CommitsList []*gitview.CommitInfo `json:"commits_list"` | ||
Name string `json:"step_name"` | ||
TrailName string `json:"trail_name"` | ||
} | ||
|
||
const attestArtifactShortDesc = `Attest an artifact creation to a Kosli flow. ` | ||
|
||
const attestArtifactLongDesc = attestArtifactShortDesc + ` | ||
` + fingerprintDesc | ||
|
||
const attestArtifactExample = ` | ||
# Attest to a Kosli flow that a file type artifact has been created | ||
kosli attest artifact FILE.tgz \ | ||
--artifact-type file \ | ||
--build-url https://exampleci.com \ | ||
--commit-url https://github.com/YourOrg/YourProject/commit/yourCommitShaThatThisArtifactWasBuiltFrom \ | ||
--git-commit yourCommitShaThatThisArtifactWasBuiltFrom \ | ||
--flow yourFlowName \ | ||
--trail yourTrailName \ | ||
--name yourTemplateArtifactName \ | ||
--api-token yourApiToken \ | ||
--org yourOrgName | ||
# Attest to a Kosli flow that an artifact with a provided fingerprint (sha256) has been created | ||
kosli attest artifact ANOTHER_FILE.txt \ | ||
--build-url https://exampleci.com \ | ||
--commit-url https://github.com/YourOrg/YourProject/commit/yourCommitShaThatThisArtifactWasBuiltFrom \ | ||
--git-commit yourCommitShaThatThisArtifactWasBuiltFrom \ | ||
--flow yourFlowName \ | ||
--fingerprint yourArtifactFingerprint \ | ||
--trail yourTrailName \ | ||
--name yourTemplateArtifactName \ | ||
--api-token yourApiToken \ | ||
--org yourOrgName | ||
` | ||
|
||
func newAttestArtifactCmd(out io.Writer) *cobra.Command { | ||
o := new(attestArtifactOptions) | ||
o.fingerprintOptions = new(fingerprintOptions) | ||
cmd := &cobra.Command{ | ||
Use: "artifact {IMAGE-NAME | FILE-PATH | DIR-PATH}", | ||
Short: attestArtifactShortDesc, | ||
Long: attestArtifactLongDesc, | ||
Example: attestArtifactExample, | ||
Args: cobra.MaximumNArgs(1), | ||
Hidden: true, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) | ||
if err != nil { | ||
return ErrorBeforePrintingUsage(cmd, err.Error()) | ||
} | ||
|
||
err = ValidateArtifactArg(args, o.fingerprintOptions.artifactType, o.payload.Fingerprint, true) | ||
if err != nil { | ||
return ErrorBeforePrintingUsage(cmd, err.Error()) | ||
} | ||
return ValidateRegistryFlags(cmd, o.fingerprintOptions) | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return o.run(args) | ||
}, | ||
} | ||
|
||
ci := WhichCI() | ||
cmd.Flags().StringVarP(&o.payload.Fingerprint, "fingerprint", "F", "", fingerprintFlag) | ||
cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlag) | ||
cmd.Flags().StringVarP(&o.gitReference, "git-commit", "g", DefaultValue(ci, "git-commit"), gitCommitFlag) | ||
cmd.Flags().StringVarP(&o.payload.BuildUrl, "build-url", "b", DefaultValue(ci, "build-url"), buildUrlFlag) | ||
cmd.Flags().StringVarP(&o.payload.CommitUrl, "commit-url", "u", DefaultValue(ci, "commit-url"), commitUrlFlag) | ||
cmd.Flags().StringVar(&o.srcRepoRoot, "repo-root", ".", repoRootFlag) | ||
cmd.Flags().StringVarP(&o.payload.Name, "name", "n", "", templateArtifactName) | ||
cmd.Flags().StringVarP(&o.displayName, "display-name", "N", "", artifactDisplayName) | ||
cmd.Flags().StringVarP(&o.payload.TrailName, "trail", "T", "", trailNameFlag) | ||
addFingerprintFlags(cmd, o.fingerprintOptions) | ||
|
||
addDryRunFlag(cmd) | ||
|
||
err := RequireFlags(cmd, []string{"trail", "flow", "name", "git-commit", "build-url", "commit-url"}) | ||
if err != nil { | ||
logger.Error("failed to configure required flags: %v", err) | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func (o *attestArtifactOptions) run(args []string) error { | ||
|
||
if o.displayName != "" { | ||
o.payload.Filename = o.displayName | ||
} else { | ||
if o.fingerprintOptions.artifactType == "dir" || o.fingerprintOptions.artifactType == "file" { | ||
o.payload.Filename = filepath.Base(args[0]) | ||
} else { | ||
o.payload.Filename = args[0] | ||
} | ||
} | ||
|
||
if o.payload.Fingerprint == "" { | ||
var err error | ||
o.payload.Fingerprint, err = GetSha256Digest(args[0], o.fingerprintOptions, logger) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
gitView, err := gitview.New(o.srcRepoRoot) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
commitObject, err := gitView.GetCommitInfoFromCommitSHA(o.gitReference) | ||
if err != nil { | ||
return err | ||
} | ||
o.payload.GitCommit = commitObject.Sha1 | ||
|
||
previousCommit, err := o.latestCommit(currentBranch(gitView)) | ||
if err == nil { | ||
o.payload.CommitsList, err = gitView.ChangeLog(o.payload.GitCommit, previousCommit, logger) | ||
if err != nil && !global.DryRun { | ||
return err | ||
} | ||
} else if !global.DryRun { | ||
return err | ||
} | ||
|
||
o.payload.RepoUrl, err = gitView.RepoUrl() | ||
if err != nil { | ||
logger.Warning("Repo URL will not be reported, %s", err.Error()) | ||
} | ||
|
||
url := fmt.Sprintf("%s/api/v2/artifacts/%s/%s", global.Host, global.Org, o.flowName) | ||
|
||
reqParams := &requests.RequestParams{ | ||
Method: http.MethodPost, | ||
URL: url, | ||
Payload: o.payload, | ||
DryRun: global.DryRun, | ||
Password: global.ApiToken, | ||
} | ||
_, err = kosliClient.Do(reqParams) | ||
if err == nil && !global.DryRun { | ||
logger.Info("artifact %s was attested with fingerprint: %s", o.payload.Filename, o.payload.Fingerprint) | ||
} | ||
return err | ||
} | ||
|
||
// latestCommit retrieves the git commit of the latest artifact for a flow in Kosli | ||
func (o *attestArtifactOptions) latestCommit(branchName string) (string, error) { | ||
latestCommitUrl := fmt.Sprintf( | ||
"%s/api/v2/artifacts/%s/%s/%s/latest_commit%s", | ||
global.Host, global.Org, o.flowName, o.payload.Fingerprint, asBranchParameter(branchName)) | ||
|
||
reqParams := &requests.RequestParams{ | ||
Method: http.MethodGet, | ||
URL: latestCommitUrl, | ||
Password: global.ApiToken, | ||
} | ||
response, err := kosliClient.Do(reqParams) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
var latestCommitResponse map[string]interface{} | ||
err = json.Unmarshal([]byte(response.Body), &latestCommitResponse) | ||
if err != nil { | ||
return "", err | ||
} | ||
latestCommit := latestCommitResponse["latest_commit"] | ||
if latestCommit == nil { | ||
logger.Debug("no previous artifacts were found for flow: %s", o.flowName) | ||
return "", nil | ||
} else { | ||
logger.Debug("latest artifact for flow: %s has the git commit: %s", o.flowName, latestCommit.(string)) | ||
return latestCommit.(string), nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
// Define the suite, and absorb the built-in basic suite | ||
// functionality from testify - including a T() method which | ||
// returns the current testing context | ||
type AttestArtifactCommandTestSuite struct { | ||
flowName string | ||
trailName string | ||
suite.Suite | ||
defaultKosliArguments string | ||
} | ||
|
||
func (suite *AttestArtifactCommandTestSuite) SetupTest() { | ||
suite.flowName = "attest-artifact" | ||
suite.trailName = "test-123" | ||
global = &GlobalOpts{ | ||
ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", | ||
Org: "docs-cmd-test-user", | ||
Host: "http://localhost:8001", | ||
} | ||
suite.defaultKosliArguments = fmt.Sprintf(" --flow %s --trail %s --repo-root ../.. --host %s --org %s --api-token %s", suite.flowName, suite.trailName, global.Host, global.Org, global.ApiToken) | ||
CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.T()) | ||
BeginTrail(suite.trailName, suite.flowName, "", suite.T()) | ||
} | ||
|
||
func (suite *AttestArtifactCommandTestSuite) TestAttestArtifactCmd() { | ||
tests := []cmdTestCase{ | ||
{ | ||
wantError: true, | ||
name: "fails when more arguments are provided", | ||
cmd: fmt.Sprintf("attest artifact foo bar %s", suite.defaultKosliArguments), | ||
golden: "Error: accepts at most 1 arg(s), received 2\n", | ||
}, | ||
{ | ||
wantError: true, | ||
name: "fails when missing a required flag", | ||
cmd: fmt.Sprintf("attest artifact foo --artifact-type file --name bar --git-commit HEAD --build-url example.com %s", suite.defaultKosliArguments), | ||
golden: "Error: required flag(s) \"commit-url\" not set\n", | ||
}, | ||
{ | ||
wantError: true, | ||
name: "fails when --fingerprint is invalid sha256 digest", | ||
cmd: fmt.Sprintf("attest artifact foo --fingerprint xxxx --name bar --git-commit HEAD --build-url example.com --commit-url example.com %s", suite.defaultKosliArguments), | ||
golden: "Error: xxxx is not a valid SHA256 fingerprint. It should match the pattern ^([a-f0-9]{64})$\nUsage: kosli attest artifact {IMAGE-NAME | FILE-PATH | DIR-PATH} [flags]\n", | ||
}, | ||
{ | ||
wantError: true, | ||
name: "fails when --name does not match artifact name in the template", | ||
cmd: fmt.Sprintf("attest artifact testdata/file1 --artifact-type file --name bar --git-commit HEAD --build-url example.com --commit-url example.com %s", suite.defaultKosliArguments), | ||
golden: "Error: Artifact 'bar' does not exist in trail template 'test-123'.\nAvailable artifacts: cli\n", | ||
}, | ||
{ | ||
name: "can attest a file artifact", | ||
cmd: fmt.Sprintf("attest artifact testdata/file1 --artifact-type file --name cli --git-commit HEAD --build-url example.com --commit-url example.com %s", suite.defaultKosliArguments), | ||
golden: "artifact file1 was attested with fingerprint: 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9\n", | ||
}, | ||
{ | ||
name: "can attest an artifact with --fingerprint", | ||
cmd: fmt.Sprintf("attest artifact testdata/file1 --fingerprint 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 --name cli --git-commit HEAD --build-url example.com --commit-url example.com %s", suite.defaultKosliArguments), | ||
golden: "artifact testdata/file1 was attested with fingerprint: 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9\n", | ||
}, | ||
} | ||
|
||
runTestCmd(suite.T(), tests) | ||
} | ||
|
||
// In order for 'go test' to run this suite, we need to create | ||
// a normal test function and pass our suite to suite.Run | ||
func TestAttestArtifactCommandTestSuite(t *testing.T) { | ||
suite.Run(t, new(AttestArtifactCommandTestSuite)) | ||
} |
Oops, something went wrong.