diff --git a/codefresh/cfclient/pipeline.go b/codefresh/cfclient/pipeline.go index 0b7c9a9..c15bd69 100644 --- a/codefresh/cfclient/pipeline.go +++ b/codefresh/cfclient/pipeline.go @@ -90,6 +90,17 @@ type RuntimeEnvironment struct { RequiredAvailableStorage string `json:"requiredAvailableStorage,omitempty"` } +type ExternalResource struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Source string `json:"source"` + Context string `json:"context"` + Destination string `json:"destination"` + IsFolder bool `json:"isFolder"` + Repo string `json:"repo"` + Revision string `json:"revision"` +} + func (t *Trigger) SetVariables(variables map[string]interface{}, encrypted bool) { for key, value := range variables { t.Variables = append(t.Variables, Variable{Key: key, Value: value.(string), Encrypted: encrypted}) @@ -123,6 +134,7 @@ type Spec struct { Hooks *Hooks `json:"hooks,omitempty"` Options map[string]bool `json:"options,omitempty"` PermitRestartFromFailedSteps bool `json:"permitRestartFromFailedSteps,omitempty"` + ExternalResources []ExternalResource `json:"externalResources,omitempty"` } type Steps struct { @@ -149,7 +161,6 @@ func (d Hooks) MarshalJSON() ([]byte, error) { bytes := []byte(d.Hooks) return bytes, nil } - func (d *Steps) UnmarshalJSON(data []byte) error { d.Steps = string(data) return nil diff --git a/codefresh/resource_pipeline.go b/codefresh/resource_pipeline.go index 0da0da6..35adc3b 100644 --- a/codefresh/resource_pipeline.go +++ b/codefresh/resource_pipeline.go @@ -641,6 +641,58 @@ Pipeline concurrency policy: Builds on 'Pending Approval' state should be: }, }, }, + "external_resource": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id" : { + Type: schema.TypeString, + Computed: true, + }, + "type" : { + Type: schema.TypeString, + Optional: true, + Description: "Type of the external resource. Currently only 'git' is supported", + ValidateFunc: validation.StringInSlice([]string{ + "git", + }, false), + Default: "git", + }, + "repo" : { + Type: schema.TypeString, + Required: true, + Description: "git repository url", + }, + "context" : { + Type: schema.TypeString, + Required: true, + Description: "Context name for the git repository", + }, + "revision": { + Type: schema.TypeString, + Required: true, + Description: "Revision/branch in the git repository", + }, + "is_folder": { + Type: schema.TypeBool, + Description: "Whether or not the resource specified in source_path is a folder", + Optional: true, + Default: false, + }, + "source_path": { + Type: schema.TypeString, + Description: "The source folder in the repository (use relative path)", + Required: true, + }, + "target_path": { + Type: schema.TypeString, + Description: "The target folder in the pipeline workspace where the file/folder will be copied to (use absolute path)", + Required: true, + }, + }, + }, + }, }, }, }, @@ -832,6 +884,10 @@ func flattenSpec(spec cfclient.Spec) []map[string]interface{} { m["runtime_environment"] = flattenSpecRuntimeEnvironment(spec.RuntimeEnvironment) } + if len(spec.ExternalResources) > 0 { + m["external_resource"] = flattenExternalResources(spec.ExternalResources) + } + if len(spec.TerminationPolicy) > 0 { m["termination_policy"] = flattenSpecTerminationPolicy(spec.TerminationPolicy) } @@ -988,6 +1044,25 @@ func flattenCronTriggers(cronTriggers []cfclient.CronTrigger) []map[string]inter return res } +func flattenExternalResources(externalResources []cfclient.ExternalResource) []map[string]interface{} { + var res = make([]map[string]interface{}, len(externalResources)) + for i, externalResource := range externalResources { + m := make(map[string]interface{}) + m["type"] = externalResource.Type + m["repo"] = externalResource.Repo + m["context"] = externalResource.Context + m["source_path"] = externalResource.Source + m["target_path"] = externalResource.Destination + m["revision"] = externalResource.Revision + m["is_folder"] = externalResource.IsFolder + m["id"] = externalResource.ID + + res[i] = m + } + + return res +} + func mapResourceToPipeline(d *schema.ResourceData) (*cfclient.Pipeline, error) { tags := d.Get("tags").(*schema.Set).List() @@ -1146,6 +1221,23 @@ func mapResourceToPipeline(d *schema.ResourceData) (*cfclient.Pipeline, error) { } } + if externalResources, ok := d.GetOk("spec.0.external_resource"); ok { + for idx := range externalResources.([]interface{}) { + codefreshExternalResource := cfclient.ExternalResource{ + Type: d.Get(fmt.Sprintf("spec.0.external_resource.%v.type", idx)).(string), + Repo: d.Get(fmt.Sprintf("spec.0.external_resource.%v.repo", idx)).(string), + Revision: d.Get(fmt.Sprintf("spec.0.external_resource.%v.revision", idx)).(string), + Context: d.Get(fmt.Sprintf("spec.0.external_resource.%v.context", idx)).(string), + Source: d.Get(fmt.Sprintf("spec.0.external_resource.%v.source_path", idx)).(string), + Destination: d.Get(fmt.Sprintf("spec.0.external_resource.%v.target_path", idx)).(string), + IsFolder: d.Get(fmt.Sprintf("spec.0.external_resource.%v.is_folder", idx)).(bool), + ID: d.Get(fmt.Sprintf("spec.0.external_resource.%v.id", idx)).(string), + } + + pipeline.Spec.ExternalResources = append(pipeline.Spec.ExternalResources, codefreshExternalResource) + } + } + var codefreshTerminationPolicy []map[string]interface{} if _, ok := d.GetOk("spec.0.termination_policy.0.on_create_branch"); ok { diff --git a/codefresh/resource_pipeline_test.go b/codefresh/resource_pipeline_test.go index 9c82d82..8e52d61 100644 --- a/codefresh/resource_pipeline_test.go +++ b/codefresh/resource_pipeline_test.go @@ -700,6 +700,61 @@ func TestAccCodefreshPipeline_IsPublic(t *testing.T) { }) } +func TestAccCodefreshPipeline_ExternalResources(t *testing.T) { + name := pipelineNamePrefix + acctest.RandString(10) + resourceName := "codefresh_pipeline.test" + var pipeline cfclient.Pipeline + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCodefreshPipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCodefreshPipelineExternalResources(name, "codefresh-contrib/react-sample-app", "./codefresh.yml", "master", "git", + "github", "codefresh-io/external-resources1", "master", "test.py", "/codefresh/volume/test.py", + "github2", "codefresh-io/external-resources2", "main", "test2.py", "/codefresh/volume/test2.py"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshPipelineExists(resourceName, &pipeline), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.context", "github"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.repo", "codefresh-io/external-resources1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.revision", "master"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.source_path", "test.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.target_path", "/codefresh/volume/test.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.context", "github2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.repo", "codefresh-io/external-resources2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.revision", "main"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.source_path", "test2.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.target_path", "/codefresh/volume/test2.py"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCodefreshPipelineExternalResources(name, "codefresh-contrib/react-sample-app", "./codefresh.yml", "master", "git", + "github2", "codefresh-io/external-resources2", "main", "test2.py", "/codefresh/volume/test2.py", + "github", "codefresh-io/external-resources1", "master", "test.py", "/codefresh/volume/test.py"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshPipelineExists(resourceName, &pipeline), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.context", "github"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.repo", "codefresh-io/external-resources1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.revision", "master"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.source_path", "test.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.1.target_path", "/codefresh/volume/test.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.context", "github2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.repo", "codefresh-io/external-resources2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.revision", "main"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.source_path", "test2.py"), + resource.TestCheckResourceAttr(resourceName, "spec.0.external_resource.0.target_path", "/codefresh/volume/test2.py"), + ), + }, + }, + }) +} + func TestAccCodefreshPipelineOnCreateBranchIgnoreTrigger(t *testing.T) { name := pipelineNamePrefix + acctest.RandString(10) resourceName := "codefresh_pipeline.test" @@ -1477,3 +1532,46 @@ resource "codefresh_pipeline" "test" { } `, rName, repo, path, revision, context, isPublic) } + +func testAccCodefreshPipelineExternalResources(rName, repo, path, revision, context, extResource1Context, extResource1Repo, extResource1Revision, extResourse1SourcePath, extResource1DestPath, extResource2Context, extResource2Repo, extResource2Revision, extResourse2SourcePath, extResource2DestPath string) string { + return fmt.Sprintf(` +resource "codefresh_pipeline" "test" { + + lifecycle { + ignore_changes = [ + revision + ] + } + + name = "%s" + + spec { + spec_template { + repo = %q + path = %q + revision = %q + context = %q + } + + external_resource { + context = %q + repo = %q + revision = %q + source_path = %q + target_path = %q + } + + external_resource { + context = %q + repo = %q + revision = %q + source_path = %q + target_path = %q + } + } +} +`, +rName, repo, path, revision, context, +extResource1Context, extResource1Repo ,extResource1Revision, extResourse1SourcePath, extResource1DestPath, +extResource2Context, extResource2Repo ,extResource2Revision, extResourse2SourcePath, extResource2DestPath) +} diff --git a/docs/resources/pipeline.md b/docs/resources/pipeline.md index c1b467c..bbc02e5 100644 --- a/docs/resources/pipeline.md +++ b/docs/resources/pipeline.md @@ -129,6 +129,7 @@ Optional: - `contexts` (List of String) A list of strings representing the contexts ([shared_configuration](https://codefresh.io/docs/docs/configure-ci-cd-pipeline/shared-configuration/)) to be configured for the pipeline. - `cron_trigger` (Block List) The pipeline's cron triggers. Conflicts with the deprecated [codefresh_pipeline_cron_trigger](https://registry.terraform.io/providers/codefresh-io/codefresh/latest/docs/resources/pipeline_cron_trigger) resource. (see [below for nested schema](#nestedblock--spec--cron_trigger)) - `encrypted_variables` (Map of String) Pipeline level encrypted variables. Please note that drift will not be detected for encrypted variables +- `external_resource` (Block List) (see [below for nested schema](#nestedblock--spec--external_resource)) - `options` (Block List, Max: 1) The options for the pipeline. (see [below for nested schema](#nestedblock--spec--options)) - `pack_id` (String) SAAS pack (`5cd1746617313f468d669013` for Small; `5cd1746717313f468d669014` for Medium; `5cd1746817313f468d669015` for Large; `5cd1746817313f468d669017` for XL; `5cd1746817313f468d669018` for XXL); `5cd1746817313f468d669020` for 4XL). - `permit_restart_from_failed_steps` (Boolean) Defines whether it is permitted to restart builds in this pipeline from failed step. Defaults to true @@ -185,6 +186,27 @@ Optional: + +### Nested Schema for `spec.external_resource` + +Required: + +- `context` (String) Context name for the git repository +- `repo` (String) git repository url +- `revision` (String) Revision/branch in the git repository +- `source_path` (String) The source folder in the repository (use relative path) +- `target_path` (String) The target folder in the pipeline workspace where the file/folder will be copied to (use absolute path) + +Optional: + +- `is_folder` (Boolean) Whether or not the resource specified in source_path is a folder +- `type` (String) Type of the external resource. Currently only 'git' is supported + +Read-Only: + +- `id` (String) The ID of this resource. + + ### Nested Schema for `spec.options`