Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Release Bundle Evidence Using Cli Client #14

Merged
merged 9 commits into from
Jul 31, 2024
9 changes: 9 additions & 0 deletions evidence/cli/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cli

import (
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
)

type EvidenceCommands interface {
CreateEvidence(*coreConfig.ServerDetails) error
}
79 changes: 49 additions & 30 deletions evidence/cli/commands.go → evidence/cli/command_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package cli

import (
"errors"
"github.com/jfrog/jfrog-cli-artifactory/evidence"
"github.com/jfrog/jfrog-cli-artifactory/evidence/cli/docs/create"
commonCliUtils "github.com/jfrog/jfrog-cli-core/v2/common/cliutils"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"strings"
)

func GetCommands() []components.Command {
Expand All @@ -26,41 +25,26 @@ func GetCommands() []components.Command {
}
}

func platformToEvidenceUrls(rtDetails *coreConfig.ServerDetails) {
rtDetails.ArtifactoryUrl = utils.AddTrailingSlashIfNeeded(rtDetails.Url) + "artifactory/"
rtDetails.EvidenceUrl = utils.AddTrailingSlashIfNeeded(rtDetails.Url) + "evidence/"
}

func createEvidence(c *components.Context) error {
if err := validateCreateEvidenceContext(c); err != nil {
return err
}

artifactoryClient, err := evidenceDetailsByFlags(c)
subject, err := getAndValidateSubject(c)
if err != nil {
return err
}

createCmd := evidence.NewEvidenceCreateCommand().
SetServerDetails(artifactoryClient).
SetPredicateFilePath(c.GetStringFlagValue(EvdPredicate)).
SetPredicateType(c.GetStringFlagValue(EvdPredicateType)).
SetRepoPath(c.GetStringFlagValue(EvdRepoPath)).
SetKey(c.GetStringFlagValue(EvdKey)).
SetKeyId(c.GetStringFlagValue(EvdKeyId))
return commands.Exec(createCmd)
}

