diff --git a/README.md b/README.md index e2a10ea55..2c57820a0 100644 --- a/README.md +++ b/README.md @@ -184,9 +184,9 @@ - [Using XSC Services](#using-xsc-services) - [Fetching XSC's Version](#fetching-xscs-version) - [Report XSC analytics metrics](#report-xsc-analytics-metrics) - - [Add analytics general event](#add-analytics-general-event) - - [Update analytics general event](#update-analytics-general-event) - - [Get analytics general event](#get-analytics-general-event) + - [Add Analytics General Event](#add-analytics-general-event) + - [Update Analytics General Event](#update-analytics-general-event) + - [Get Analytics General Event](#get-analytics-general-event) - [Pipelines APIs](#pipelines-apis) - [Creating Pipelines Service Manager](#creating-pipelines-service-manager) - [Creating Pipelines Details](#creating-pipelines-details) @@ -2468,8 +2468,8 @@ xscManager, err := xsc.New(serviceConfig) version, err := xscManager.GetVersion() ``` -#### Report XSC analytics metrics -##### Add analytics general event +#### Report XSC Analytics Metrics +##### Add Analytics General Event Sent XSC a new event which contains analytics data, and get multi-scan id back from XSC. ```go event := services.XscAnalyticsGeneralEvent{ @@ -2486,7 +2486,7 @@ event := services.XscAnalyticsGeneralEvent{ }} msi, err := xscManager.AddAnalyticsGeneralEvent(event) ``` -##### Update analytics general event +##### Update Analytics General Event Sent XSC a finalized analytics metrics event with information matching an existing event's msi. ```go finalizeEvent := services.XscAnalyticsGeneralEventFinalize{ @@ -2501,7 +2501,7 @@ finalizeEvent := services.XscAnalyticsGeneralEventFinalize{ err := xscManager.UpdateAnalyticsGeneralEvent(finalizeEvent) ``` -##### Get analytics general event +##### Get Analytics General Event Get a general event from XSC matching the provided msi. ```go event, err := xscManager.GetAnalyticsGeneralEvent(msi) diff --git a/tests/testdata/configprofile/configProfileExample.json b/tests/testdata/configprofile/configProfileExample.json new file mode 100644 index 000000000..e4fb139cf --- /dev/null +++ b/tests/testdata/configprofile/configProfileExample.json @@ -0,0 +1,49 @@ +{ + "profile_name": "default-profile", + "frogbot_config": { + "email_author": "my-user@jfrog.com", + "aggregate_fixes": true, + "avoid_previous_pr_comments_deletion": true, + "branch_name_template": "frogbot-${IMPACTED_PACKAGE}-${BRANCH_NAME_HASH}", + "pr_title_template": "[🐸 Frogbot] Upgrade {IMPACTED_PACKAGE} to {FIX_VERSION}", + "pr_comment_title": "Frogbot notes:", + "commit_message_template": "Upgrade {IMPACTED_PACKAGE} to {FIX_VERSION}", + "show_secrets_as_pr_comment": false + }, + "modules": [ + { + "module_name": "default-module", + "path_from_root": ".", + "releases_repo": "nuget-remote", + "analyzer_manager_version": "1.8.1", + "additional_paths_for_module": ["lib1", "utils/lib2"], + "exclude_paths": ["**/.git/**", "**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/target/**"], + "scan_config": { + "scan_timeout": 600, + "exclude_pattern": "*.md", + "enable_sca_scan": true, + "enable_contextual_analysis_scan": true, + "sast_scanner_config": { + "enable_sast_scan": true + }, + "secrets_scanner_config": { + "enable_secrets_scan": true + }, + "iac_scanner_config": { + "enable_iac_scan": true + }, + "applications_scanner_config": { + "enable_applications_scan": true + }, + "services_scanner_config": { + "enable_services_scan": true + } + }, + "protected_branches": ["main", "master"], + "include_exclude_mode": 0, + "include_exclude_pattern": "*test*", + "report_analytics": true + } + ], + "is_default": true +} \ No newline at end of file diff --git a/tests/xscconfigprofile_test.go b/tests/xscconfigprofile_test.go new file mode 100644 index 000000000..44c5932ba --- /dev/null +++ b/tests/xscconfigprofile_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "encoding/json" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/xsc/services" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "os" + "testing" +) + +func TestGetConfigurationProfile(t *testing.T) { + initXscTest(t, services.ConfigProfileMinXscVersion) + + mockServer, configProfileService := createXscMockServerForConfigProfile(t) + defer mockServer.Close() + + configProfile, err := configProfileService.GetConfigurationProfile("default-test-profile") + assert.NoError(t, err) + + profileFileContent, err := os.ReadFile("testdata/configprofile/configProfileExample.json") + assert.NoError(t, err) + var configProfileForComparison services.ConfigProfile + err = json.Unmarshal(profileFileContent, &configProfileForComparison) + assert.NoError(t, err) + assert.Equal(t, &configProfileForComparison, configProfile) +} + +func createXscMockServerForConfigProfile(t *testing.T) (mockServer *httptest.Server, configProfileService *services.ConfigurationProfileService) { + mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/xsc/api/v1/profile/default-test-profile" && r.Method == http.MethodGet { + w.WriteHeader(http.StatusOK) + content, err := os.ReadFile("testdata/configprofile/configProfileExample.json") + assert.NoError(t, err) + _, err = w.Write(content) + assert.NoError(t, err) + } else { + assert.Fail(t, "received an unexpected request") + } + })) + + xscDetails := GetXscDetails() + xscDetails.SetUrl(mockServer.URL + "/xsc") + xscDetails.SetAccessToken("") + + client, err := jfroghttpclient.JfrogClientBuilder().Build() + assert.NoError(t, err) + + configProfileService = services.NewConfigurationProfileService(client) + configProfileService.XscDetails = xscDetails + return +} diff --git a/tests/xsclogerrorevent_test.go b/tests/xsclogerrorevent_test.go index 13b834428..c56273633 100644 --- a/tests/xsclogerrorevent_test.go +++ b/tests/xsclogerrorevent_test.go @@ -14,7 +14,7 @@ const errorMessageContentForTest = "THIS IS NOT A REAL ERROR! This Error is post func TestXscSendLogErrorEvent(t *testing.T) { initXscTest(t, services.LogErrorMinXscVersion) - mockServer, logErrorService := createXscMockServer(t) + mockServer, logErrorService := createXscMockServerForLogEvent(t) defer mockServer.Close() event := &services.ExternalErrorLog{ @@ -26,7 +26,7 @@ func TestXscSendLogErrorEvent(t *testing.T) { assert.NoError(t, logErrorService.SendLogErrorEvent(event)) } -func createXscMockServer(t *testing.T) (mockServer *httptest.Server, logErrorService *services.LogErrorEventService) { +func createXscMockServerForLogEvent(t *testing.T) (mockServer *httptest.Server, logErrorService *services.LogErrorEventService) { mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/xsc/api/v1/event/logMessage" && r.Method == http.MethodPost { var reqBody services.ExternalErrorLog @@ -42,8 +42,9 @@ func createXscMockServer(t *testing.T) (mockServer *httptest.Server, logErrorSer assert.Equal(t, errorMessageContentForTest, reqBody.Message) w.WriteHeader(http.StatusCreated) return + } else { + assert.Fail(t, "received an unexpected request") } - assert.Fail(t, "received an unexpected request") })) xscDetails := GetXscDetails() diff --git a/xsc/manager.go b/xsc/manager.go index 010dac24e..ff07b47b5 100644 --- a/xsc/manager.go +++ b/xsc/manager.go @@ -75,3 +75,9 @@ func (sm *XscServicesManager) GetAnalyticsGeneralEvent(msi string) (*services.Xs eventService.XscDetails = sm.config.GetServiceDetails() return eventService.GetGeneralEvent(msi) } + +func (sm *XscServicesManager) GetConfigProfile(profileName string) (*services.ConfigProfile, error) { + configProfileService := services.NewConfigurationProfileService(sm.client) + configProfileService.XscDetails = sm.config.GetServiceDetails() + return configProfileService.GetConfigurationProfile(profileName) +} diff --git a/xsc/services/profile.go b/xsc/services/profile.go new file mode 100644 index 000000000..7b750f91b --- /dev/null +++ b/xsc/services/profile.go @@ -0,0 +1,113 @@ +package services + +import ( + "encoding/json" + "fmt" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "net/http" +) + +const ( + ConfigProfileMinXscVersion = "1.11.0" + xscConfigProfileApi = "api/v1/profile" +) + +type ConfigurationProfileService struct { + client *jfroghttpclient.JfrogHttpClient + XscDetails auth.ServiceDetails +} + +func NewConfigurationProfileService(client *jfroghttpclient.JfrogHttpClient) *ConfigurationProfileService { + return &ConfigurationProfileService{client: client} +} + +type ConfigProfile struct { + ProfileName string `json:"profile_name"` + FrogbotConfig FrogbotConfig `json:"frogbot_config,omitempty"` + Modules []Module `json:"modules"` + IsDefault bool `json:"is_default,omitempty"` +} + +type FrogbotConfig struct { + EmailAuthor string `json:"email_author,omitempty"` + AggregateFixes bool `json:"aggregate_fixes,omitempty"` + AvoidPreviousPrCommentsDeletion bool `json:"avoid_previous_pr_comments_deletion,omitempty"` + BranchNameTemplate string `json:"branch_name_template,omitempty"` + PrTitleTemplate string `json:"pr_title_template,omitempty"` + PrCommentTitle string `json:"pr_comment_title,omitempty"` + CommitMessageTemplate string `json:"commit_message_template,omitempty"` + ShowSecretsAsPrComment bool `json:"show_secrets_as_pr_comment,omitempty"` +} + +type Module struct { + ModuleId int32 `json:"module_id,omitempty"` + ModuleName string `json:"module_name"` + PathFromRoot string `json:"path_from_root"` + ReleasesRepo string `json:"releases_repo,omitempty"` + AnalyzerManagerVersion string `json:"analyzer_manager_version,omitempty"` + AdditionalPathsForModule []string `json:"additional_paths_for_module,omitempty"` + ExcludePaths []string `json:"exclude_paths,omitempty"` + ScanConfig ScanConfig `json:"scan_config"` + ProtectedBranches []string `json:"protected_branches,omitempty"` + IncludeExcludeMode int32 `json:"include_exclude_mode,omitempty"` + IncludeExcludePattern string `json:"include_exclude_pattern,omitempty"` + ReportAnalytics bool `json:"report_analytics,omitempty"` +} + +type ScanConfig struct { + ScanTimeout int32 `json:"scan_timeout,omitempty"` + ExcludePattern string `json:"exclude_pattern,omitempty"` + EnableScaScan bool `json:"enable_sca_scan,omitempty"` + EnableContextualAnalysisScan bool `json:"enable_contextual_analysis_scan,omitempty"` + SastScannerConfig SastScannerConfig `json:"sast_scanner_config,omitempty"` + SecretsScannerConfig SecretsScannerConfig `json:"secrets_scanner_config,omitempty"` + IacScannerConfig IacScannerConfig `json:"iac_scanner_config,omitempty"` + ApplicationsScannerConfig ApplicationsScannerConfig `json:"applications_scanner_config,omitempty"` + ServicesScannerConfig ServicesScannerConfig `json:"services_scanner_config,omitempty"` +} + +type SastScannerConfig struct { + EnableSastScan bool `json:"enable_sast_scan,omitempty"` + Language string `json:"language,omitempty"` + ExcludePatterns []string `json:"exclude_patterns,omitempty"` + ExcludeRules []string `json:"exclude_rules,omitempty"` +} + +type SecretsScannerConfig struct { + EnableSecretsScan bool `json:"enable_secrets_scan,omitempty"` + ExcludePatterns []string `json:"exclude_patterns,omitempty"` +} + +type IacScannerConfig struct { + EnableIacScan bool `json:"enable_iac_scan,omitempty"` + ExcludePatterns []string `json:"exclude_patterns,omitempty"` +} + +type ApplicationsScannerConfig struct { + EnableApplicationsScan bool `json:"enable_applications_scan,omitempty"` + ExcludePatterns []string `json:"exclude_patterns,omitempty"` +} + +type ServicesScannerConfig struct { + EnableServicesScan bool `json:"enable_services_scan,omitempty"` + ExcludePatterns []string `json:"exclude_patterns,omitempty"` +} + +func (cp *ConfigurationProfileService) GetConfigurationProfile(profileName string) (*ConfigProfile, error) { + httpDetails := cp.XscDetails.CreateHttpClientDetails() + url := fmt.Sprintf("%s%s/%s", utils.AddTrailingSlashIfNeeded(cp.XscDetails.GetUrl()), xscConfigProfileApi, profileName) + res, body, _, err := cp.client.SendGet(url, true, &httpDetails) + if err != nil { + return nil, fmt.Errorf("failed to send GET query to '%s': %q", url, err) + } + if err = errorutils.CheckResponseStatusWithBody(res, body, http.StatusOK); err != nil { + return nil, err + } + + var profile ConfigProfile + err = errorutils.CheckError(json.Unmarshal(body, &profile)) + return &profile, err +}