diff --git a/cmd/root.go b/cmd/root.go index 5d4d3db..0c79a9e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -69,6 +69,9 @@ Dependency-Track APK key permissions required: viper.GetString("project-name"), viper.GetString("project-version"), viper.GetStringSlice("project-tags"), + viper.GetFloat64("dtrack-client-timeout"), + viper.GetFloat64("sbom-upload-timeout-sec"), + viper.GetFloat64("sbom-upload-check-interval-sec"), ) if err := c.Validate(); err != nil { return err @@ -100,12 +103,18 @@ func init() { flags.StringP("project-name", "", "[[.sbomReport.report.artifact.repository]]", "Project name template (env: DT_PROJECT_NAME)") flags.StringP("project-version", "", "[[.sbomReport.report.artifact.tag]]", "Project version template (env: DT_PROJECT_VERSION)") flags.StringSliceP("project-tags", "t", []string{}, "Project tags template (env: DT_PROJECT_TAGS (comma separated))") + flags.Float64P("dtrack-client-timeout", "", 10, "Dependency Track client timeout seconds") + flags.Float64P("sbom-upload-timeout-sec", "", 30, "Seconds to timeout waiting for completion of SBOM upload of Dependency Track") + flags.Float64P("sbom-upload-check-interval-sec", "", 1, "Interval seconds to check for completion of SBOM upload of Dependency Track") viper.BindPFlag("base-url", flags.Lookup("base-url")) viper.BindPFlag("api-key", flags.Lookup("api-key")) viper.BindPFlag("project-name", flags.Lookup("project-name")) viper.BindPFlag("project-version", flags.Lookup("project-version")) viper.BindPFlag("project-tags", flags.Lookup("project-tags")) + viper.BindPFlag("dtrack-client-timeout", flags.Lookup("dtrack-client-timeout")) + viper.BindPFlag("sbom-upload-timeout-sec", flags.Lookup("sbom-upload-timeout-sec")) + viper.BindPFlag("sbom-upload-check-interval-sec", flags.Lookup("sbom-upload-check-interval-sec")) } func Execute() error { diff --git a/cmd/server.go b/cmd/server.go index 438118d..061476d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -23,6 +23,9 @@ var serverCmd = &cobra.Command{ viper.GetString("project-name"), viper.GetString("project-version"), viper.GetStringSlice("project-tags"), + viper.GetFloat64("dtrack-client-timeout-sec"), + viper.GetFloat64("sbom-upload-timeout-sec"), + viper.GetFloat64("sbom-upload-check-interval-sec"), ) if err := c.Validate(); err != nil { return err diff --git a/config/config.go b/config/config.go index 99a762e..ae27679 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "strings" + "time" ) type Config struct { @@ -12,21 +13,28 @@ type Config struct { ProjectName string ProjectVersion string ProjectTags []string + + DtrackClientTimeout time.Duration + SBOMUploadTimeout time.Duration + SBOMUploadCheckInterval time.Duration } var ErrAPIKeyIsRequired = errors.New("api-key is required") -func New(baseURL, apiKey, projectName, projectVersion string, projectTags []string) *Config { +func New(baseURL, apiKey, projectName, projectVersion string, projectTags []string, dtrackClientTimeoutSec, sbomUploadTimeoutSec, sbomUploadCheckIntervalSec float64) *Config { if len(projectTags) == 1 && strings.Contains(projectTags[0], ",") { projectTags = strings.Split(projectTags[0], ",") } return &Config{ - BaseURL: baseURL, - APIKey: apiKey, - ProjectName: projectName, - ProjectVersion: projectVersion, - ProjectTags: projectTags, + BaseURL: baseURL, + APIKey: apiKey, + ProjectName: projectName, + ProjectVersion: projectVersion, + ProjectTags: projectTags, + DtrackClientTimeout: time.Duration(dtrackClientTimeoutSec) * time.Second, + SBOMUploadTimeout: time.Duration(sbomUploadTimeoutSec) * time.Second, + SBOMUploadCheckInterval: time.Duration(sbomUploadCheckIntervalSec) * time.Second, } } diff --git a/config/config_test.go b/config/config_test.go index 346b507..a5e0db2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,15 +3,19 @@ package config import ( "reflect" "testing" + "time" ) func TestNew(t *testing.T) { type args struct { - baseURL string - apiKey string - projectName string - projectVersion string - projectTags []string + baseURL string + apiKey string + projectName string + projectVersion string + projectTags []string + dtrackClientTimeoutSec float64 + sbomUploadTimeoutSec float64 + sbomUploadCheckIntervalSec float64 } tests := []struct { name string @@ -21,41 +25,53 @@ func TestNew(t *testing.T) { { name: "success", args: args{ - baseURL: "https://example.com", - apiKey: "12345", - projectName: "test-project", - projectVersion: "1.0.0", - projectTags: []string{"tag1", "tag2"}, + baseURL: "https://example.com", + apiKey: "12345", + projectName: "test-project", + projectVersion: "1.0.0", + projectTags: []string{"tag1", "tag2"}, + dtrackClientTimeoutSec: 10, + sbomUploadTimeoutSec: 30, + sbomUploadCheckIntervalSec: 1, }, want: &Config{ - BaseURL: "https://example.com", - APIKey: "12345", - ProjectName: "test-project", - ProjectVersion: "1.0.0", - ProjectTags: []string{"tag1", "tag2"}, + BaseURL: "https://example.com", + APIKey: "12345", + ProjectName: "test-project", + ProjectVersion: "1.0.0", + ProjectTags: []string{"tag1", "tag2"}, + DtrackClientTimeout: time.Duration(10) * time.Second, + SBOMUploadTimeout: time.Duration(30) * time.Second, + SBOMUploadCheckInterval: time.Duration(1) * time.Second, }, }, { name: "success tag separator is comma", args: args{ - baseURL: "https://example.com", - apiKey: "12345", - projectName: "test-project", - projectVersion: "1.0.0", - projectTags: []string{"tag1,tag2"}, + baseURL: "https://example.com", + apiKey: "12345", + projectName: "test-project", + projectVersion: "1.0.0", + projectTags: []string{"tag1,tag2"}, + dtrackClientTimeoutSec: 10, + sbomUploadTimeoutSec: 30, + sbomUploadCheckIntervalSec: 1, }, want: &Config{ - BaseURL: "https://example.com", - APIKey: "12345", - ProjectName: "test-project", - ProjectVersion: "1.0.0", - ProjectTags: []string{"tag1", "tag2"}, + BaseURL: "https://example.com", + APIKey: "12345", + ProjectName: "test-project", + ProjectVersion: "1.0.0", + ProjectTags: []string{"tag1", "tag2"}, + DtrackClientTimeout: time.Duration(10) * time.Second, + SBOMUploadTimeout: time.Duration(30) * time.Second, + SBOMUploadCheckInterval: time.Duration(1) * time.Second, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := New(tt.args.baseURL, tt.args.apiKey, tt.args.projectName, tt.args.projectVersion, tt.args.projectTags); !reflect.DeepEqual(got, tt.want) { + if got := New(tt.args.baseURL, tt.args.apiKey, tt.args.projectName, tt.args.projectVersion, tt.args.projectTags, tt.args.dtrackClientTimeoutSec, tt.args.sbomUploadTimeoutSec, tt.args.sbomUploadCheckIntervalSec); !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) diff --git a/dependencytrack/dependencytrack.go b/dependencytrack/dependencytrack.go index 18446fd..d5f2178 100644 --- a/dependencytrack/dependencytrack.go +++ b/dependencytrack/dependencytrack.go @@ -3,6 +3,7 @@ package dependencytrack import ( "context" "encoding/base64" + "fmt" "log" "time" @@ -16,16 +17,21 @@ type DependencyTrackClient interface { type DependencyTrack struct { Client *dtrack.Client + + SBOMUploadTimeout time.Duration + SBOMUploadCheckInterval time.Duration } -func New(baseURL, apiKey string, timeout time.Duration) (*DependencyTrack, error) { - client, err := dtrack.NewClient(baseURL, dtrack.WithAPIKey(apiKey), dtrack.WithTimeout(timeout)) +func New(baseURL, apiKey string, dtrackClientTimeout, sbomUploadTimeout, sbomUploadCheckInterval time.Duration) (*DependencyTrack, error) { + client, err := dtrack.NewClient(baseURL, dtrack.WithAPIKey(apiKey), dtrack.WithTimeout(dtrackClientTimeout)) if err != nil { return nil, err } return &DependencyTrack{ - Client: client, + Client: client, + SBOMUploadTimeout: sbomUploadTimeout, + SBOMUploadCheckInterval: sbomUploadCheckInterval, }, nil } @@ -53,7 +59,7 @@ func (dt *DependencyTrack) UploadBOM(ctx context.Context, projectName, projectVe close(errChan) }() - ticker := time.NewTicker(1 * time.Second) + ticker := time.NewTicker(dt.SBOMUploadCheckInterval) defer ticker.Stop() for { @@ -68,6 +74,9 @@ func (dt *DependencyTrack) UploadBOM(ctx context.Context, projectName, projectVe doneChan <- struct{}{} return } + case <-time.After(dt.SBOMUploadTimeout): + errChan <- fmt.Errorf("timeout exceeded") + return case <-ctx.Done(): errChan <- ctx.Err() return diff --git a/uploader/uploader.go b/uploader/uploader.go index 23a0c1d..ac4c62a 100644 --- a/uploader/uploader.go +++ b/uploader/uploader.go @@ -4,7 +4,6 @@ import ( "context" "errors" "log" - "time" "github.com/takumakume/sbomreport-to-dependencytrack/config" "github.com/takumakume/sbomreport-to-dependencytrack/dependencytrack" @@ -12,8 +11,6 @@ import ( tmpl "github.com/takumakume/sbomreport-to-dependencytrack/template" ) -const TIMEOUT = 10 - type Uploader interface { Run(ctx context.Context, input []byte) error } @@ -24,7 +21,7 @@ type Upload struct { } func New(c *config.Config) (*Upload, error) { - dtrack, err := dependencytrack.New(c.BaseURL, c.APIKey, time.Duration(TIMEOUT)*time.Second) + dtrack, err := dependencytrack.New(c.BaseURL, c.APIKey, c.DtrackClientTimeout, c.SBOMUploadTimeout, c.SBOMUploadCheckInterval) if err != nil { return nil, err }