From c650c77a9b93514708fed3933a6f3b3c7d3bfee4 Mon Sep 17 00:00:00 2001
From: ilia-medvedev-codefresh <ilia.medvedev@codefresh.io>
Date: Mon, 26 Feb 2024 17:43:23 +0200
Subject: [PATCH] Feat: Add permit restart from failed steps to pipeline
 resource (#137)

## What
Add possibility to disable restart From failed step in pipeline
resource.
## Why
This property was absent from the terraform provider, causing the
default "true" value to always be applied and would reset manual changes
on apply
## 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)._
---
 codefresh/cfclient/pipeline.go      | 39 ++++++++++---------
 codefresh/resource_pipeline.go      | 20 +++++++---
 codefresh/resource_pipeline_test.go | 60 +++++++++++++++++++++++++++++
 docs/resources/pipeline.md          |  1 +
 4 files changed, 95 insertions(+), 25 deletions(-)

diff --git a/codefresh/cfclient/pipeline.go b/codefresh/cfclient/pipeline.go
index 442c8d5..d25de84 100644
--- a/codefresh/cfclient/pipeline.go
+++ b/codefresh/cfclient/pipeline.go
@@ -103,25 +103,26 @@ func (t *CronTrigger) SetVariables(variables map[string]interface{}) {
 }
 
 type Spec struct {
-	Variables                []Variable               `json:"variables,omitempty"`
-	SpecTemplate             *SpecTemplate            `json:"specTemplate,omitempty"`
-	Triggers                 []Trigger                `json:"triggers,omitempty"`
-	CronTriggers             []CronTrigger            `json:"cronTriggers,omitempty"`
-	Priority                 int                      `json:"priority,omitempty"`
-	Concurrency              int                      `json:"concurrency,omitempty"`
-	BranchConcurrency        int                      `json:"branchConcurrency,omitempty"`
-	TriggerConcurrency       int                      `json:"triggerConcurrency,omitempty"`
-	Contexts                 []interface{}            `json:"contexts,omitempty"`
-	Steps                    *Steps                   `json:"steps,omitempty"`
-	Stages                   *Stages                  `json:"stages,omitempty"`
-	Mode                     string                   `json:"mode,omitempty"`
-	FailFast                 *bool                    `json:"fail_fast,omitempty"`
-	RuntimeEnvironment       RuntimeEnvironment       `json:"runtimeEnvironment,omitempty"`
-	TerminationPolicy        []map[string]interface{} `json:"terminationPolicy,omitempty"`
-	PackId                   string                   `json:"packId,omitempty"`
-	RequiredAvailableStorage string                   `json:"requiredAvailableStorage,omitempty"`
-	Hooks                    *Hooks                   `json:"hooks,omitempty"`
-	Options                  map[string]bool          `json:"options,omitempty"`
+	Variables                    []Variable               `json:"variables,omitempty"`
+	SpecTemplate                 *SpecTemplate            `json:"specTemplate,omitempty"`
+	Triggers                     []Trigger                `json:"triggers,omitempty"`
+	CronTriggers                 []CronTrigger            `json:"cronTriggers,omitempty"`
+	Priority                     int                      `json:"priority,omitempty"`
+	Concurrency                  int                      `json:"concurrency,omitempty"`
+	BranchConcurrency            int                      `json:"branchConcurrency,omitempty"`
+	TriggerConcurrency           int                      `json:"triggerConcurrency,omitempty"`
+	Contexts                     []interface{}            `json:"contexts,omitempty"`
+	Steps                        *Steps                   `json:"steps,omitempty"`
+	Stages                       *Stages                  `json:"stages,omitempty"`
+	Mode                         string                   `json:"mode,omitempty"`
+	FailFast                     *bool                    `json:"fail_fast,omitempty"`
+	RuntimeEnvironment           RuntimeEnvironment       `json:"runtimeEnvironment,omitempty"`
+	TerminationPolicy            []map[string]interface{} `json:"terminationPolicy,omitempty"`
+	PackId                       string                   `json:"packId,omitempty"`
+	RequiredAvailableStorage     string                   `json:"requiredAvailableStorage,omitempty"`
+	Hooks                        *Hooks                   `json:"hooks,omitempty"`
+	Options                      map[string]bool          `json:"options,omitempty"`
+	PermitRestartFromFailedSteps bool                     `json:"permitRestartFromFailedSteps,omitempty"`
 }
 
 type Steps struct {
diff --git a/codefresh/resource_pipeline.go b/codefresh/resource_pipeline.go
index 4430262..b3fd98c 100644
--- a/codefresh/resource_pipeline.go
+++ b/codefresh/resource_pipeline.go
@@ -101,6 +101,12 @@ Or: <code>original_yaml_string = file("/path/to/my/codefresh.yml")</code>
 							Optional:    true,
 							Default:     0,
 						},
+						"permit_restart_from_failed_steps": {
+							Description: "Defines whether it is permitted to restart builds in this pipeline from failed step. Defaults to true",
+							Type:        schema.TypeBool,
+							Optional:    true,
+							Default:     true,
+						},
 						"spec_template": {
 							Description: "The pipeline's spec template.",
 							Type:        schema.TypeList,
@@ -774,6 +780,7 @@ func flattenSpec(spec cfclient.Spec) []interface{} {
 	m["concurrency"] = spec.Concurrency
 	m["branch_concurrency"] = spec.BranchConcurrency
 	m["trigger_concurrency"] = spec.TriggerConcurrency
+	m["permit_restart_from_failed_steps"] = spec.PermitRestartFromFailedSteps
 
 	m["priority"] = spec.Priority
 
@@ -923,12 +930,13 @@ func mapResourceToPipeline(d *schema.ResourceData) (*cfclient.Pipeline, error) {
 			OriginalYamlString: originalYamlString,
 		},
 		Spec: cfclient.Spec{
-			PackId:                   d.Get("spec.0.pack_id").(string),
-			RequiredAvailableStorage: d.Get("spec.0.required_available_storage").(string),
-			Priority:                 d.Get("spec.0.priority").(int),
-			Concurrency:              d.Get("spec.0.concurrency").(int),
-			BranchConcurrency:        d.Get("spec.0.branch_concurrency").(int),
-			TriggerConcurrency:       d.Get("spec.0.trigger_concurrency").(int),
+			PackId:                       d.Get("spec.0.pack_id").(string),
+			RequiredAvailableStorage:     d.Get("spec.0.required_available_storage").(string),
+			Priority:                     d.Get("spec.0.priority").(int),
+			Concurrency:                  d.Get("spec.0.concurrency").(int),
+			BranchConcurrency:            d.Get("spec.0.branch_concurrency").(int),
+			TriggerConcurrency:           d.Get("spec.0.trigger_concurrency").(int),
+			PermitRestartFromFailedSteps: d.Get("spec.0.permit_restart_from_failed_steps").(bool),
 		},
 	}
 
diff --git a/codefresh/resource_pipeline_test.go b/codefresh/resource_pipeline_test.go
index 07dff4a..d3530aa 100644
--- a/codefresh/resource_pipeline_test.go
+++ b/codefresh/resource_pipeline_test.go
@@ -79,6 +79,39 @@ func TestAccCodefreshPipeline_Concurrency(t *testing.T) {
 	})
 }
 
