diff --git a/cmd/root.go b/cmd/root.go index 0c79a9e..85ec901 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -69,6 +69,8 @@ Dependency-Track APK key permissions required: viper.GetString("project-name"), viper.GetString("project-version"), viper.GetStringSlice("project-tags"), + viper.GetString("parent-name"), + viper.GetString("parent-version"), viper.GetFloat64("dtrack-client-timeout"), viper.GetFloat64("sbom-upload-timeout-sec"), viper.GetFloat64("sbom-upload-check-interval-sec"), @@ -103,6 +105,8 @@ 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.StringP("parent-name", "", "", "Parent project name template (env: DT_PARENT_PROJECT_NAME)") + flags.StringP("parent-version", "", "", "Parent project version template (env: DT_PARENT_PROJECT_VERSION)") 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") @@ -112,6 +116,8 @@ func init() { 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("parent-name", flags.Lookup("parent-name")) + viper.BindPFlag("parent-version", flags.Lookup("parent-version")) 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")) diff --git a/cmd/server.go b/cmd/server.go index 061476d..440d54d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -23,6 +23,8 @@ var serverCmd = &cobra.Command{ viper.GetString("project-name"), viper.GetString("project-version"), viper.GetStringSlice("project-tags"), + viper.GetString("parent-name"), + viper.GetString("parent-version"), viper.GetFloat64("dtrack-client-timeout-sec"), viper.GetFloat64("sbom-upload-timeout-sec"), viper.GetFloat64("sbom-upload-check-interval-sec"), diff --git a/config/config.go b/config/config.go index ae27679..dd82ccc 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,9 @@ type Config struct { ProjectVersion string ProjectTags []string + ParentName string `json:"parentName,omitempty"` + ParentVersion string `json:"parentVersion,omitempty"` + DtrackClientTimeout time.Duration SBOMUploadTimeout time.Duration SBOMUploadCheckInterval time.Duration @@ -21,7 +24,7 @@ type Config struct { var ErrAPIKeyIsRequired = errors.New("api-key is required") -func New(baseURL, apiKey, projectName, projectVersion string, projectTags []string, dtrackClientTimeoutSec, sbomUploadTimeoutSec, sbomUploadCheckIntervalSec float64) *Config { +func New(baseURL, apiKey, projectName, projectVersion string, projectTags []string, parentName string, parentVersion string, dtrackClientTimeoutSec, sbomUploadTimeoutSec, sbomUploadCheckIntervalSec float64) *Config { if len(projectTags) == 1 && strings.Contains(projectTags[0], ",") { projectTags = strings.Split(projectTags[0], ",") } @@ -32,6 +35,8 @@ func New(baseURL, apiKey, projectName, projectVersion string, projectTags []stri ProjectName: projectName, ProjectVersion: projectVersion, ProjectTags: projectTags, + ParentName: parentName, + ParentVersion: parentVersion, 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 a5e0db2..0abbb6a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -13,6 +13,8 @@ func TestNew(t *testing.T) { projectName string projectVersion string projectTags []string + parentName string + parentVersion string dtrackClientTimeoutSec float64 sbomUploadTimeoutSec float64 sbomUploadCheckIntervalSec float64 @@ -30,6 +32,8 @@ func TestNew(t *testing.T) { projectName: "test-project", projectVersion: "1.0.0", projectTags: []string{"tag1", "tag2"}, + parentName: "TEST", + parentVersion: "1.0.0", dtrackClientTimeoutSec: 10, sbomUploadTimeoutSec: 30, sbomUploadCheckIntervalSec: 1, @@ -40,6 +44,8 @@ func TestNew(t *testing.T) { ProjectName: "test-project", ProjectVersion: "1.0.0", ProjectTags: []string{"tag1", "tag2"}, + ParentName: "TEST", + ParentVersion: "1.0.0", DtrackClientTimeout: time.Duration(10) * time.Second, SBOMUploadTimeout: time.Duration(30) * time.Second, SBOMUploadCheckInterval: time.Duration(1) * time.Second, @@ -53,6 +59,8 @@ func TestNew(t *testing.T) { projectName: "test-project", projectVersion: "1.0.0", projectTags: []string{"tag1,tag2"}, + parentName: "TEST", + parentVersion: "1.0.0", dtrackClientTimeoutSec: 10, sbomUploadTimeoutSec: 30, sbomUploadCheckIntervalSec: 1, @@ -63,6 +71,8 @@ func TestNew(t *testing.T) { ProjectName: "test-project", ProjectVersion: "1.0.0", ProjectTags: []string{"tag1", "tag2"}, + ParentName: "TEST", + ParentVersion: "1.0.0", DtrackClientTimeout: time.Duration(10) * time.Second, SBOMUploadTimeout: time.Duration(30) * time.Second, SBOMUploadCheckInterval: time.Duration(1) * time.Second, @@ -71,7 +81,7 @@ func TestNew(t *testing.T) { } 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, tt.args.dtrackClientTimeoutSec, tt.args.sbomUploadTimeoutSec, tt.args.sbomUploadCheckIntervalSec); !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.parentName, tt.args.parentVersion, tt.args.dtrackClientTimeoutSec, tt.args.sbomUploadTimeoutSec, tt.args.sbomUploadCheckIntervalSec); !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) @@ -85,6 +95,8 @@ func TestConfig_Validate(t *testing.T) { ProjectName string ProjectVersion string ProjectTags []string + ParentName string `json:"parentName,omitempty"` + ParentVersion string `json:"parentVersion,omitempty"` } tests := []struct { name string @@ -99,6 +111,8 @@ func TestConfig_Validate(t *testing.T) { ProjectName: "test-project", ProjectVersion: "1.0.0", ProjectTags: []string{"tag1", "tag2"}, + ParentName: "TEST", + ParentVersion: "1.0.0", }, wantErr: false, }, @@ -109,6 +123,8 @@ func TestConfig_Validate(t *testing.T) { ProjectName: "test-project", ProjectVersion: "1.0.0", ProjectTags: []string{"tag1", "tag2"}, + ParentName: "TEST", + ParentVersion: "1.0.0", }, wantErr: true, }, @@ -121,6 +137,8 @@ func TestConfig_Validate(t *testing.T) { ProjectName: tt.fields.ProjectName, ProjectVersion: tt.fields.ProjectVersion, ProjectTags: tt.fields.ProjectTags, + ParentName: tt.fields.ParentName, + ParentVersion: tt.fields.ParentVersion, } if err := c.Validate(); (err != nil) != tt.wantErr { t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/dependencytrack/dependencytrack.go b/dependencytrack/dependencytrack.go index d5f2178..62e0a86 100644 --- a/dependencytrack/dependencytrack.go +++ b/dependencytrack/dependencytrack.go @@ -11,7 +11,7 @@ import ( ) type DependencyTrackClient interface { - UploadBOM(ctx context.Context, projectName, projectVersion string, bom []byte) error + UploadBOM(ctx context.Context, projectName, projectVersion string, parentName string, parentVersion string, bom []byte) error AddTagsToProject(ctx context.Context, projectName, projectVersion string, tags []string) error } @@ -35,12 +35,14 @@ func New(baseURL, apiKey string, dtrackClientTimeout, sbomUploadTimeout, sbomUpl }, nil } -func (dt *DependencyTrack) UploadBOM(ctx context.Context, projectName, projectVersion string, bom []byte) error { +func (dt *DependencyTrack) UploadBOM(ctx context.Context, projectName, projectVersion string, parentName string, parentVersion string, bom []byte) error { log.Printf("Uploading BOM: project %s:%s", projectName, projectVersion) uploadToken, err := dt.Client.BOM.Upload(ctx, dtrack.BOMUploadRequest{ ProjectName: projectName, ProjectVersion: projectVersion, + ParentName: parentName, + ParentVersion: parentVersion, AutoCreate: true, BOM: base64.StdEncoding.EncodeToString(bom), }) diff --git a/mock/dependencytrack_mock.go b/mock/dependencytrack_mock.go index 57f3ede..c7229f5 100644 --- a/mock/dependencytrack_mock.go +++ b/mock/dependencytrack_mock.go @@ -49,15 +49,15 @@ func (mr *MockDependencyTrackClientMockRecorder) AddTagsToProject(ctx, projectNa } // UploadBOM mocks base method. -func (m *MockDependencyTrackClient) UploadBOM(ctx context.Context, projectName, projectVersion string, bom []byte) error { +func (m *MockDependencyTrackClient) UploadBOM(ctx context.Context, projectName, projectVersion, parentName, parentVersion string, bom []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UploadBOM", ctx, projectName, projectVersion, bom) + ret := m.ctrl.Call(m, "UploadBOM", ctx, projectName, projectVersion, parentName, parentVersion, bom) ret0, _ := ret[0].(error) return ret0 } // UploadBOM indicates an expected call of UploadBOM. -func (mr *MockDependencyTrackClientMockRecorder) UploadBOM(ctx, projectName, projectVersion, bom interface{}) *gomock.Call { +func (mr *MockDependencyTrackClientMockRecorder) UploadBOM(ctx, projectName, projectVersion, parentName, parentVersion, bom interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadBOM", reflect.TypeOf((*MockDependencyTrackClient)(nil).UploadBOM), ctx, projectName, projectVersion, bom) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadBOM", reflect.TypeOf((*MockDependencyTrackClient)(nil).UploadBOM), ctx, projectName, projectVersion, parentName, parentVersion, bom) } diff --git a/uploader/uploader.go b/uploader/uploader.go index ac4c62a..4003744 100644 --- a/uploader/uploader.go +++ b/uploader/uploader.go @@ -72,7 +72,17 @@ func (u *Upload) Run(ctx context.Context, input []byte) error { projectTags = append(projectTags, t) } - if err := u.dtrack.UploadBOM(ctx, projectName, projectVersion, sbom.BOM()); err != nil { + parentName, err := tpl.Render(u.config.ParentName) + if err != nil { + return err + } + + parentVersion, err := tpl.Render(u.config.ParentVersion) + if err != nil { + return err + } + + if err := u.dtrack.UploadBOM(ctx, projectName, projectVersion, parentName, parentVersion, sbom.BOM()); err != nil { return err } diff --git a/uploader/uploader_test.go b/uploader/uploader_test.go index 11bd2fb..4197599 100644 --- a/uploader/uploader_test.go +++ b/uploader/uploader_test.go @@ -33,6 +33,8 @@ func TestUpload_Run(t *testing.T) { projectName string projectVersion string projectTags []string + parentName string + parentVersion string err error } type mockAddTagsToProject struct { @@ -57,6 +59,8 @@ func TestUpload_Run(t *testing.T) { APIKey: "apiKey", ProjectName: "[[.sbomReport.report.artifact.repository]]", ProjectVersion: "[[.sbomReport.report.artifact.tag]]", + ParentName: "[[.sbomReport.metadata.namespace]]", + ParentVersion: "[[.sbomReport.report.artifact.tag]]", ProjectTags: []string{ "test", "kube_namespace:[[.sbomReport.metadata.namespace]]", @@ -67,6 +71,8 @@ func TestUpload_Run(t *testing.T) { enable: true, projectName: "library/alpine", projectVersion: "latest", + parentName: "default", + parentVersion: "latest", projectTags: []string{ "test", "kube_namespace:default", @@ -92,6 +98,8 @@ func TestUpload_Run(t *testing.T) { APIKey: "apiKey", ProjectName: "[[.sbomReport.report.artifact.repository]]", ProjectVersion: "[[.sbomReport.report.artifact.tag]]", + ParentName: "[[.sbomReport.metadata.namespace]]", + ParentVersion: "[[.sbomReport.report.artifact.tag]]", ProjectTags: []string{ "test", "kube_namespace:[[.sbomReport.metadata.namespace]]", @@ -107,6 +115,8 @@ func TestUpload_Run(t *testing.T) { APIKey: "apiKey", ProjectName: "[[.sbomReport.report.artifact.repository]]", ProjectVersion: "[[.sbomReport.report.artifact.tag]]", + ParentName: "[[.sbomReport.metadata.namespace]]", + ParentVersion: "[[.sbomReport.report.artifact.tag]]", ProjectTags: []string{}, }, input: sbomReportV1alpha1, @@ -114,6 +124,8 @@ func TestUpload_Run(t *testing.T) { enable: true, projectName: "library/alpine", projectVersion: "latest", + parentName: "default", + parentVersion: "latest", projectTags: []string{ "test", "kube_namespace:default", @@ -129,7 +141,7 @@ func TestUpload_Run(t *testing.T) { for _, tc := range testCases { if tc.mockUploadBOM.enable { - mockDTrack.EXPECT().UploadBOM(ctx, tc.mockUploadBOM.projectName, tc.mockUploadBOM.projectVersion, gomock.Any()).Return(tc.mockUploadBOM.err) + mockDTrack.EXPECT().UploadBOM(ctx, tc.mockUploadBOM.projectName, tc.mockUploadBOM.projectVersion, tc.mockUploadBOM.parentName, tc.mockUploadBOM.parentVersion, gomock.Any()).Return(tc.mockUploadBOM.err) } if tc.mockAddTagsToProject.enable { mockDTrack.EXPECT().AddTagsToProject(ctx, tc.mockAddTagsToProject.projectName, tc.mockAddTagsToProject.projectVersion, tc.mockAddTagsToProject.projectTags).Return(tc.mockAddTagsToProject.err)