Skip to content

Commit

Permalink
Split files and release asset cli commands
Browse files Browse the repository at this point in the history
Signed-off-by: Marco Franssen <marco.franssen@philips.com>
  • Loading branch information
marcofranssen committed Nov 30, 2021
1 parent 3125396 commit b8149b4
Show file tree
Hide file tree
Showing 6 changed files with 503 additions and 319 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
package cli_test

import (
"context"
"fmt"
"os"
"path"
"runtime"
"strings"
"testing"

gh "github.com/google/go-github/v39/github"
"github.com/stretchr/testify/assert"

"github.com/philips-labs/slsa-provenance-action/cmd/slsa-provenance/cli"
"github.com/philips-labs/slsa-provenance-action/lib/github"
)

const (
githubContext = `{
"token": "***",
Expand Down Expand Up @@ -265,257 +249,6 @@ const (
}`
)

func TestGenerateCliOptions(t *testing.T) {
_, filename, _, _ := runtime.Caller(0)
rootDir := path.Join(path.Dir(filename), "../../..")
provenanceFile := path.Join(rootDir, "bin/unittest.provenance")

testCases := []struct {
name string
err error
arguments []string
}{
{
name: "without commandline flags",
err: cli.RequiredFlagError("-artifact_path"),
arguments: make([]string, 0),
},
{
name: "only providing artifact_path",
err: cli.RequiredFlagError("-github_context"),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
},
},
{
name: "without runner_context",
err: cli.RequiredFlagError("-runner_context"),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
},
},
{
name: "invalid artifact_path",
err: fmt.Errorf("failed to generate provenance: resource path not found: [provided=non-existing-folder/unknown-file]"),
arguments: []string{
"-artifact_path",
"non-existing-folder/unknown-file",
"-github_context",
githubContext,
"-runner_context",
runnerContext,
},
},
{
name: "all arguments explicit",
err: nil,
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
},
},
{
name: "With extra materials",
err: nil,
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-extra_materials",
path.Join(rootDir, "test-data/materials-valid.json"),
},
},
{
name: "With broken extra materials",
err: fmt.Errorf("invalid JSON in extra materials file %s: unexpected end of JSON input", path.Join(rootDir, "test-data/materials-broken.not-json")),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-extra_materials",
path.Join(rootDir, "test-data/materials-broken.not-json"),
},
},
{
name: "With non-existent extra materials",
err: fmt.Errorf("could not load extra materials from non-existing-folder/unknown-file: open non-existing-folder/unknown-file: no such file or directory"),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-extra_materials",
fmt.Sprintf("%s %s", path.Join(rootDir, "test-data/materials-valid.json"), "non-existing-folder/unknown-file"),
},
},
{
name: "With broken extra materials (no uri)",
err: fmt.Errorf("empty or missing \"uri\" field in %s", path.Join(rootDir, "test-data/materials-no-uri.json")),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-extra_materials",
path.Join(rootDir, "test-data/materials-no-uri.json"),
},
},
{
name: "With broken extra materials (no digest)",
err: fmt.Errorf("empty or missing \"digest\" in %s", path.Join(rootDir, "test-data/materials-no-digest.json")),
arguments: []string{
"-artifact_path",
path.Join(rootDir, "bin/slsa-provenance"),
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-extra_materials",
path.Join(rootDir, "test-data/materials-no-digest.json"),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
assert := assert.New(tt)

sb := strings.Builder{}

cli := cli.Generate(&sb)
err := cli.ParseAndRun(context.Background(), tc.arguments)
defer func() {
_ = os.Remove(provenanceFile)
}()

if tc.err != nil {
assert.EqualError(err, tc.err.Error())
} else {
assert.NoError(err)
assert.Contains(sb.String(), "Saving provenance to")
if assert.FileExists(provenanceFile) {
content, err := os.ReadFile(provenanceFile)
assert.NoError(err)
assert.Greater(len(content), 1)
}
}
})
}
}

func TestProvenenaceGitHubRelease(t *testing.T) {
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken == "" {
t.Skip("skipping as GITHUB_TOKEN environment variable isn't set")
}
assert := assert.New(t)

_, filename, _, _ := runtime.Caller(0)
rootDir := path.Join(path.Dir(filename), "../../..")
artifactPath := path.Join(rootDir, "gh-release-test")
provenanceFile := path.Join(artifactPath, "unittest.provenance")

ctx := context.Background()
owner, repo := "philips-labs", "slsa-provenance-action"
oauthClient := github.NewOAuth2Client(ctx, func() string { return githubToken })
client := github.NewReleaseClient(oauthClient)

releaseID, err := createGitHubRelease(
ctx,
client,
owner,
repo,
"v0.0.0-generate-test",
path.Join(rootDir, "bin", "slsa-provenance"),
path.Join(rootDir, "README.md"),
)
assert.NoError(err)

defer func() {
_ = os.RemoveAll(artifactPath)
_, err = client.Repositories.DeleteRelease(ctx, owner, repo, releaseID)
}()

sb := strings.Builder{}
cli := cli.Generate(&sb)
err = cli.ParseAndRun(
context.Background(),
[]string{
"-artifact_path",
artifactPath,
"-github_context",
githubContext,
"-output_path",
provenanceFile,
"-runner_context",
runnerContext,
"-tag_name",
"v0.0.0-generate-test",
},
)
assert.NoError(err)
assert.Contains(sb.String(), "Saving provenance to")
if assert.FileExists(provenanceFile) {
content, err := os.ReadFile(provenanceFile)
assert.NoError(err)
assert.Greater(len(content), 1)
}
}

