diff --git a/pipelines/manager.go b/pipelines/manager.go index f6f723b5a..9e68826f7 100644 --- a/pipelines/manager.go +++ b/pipelines/manager.go @@ -136,3 +136,85 @@ func (sm *PipelinesServicesManager) CancelRun(runID int) error { runService.ServiceDetails = sm.config.GetServiceDetails() return runService.CancelRun(runID) } + +func (sm *PipelinesServicesManager) ValidatePipelineSources(data []byte) error { + validateService := services.NewValidateService(sm.client) + validateService.ServiceDetails = sm.config.GetServiceDetails() + return validateService.ValidatePipeline(data) +} + +func (sm *PipelinesServicesManager) WorkspaceSync(projectKey string) error { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.WorkspaceSync(projectKey) +} + +func (sm *PipelinesServicesManager) WorkspacePollSyncStatus() ([]services.WorkspacesResponse, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.WorkspacePollSyncStatus() +} + +func (sm *PipelinesServicesManager) GetWorkspacePipelines() (map[string]string, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + workspacesResponseStatus, err := sm.WorkspacePollSyncStatus() + if err != nil { + return nil, err + } + return workspaceService.GetWorkspacePipelines(workspacesResponseStatus) +} + +func (sm *PipelinesServicesManager) WorkspaceTriggerPipelines(branch, pipName string, isMultiBranch bool) error { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return sm.TriggerPipelineRun(branch, pipName, isMultiBranch) +} + +func (sm *PipelinesServicesManager) GetWorkspaceRunIDs(pipelineNames []string) ([]services.PipelinesRunID, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.WorkspaceRunIDs(pipelineNames) +} + +func (sm *PipelinesServicesManager) GetWorkspaceRunStatus(pipeRunID int) ([]byte, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.WorkspaceRunStatus(pipeRunID) +} + +func (sm *PipelinesServicesManager) GetWorkspaceStepStatus(pipeRunID int) ([]byte, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.WorkspaceStepStatus(pipeRunID) +} + +func (sm *PipelinesServicesManager) ValidateWorkspace(data []byte) error { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.ValidateWorkspace(data) +} + +func (sm *PipelinesServicesManager) GetStepConsoles(stepID string) (map[string][]services.Console, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.GetStepLogsUsingStepID(stepID) +} + +func (sm *PipelinesServicesManager) GetStepletConsoles(stepID string) (map[string][]services.Console, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.GetStepletLogsUsingStepID(stepID) +} + +func (sm *PipelinesServicesManager) GetWorkspace() ([]services.WorkspacesResponse, error) { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.GetWorkspace() +} + +func (sm *PipelinesServicesManager) DeleteWorkspace(projectKey string) error { + workspaceService := services.NewWorkspaceService(sm.client) + workspaceService.ServiceDetails = sm.config.GetServiceDetails() + return workspaceService.DeleteWorkspace(projectKey) +} diff --git a/pipelines/services/run.go b/pipelines/services/run.go index 15e69242b..395a211cf 100644 --- a/pipelines/services/run.go +++ b/pipelines/services/run.go @@ -13,6 +13,7 @@ import ( "net/http" "strconv" "strings" + "time" ) type RunService struct { @@ -99,19 +100,29 @@ func (rs *RunService) TriggerPipelineRun(branch, pipeline string, isMultiBranch return err } - // Prepare Request - resp, body, err := rs.client.SendPost(uri, buf.Bytes(), &httpDetails) - if err != nil { - return err + pollingAction := func() (shouldStop bool, responseBody []byte, err error) { + // Prepare Request + resp, body, err := rs.client.SendPost(uri, buf.Bytes(), &httpDetails) + if err != nil { + return true, body, err + } + + // Response Analysis + if err := errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return false, body, err + } + log.Info(fmt.Sprintf("Triggered successfully\n%s %s \n%14s %s", "PipelineName:", pipeline, "Branch:", branch)) + return true, body, err } - - // Response Analysis - if err := errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { - return err + pollingExecutor := &httputils.PollingExecutor{ + Timeout: 2 * time.Minute, + PollingInterval: 5 * time.Second, + PollingAction: pollingAction, + MsgPrefix: "Get pipeline workspace sync status...", } - log.Info(fmt.Sprintf("Triggered successfully\n%s %s \n%14s %s", "PipelineName :", pipeline, "Branch :", branch)) - - return nil + // Polling execution + _, err = pollingExecutor.Execute() + return err } func (rs *RunService) CancelRun(runID int) error { diff --git a/pipelines/services/types.go b/pipelines/services/types.go index 9e9b332ef..ca2bd1a2f 100644 --- a/pipelines/services/types.go +++ b/pipelines/services/types.go @@ -87,3 +87,70 @@ type PipelineResources struct { CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` } + +type WorkspacesResponse struct { + ValuesYmlPropertyBag interface{} `json:"valuesYmlPropertyBag,omitempty"` + PipelinesYmlPropertyBag PipelinesYmlPropertyBag `json:"pipelinesYmlPropertyBag,omitempty"` + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + ProjectID int `json:"projectId,omitempty"` + IsSyncing *bool `json:"isSyncing,omitempty"` + LastSyncStatusCode int `json:"lastSyncStatusCode,omitempty"` + LastSyncStartedAt time.Time `json:"lastSyncStartedAt,omitempty"` + LastSyncEndedAt interface{} `json:"lastSyncEndedAt,omitempty"` + LastSyncLogs string `json:"lastSyncLogs,omitempty"` + CreatedBy int `json:"createdBy,omitempty"` +} + +type PipelinesYmlPropertyBag struct { + Resources []Resources `json:"resources,omitempty"` + Pipelines []Pipelines `json:"pipelines,omitempty"` +} + +type Resources struct { + Configuration Configuration `json:"configuration,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} + +type Configuration struct { + Branches Branches `json:"branches,omitempty"` + GitProvider string `json:"gitProvider,omitempty"` + Path string `json:"path,omitempty"` +} + +type Branches struct { + Include string `json:"include,omitempty"` +} + +type PipelinesRunID struct { + LatestRunID int `json:"latestRunId,omitempty"` + Name string `json:"name,omitempty"` + SyntaxVersion string `json:"syntaxVersion,omitempty"` +} + +type Console struct { + ConsoleID string `json:"consoleId,omitempty"` + IsSuccess *bool `json:"isSuccess,omitempty"` + IsShown interface{} `json:"isShown,omitempty"` + ParentConsoleID string `json:"parentConsoleId,omitempty"` + StepletID int `json:"stepletId,omitempty"` + PipelineID int `json:"pipelineId,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` + TimestampEndedAt interface{} `json:"timestampEndedAt,omitempty"` + Type string `json:"type,omitempty"` + Message string `json:"message,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` +} + +// Validation Types +type ValidationResponse struct { + IsValid *bool `json:"isValid,omitempty"` + Errors []ValidationErrors `json:"errors,omitempty"` +} + +type ValidationErrors struct { + Text string `json:"text,omitempty"` + LineNumber int `json:"lineNumber,omitempty"` +} diff --git a/pipelines/services/validate.go b/pipelines/services/validate.go new file mode 100644 index 000000000..74afff134 --- /dev/null +++ b/pipelines/services/validate.go @@ -0,0 +1,115 @@ +package services + +import ( + "encoding/json" + "errors" + "github.com/gookit/color" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" + "github.com/jfrog/jfrog-client-go/utils/log" + "net/http" + "net/url" + "strconv" +) + +const ( + validatePipResourcePath = "api/v1/validateYaml" +) + +type ValidateService struct { + client *jfroghttpclient.JfrogHttpClient + auth.ServiceDetails +} + +func NewValidateService(client *jfroghttpclient.JfrogHttpClient) *ValidateService { + return &ValidateService{client: client} +} + +func (vs *ValidateService) getHttpDetails() httputils.HttpClientDetails { + httpDetails := vs.CreateHttpClientDetails() + return httpDetails +} + +func (vs *ValidateService) ValidatePipeline(data []byte) error { + var err error + httpDetails := vs.getHttpDetails() + + // Query params + m := make(map[string]string, 0) + + // URL Construction + uri := vs.constructValidateAPIURL(m, validatePipResourcePath) + + // Headers + headers := make(map[string]string) + utils.SetContentType("application/json", &headers) + httpDetails.Headers = headers + + // Send post request + resp, body, err := vs.client.SendPost(uri, data, &httpDetails) + if err != nil { + return err + } + if err := errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return err + } + + // Process response + if nil != resp && resp.StatusCode == http.StatusOK { + log.Info("Processing resources yaml response ") + log.Info(string(body)) + err = processValidatePipResourceResponse(body) + if err != nil { + log.Error("Failed to process resources validation response") + } + } + return nil +} + +// processValidatePipResourceResponse process validate pipeline resource response +func processValidatePipResourceResponse(resp []byte) error { + validationResponse := make(map[string]ValidationResponse) + err := json.Unmarshal(resp, &validationResponse) + if err != nil { + return errorutils.CheckError(err) + } + if len(validationResponse) == 0 { + return errorutils.CheckErrorf("pipelines not found") + } + for k, v := range validationResponse { + if v.IsValid != nil && *v.IsValid { + log.Info("Validation of pipeline resources completed successfully") + msg := color.Green.Sprintf("Validation completed") + log.Info(msg) + } else { + fileName := color.Red.Sprintf("%s", k) + log.Error(fileName) + validationErrors := v.Errors + for _, validationError := range validationErrors { + lineNum := validationError.LineNumber + errorMessage := color.Red.Sprintf("%s", validationError.Text+":"+strconv.Itoa(lineNum)) + log.Error(errorMessage) + } + } + } + return nil +} + +// constructPipelinesURL creates URL with all required details to make api call +// like headers, queryParams, apiPath +func (vs *ValidateService) constructValidateAPIURL(qParams map[string]string, apiPath string) string { + uri, err := url.Parse(vs.ServiceDetails.GetUrl() + apiPath) + if err != nil { + log.Error("Failed to parse pipelines fetch run status url") + } + + queryString := uri.Query() + for k, v := range qParams { + queryString.Set(k, v) + } + uri.RawQuery = queryString.Encode() + + return uri.String() +} diff --git a/pipelines/services/workspace.go b/pipelines/services/workspace.go new file mode 100644 index 000000000..4b88ff423 --- /dev/null +++ b/pipelines/services/workspace.go @@ -0,0 +1,341 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" + "github.com/jfrog/jfrog-client-go/utils/log" + "net/http" + "strconv" + "strings" + "time" +) + +const ( + workspaces = "api/v1/workspaces" + deleteWorkspace = "api/v1/workspaces/:project" + validateWorkspace = "api/v1/validateWorkspace" + workspaceSync = "api/v1/syncWorkspace" + workspacePipelines = "api/v1/pipelines" + workspaceRuns = "api/v1/runs" + workspaceSteps = "api/v1/steps" + stepConsoles = "api/v1/steps/:stepID/consoles" + stepletConsoles = "api/v1/steplets/:stepID/consoles" +) + +type WorkspaceService struct { + client *jfroghttpclient.JfrogHttpClient + auth.ServiceDetails +} + +func NewWorkspaceService(client *jfroghttpclient.JfrogHttpClient) *WorkspaceService { + return &WorkspaceService{client: client} +} + +func (ws *WorkspaceService) getHttpDetails() httputils.HttpClientDetails { + return ws.CreateHttpClientDetails() +} + +func (ws *WorkspaceService) GetWorkspace() ([]WorkspacesResponse, error) { + httpDetails := ws.getHttpDetails() + // Query params + queryParams := make(map[string]string) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.ServiceDetails.GetUrl(), workspaces) + if err != nil { + return nil, err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return nil, err + } + // Response Analysis + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return nil, err + } + wsStatusResp := make([]WorkspacesResponse, 0) + err = json.Unmarshal(body, &wsStatusResp) + return wsStatusResp, errorutils.CheckError(err) +} + +func (ws *WorkspaceService) DeleteWorkspace(projectName string) error { + httpDetails := ws.getHttpDetails() + deleteWorkspaceAPI := strings.Replace(deleteWorkspace, ":project", projectName, 1) + // Query params + queryParams := make(map[string]string, 0) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), deleteWorkspaceAPI) + if err != nil { + return err + } + // Prepare request + resp, body, err := ws.client.SendDelete(uri, nil, &httpDetails) + if err != nil { + return err + } + // Response analysis + err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) + return err +} + +func (ws *WorkspaceService) ValidateWorkspace(data []byte) error { + httpDetails := ws.getHttpDetails() + // Query params + queryParams := make(map[string]string) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), validateWorkspace) + if err != nil { + return err + } + // Headers + headers := make(map[string]string) + utils.SetContentType("application/json", &headers) + headers["User-Agent"] = "jfrog-client-go/1.24.3" + httpDetails.Headers = headers + // Prepare request + resp, body, err := ws.client.SendPost(uri, data, &httpDetails) + if err != nil { + return err + } + // Response analysis + return errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) +} + +func (ws *WorkspaceService) WorkspaceSync(project string) error { + httpDetails := ws.getHttpDetails() + // Query params + queryParams := make(map[string]string) + queryParams["projectName"] = project + syncWorkspaceAPI := strings.Replace(workspaceSync, ":project", project, 1) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), syncWorkspaceAPI) + if err != nil { + return err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return err + } + // Response analysis + return errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) +} + +func (ws *WorkspaceService) GetWorkspaceRunIDs(pipelines []string) ([]PipelinesRunID, error) { + httpDetails := ws.getHttpDetails() + pipelineFilter := strings.Join(pipelines, ",") + // Query params + queryParams := map[string]string{ + "names": pipelineFilter, + "limit": "1", + "include": "latestRunId,name", + } + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), workspacePipelines) + if err != nil { + return nil, err + } + pollingAction := func() (shouldStop bool, responseBody []byte, err error) { + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return true, body, err + } + // Response analysis + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return false, body, err + } + pipeRunIDs := make([]PipelinesRunID, 0) + err = json.Unmarshal(body, &pipeRunIDs) + if err != nil { + return false, body, errorutils.CheckErrorf("failed to parse response to get run status") + } + for i := range pipeRunIDs { + if i > 0 { + break + } + if pipeRunIDs[i].LatestRunID == 0 { + return false, body, errorutils.CheckErrorf("pipeline didn't start running yet") + } + } + return true, body, err + } + pollingExecutor := &httputils.PollingExecutor{ + Timeout: 2 * time.Minute, + PollingInterval: 5 * time.Second, + PollingAction: pollingAction, + MsgPrefix: "Get pipeline workspace sync status...", + } + pipeRunIDs := make([]PipelinesRunID, 0) + // Polling execution + body, err := pollingExecutor.Execute() + if err != nil { + return pipeRunIDs, err + } + err = json.Unmarshal(body, &pipeRunIDs) + return pipeRunIDs, errorutils.CheckError(err) +} + +func (ws *WorkspaceService) GetWorkspaceRunStatus(pipelinesRunID int) ([]byte, error) { + httpDetails := ws.getHttpDetails() + // Query params + // TODO ADD include in query param if needed + queryParams := map[string]string{} + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.ServiceDetails.GetUrl(), workspaceRuns+"/"+strconv.Itoa(pipelinesRunID)) + if err != nil { + return nil, err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return nil, err + } + // Response analysis + err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) + return body, err +} + +func (ws *WorkspaceService) GetWorkspaceStepStatus(pipelinesRunID int) ([]byte, error) { + httpDetails := ws.getHttpDetails() + // Query params + queryParams := map[string]string{ + "runIds": strconv.Itoa(pipelinesRunID), + "limit": "15", + } + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.ServiceDetails.GetUrl(), workspaceSteps) + if err != nil { + return nil, err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return nil, err + } + // Response analysis + err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) + return body, err +} + +func (ws *WorkspaceService) GetWorkspacePipelines(workspaces []WorkspacesResponse) map[string]string { + pipelineNames := make(map[string]string, 1) + log.Info("Collecting pipeline names configured") + // Validate and return pipeline names and branch as map + if len(workspaces) > 0 && !(*workspaces[0].IsSyncing) { + pipelines := workspaces[0].PipelinesYmlPropertyBag.Pipelines + for _, pi := range pipelines { + pipelineNames[pi.Name] = pi.PipelineSourceBranch + } + } + return pipelineNames, nil +} + +func (ws *WorkspaceService) WorkspacePollSyncStatus() ([]WorkspacesResponse, error) { + httpDetails := ws.getHttpDetails() + // Query params + queryParams := make(map[string]string, 0) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), workspaces) + if err != nil { + return nil, err + } + pollingAction := func() (shouldStop bool, responseBody []byte, err error) { + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return false, body, err + } + // Response Analysis + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return false, body, err + } + log.Debug("response received ", string(body)) + wsStatusResp := make([]WorkspacesResponse, 0) + err = json.Unmarshal(body, &wsStatusResp) + if err != nil { + return true, body, errorutils.CheckError(err) + } + for i := range wsStatusResp { + if i > 0 { + break + } + if *wsStatusResp[i].IsSyncing { + fmt.Printf("%+v \n", wsStatusResp) + return false, body, err + } else if wsStatusResp[i].LastSyncStatusCode == 4003 || wsStatusResp[i].LastSyncStatusCode == 4004 { + return true, body, err + } + } + return true, body, err + } + pollingExecutor := &httputils.PollingExecutor{ + Timeout: 2 * time.Minute, + PollingInterval: 5 * time.Second, + PollingAction: pollingAction, + MsgPrefix: "Get pipeline workspace sync status...", + } + // Polling execution + body, err := pollingExecutor.Execute() + if err != nil { + return nil, err + } + workspaceStatusResponse := make([]WorkspacesResponse, 0) + err = json.Unmarshal(body, &workspaceStatusResponse) + return workspaceStatusResponse, errorutils.CheckError(err) +} + +// GetStepLogsUsingStepID retrieve steps logs using step id +func (ws *WorkspaceService) GetStepLogsUsingStepID(stepID string) (map[string][]Console, error) { + httpDetails := ws.getHttpDetails() + stepConsolesAPI := strings.Replace(stepConsoles, ":stepID", stepID, 1) + // Query params + queryParams := make(map[string]string, 0) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.GetUrl(), stepConsolesAPI) + if err != nil { + return nil, err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return nil, err + } + // Response Analysis + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return nil, err + } + consoles := make(map[string][]Console) + err = json.Unmarshal(body, &consoles) + return consoles, errorutils.CheckError(err) +} + +// GetStepletLogsUsingStepID retrieve steps logs using step id +func (ws *WorkspaceService) GetStepletLogsUsingStepID(stepID string) (map[string][]Console, error) { + httpDetails := ws.getHttpDetails() + stepConsolesAPI := strings.Replace(stepletConsoles, ":stepID", stepID, 1) + // Query params + queryParams := make(map[string]string, 0) + // URL construction + uri, err := constructPipelinesURL(queryParams, ws.ServiceDetails.GetUrl(), stepConsolesAPI) + if err != nil { + return nil, err + } + // Prepare request + resp, body, _, err := ws.client.SendGet(uri, true, &httpDetails) + if err != nil { + return nil, err + } + // Response Analysis + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + return nil, err + } + consoles := make(map[string][]Console) + err = json.Unmarshal(body, &consoles) + return consoles, errorutils.CheckError(err) +} diff --git a/tests/jfrogclient_test.go b/tests/jfrogclient_test.go index f48602460..20a70c71d 100644 --- a/tests/jfrogclient_test.go +++ b/tests/jfrogclient_test.go @@ -71,6 +71,7 @@ func setupIntegrationTests() { createPipelinesRunManager() createPipelinesSyncManager() createPipelinesSyncStatusManager() + createPipelinesWorkspaceServiceManager() } if *TestAccess { createAccessPingManager() diff --git a/tests/pipelinesworkspaces_test.go b/tests/pipelinesworkspaces_test.go new file mode 100644 index 000000000..efe6cf403 --- /dev/null +++ b/tests/pipelinesworkspaces_test.go @@ -0,0 +1,158 @@ +package tests + +import ( + "encoding/json" + "fmt" + "github.com/jfrog/jfrog-client-go/pipelines/services" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestPipelinesWorkspaceService(t *testing.T) { + t.Run("test trigger pipelines workspace when resources are not valid", TestWorkspaceValidationWhenPipelinesResourcesAreNotValid) + t.Run("test trigger pipelines workspace when resources are valid", TestWorkspaceValidationWhenPipelinesResourcesAreValid) + t.Run("test workspace sync when it fails for the first time and succeeds later", TestWorkspaceValidationFailureAndSucceedsWhenValidIntegrationCreated) +} + +func TestWorkspaceValidationWhenPipelinesResourcesAreNotValid(t *testing.T) { + + if !assert.NotEmpty(t, *PipelinesAccessToken, "cannot run pipelines tests without access token configured") { + return + } + filePath := "testdata/pipelines.yml" + + // get pipelines.yaml content + pipelineRes, err := getWorkspaceRunPayload([]string{filePath}) + if !assert.NoError(t, err) { + return + } + if !assert.NoError(t, err) { + return + } + err = testPipelinesWorkspaceService.ValidateWorkspace(pipelineRes) + assert.NoError(t, err) +} + +func TestWorkspaceValidationWhenPipelinesResourcesAreValid(t *testing.T) { + if !assert.NotEmpty(t, *PipelinesAccessToken, "cannot run pipelines tests without access token configured") { + return + } + filePath := "testdata/pipelines.yml" + + // get pipelines.yaml content + pipelineRes, err := getWorkspaceRunPayload([]string{filePath}) + if !assert.NoError(t, err) { + return + } + err = testPipelinesWorkspaceService.ValidateWorkspace(pipelineRes) + assert.NoError(t, err) + + wsResp, err := testPipelinesWorkspaceService.GetWorkspace() + assert.NoError(t, err) + if len(wsResp) < 1 { + assert.Fail(t, "No workspace created") + } + syncStatusResp, err := testPipelinesWorkspaceService.WorkspacePollSyncStatus() + assert.NoError(t, err) + for _, ws := range syncStatusResp { + fmt.Printf("%+v \n", ws) + err = testPipelinesWorkspaceService.DeleteWorkspace("default") + } + assert.NoError(t, err) +} + +func TestWorkspaceValidationFailureAndSucceedsWhenValidIntegrationCreated(t *testing.T) { + if !assert.NotEmpty(t, *PipelinesAccessToken, "cannot run pipelines tests without access token configured") { + return + } + filePath := "testdata/pipelines-integration.yml" + + // Get pipelines-integration.yaml content + pipelineRes, err := getWorkspaceRunPayload([]string{filePath}) + if !assert.NoError(t, err) { + return + } + // Call workspace validation + err = testPipelinesWorkspaceService.ValidateWorkspace(pipelineRes) + assert.Error(t, err) + + // Create valid integration required for pipelines + id, err := testsPipelinesIntegrationsService.CreateArtifactoryIntegration("int_workspace_artifactory", testsDummyRtUrl, testsDummyUser, testsDummyApiKey) + if !assert.NoError(t, err) { + return + } + defer deleteIntegrationAndAssert(t, id) + getIntegrationAndAssert(t, id, "int_workspace_artifactory", services.ArtifactoryName) + + // Validate workspace pipelines should be successful here + err = testPipelinesWorkspaceService.ValidateWorkspace(pipelineRes) + assert.NoError(t, err) + + wsResp, err := testPipelinesWorkspaceService.GetWorkspace() + assert.NoError(t, err) + if len(wsResp) < 1 { + assert.Fail(t, "No workspace created") + } + _, err = testPipelinesWorkspaceService.WorkspacePollSyncStatus() + if !assert.NoError(t, err) { + return + } + + err = testPipelinesWorkspaceService.WorkspaceSync("default") + assert.NoError(t, err) + syncStatusRespSuccess, err := testPipelinesWorkspaceService.WorkspacePollSyncStatus() + assert.NoError(t, err) + for _, ws := range syncStatusRespSuccess { + fmt.Printf("%+v \n", ws) + err = testPipelinesWorkspaceService.DeleteWorkspace("default") + } + assert.NoError(t, err) +} + +type PipelineDefinition struct { + FileName string `json:"fileName,omitempty"` + Content string `json:"content,omitempty"` + YmlType string `json:"ymlType,omitempty"` +} + +type WorkSpaceValidation struct { + ProjectId string `json:"-"` + Files []PipelineDefinition `json:"files,omitempty"` + ProjectName string `json:"projectName,omitempty"` + Name string `json:"name,omitempty"` +} + +func getWorkspaceRunPayload(resources []string) ([]byte, error) { + var pipelineDefinitions []PipelineDefinition + for _, pathToFile := range resources { + fileContent, fileInfo, err := getFileContentAndBaseName(pathToFile) + if err != nil { + return nil, err + } + pipeDefinition := PipelineDefinition{ + FileName: fileInfo.Name(), + Content: string(fileContent), + YmlType: "pipelines", + } + pipelineDefinitions = append(pipelineDefinitions, pipeDefinition) + } + workSpaceValidation := WorkSpaceValidation{ + Files: pipelineDefinitions, + ProjectName: "default", + Name: "", + } + return json.Marshal(workSpaceValidation) +} + +func getFileContentAndBaseName(pathToFile string) ([]byte, os.FileInfo, error) { + fileContent, err := os.ReadFile(pathToFile) + if err != nil { + return nil, nil, err + } + fileInfo, err := os.Stat(pathToFile) + if err != nil { + return nil, nil, err + } + return fileContent, fileInfo, nil +} diff --git a/tests/testdata/pip.yaml b/tests/testdata/pip.yaml new file mode 100644 index 000000000..c1ac9a8ff --- /dev/null +++ b/tests/testdata/pip.yaml @@ -0,0 +1,8 @@ +pipelines: +- name: test + steps: + - name: test1 + type: bash + execution: + onSuccess: + - echo "test" diff --git a/tests/testdata/pipelines-integration.yml b/tests/testdata/pipelines-integration.yml new file mode 100644 index 000000000..09fe954b7 --- /dev/null +++ b/tests/testdata/pipelines-integration.yml @@ -0,0 +1,24 @@ +pipelines: + - name: pipelines_run_int_test + steps: + - name: prepare_env + type: Bash + execution: + onExecute: + - cd /tmp + - mkdir -p "jfrog-cli" + - touch test-pilines.yaml + - echo "test" >> test-pipelines.yaml + - name: build_binary + type: Bash + configuration: + inputSteps: + - name: prepare_env + integrations: + - name: int_workspace_artifactory + execution: + onExecute: + - cd /tmp + - mkdir -p "target/pipelines" + - cd target/pipelines + - touch make_data \ No newline at end of file diff --git a/tests/testdata/pipelines.yml b/tests/testdata/pipelines.yml new file mode 100644 index 000000000..8c3d7f432 --- /dev/null +++ b/tests/testdata/pipelines.yml @@ -0,0 +1,22 @@ +pipelines: + - name: pipelines_run_int_test + steps: + - name: prepare_env + type: Bash + execution: + onExecute: + - cd /tmp + - mkdir -p "jfrog-cli" + - touch test-pilines.yaml + - echo "test" >> test-pipelines.yaml + - name: build_binary + type: Bash + configuration: + inputSteps: + - name: prepare_env + execution: + onExecute: + - cd /tmp + - mkdir -p "target/pipelines" + - cd target/pipelines + - touch make_data \ No newline at end of file diff --git a/tests/utils_test.go b/tests/utils_test.go index 7bd12c275..475edb5d6 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -117,6 +117,7 @@ var ( testPipelinesRunService *pipelinesServices.RunService testPipelinesSyncService *pipelinesServices.SyncService testPipelinesSyncStatusService *pipelinesServices.SyncStatusService + testPipelinesWorkspaceService *pipelinesServices.WorkspaceService // Access Services testsAccessPingService *accessServices.PingService @@ -519,6 +520,18 @@ func createPipelinesSyncStatusManager() { testPipelinesSyncStatusService.ServiceDetails = pipelinesDetails } +func createPipelinesWorkspaceServiceManager() { + pipelinesDetails := GetPipelinesDetails() + client, err := jfroghttpclient.JfrogClientBuilder(). + SetClientCertPath(pipelinesDetails.GetClientCertPath()). + SetClientCertKeyPath(pipelinesDetails.GetClientCertKeyPath()). + AppendPreRequestInterceptor(pipelinesDetails.RunPreRequestFunctions). + Build() + failOnHttpClientCreation(err) + testPipelinesWorkspaceService = pipelinesServices.NewWorkspaceService(client) + testPipelinesWorkspaceService.ServiceDetails = pipelinesDetails +} + func failOnHttpClientCreation(err error) { if err != nil { log.Error(fmt.Sprintf(HttpClientCreationFailureMessage, err.Error()))