Skip to content

Commit

Permalink
Feat: Add external resources to pipeline spec (#144)
Browse files Browse the repository at this point in the history
## What
Add external resources to pipeline spec
## Why
Closes #75
## Notes
<!-- Add any notes here -->

## Checklist

* [x] _I have read
[CONTRIBUTING.md](https://github.com/codefresh-io/terraform-provider-codefresh/blob/master/CONTRIBUTING.md)._
* [x] _I have [allowed changes to my fork to be
made](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)._
* [x] _I have added tests, assuming new tests are warranted_.
* [x] _I understand that the `/test` comment will be ignored by the CI
trigger [unless it is made by a repo admin or
collaborator](https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/#support-for-building-pull-requests-from-forks)._

---------

Co-authored-by: Yonatan Koren <[email protected]>
  • Loading branch information
ilia-medvedev-codefresh and korenyoni authored Mar 26, 2024
1 parent e85e923 commit 1877a9d
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 1 deletion.
13 changes: 12 additions & 1 deletion codefresh/cfclient/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
92 changes: 92 additions & 0 deletions codefresh/resource_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
},
},
},
},
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
98 changes: 98 additions & 0 deletions codefresh/resource_pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
22 changes: 22 additions & 0 deletions docs/resources/pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -185,6 +186,27 @@ Optional:



<a id="nestedblock--spec--external_resource"></a>
### 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.


<a id="nestedblock--spec--options"></a>
### Nested Schema for `spec.options`

Expand Down

0 comments on commit 1877a9d

Please sign in to comment.