func createGitHubRelease(ctx context.Context, client *github.ReleaseClient, owner, repo, version string, assets ...string) (int64, error) {
rel, _, err := client.Repositories.CreateRelease(
ctx,
owner,
repo,
&gh.RepositoryRelease{TagName: stringPointer(version), Name: stringPointer(version), Draft: boolPointer(true), Prerelease: boolPointer(true)},
)
if err != nil {
return 0, err
}

for _, a := range assets {
asset, err := os.Open(a)
if err != nil {
return 0, err
}
client.AddProvenanceToRelease(ctx, owner, repo, rel.GetID(), asset)
}

return rel.GetID(), nil
}

func stringPointer(s string) *string {
return &s
}
Expand Down
103 changes: 103 additions & 0 deletions cmd/slsa-provenance/cli/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cli

import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"strings"

"github.com/peterbourgon/ff/v3/ffcli"

"github.com/philips-labs/slsa-provenance-action/lib/github"
"github.com/philips-labs/slsa-provenance-action/lib/intoto"
)

// Files creates an instance of *ffcli.Command to manage file provenance
func Files(w io.Writer) *ffcli.Command {
var (
flagset = flag.NewFlagSet("slsa-provenance generate files", flag.ExitOnError)
artifactPath = flagset.String("artifact_path", "", "The file or dir path of the artifacts for which provenance should be generated.")
outputPath = flagset.String("output_path", "build.provenance", "The path to which the generated provenance should be written.")
githubContext = flagset.String("github_context", "", "The '${github}' context value.")
runnerContext = flagset.String("runner_context", "", "The '${runner}' context value.")
extraMaterials = []string{}
)
flagset.Func("extra_materials", "paths to files containing SLSA v0.1 formatted materials (JSON array) in to include in the provenance", func(s string) error {
extraMaterials = append(extraMaterials, strings.Fields(s)...)
return nil
})

flagset.SetOutput(w)

return &ffcli.Command{
Name: "files",
ShortUsage: "slsa-provenance generate files",
ShortHelp: "Generates slsa provenance for file(s)",
FlagSet: flagset,
Exec: func(ctx context.Context, args []string) error {
if *artifactPath == "" {
flagset.Usage()
return RequiredFlagError("-artifact_path")
}
if *outputPath == "" {
flagset.Usage()
return RequiredFlagError("-output_path")
}
if *githubContext == "" {
flagset.Usage()
return RequiredFlagError("-github_context")
}
if *runnerContext == "" {
flagset.Usage()
return RequiredFlagError("-runner_context")
}

var gh github.Context
if err := json.Unmarshal([]byte(*githubContext), &gh); err != nil {
return fmt.Errorf("failed to unmarshal github context json: %w", err)
}

var runner github.RunnerContext
if err := json.Unmarshal([]byte(*runnerContext), &runner); err != nil {
return fmt.Errorf("failed to unmarshal runner context json: %w", err)
}

env := &github.Environment{
Context: &gh,
Runner: &runner,
}

stmt, err := env.GenerateProvenanceStatement(ctx, *artifactPath)
if err != nil {
return fmt.Errorf("failed to generate provenance: %w", err)
}

for _, extra := range extraMaterials {
content, err := ioutil.ReadFile(extra)
if err != nil {
return fmt.Errorf("could not load extra materials from %s: %w", extra, err)
}
var materials []intoto.Item
if err = json.Unmarshal(content, &materials); err != nil {
return fmt.Errorf("invalid JSON in extra materials file %s: %w", extra, err)
}
for _, material := range materials {
if material.URI == "" {
return fmt.Errorf("empty or missing \"uri\" field in %s", extra)
}
if len(material.Digest) == 0 {
return fmt.Errorf("empty or missing \"digest\" in %s", extra)
}
}
stmt.Predicate.Materials = append(stmt.Predicate.Materials, materials...)
}

fmt.Fprintf(w, "Saving provenance to %s\n", *outputPath)

return env.PersistProvenanceStatement(ctx, stmt, *outputPath)
},
}
}
Loading

0 comments on commit b8149b4

Please sign in to comment.