From 125528831ba7fee74746d63e6fe327b8db134ef7 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 7 Sep 2025 18:36:28 +0300 Subject: [PATCH 1/5] initial - only create works --- codefresh/cfclient/gitops_environments.go | 194 +++++++++++++++++++ codefresh/provider.go | 1 + codefresh/resource_gitops_environment.go | 221 ++++++++++++++++++++++ 3 files changed, 416 insertions(+) create mode 100644 codefresh/cfclient/gitops_environments.go create mode 100644 codefresh/resource_gitops_environment.go diff --git a/codefresh/cfclient/gitops_environments.go b/codefresh/cfclient/gitops_environments.go new file mode 100644 index 0000000..f19ac90 --- /dev/null +++ b/codefresh/cfclient/gitops_environments.go @@ -0,0 +1,194 @@ +package cfclient + +import ( + "fmt" +) + +const ( + environmentQueryFields = ` + id + name + kind + clusters { + runtimeName + name + server + namespaces + } + labelPairs + ` +) + +// GitopsEnvironment represents a GitOps environment configuration. +type GitopsEnvironment struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Kind string `json:"kind"` + Clusters []GitopsEnvironmentCluster `json:"clusters"` + LabelPairs []string `json:"labelPairs"` +} + +// GitopsCluster represents a cluster within a GitOps environment. +type GitopsEnvironmentCluster struct { + Name string `json:"name"` + Server string `json:"server"` + RuntimeName string `json:"runtimeName"` + Namespaces []string `json:"namespaces"` +} + +type GitopsEnvironmentResponse struct { + Data struct { + Environment GitopsEnvironment `json:"environment,omitempty"` + CreateEnvironment GitopsEnvironment `json:"createEnvironment,omitempty"` + UpdateEnvironment GitopsEnvironment `json:"updateEnvironment,omitempty"` + DeleteEnvironment GitopsEnvironment `json:"deleteEnvironment,omitempty"` + } `json:"data"` +} + +type GitopsEnvironmentsResponse struct { + Data struct { + Environments []GitopsEnvironment `json:"environments,omitempty"` + } `json:"data"` +} + +// At the time of writing Codefresh Graphql API does not support fetching environment by ID, So all environemnts are fetched and filtered within this client +func (client *Client) GetGitopsEnvironments() (*[]GitopsEnvironment, error) { + + request := GraphQLRequest{ + Query: fmt.Sprintf(` + query ($filters: EnvironmentFilterArgs) { + environments(filters: $filters) { + %s + } + } + `, environmentQueryFields), + Variables: map[string]interface{}{}, + } + + response, err := client.SendGqlRequest(request) + + if err != nil { + return nil, err + } + + var gitopsEnvironmentsResponse GitopsEnvironmentsResponse + err = DecodeGraphQLResponseInto(response, &gitopsEnvironmentsResponse) + + if err != nil { + return nil, err + } + + return &gitopsEnvironmentsResponse.Data.Environments, nil +} + +func (client *Client) GetGitopsEnvironmentById(id string) (*GitopsEnvironment, error) { + environments, err := client.GetGitopsEnvironments() + + if err != nil { + return nil, err + } + + for _, env := range *environments { + if env.ID == id { + return &env, nil + } + } + + return nil, nil +} + +func (client *Client) CreateGitopsEnvironment(environment *GitopsEnvironment) (*GitopsEnvironment, error) { + request := GraphQLRequest{ + Query: fmt.Sprintf(` + mutation ($environment: CreateEnvironmentArgs!) { + createEnvironment(environment: $environment) { + %s + } + } + `, environmentQueryFields), + Variables: map[string]interface{}{ + "environment": environment, + }, + } + + response, err := client.SendGqlRequest(request) + + if err != nil { + return nil, err + } + + var gitopsEnvironmentResponse GitopsEnvironmentResponse + err = DecodeGraphQLResponseInto(response, &gitopsEnvironmentResponse) + + if err != nil { + return nil, err + } + + return &gitopsEnvironmentResponse.Data.CreateEnvironment, nil +} + +func (client *Client) DeleteGitopsEnvironment(id string) (*GitopsEnvironment, error) { + + type deleteEnvironmentArgs struct { + ID string `json:"id"` + } + + request := GraphQLRequest{ + Query: fmt.Sprintf(` + mutation ($environment: DeleteEnvironmentArgs!) { + deleteEnvironment(environment: $environment) { + %s + } + } + `, environmentQueryFields), + + Variables: map[string]interface{}{ + "environment": deleteEnvironmentArgs{ + ID: id, + }, + }, + } + + response, err := client.SendGqlRequest(request) + + if err != nil { + return nil, err + } + var gitopsEnvironmentResponse GitopsEnvironmentResponse + err = DecodeGraphQLResponseInto(response, &gitopsEnvironmentResponse) + + if err != nil { + return nil, err + } + + return &gitopsEnvironmentResponse.Data.DeleteEnvironment, nil +} + +func (client *Client) UpdateGitopsEnvironment(environment *GitopsEnvironment) (*GitopsEnvironment, error) { + request := GraphQLRequest{ + Query: fmt.Sprintf(` + mutation ($environment: UpdateEnvironmentArgs!) { + updateEnvironment(environment: $environment) { + %s + } + } + `, environmentQueryFields), + Variables: map[string]interface{}{ + "environment": environment, + }, + } + + response, err := client.SendGqlRequest(request) + + if err != nil { + return nil, err + } + var gitopsEnvironmentResponse GitopsEnvironmentResponse + err = DecodeGraphQLResponseInto(response, &gitopsEnvironmentResponse) + + if err != nil { + return nil, err + } + + return &gitopsEnvironmentResponse.Data.UpdateEnvironment, nil +} diff --git a/codefresh/provider.go b/codefresh/provider.go index 39d2fbf..be0c132 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -76,6 +76,7 @@ func Provider() *schema.Provider { "codefresh_account_idp": resourceAccountIdp(), "codefresh_account_gitops_settings": resourceAccountGitopsSettings(), "codefresh_service_account": resourceServiceAccount(), + "codefresh_gitops_environment": resourceGitopsEnvironment(), }, ConfigureFunc: configureProvider, } diff --git a/codefresh/resource_gitops_environment.go b/codefresh/resource_gitops_environment.go new file mode 100644 index 0000000..c762ef5 --- /dev/null +++ b/codefresh/resource_gitops_environment.go @@ -0,0 +1,221 @@ +package codefresh + +import ( + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGitopsEnvironment() *schema.Resource { + return &schema.Resource{ + Description: "Codefresh GitOps environment resource. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/).", + Create: resourceGitopsEnvironmentCreate, + Read: resourceGitopsEnvironmentRead, + Update: resourceGitopsEnvironmentUpdate, + Delete: resourceGitopsEnvironmentDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "id": { + Description: "Environment ID", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the environment. Must be unique per account", + }, + "kind": { + Type: schema.TypeString, + Required: true, + Description: "The type of environment. Possible values: NON_PROD, PROD", + ValidateFunc: validation.StringInSlice([]string{"NON_PROD", "PROD"}, false), + }, + "cluster": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Target cluster name", + }, + "server": { + Type: schema.TypeString, + Optional: true, + Description: "Target cluster server url. Defaults to https://kubernetes.default.svc which is the default in-cluster url", + Default: "https://kubernetes.default.svc", + }, + "runtime_name": { + Type: schema.TypeString, + Required: true, + Description: "Runtime name where the target cluster is registered", + }, + "namespaces": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Description: "List of namespaces in the target cluster", + }, + }, + }, + }, + "label_pairs": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "List of labels and values in the format label=value that can be used to assign applications to the environment. Example: ['codefresh.io/environment=prod']", + }, + }, + } +} + +//func resourceGitopsEnvironmentCreate(d *schema.ResourceData, m interface{}) error { +func resourceGitopsEnvironmentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + + environment := mapResourceToGitopsEnvironment(d) + newEnvironment, err := client.CreateGitopsEnvironment(environment) + if err != nil { + return err + } + + d.SetId(newEnvironment.ID) + + return mapGitopsEnvironmentToResource(d, newEnvironment) +} + +func resourceGitopsEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + + environment := mapResourceToGitopsEnvironment(d) + updatedEnvironment, err := client.UpdateGitopsEnvironment(environment) + if err != nil { + return err + } + + return mapGitopsEnvironmentToResource(d, updatedEnvironment) +} + +func resourceGitopsEnvironmentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + + id := d.Id() + if id == "" { + d.SetId("") + return nil + } + + _, err := client.DeleteGitopsEnvironment(id) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} + +func resourceGitopsEnvironmentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + + id := d.Id() + if id == "" { + d.SetId("") + return nil + } + + environment, err := client.GetGitopsEnvironmentById(id) + + if err != nil { + return err + } + + if environment == nil { + d.SetId("") + return nil + } + + return mapGitopsEnvironmentToResource(d, environment) +} + +func mapResourceToGitopsEnvironment(d *schema.ResourceData) *cfclient.GitopsEnvironment { + + + clusters := expandClusters(d.Get("cluster").([]interface{})) + + labelPairs := []string{} + + if len(d.Get("label_pairs").([]interface{})) > 0 { + labelPairs = datautil.ConvertStringArr(d.Get("label_pairs").([]interface{})) + } + + return &cfclient.GitopsEnvironment{ + ID: d.Get("id").(string), + Name: d.Get("name").(string), + Kind: d.Get("kind").(string), + Clusters: clusters, + LabelPairs: labelPairs, + } +} + +func mapGitopsEnvironmentToResource(d *schema.ResourceData, environment *cfclient.GitopsEnvironment) error { + if err := d.Set("id", environment.ID); err != nil { + return err + } + + if err := d.Set("name", environment.Name); err != nil { + return err + } + + if err := d.Set("kind", environment.Kind); err != nil { + return err + } + + if err := d.Set("cluster", flattenClusters(environment.Clusters)); err != nil { + return err + } + + if err := d.Set("label_pairs", environment.LabelPairs); err != nil { + return err + } + return nil +} + +func flattenClusters(clusters []cfclient.GitopsEnvironmentCluster) []map[string]interface{} { + + var res = make([]map[string]interface{}, len(clusters)) + + for _, cluster := range clusters { + m := make(map[string]interface{}) + m["name"] = cluster.Name + m["server"] = cluster.Server + m["runtime_name"] = cluster.RuntimeName + m["namespaces"] = cluster.Namespaces + res = append(res, m) + } + + return res +} + +func expandClusters(list []interface{}) []cfclient.GitopsEnvironmentCluster { + var clusters = make([]cfclient.GitopsEnvironmentCluster, 0) + + for _, item := range list { + clusterMap := item.(map[string]interface{}) + cluster := cfclient.GitopsEnvironmentCluster{ + Name: clusterMap["name"].(string), + Server: clusterMap["server"].(string), + RuntimeName: clusterMap["runtime_name"].(string), + Namespaces: datautil.ConvertStringArr(clusterMap["namespaces"].([]interface{})), + } + clusters = append(clusters, cluster) + } + return clusters +} From 5117fb6b4cda3f38ad370267062784d0b784cb04 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 8 Sep 2025 08:29:40 +0300 Subject: [PATCH 2/5] working and documented resource --- codefresh/cfclient/gitops_environments.go | 8 +- codefresh/resource_gitops_environment.go | 59 ++++---- codefresh/resource_gitops_environment_test.go | 133 ++++++++++++++++++ docs/resources/gitops_environment.md | 64 +++++++++ .../resources/gitops_environment.md.tmpl | 39 +++++ 5 files changed, 270 insertions(+), 33 deletions(-) create mode 100644 codefresh/resource_gitops_environment_test.go create mode 100644 docs/resources/gitops_environment.md create mode 100644 templates/resources/gitops_environment.md.tmpl diff --git a/codefresh/cfclient/gitops_environments.go b/codefresh/cfclient/gitops_environments.go index f19ac90..d9368ee 100644 --- a/codefresh/cfclient/gitops_environments.go +++ b/codefresh/cfclient/gitops_environments.go @@ -21,11 +21,11 @@ const ( // GitopsEnvironment represents a GitOps environment configuration. type GitopsEnvironment struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Kind string `json:"kind"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Kind string `json:"kind"` Clusters []GitopsEnvironmentCluster `json:"clusters"` - LabelPairs []string `json:"labelPairs"` + LabelPairs []string `json:"labelPairs"` } // GitopsCluster represents a cluster within a GitOps environment. diff --git a/codefresh/resource_gitops_environment.go b/codefresh/resource_gitops_environment.go index c762ef5..ccfd99c 100644 --- a/codefresh/resource_gitops_environment.go +++ b/codefresh/resource_gitops_environment.go @@ -25,14 +25,14 @@ func resourceGitopsEnvironment() *schema.Resource { Computed: true, }, "name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "The name of the environment. Must be unique per account", }, "kind": { - Type: schema.TypeString, - Required: true, - Description: "The type of environment. Possible values: NON_PROD, PROD", + Type: schema.TypeString, + Required: true, + Description: "The type of environment. Possible values: NON_PROD, PROD", ValidateFunc: validation.StringInSlice([]string{"NON_PROD", "PROD"}, false), }, "cluster": { @@ -41,65 +41,67 @@ func resourceGitopsEnvironment() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Target cluster name", }, "server": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "Target cluster server url. Defaults to https://kubernetes.default.svc which is the default in-cluster url", - Default: "https://kubernetes.default.svc", + Default: "https://kubernetes.default.svc", }, "runtime_name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Runtime name where the target cluster is registered", }, "namespaces": { - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, Description: "List of namespaces in the target cluster", }, }, }, }, "label_pairs": { - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, Description: "List of labels and values in the format label=value that can be used to assign applications to the environment. Example: ['codefresh.io/environment=prod']", }, }, } } -//func resourceGitopsEnvironmentCreate(d *schema.ResourceData, m interface{}) error { +// func resourceGitopsEnvironmentCreate(d *schema.ResourceData, m interface{}) error { func resourceGitopsEnvironmentCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) environment := mapResourceToGitopsEnvironment(d) newEnvironment, err := client.CreateGitopsEnvironment(environment) + if err != nil { return err } d.SetId(newEnvironment.ID) - return mapGitopsEnvironmentToResource(d, newEnvironment) + return resourceGitopsEnvironmentRead(d, meta) } func resourceGitopsEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) environment := mapResourceToGitopsEnvironment(d) - updatedEnvironment, err := client.UpdateGitopsEnvironment(environment) + _, err := client.UpdateGitopsEnvironment(environment) + if err != nil { return err } - return mapGitopsEnvironmentToResource(d, updatedEnvironment) + return resourceGitopsEnvironmentRead(d, meta) } func resourceGitopsEnvironmentDelete(d *schema.ResourceData, meta interface{}) error { @@ -147,7 +149,6 @@ func resourceGitopsEnvironmentRead(d *schema.ResourceData, meta interface{}) err func mapResourceToGitopsEnvironment(d *schema.ResourceData) *cfclient.GitopsEnvironment { - clusters := expandClusters(d.Get("cluster").([]interface{})) labelPairs := []string{} @@ -159,8 +160,8 @@ func mapResourceToGitopsEnvironment(d *schema.ResourceData) *cfclient.GitopsEnvi return &cfclient.GitopsEnvironment{ ID: d.Get("id").(string), Name: d.Get("name").(string), - Kind: d.Get("kind").(string), - Clusters: clusters, + Kind: d.Get("kind").(string), + Clusters: clusters, LabelPairs: labelPairs, } } @@ -190,7 +191,7 @@ func mapGitopsEnvironmentToResource(d *schema.ResourceData, environment *cfclien func flattenClusters(clusters []cfclient.GitopsEnvironmentCluster) []map[string]interface{} { - var res = make([]map[string]interface{}, len(clusters)) + var res = make([]map[string]interface{}, 0) for _, cluster := range clusters { m := make(map[string]interface{}) @@ -210,10 +211,10 @@ func expandClusters(list []interface{}) []cfclient.GitopsEnvironmentCluster { for _, item := range list { clusterMap := item.(map[string]interface{}) cluster := cfclient.GitopsEnvironmentCluster{ - Name: clusterMap["name"].(string), - Server: clusterMap["server"].(string), + Name: clusterMap["name"].(string), + Server: clusterMap["server"].(string), RuntimeName: clusterMap["runtime_name"].(string), - Namespaces: datautil.ConvertStringArr(clusterMap["namespaces"].([]interface{})), + Namespaces: datautil.ConvertStringArr(clusterMap["namespaces"].([]interface{})), } clusters = append(clusters, cluster) } diff --git a/codefresh/resource_gitops_environment_test.go b/codefresh/resource_gitops_environment_test.go new file mode 100644 index 0000000..019ddca --- /dev/null +++ b/codefresh/resource_gitops_environment_test.go @@ -0,0 +1,133 @@ +package codefresh + +import ( + "fmt" + "strings" + "testing" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccCodefreshGitopsEnvironmentsResource(t *testing.T) { + resourceName := "codefresh_gitops_environment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCodefreshGitopsEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCodefreshGitopsEnvironmentConfig( + "test-for-tf", + "NON_PROD", + []cfclient.GitopsEnvironmentCluster{{ + Name: "in-cluster2", + Server: "https://kubernetes.default.svc", + RuntimeName: "test-runtime", + Namespaces: []string{"test-ns-1", "test-ns2"}, + }}, + []string{"codefresh.io/environment=test-for-tf", "codefresh.io/environment-1=test-for-tf1"}, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshGitopsEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "test-for-tf"), + resource.TestCheckResourceAttr(resourceName, "kind", "NON_PROD"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.name", "in-cluster2"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.server", "https://kubernetes.default.svc"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.runtime_name", "test-runtime"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.namespaces.0", "test-ns-1"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.namespaces.1", "test-ns2"), + resource.TestCheckResourceAttr(resourceName, "label_pairs.0", "codefresh.io/environment=test-for-tf"), + resource.TestCheckResourceAttr(resourceName, "label_pairs.1", "codefresh.io/environment-1=test-for-tf1"), + ), + }, + { + Config: testAccCodefreshGitopsEnvironmentConfig( + "test-for-tf", + "NON_PROD", + []cfclient.GitopsEnvironmentCluster{ + { + Name: "in-cluster2", + Server: "https://kubernetes.default.svc", + RuntimeName: "test-runtime", + Namespaces: []string{"test-ns-1", "test-ns2"}, + }, + { + Name: "in-cluster3", + Server: "https://kubernetes2.default.svc", + RuntimeName: "test-runtime-2", + Namespaces: []string{"test-ns-3"}, + }, + }, + []string{"codefresh.io/environment=test-for-tf", "codefresh.io/environment-1=test-for-tf1"}, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshGitopsEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "test-for-tf"), + resource.TestCheckResourceAttr(resourceName, "kind", "NON_PROD"), + resource.TestCheckResourceAttr(resourceName, "cluster.0.name", "in-cluster2"), + resource.TestCheckResourceAttr(resourceName, "cluster.1.name", "in-cluster3"), + resource.TestCheckResourceAttr(resourceName, "cluster.1.server", "https://kubernetes2.default.svc"), + resource.TestCheckResourceAttr(resourceName, "cluster.1.runtime_name", "test-runtime-2"), + resource.TestCheckResourceAttr(resourceName, "cluster.1.namespaces.0", "test-ns-3"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCodefreshGitopsEnvironmentExists(resource string) resource.TestCheckFunc { + return func(state *terraform.State) error { + rs, ok := state.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + envID := rs.Primary.ID + apiClient := testAccProvider.Meta().(*cfclient.Client) + _, err := apiClient.GetGitopsEnvironmentById(envID) + if err != nil { + return fmt.Errorf("error fetching gitops environment with ID %s. %s", envID, err) + } + return nil + } +} + +func testAccCheckCodefreshGitopsEnvironmentDestroy(state *terraform.State) error { + // Implement destroy check if needed + return nil +} + +// CONFIG +func testAccCodefreshGitopsEnvironmentConfig(name, kind string, clusters []cfclient.GitopsEnvironmentCluster, labelPairs []string) string { + var clusterBlocks []string + for _, c := range clusters { + ns := fmt.Sprintf("[\"%s\"]", strings.Join(c.Namespaces, "\", \"")) + block := fmt.Sprintf(` cluster { + name = "%s" + server = "%s" + runtime_name = "%s" + namespaces = %s + }`, c.Name, c.Server, c.RuntimeName, ns) + clusterBlocks = append(clusterBlocks, block) + } + labelsStr := fmt.Sprintf("[\"%s\"]", strings.Join(labelPairs, "\", \"")) + return fmt.Sprintf(` +resource "codefresh_gitops_environment" "test" { + name = "%s" + kind = "%s" +%s + label_pairs = %s +} +`, name, kind, strings.Join(clusterBlocks, "\n"), labelsStr) +} diff --git a/docs/resources/gitops_environment.md b/docs/resources/gitops_environment.md new file mode 100644 index 0000000..84bde2f --- /dev/null +++ b/docs/resources/gitops_environment.md @@ -0,0 +1,64 @@ +--- +page_title: "codefresh_gitops_environment Resource - terraform-provider-codefresh" +subcategory: "" +description: |- + Codefresh GitOps environment resource. See official documentation https://codefresh.io/docs/gitops/environments/environments-overview/. +--- + +# codefresh_gitops_environment (Resource) + +Codefresh GitOps environment resource. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/). + +## Example Usage + +```hcl +resource "codefresh_gitops_environment" "example" { + name = "test-gitops-env" + kind = "NON_PROD" + + cluster { + name = "test-cluster" + server = "https://kubernetes.default.svc" + runtime_name = "test-runtime" + namespaces = ["test-ns-1", "test-ns-2"] + } + + label_pairs = [ + "codefresh.io/environment=test-gitops-env", + "codefresh.io/environment-1=test-gitops-env1" + ] +} +``` + + +## Schema + +### Required + +- `cluster` (Block List, Min: 1) (see [below for nested schema](#nestedblock--cluster)) +- `kind` (String) The type of environment. Possible values: NON_PROD, PROD +- `name` (String) The name of the environment. Must be unique per account + +### Optional + +- `id` (String) Environment ID +- `label_pairs` (List of String) List of labels and values in the format label=value that can be used to assign applications to the environment. Example: ['codefresh.io/environment=prod'] + + +### Nested Schema for `cluster` + +Required: + +- `name` (String) Target cluster name +- `namespaces` (List of String) List of namespaces in the target cluster +- `runtime_name` (String) Runtime name where the target cluster is registered + +Optional: + +- `server` (String) Target cluster server url. Defaults to https://kubernetes.default.svc which is the default in-cluster url + +## Import + +```sh +terraform import codefresh_gitops_environment.example +``` diff --git a/templates/resources/gitops_environment.md.tmpl b/templates/resources/gitops_environment.md.tmpl new file mode 100644 index 0000000..12e08aa --- /dev/null +++ b/templates/resources/gitops_environment.md.tmpl @@ -0,0 +1,39 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- + {{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +```hcl +resource "codefresh_gitops_environment" "example" { + name = "test-gitops-env" + kind = "NON_PROD" + + cluster { + name = "test-cluster" + server = "https://kubernetes.default.svc" + runtime_name = "test-runtime" + namespaces = ["test-ns-1", "test-ns-2"] + } + + label_pairs = [ + "codefresh.io/environment=test-gitops-env", + "codefresh.io/environment-1=test-gitops-env1" + ] +} +``` + +{{ .SchemaMarkdown | trimspace }} + +## Import + +```sh +terraform import codefresh_gitops_environment.example +``` From 43a498a5e15196f1e6c28c95479f761ec947bdbe Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 8 Sep 2025 09:57:57 +0300 Subject: [PATCH 3/5] update docs --- codefresh/resource_gitops_environment.go | 4 ++-- docs/resources/gitops_environment.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codefresh/resource_gitops_environment.go b/codefresh/resource_gitops_environment.go index ccfd99c..8c3a0e4 100644 --- a/codefresh/resource_gitops_environment.go +++ b/codefresh/resource_gitops_environment.go @@ -9,7 +9,7 @@ import ( func resourceGitopsEnvironment() *schema.Resource { return &schema.Resource{ - Description: "Codefresh GitOps environment resource. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/).", + Description: "An environment in Codefresh GitOps is a logical grouping of one or more Kubernetes clusters and namespaces, representing a deployment context for your Argo CD applications. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/) for more information.", Create: resourceGitopsEnvironmentCreate, Read: resourceGitopsEnvironmentRead, Update: resourceGitopsEnvironmentUpdate, @@ -48,7 +48,7 @@ func resourceGitopsEnvironment() *schema.Resource { "server": { Type: schema.TypeString, Optional: true, - Description: "Target cluster server url. Defaults to https://kubernetes.default.svc which is the default in-cluster url", + Description: "Target cluster server url. Defaults to `https://kubernetes.default.svc` which is the default in-cluster url", Default: "https://kubernetes.default.svc", }, "runtime_name": { diff --git a/docs/resources/gitops_environment.md b/docs/resources/gitops_environment.md index 84bde2f..82ddffa 100644 --- a/docs/resources/gitops_environment.md +++ b/docs/resources/gitops_environment.md @@ -2,12 +2,12 @@ page_title: "codefresh_gitops_environment Resource - terraform-provider-codefresh" subcategory: "" description: |- - Codefresh GitOps environment resource. See official documentation https://codefresh.io/docs/gitops/environments/environments-overview/. + An environment in Codefresh GitOps is a logical grouping of one or more Kubernetes clusters and namespaces, representing a deployment context for your Argo CD applications. See official documentation https://codefresh.io/docs/gitops/environments/environments-overview/ for more information. --- # codefresh_gitops_environment (Resource) -Codefresh GitOps environment resource. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/). +An environment in Codefresh GitOps is a logical grouping of one or more Kubernetes clusters and namespaces, representing a deployment context for your Argo CD applications. See [official documentation](https://codefresh.io/docs/gitops/environments/environments-overview/) for more information. ## Example Usage @@ -55,7 +55,7 @@ Required: Optional: -- `server` (String) Target cluster server url. Defaults to https://kubernetes.default.svc which is the default in-cluster url +- `server` (String) Target cluster server url. Defaults to `https://kubernetes.default.svc` which is the default in-cluster url ## Import From 1acd9490eae80e62dcdc48c8e930f16910324853 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sat, 13 Sep 2025 22:15:56 +0300 Subject: [PATCH 4/5] remove server --- codefresh/cfclient/gitops_environments.go | 14 +++++++++++++- codefresh/cfclient/gql_client.go | 5 +++++ codefresh/resource_gitops_environment.go | 8 -------- codefresh/resource_gitops_environment_test.go | 6 +----- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/codefresh/cfclient/gitops_environments.go b/codefresh/cfclient/gitops_environments.go index d9368ee..3fbbca4 100644 --- a/codefresh/cfclient/gitops_environments.go +++ b/codefresh/cfclient/gitops_environments.go @@ -31,12 +31,12 @@ type GitopsEnvironment struct { // GitopsCluster represents a cluster within a GitOps environment. type GitopsEnvironmentCluster struct { Name string `json:"name"` - Server string `json:"server"` RuntimeName string `json:"runtimeName"` Namespaces []string `json:"namespaces"` } type GitopsEnvironmentResponse struct { + Errors []GraphQLError `json:"errors,omitempty"` Data struct { Environment GitopsEnvironment `json:"environment,omitempty"` CreateEnvironment GitopsEnvironment `json:"createEnvironment,omitempty"` @@ -124,6 +124,10 @@ func (client *Client) CreateGitopsEnvironment(environment *GitopsEnvironment) (* return nil, err } + if len(gitopsEnvironmentResponse.Errors) > 0 { + return nil, fmt.Errorf("CreateGitopsEnvironment - %s", gitopsEnvironmentResponse.Errors) + } + return &gitopsEnvironmentResponse.Data.CreateEnvironment, nil } @@ -161,6 +165,10 @@ func (client *Client) DeleteGitopsEnvironment(id string) (*GitopsEnvironment, er return nil, err } + if len(gitopsEnvironmentResponse.Errors) > 0 { + return nil, fmt.Errorf("DeleteGitopsEnvironment - %s", gitopsEnvironmentResponse.Errors) + } + return &gitopsEnvironmentResponse.Data.DeleteEnvironment, nil } @@ -190,5 +198,9 @@ func (client *Client) UpdateGitopsEnvironment(environment *GitopsEnvironment) (* return nil, err } + if len(gitopsEnvironmentResponse.Errors) > 0 { + return nil, fmt.Errorf("UpdateGitopsEnvironment - %s", gitopsEnvironmentResponse.Errors) + } + return &gitopsEnvironmentResponse.Data.UpdateEnvironment, nil } diff --git a/codefresh/cfclient/gql_client.go b/codefresh/cfclient/gql_client.go index ce3d72d..e4cbf72 100644 --- a/codefresh/cfclient/gql_client.go +++ b/codefresh/cfclient/gql_client.go @@ -14,6 +14,11 @@ type GraphQLRequest struct { Variables map[string]interface{} `json:"variables,omitempty"` } +type GraphQLError struct { + Message string `json:"message,omitempty"` + Extensions string `json:"extensions,omitempty"` +} + func (client *Client) SendGqlRequest(request GraphQLRequest) ([]byte, error) { jsonRequest, err := json.Marshal(request) if err != nil { diff --git a/codefresh/resource_gitops_environment.go b/codefresh/resource_gitops_environment.go index 8c3a0e4..1bcbd0d 100644 --- a/codefresh/resource_gitops_environment.go +++ b/codefresh/resource_gitops_environment.go @@ -45,12 +45,6 @@ func resourceGitopsEnvironment() *schema.Resource { Required: true, Description: "Target cluster name", }, - "server": { - Type: schema.TypeString, - Optional: true, - Description: "Target cluster server url. Defaults to `https://kubernetes.default.svc` which is the default in-cluster url", - Default: "https://kubernetes.default.svc", - }, "runtime_name": { Type: schema.TypeString, Required: true, @@ -196,7 +190,6 @@ func flattenClusters(clusters []cfclient.GitopsEnvironmentCluster) []map[string] for _, cluster := range clusters { m := make(map[string]interface{}) m["name"] = cluster.Name - m["server"] = cluster.Server m["runtime_name"] = cluster.RuntimeName m["namespaces"] = cluster.Namespaces res = append(res, m) @@ -212,7 +205,6 @@ func expandClusters(list []interface{}) []cfclient.GitopsEnvironmentCluster { clusterMap := item.(map[string]interface{}) cluster := cfclient.GitopsEnvironmentCluster{ Name: clusterMap["name"].(string), - Server: clusterMap["server"].(string), RuntimeName: clusterMap["runtime_name"].(string), Namespaces: datautil.ConvertStringArr(clusterMap["namespaces"].([]interface{})), } diff --git a/codefresh/resource_gitops_environment_test.go b/codefresh/resource_gitops_environment_test.go index 019ddca..59a58ef 100644 --- a/codefresh/resource_gitops_environment_test.go +++ b/codefresh/resource_gitops_environment_test.go @@ -24,7 +24,6 @@ func TestAccCodefreshGitopsEnvironmentsResource(t *testing.T) { "NON_PROD", []cfclient.GitopsEnvironmentCluster{{ Name: "in-cluster2", - Server: "https://kubernetes.default.svc", RuntimeName: "test-runtime", Namespaces: []string{"test-ns-1", "test-ns2"}, }}, @@ -50,13 +49,11 @@ func TestAccCodefreshGitopsEnvironmentsResource(t *testing.T) { []cfclient.GitopsEnvironmentCluster{ { Name: "in-cluster2", - Server: "https://kubernetes.default.svc", RuntimeName: "test-runtime", Namespaces: []string{"test-ns-1", "test-ns2"}, }, { Name: "in-cluster3", - Server: "https://kubernetes2.default.svc", RuntimeName: "test-runtime-2", Namespaces: []string{"test-ns-3"}, }, @@ -115,10 +112,9 @@ func testAccCodefreshGitopsEnvironmentConfig(name, kind string, clusters []cfcli ns := fmt.Sprintf("[\"%s\"]", strings.Join(c.Namespaces, "\", \"")) block := fmt.Sprintf(` cluster { name = "%s" - server = "%s" runtime_name = "%s" namespaces = %s - }`, c.Name, c.Server, c.RuntimeName, ns) + }`, c.Name, c.RuntimeName, ns) clusterBlocks = append(clusterBlocks, block) } labelsStr := fmt.Sprintf("[\"%s\"]", strings.Join(labelPairs, "\", \"")) From 43f53d92a24a189a209cb73a0ec8c1245c0059ae Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Fri, 26 Sep 2025 15:54:44 +0300 Subject: [PATCH 5/5] prepare pr --- codefresh/cfclient/gitops_environments.go | 4 ++-- codefresh/cfclient/gql_client.go | 2 +- codefresh/resource_gitops_environment_test.go | 2 -- docs/resources/gitops_environment.md | 4 ---- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/codefresh/cfclient/gitops_environments.go b/codefresh/cfclient/gitops_environments.go index 3fbbca4..7cad1d9 100644 --- a/codefresh/cfclient/gitops_environments.go +++ b/codefresh/cfclient/gitops_environments.go @@ -36,8 +36,8 @@ type GitopsEnvironmentCluster struct { } type GitopsEnvironmentResponse struct { - Errors []GraphQLError `json:"errors,omitempty"` - Data struct { + Errors []GraphQLError `json:"errors,omitempty"` + Data struct { Environment GitopsEnvironment `json:"environment,omitempty"` CreateEnvironment GitopsEnvironment `json:"createEnvironment,omitempty"` UpdateEnvironment GitopsEnvironment `json:"updateEnvironment,omitempty"` diff --git a/codefresh/cfclient/gql_client.go b/codefresh/cfclient/gql_client.go index e4cbf72..a887668 100644 --- a/codefresh/cfclient/gql_client.go +++ b/codefresh/cfclient/gql_client.go @@ -15,7 +15,7 @@ type GraphQLRequest struct { } type GraphQLError struct { - Message string `json:"message,omitempty"` + Message string `json:"message,omitempty"` Extensions string `json:"extensions,omitempty"` } diff --git a/codefresh/resource_gitops_environment_test.go b/codefresh/resource_gitops_environment_test.go index 59a58ef..001b231 100644 --- a/codefresh/resource_gitops_environment_test.go +++ b/codefresh/resource_gitops_environment_test.go @@ -34,7 +34,6 @@ func TestAccCodefreshGitopsEnvironmentsResource(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "test-for-tf"), resource.TestCheckResourceAttr(resourceName, "kind", "NON_PROD"), resource.TestCheckResourceAttr(resourceName, "cluster.0.name", "in-cluster2"), - resource.TestCheckResourceAttr(resourceName, "cluster.0.server", "https://kubernetes.default.svc"), resource.TestCheckResourceAttr(resourceName, "cluster.0.runtime_name", "test-runtime"), resource.TestCheckResourceAttr(resourceName, "cluster.0.namespaces.0", "test-ns-1"), resource.TestCheckResourceAttr(resourceName, "cluster.0.namespaces.1", "test-ns2"), @@ -66,7 +65,6 @@ func TestAccCodefreshGitopsEnvironmentsResource(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "kind", "NON_PROD"), resource.TestCheckResourceAttr(resourceName, "cluster.0.name", "in-cluster2"), resource.TestCheckResourceAttr(resourceName, "cluster.1.name", "in-cluster3"), - resource.TestCheckResourceAttr(resourceName, "cluster.1.server", "https://kubernetes2.default.svc"), resource.TestCheckResourceAttr(resourceName, "cluster.1.runtime_name", "test-runtime-2"), resource.TestCheckResourceAttr(resourceName, "cluster.1.namespaces.0", "test-ns-3"), ), diff --git a/docs/resources/gitops_environment.md b/docs/resources/gitops_environment.md index 82ddffa..0e0a7eb 100644 --- a/docs/resources/gitops_environment.md +++ b/docs/resources/gitops_environment.md @@ -53,10 +53,6 @@ Required: - `namespaces` (List of String) List of namespaces in the target cluster - `runtime_name` (String) Runtime name where the target cluster is registered -Optional: - -- `server` (String) Target cluster server url. Defaults to `https://kubernetes.default.svc` which is the default in-cluster url - ## Import ```sh