From b7ce66013633ead2a39d6311626df3270e4d1ff9 Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Tue, 23 Jan 2024 16:09:22 -0500 Subject: [PATCH] allow passing of commit hash on the cli (#278) Signed-off-by: Benji Visser --- cmd/xeol/cli/options/xeol.go | 30 +++++++++++++- cmd/xeol/internal/types/commit.go | 16 +++++++ cmd/xeol/internal/types/commit_test.go | 27 ++++++++++++ cmd/xeol/internal/types/projectname.go | 16 +++++++ cmd/xeol/internal/types/projectname_test.go | 46 +++++++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 cmd/xeol/internal/types/commit.go create mode 100644 cmd/xeol/internal/types/commit_test.go create mode 100644 cmd/xeol/internal/types/projectname.go create mode 100644 cmd/xeol/internal/types/projectname_test.go diff --git a/cmd/xeol/cli/options/xeol.go b/cmd/xeol/cli/options/xeol.go index 3cc89889..f20423ef 100644 --- a/cmd/xeol/cli/options/xeol.go +++ b/cmd/xeol/cli/options/xeol.go @@ -9,6 +9,7 @@ import ( git "github.com/go-git/go-git/v5" "github.com/karrick/tparse" + "github.com/xeol-io/xeol/cmd/xeol/internal/types" "github.com/xeol-io/xeol/internal/format" ) @@ -99,6 +100,11 @@ func (o *Xeol) AddFlags(flags clio.FlagSet) { "manually set the name of the project being analyzed for xeol.io. If you are running xeol inside a git repository, this will be automatically detected.", ) + flags.StringVarP(&o.CommitHash, + "commit-hash", "", + "manually set the commit hash of the project being analyzed for xeol.io. If you are running xeol inside a git repository, this will be automatically detected.", + ) + flags.StringVarP(&o.APIKey, "api-key", "", "set the API key for xeol.io. When this is set, scans will be uploaded to xeol.io.", @@ -149,6 +155,28 @@ func (o *Xeol) parseLookaheadOption() (err error) { return nil } +func (o *Xeol) parseProjectAndCommitOption() (err error) { + if o.APIKey != "" { + if o.ProjectName == "" { + return fmt.Errorf("must specify a project name when using --api-key. This is usually inferred automatically when running inside a git repository, but you may also pass it manually with --project-name") + } + if err := types.ProjectName(o.ProjectName).IsValid(); err != nil { + return err + } + + if o.CommitHash == "" { + return fmt.Errorf("must specify a commit hash when using --api-key. This is usually inferred automatically when running inside a git repository, but you may also pass it manually with --commit-hash") + } + if err := types.CommitHash(o.CommitHash).IsValid(); err != nil { + return err + } + } + return nil +} + func (o *Xeol) PostLoad() error { - return o.parseLookaheadOption() + if err := o.parseLookaheadOption(); err != nil { + return err + } + return o.parseProjectAndCommitOption() } diff --git a/cmd/xeol/internal/types/commit.go b/cmd/xeol/internal/types/commit.go new file mode 100644 index 00000000..d8951672 --- /dev/null +++ b/cmd/xeol/internal/types/commit.go @@ -0,0 +1,16 @@ +package types + +import ( + "fmt" + "regexp" +) + +type CommitHash string + +func (c CommitHash) IsValid() error { + re := regexp.MustCompile(`^[a-fA-F0-9]{40}$`) + if !re.MatchString(string(c)) { + return fmt.Errorf("invalid SHA1 hash format for commit hash '%s'", string(c)) + } + return nil +} diff --git a/cmd/xeol/internal/types/commit_test.go b/cmd/xeol/internal/types/commit_test.go new file mode 100644 index 00000000..3258599a --- /dev/null +++ b/cmd/xeol/internal/types/commit_test.go @@ -0,0 +1,27 @@ +package types + +import ( + "testing" +) + +func TestCommitHash_IsValid(t *testing.T) { + tests := []struct { + name string + hash CommitHash + wantErr bool + }{ + {"Valid SHA1", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", false}, + {"Invalid SHA1 - Short", "a94a8fe5cc", true}, + {"Invalid SHA1 - Long", "a94a8fe5ccb19ba61c4c0873d391e9879", true}, + {"Invalid SHA1 - Special Characters", "a94a8fe5cc#19ba61c4c0873d391e9$", true}, + {"Invalid SHA1 - Empty", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.hash.IsValid(); (err != nil) != tt.wantErr { + t.Errorf("CommitHash.IsValid() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cmd/xeol/internal/types/projectname.go b/cmd/xeol/internal/types/projectname.go new file mode 100644 index 00000000..fa6e84cf --- /dev/null +++ b/cmd/xeol/internal/types/projectname.go @@ -0,0 +1,16 @@ +package types + +import ( + "fmt" + "regexp" +) + +type ProjectName string + +func (p ProjectName) IsValid() error { + re := regexp.MustCompile(`^(gitlab|github|azure)//([a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+(/[a-zA-Z0-9\-_]+)?)$`) + if ok := re.MatchString(string(p)); !ok { + return fmt.Errorf("invalid project name. Accepted formats: 'gitlab///', 'github///', 'azure////'") + } + return nil +} diff --git a/cmd/xeol/internal/types/projectname_test.go b/cmd/xeol/internal/types/projectname_test.go new file mode 100644 index 00000000..9a8c1d58 --- /dev/null +++ b/cmd/xeol/internal/types/projectname_test.go @@ -0,0 +1,46 @@ +package types + +import "testing" + +func TestIsValidProjectName(t *testing.T) { + tests := []struct { + projectName string + wantErr bool + }{ + { + projectName: "gitlab//noqcks/test", + wantErr: false, + }, + { + projectName: "github//noqcks/test", + wantErr: false, + }, + { + projectName: "azure//noqcks/test", + wantErr: false, + }, + { + projectName: "azure//noqcks/test/test", + wantErr: false, + }, + { + projectName: "azure//noqcks/test/test/test", + wantErr: true, + }, + { + projectName: "azure//noqcks", + wantErr: true, + }, + { + projectName: "test//test", + wantErr: true, + }, + } + + for _, test := range tests { + err := ProjectName(test.projectName).IsValid() + if test.wantErr && err == nil { + t.Errorf("Expected error for '%s', but got nil", test.projectName) + } + } +}