diff --git a/codefresh/cfclient/gitops_environments.go b/codefresh/cfclient/gitops_environments.go
new file mode 100644
index 0000000..7cad1d9
--- /dev/null
+++ b/codefresh/cfclient/gitops_environments.go
@@ -0,0 +1,206 @@
+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"`
+ 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"`
+ 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
+ }
+
+ if len(gitopsEnvironmentResponse.Errors) > 0 {
+ return nil, fmt.Errorf("CreateGitopsEnvironment - %s", gitopsEnvironmentResponse.Errors)
+ }
+
+ 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
+ }
+
+ if len(gitopsEnvironmentResponse.Errors) > 0 {
+ return nil, fmt.Errorf("DeleteGitopsEnvironment - %s", gitopsEnvironmentResponse.Errors)
+ }
+
+ 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
+ }
+
+ 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..a887668 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/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..1bcbd0d
--- /dev/null
+++ b/codefresh/resource_gitops_environment.go
@@ -0,0 +1,214 @@
+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: "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,
+ 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",
+ },
+ "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 resourceGitopsEnvironmentRead(d, meta)
+}
+
+func resourceGitopsEnvironmentUpdate(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*cfclient.Client)
+
+ environment := mapResourceToGitopsEnvironment(d)
+ _, err := client.UpdateGitopsEnvironment(environment)
+
+ if err != nil {
+ return err
+ }
+
+ return resourceGitopsEnvironmentRead(d, meta)
+}
+
+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{}, 0)
+
+ for _, cluster := range clusters {
+ m := make(map[string]interface{})
+ m["name"] = cluster.Name
+ 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),
+ RuntimeName: clusterMap["runtime_name"].(string),
+ Namespaces: datautil.ConvertStringArr(clusterMap["namespaces"].([]interface{})),
+ }
+ clusters = append(clusters, cluster)
+ }
+ return clusters
+}
diff --git a/codefresh/resource_gitops_environment_test.go b/codefresh/resource_gitops_environment_test.go
new file mode 100644
index 0000000..001b231
--- /dev/null
+++ b/codefresh/resource_gitops_environment_test.go
@@ -0,0 +1,127 @@
+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",
+ 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.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",
+ RuntimeName: "test-runtime",
+ Namespaces: []string{"test-ns-1", "test-ns2"},
+ },
+ {
+ Name: "in-cluster3",
+ 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.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"
+ runtime_name = "%s"
+ namespaces = %s
+ }`, c.Name, 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..0e0a7eb
--- /dev/null
+++ b/docs/resources/gitops_environment.md
@@ -0,0 +1,60 @@
+---
+page_title: "codefresh_gitops_environment Resource - terraform-provider-codefresh"
+subcategory: ""
+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.
+---
+
+# codefresh_gitops_environment (Resource)
+
+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
+
+```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
+
+## 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
+```