func evidenceDetailsByFlags(c *components.Context) (*coreConfig.ServerDetails, error) {
artifactoryClient, err := pluginsCommon.CreateServerDetailsWithConfigOffer(c, true, commonCliUtils.Platform)
artifactoryClient, err := evidenceDetailsByFlags(c)
if err != nil {
return nil, err
return err
}
if artifactoryClient.Url == "" {
return nil, errors.New("platform URL is mandatory for evidence commands")
var command EvidenceCommands
if subject == EvdRepoPath {
command = NewEvidenceCustomCommand(c)
}
platformToEvidenceUrls(artifactoryClient)
return artifactoryClient, nil
if subject == releaseBundle {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be either a switch or if/else (no need to check for if after the match).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

command = NewEvidenceReleaseBundleCommand(c)
}
return command.CreateEvidence(artifactoryClient)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe code, the command may be nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

func validateCreateEvidenceContext(c *components.Context) error {
Expand All @@ -78,16 +62,51 @@ func validateCreateEvidenceContext(c *components.Context) error {
if !c.IsFlagSet(EvdPredicateType) || assertValueProvided(c, EvdPredicateType) != nil {
return errorutils.CheckErrorf("'predicate' is a mandatory field for creating a custom evidence: --%s", EvdPredicateType)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: predicate-type instead of predicate in the message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
if !c.IsFlagSet(EvdRepoPath) || assertValueProvided(c, EvdRepoPath) != nil {
return errorutils.CheckErrorf("'repo-path' is a mandatory field for creating a custom evidence: --%s", EvdRepoPath)
}
if !c.IsFlagSet(EvdKey) || assertValueProvided(c, EvdKey) != nil {
return errorutils.CheckErrorf("'key' is a mandatory field for creating a custom evidence: --%s", EvdKey)
}

return nil
}

func getAndValidateSubject(c *components.Context) (string, error) {
subjects := []string{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved outside of the function (no need to re-allocate each time).,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

EvdRepoPath,
releaseBundle,
}
var foundSubjects []string
for _, key := range subjects {
if c.GetStringFlagValue(key) != "" {
foundSubjects = append(foundSubjects, key)
}
}

if len(foundSubjects) == 0 {
return "", errorutils.CheckErrorf("Subject must be one of the fields: [%s]", strings.Join(subjects, ", "))
}
if len(foundSubjects) > 1 {
return "", errorutils.CheckErrorf("multiple subjects found: [%s]", strings.Join(foundSubjects, ", "))
}
return foundSubjects[0], nil
}

func evidenceDetailsByFlags(c *components.Context) (*coreConfig.ServerDetails, error) {
artifactoryClient, err := pluginsCommon.CreateServerDetailsWithConfigOffer(c, true, commonCliUtils.Platform)
if err != nil {
return nil, err
}
if artifactoryClient.Url == "" {
return nil, errors.New("platform URL is mandatory for evidence commands")
}
platformToEvidenceUrls(artifactoryClient)
return artifactoryClient, nil
}

func platformToEvidenceUrls(rtDetails *coreConfig.ServerDetails) {
rtDetails.ArtifactoryUrl = utils.AddTrailingSlashIfNeeded(rtDetails.Url) + "artifactory/"
rtDetails.EvidenceUrl = utils.AddTrailingSlashIfNeeded(rtDetails.Url) + "evidence/"
}

func assertValueProvided(c *components.Context, fieldName string) error {
if c.GetStringFlagValue(fieldName) == "" {
return errorutils.CheckErrorf("the --%s option is mandatory", fieldName)
Expand Down
72 changes: 72 additions & 0 deletions evidence/cli/command_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cli

import (
"reflect"
"testing"
"unsafe"

"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
"github.com/stretchr/testify/assert"
)

func TestCreateEvidence_Context(t *testing.T) {
tests := []struct {
name string
context *components.Context
expectErr bool
}{
{
name: "InvalidContext - Missing Subject",
context: createContext("somePredicate", "InToto", "PGP", "", ""),
expectErr: true,
},
{
name: "InvalidContext - Missing Predicate",
context: createContext("", "InToto", "PGP", "someBundle", ""),
expectErr: true,
},
{
name: "InvalidContext - Subject Duplication",
context: createContext("somePredicate", "InToto", "PGP", "someBundle", "path"),
expectErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := createEvidence(tt.context)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NoError is not reachable (all cases are negative, please add one with expectErr: false)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
})
}
}

func createContext(predicate string, predicateType string, key string, rb string, repoPath string) *components.Context {
ctx := &components.Context{
Arguments: []string{},
}
setStringFlagValue(ctx, EvdPredicate, predicate)
setStringFlagValue(ctx, EvdPredicateType, predicateType)
setStringFlagValue(ctx, EvdKey, key)
setStringFlagValue(ctx, EvdRepoPath, repoPath)
setStringFlagValue(ctx, releaseBundle, rb)
return ctx
}

func setStringFlagValue(ctx *components.Context, flagName, value string) {
val := reflect.ValueOf(ctx).Elem()
stringFlags := val.FieldByName("stringFlags")

// If the field is not settable, we need to make it settable
if !stringFlags.CanSet() {
stringFlags = reflect.NewAt(stringFlags.Type(), unsafe.Pointer(stringFlags.UnsafeAddr())).Elem()
}

if stringFlags.IsNil() {
stringFlags.Set(reflect.MakeMap(stringFlags.Type()))
}
stringFlags.SetMapIndex(reflect.ValueOf(flagName), reflect.ValueOf(value))
}
29 changes: 29 additions & 0 deletions evidence/cli/command_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cli

import (
"github.com/jfrog/jfrog-cli-artifactory/evidence"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
)

type EvidenceCustomCommand struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it an exported struct and not an interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

c *components.Context
}

func NewEvidenceCustomCommand(ctx *components.Context) EvidenceCommands {
return &EvidenceCustomCommand{
c: ctx,
}
}

func (ecc *EvidenceCustomCommand) CreateEvidence(artifactoryClient *coreConfig.ServerDetails) error {
createCmd := evidence.NewCreateEvidenceCustom().
SetServerDetails(artifactoryClient).
SetPredicateFilePath(ecc.c.GetStringFlagValue(EvdPredicate)).
SetPredicateType(ecc.c.GetStringFlagValue(EvdPredicateType)).
SetRepoPath(ecc.c.GetStringFlagValue(EvdRepoPath)).
SetKey(ecc.c.GetStringFlagValue(EvdKey)).
SetKeyId(ecc.c.GetStringFlagValue(EvdKeyId))
return commands.Exec(createCmd)
}
30 changes: 30 additions & 0 deletions evidence/cli/command_relesae_bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cli

import (
"github.com/jfrog/jfrog-cli-artifactory/evidence"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
)

type EvidenceReleaseBundleCommand struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it an exported struct and not an interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

c *components.Context
}

func NewEvidenceReleaseBundleCommand(ctx *components.Context) EvidenceCommands {
return &EvidenceReleaseBundleCommand{
c: ctx,
}
}

func (erc *EvidenceReleaseBundleCommand) CreateEvidence(artifactoryClient *coreConfig.ServerDetails) error {
createCmd := evidence.NewCreateEvidenceReleaseBundle().
SetServerDetails(artifactoryClient).
SetPredicateFilePath(erc.c.GetStringFlagValue(EvdPredicate)).
SetPredicateType(erc.c.GetStringFlagValue(EvdPredicateType)).
SetProject(erc.c.GetStringFlagValue(project)).
SetReleaseBundle(erc.c.GetStringFlagValue(releaseBundle)).
SetKey(erc.c.GetStringFlagValue(EvdKey)).
SetKeyId(erc.c.GetStringFlagValue(EvdKeyId))
return commands.Exec(createCmd)
}
11 changes: 9 additions & 2 deletions evidence/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const (
user = "user"
password = "password"
accessToken = "access-token"
project = "project"

// RLM flags keys
releaseBundle = "release-bundle"

// Unique evidence flags
evidencePrefix = "evd-"
Expand All @@ -35,17 +39,20 @@ var flagsMap = map[string]components.Flag{
user: components.NewStringFlag(user, "JFrog username.", func(f *components.StringFlag) { f.Mandatory = false }),
password: components.NewStringFlag(password, "JFrog password.", func(f *components.StringFlag) { f.Mandatory = false }),
accessToken: components.NewStringFlag(accessToken, "JFrog access token.", func(f *components.StringFlag) { f.Mandatory = false }),
project: components.NewStringFlag(project, "Project key associated with the created evidence.", func(f *components.StringFlag) { f.Mandatory = false }),

releaseBundle: components.NewStringFlag(releaseBundle, "Release Bundle name and version. Format: <name>:<version>", func(f *components.StringFlag) { f.Mandatory = false }),

EvdPredicate: components.NewStringFlag(EvdPredicate, "Path to the predicate, arbitrary JSON.", func(f *components.StringFlag) { f.Mandatory = true }),
EvdPredicateType: components.NewStringFlag(EvdPredicateType, "Type of the predicate.", func(f *components.StringFlag) { f.Mandatory = true }),
EvdRepoPath: components.NewStringFlag(EvdRepoPath, "Full path to some artifact' location.", func(f *components.StringFlag) { f.Mandatory = true }),
EvdRepoPath: components.NewStringFlag(EvdRepoPath, "Full path to some artifact' location.", func(f *components.StringFlag) { f.Mandatory = false }),
EvdKey: components.NewStringFlag(EvdKey, "Path to a private key that will sign the DSSE. Supported keys: 'ecdsa','rsa' and 'ed25519'.", func(f *components.StringFlag) { f.Mandatory = true }),
EvdKeyId: components.NewStringFlag(EvdKeyId, "KeyId", func(f *components.StringFlag) { f.Mandatory = false }),
}

var commandFlags = map[string][]string{
CreateEvidence: {
url, user, password, accessToken, ServerId, EvdPredicate, EvdPredicateType, EvdRepoPath, EvdKey, EvdKeyId,
url, user, password, accessToken, ServerId, project, releaseBundle, EvdPredicate, EvdPredicateType, EvdRepoPath, EvdKey, EvdKeyId,
},
}

Expand Down
Loading
Loading