+func TestAccCodefreshPipeline_PremitRestartFromFailedSteps(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: testAccCodefreshPipelineBasicConfigPermitRestartFromFailedSteps(name, "codefresh-contrib/react-sample-app", "./codefresh.yml", "master", "git", true),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCodefreshPipelineExists(resourceName, &pipeline),
+					resource.TestCheckResourceAttr(resourceName, "spec.0.permit_restart_from_failed_steps", "true"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+			{
+				Config: testAccCodefreshPipelineBasicConfigPermitRestartFromFailedSteps(name, "codefresh-contrib/react-sample-app", "./codefresh.yml", "master", "git", false),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCodefreshPipelineExists(resourceName, &pipeline),
+					resource.TestCheckResourceAttr(resourceName, "spec.0.permit_restart_from_failed_steps", "false"),
+				),
+			},
+		},
+	})
+}
+
 func TestAccCodefreshPipeline_Tags(t *testing.T) {
 	name := pipelineNamePrefix + acctest.RandString(10)
 	resourceName := "codefresh_pipeline.test"
@@ -955,6 +988,33 @@ resource "codefresh_pipeline" "test" {
 `, rName, repo, path, revision, context, concurrency, concurrencyBranch, concurrencyTrigger)
 }
 
+func testAccCodefreshPipelineBasicConfigPermitRestartFromFailedSteps(rName string, repo string, path string, revision string, context string, permitRestartFromFailedSteps bool) 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
+	}
+
+	permit_restart_from_failed_steps = %t
+
+  }
+}
+`, rName, repo, path, revision, context, permitRestartFromFailedSteps)
+}
+
 func testAccCodefreshPipelineBasicConfigTriggers(
 	rName,
 	repo,
diff --git a/docs/resources/pipeline.md b/docs/resources/pipeline.md
index 9313d8a..8d4014e 100644
--- a/docs/resources/pipeline.md
+++ b/docs/resources/pipeline.md
@@ -130,6 +130,7 @@ Optional:
 - `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))
 - `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
 - `priority` (Number) Helps to organize the order of builds execution in case of reaching the concurrency limit (default: `0`).
 - `required_available_storage` (String) Minimum disk space required for build filesystem ( unit Gi is required).
 - `runtime_environment` (Block List) The runtime environment for the pipeline. (see [below for nested schema](#nestedblock--spec--runtime_environment))