Skip to content

Commit

Permalink
Add support for multi-arch image index to ec validate command
Browse files Browse the repository at this point in the history
For an image index that is passed to the ec validate command,
expand it into its image manifests and replace the original
image index spec with the individual manifests in snapshot
Spec

resolved: CVP-4038

Signed-off-by: Yashvardhan Nanavati <[email protected]>
  • Loading branch information
yashvardhannanavati committed Apr 10, 2024
1 parent ab1f915 commit 4b7f464
Show file tree
Hide file tree
Showing 3 changed files with 459 additions and 21 deletions.
169 changes: 154 additions & 15 deletions cmd/validate/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ import (

hd "github.com/MakeNowJust/heredoc"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
app "github.com/redhat-appstudio/application-api/api/v1alpha1"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/enterprise-contract/ec-cli/cmd/root"
"github.com/enterprise-contract/ec-cli/internal/applicationsnapshot"
Expand All @@ -54,6 +58,34 @@ var rootArgs = []string{
"image",
}

type MockRemoteClient struct {
mock.Mock
}

func (m *MockRemoteClient) Get(ref name.Reference) (*remote.Descriptor, error) {
args := m.Called(ref)
result := args.Get(0)
if result != nil {
return result.(*remote.Descriptor), args.Error(1)
}
return nil, args.Error(1)
}

func (m *MockRemoteClient) Index(ref name.Reference) (v1.ImageIndex, error) {
args := m.Called(ref)
result := args.Get(0)
if result != nil {
return args.Get(0).(v1.ImageIndex), args.Error(1)
}
return nil, args.Error(1)
}

func commonMockClient(mockClient *MockRemoteClient) {
imageManifestJson := `{"mediaType": "application/vnd.oci.image.manifest.v1+json"}`
imageManifestJsonBytes := []byte(imageManifestJson)
mockClient.On("Get", mock.Anything).Return(&remote.Descriptor{Manifest: imageManifestJsonBytes}, nil)
}

func Test_determineInputSpec(t *testing.T) {
cases := []struct {
name string
Expand Down Expand Up @@ -230,10 +262,12 @@ func Test_determineInputSpec(t *testing.T) {
},
},
}

mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := context.WithValue(context.Background(), applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s, err := applicationsnapshot.DetermineInputSpec(context.Background(), applicationsnapshot.Input{
s, err := applicationsnapshot.DetermineInputSpec(ctx, applicationsnapshot.Input{
File: c.arguments.filePath,
JSON: c.arguments.input,
Image: c.arguments.imageRef,
Expand Down Expand Up @@ -286,7 +320,11 @@ func Test_ValidateImageCommand(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

cmd.SetContext(utils.WithFS(context.TODO(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

Expand Down Expand Up @@ -362,7 +400,11 @@ func Test_ValidateImageCommandImages(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

cmd.SetContext(utils.WithFS(context.Background(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

Expand Down Expand Up @@ -458,7 +500,11 @@ func Test_ValidateImageCommandKeyless(t *testing.T) {
})
cmd := setUpCobra(validateImageCmd)

cmd.SetContext(utils.WithFS(context.Background(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

cmd.SetArgs(append(rootArgs, []string{
"--image",
Expand Down Expand Up @@ -520,9 +566,12 @@ func Test_ValidateImageCommandYAMLPolicyFile(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
fs := afero.NewMemMapFs()

cmd.SetContext(utils.WithFS(context.TODO(), fs))
ctx := utils.WithFS(context.TODO(), fs)
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

cases := []struct {
name string
Expand Down Expand Up @@ -634,9 +683,12 @@ func Test_ValidateImageCommandJSONPolicyFile(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
fs := afero.NewMemMapFs()

cmd.SetContext(utils.WithFS(context.TODO(), fs))
ctx := utils.WithFS(context.TODO(), fs)
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

testPolicyJSON := `sources:
- policy:
Expand Down Expand Up @@ -710,9 +762,12 @@ func Test_ValidateImageCommandEmptyPolicyFile(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
fs := afero.NewMemMapFs()

cmd.SetContext(utils.WithFS(context.TODO(), fs))
ctx := utils.WithFS(context.TODO(), fs)
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

err := afero.WriteFile(fs, "/policy.yaml", []byte(nil), 0644)
if err != nil {
Expand Down Expand Up @@ -805,7 +860,11 @@ func Test_ValidateErrorCommand(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

cmd.SetContext(utils.WithFS(context.TODO(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

cmd.SetArgs(append([]string{"validate", "image"}, c.args...))

Expand Down Expand Up @@ -846,7 +905,11 @@ func Test_FailureImageAccessibility(t *testing.T) {
cmd := setUpCobra(validateImageCmd)
cmd.SilenceUsage = true // The root command is set to prevent usage printouts when running the CLI directly. This setup is temporary workaround.

cmd.SetContext(utils.WithFS(context.TODO(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

Expand Down Expand Up @@ -912,7 +975,11 @@ func Test_FailureOutput(t *testing.T) {
cmd := setUpCobra(validateImageCmd)
cmd.SilenceUsage = true // The root command is set to prevent usage printouts when running the CLI directly. This setup is temporary workaround.

cmd.SetContext(utils.WithFS(context.TODO(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

Expand Down Expand Up @@ -982,7 +1049,11 @@ func Test_WarningOutput(t *testing.T) {
validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

cmd.SetContext(utils.WithFS(context.TODO(), afero.NewMemMapFs()))
mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

Expand Down Expand Up @@ -1025,6 +1096,74 @@ func Test_WarningOutput(t *testing.T) {
}`, effectiveTimeTest, utils.TestPublicKeyJSON, utils.TestPublicKeyJSON), out.String())
}

func Test_FailureImageAccessibilityNonStrict(t *testing.T) {
validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ bool) (*output.Output, error) {
return &output.Output{
ImageSignatureCheck: output.VerificationStatus{
Passed: true,
},
ImageAccessibleCheck: output.VerificationStatus{
Passed: false,
Result: &evaluator.Result{Message: "Image URL is not accessible: HEAD http://${REGISTRY}/v2/acceptance/does-not-exist/manifests/latest: unexpected status code 404 Not Found (HEAD responses have no body, use GET for details)"},
},
ImageURL: component.ContainerImage,
}, nil
}

validateImageCmd := validateImageCmd(validate)

Check failure on line 1113 in cmd/validate/image_test.go

View workflow job for this annotation

GitHub Actions / Lint

cannot use validate (variable of type func(_ "context".Context, component "github.com/redhat-appstudio/application-api/api/v1alpha1".SnapshotComponent, _ "github.com/enterprise-contract/ec-cli/internal/policy".Policy, _ bool) (*"github.com/enterprise-contract/ec-cli/internal/output".Output, error)) as imageValidationFunc value in argument to validateImageCmd (typecheck)
cmd := setUpCobra(validateImageCmd)
cmd.SilenceUsage = true // The root command is set to prevent usage printouts when running the CLI directly. This setup is temporary workaround.

mockRemoteClient := &MockRemoteClient{}
commonMockClient(mockRemoteClient)
ctx := utils.WithFS(context.TODO(), afero.NewMemMapFs())
ctx = context.WithValue(ctx, applicationsnapshot.RemoteClientKey{}, mockRemoteClient)
cmd.SetContext(ctx)

effectiveTimeTest := time.Now().UTC().Format(time.RFC3339Nano)

cmd.SetArgs(append(rootArgs, []string{
"--image",
"registry/image:tag",
"--policy",
fmt.Sprintf(`{"publicKey": %s}`, utils.TestPublicKeyJSON),
"--effective-time",
effectiveTimeTest,
"--strict",
"false",
"--ignore-rekor",
"true",
}...))

var out bytes.Buffer
cmd.SetOut(&out)

utils.SetTestRekorPublicKey(t)

err := cmd.Execute()
assert.Error(t, err)
assert.JSONEq(t, fmt.Sprintf(`{
"success": false,
"ec-version": "development",
"effective-time": %q,
"key": %s,
"components": [
{
"name": "Unnamed",
"containerImage": "registry/image:tag",
"source": {},
"violations": [
{"msg": "Image URL is not accessible: HEAD http://${REGISTRY}/v2/acceptance/does-not-exist/manifests/latest: unexpected status code 404 Not Found (HEAD responses have no body, use GET for details)"}
],
"success": false
}
],
"policy": {
"publicKey": %s
}
}`, effectiveTimeTest, utils.TestPublicKeyJSON, utils.TestPublicKeyJSON), out.String())
}

func setUpCobra(command *cobra.Command) *cobra.Command {
validateCmd := NewValidateCmd()
validateCmd.AddCommand(command)
Expand Down
Loading

0 comments on commit 4b7f464

Please sign in to comment.