From 09fbbad1b5c36e961645c93e686524d343a3b46b Mon Sep 17 00:00:00 2001 From: Steve Hipwell Date: Wed, 14 Jan 2026 18:55:41 +0000 Subject: [PATCH] fix: Escape environment name for id Signed-off-by: Steve Hipwell --- ...e_github_actions_environment_public_key.go | 23 +- ...urce_github_actions_environment_secrets.go | 31 +- ...github_actions_environment_secrets_test.go | 13 +- ...ce_github_actions_environment_variables.go | 29 +- ...ository_environment_deployment_policies.go | 16 +- ...a_source_github_repository_environments.go | 14 +- ...ource_github_actions_environment_secret.go | 86 ++- ..._github_actions_environment_secret_test.go | 3 + ...rce_github_actions_environment_variable.go | 82 +- ...ithub_actions_environment_variable_test.go | 4 +- .../resource_github_repository_environment.go | 107 +-- ...epository_environment_deployment_policy.go | 79 +- ...tory_environment_deployment_policy_test.go | 724 ++---------------- ...urce_github_repository_environment_test.go | 115 ++- github/util.go | 65 ++ github/util_test.go | 315 ++++++++ ...actions_environment_variable.html.markdown | 19 +- .../r/repository_environment.html.markdown | 11 +- ...nvironment_deployment_policy.html.markdown | 8 +- 19 files changed, 860 insertions(+), 884 deletions(-) diff --git a/github/data_source_github_actions_environment_public_key.go b/github/data_source_github_actions_environment_public_key.go index 18f43523a9..f36a411bbb 100644 --- a/github/data_source_github_actions_environment_public_key.go +++ b/github/data_source_github_actions_environment_public_key.go @@ -4,12 +4,13 @@ import ( "context" "net/url" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceGithubActionsEnvironmentPublicKey() *schema.Resource { return &schema.Resource{ - Read: dataSourceGithubActionsEnvironmentPublicKeyRead, + ReadContext: dataSourceGithubActionsEnvironmentPublicKeyRead, Schema: map[string]*schema.Schema{ "repository": { @@ -32,33 +33,29 @@ func dataSourceGithubActionsEnvironmentPublicKey() *schema.Resource { } } -func dataSourceGithubActionsEnvironmentPublicKeyRead(d *schema.ResourceData, meta any) error { - ctx := context.Background() +func dataSourceGithubActionsEnvironmentPublicKeyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name repository := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) repo, _, err := client.Repositories.Get(ctx, owner, repository) if err != nil { - return err + return diag.FromErr(err) } - publicKey, _, err := client.Actions.GetEnvPublicKey(ctx, int(repo.GetID()), escapedEnvName) + publicKey, _, err := client.Actions.GetEnvPublicKey(ctx, int(repo.GetID()), url.PathEscape(envName)) if err != nil { - return err + return diag.FromErr(err) } d.SetId(publicKey.GetKeyID()) - err = d.Set("key_id", publicKey.GetKeyID()) - if err != nil { - return err + if err := d.Set("key_id", publicKey.GetKeyID()); err != nil { + return diag.FromErr(err) } - err = d.Set("key", publicKey.GetKey()) - if err != nil { - return err + if err := d.Set("key", publicKey.GetKey()); err != nil { + return diag.FromErr(err) } return nil diff --git a/github/data_source_github_actions_environment_secrets.go b/github/data_source_github_actions_environment_secrets.go index 6a81163003..608e663b0f 100644 --- a/github/data_source_github_actions_environment_secrets.go +++ b/github/data_source_github_actions_environment_secrets.go @@ -2,17 +2,17 @@ package github import ( "context" - "fmt" "net/url" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceGithubActionsEnvironmentSecrets() *schema.Resource { return &schema.Resource{ - Read: dataSourceGithubActionsEnvironmentSecretsRead, + ReadContext: dataSourceGithubActionsEnvironmentSecretsRead, Schema: map[string]*schema.Schema{ "full_name": { @@ -55,20 +55,18 @@ func dataSourceGithubActionsEnvironmentSecrets() *schema.Resource { } } -func dataSourceGithubActionsEnvironmentSecretsRead(d *schema.ResourceData, meta any) error { - ctx := context.Background() +func dataSourceGithubActionsEnvironmentSecretsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name var repoName string envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) if fullName, ok := d.GetOk("full_name"); ok { var err error owner, repoName, err = splitRepoFullName(fullName.(string)) if err != nil { - return err + return diag.FromErr(err) } } @@ -77,23 +75,23 @@ func dataSourceGithubActionsEnvironmentSecretsRead(d *schema.ResourceData, meta } if repoName == "" { - return fmt.Errorf("one of %q or %q has to be provided", "full_name", "name") + return diag.Errorf("one of %q or %q has to be provided", "full_name", "name") } repo, _, err := client.Repositories.Get(ctx, owner, repoName) if err != nil { - return err + return diag.FromErr(err) } options := github.ListOptions{ - PerPage: 100, + PerPage: maxPerPage, } var all_secrets []map[string]string for { - secrets, resp, err := client.Actions.ListEnvSecrets(ctx, int(repo.GetID()), escapedEnvName, &options) + secrets, resp, err := client.Actions.ListEnvSecrets(ctx, int(repo.GetID()), url.PathEscape(envName), &options) if err != nil { - return err + return diag.FromErr(err) } for _, secret := range secrets.Secrets { new_secret := map[string]string{ @@ -109,8 +107,15 @@ func dataSourceGithubActionsEnvironmentSecretsRead(d *schema.ResourceData, meta options.Page = resp.NextPage } - d.SetId(buildTwoPartID(repoName, envName)) - _ = d.Set("secrets", all_secrets) + if id, err := buildID(repoName, escapeIDPart(envName)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + + if err := d.Set("secrets", all_secrets); err != nil { + return diag.FromErr(err) + } return nil } diff --git a/github/data_source_github_actions_environment_secrets_test.go b/github/data_source_github_actions_environment_secrets_test.go index 0e960aa8f4..17c26fe1ca 100644 --- a/github/data_source_github_actions_environment_secrets_test.go +++ b/github/data_source_github_actions_environment_secrets_test.go @@ -15,19 +15,18 @@ func TestAccGithubActionsEnvironmentSecretsDataSource(t *testing.T) { config := fmt.Sprintf(` resource "github_repository" "test" { - name = "%s" - auto_init = true + name = "%s" } resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment / test" + repository = github_repository.test.name + environment = "environment / test" } resource "github_actions_environment_secret" "test" { - secret_name = "secret_1" - environment = github_repository_environment.test.environment - repository = github_repository.test.name + repository = github_repository.test.name + environment = github_repository_environment.test.environment + secret_name = "secret_1" plaintext_value = "foo" } `, repoName) diff --git a/github/data_source_github_actions_environment_variables.go b/github/data_source_github_actions_environment_variables.go index bb02350b93..48ad009866 100644 --- a/github/data_source_github_actions_environment_variables.go +++ b/github/data_source_github_actions_environment_variables.go @@ -2,17 +2,17 @@ package github import ( "context" - "fmt" "net/url" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceGithubActionsEnvironmentVariables() *schema.Resource { return &schema.Resource{ - Read: dataSourceGithubActionsEnvironmentVariablesRead, + ReadContext: dataSourceGithubActionsEnvironmentVariablesRead, Schema: map[string]*schema.Schema{ "full_name": { @@ -59,20 +59,18 @@ func dataSourceGithubActionsEnvironmentVariables() *schema.Resource { } } -func dataSourceGithubActionsEnvironmentVariablesRead(d *schema.ResourceData, meta any) error { - ctx := context.Background() +func dataSourceGithubActionsEnvironmentVariablesRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name var repoName string envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) if fullName, ok := d.GetOk("full_name"); ok { var err error owner, repoName, err = splitRepoFullName(fullName.(string)) if err != nil { - return err + return diag.FromErr(err) } } @@ -81,18 +79,18 @@ func dataSourceGithubActionsEnvironmentVariablesRead(d *schema.ResourceData, met } if repoName == "" { - return fmt.Errorf("one of %q or %q has to be provided", "full_name", "name") + return diag.Errorf("one of %q or %q has to be provided", "full_name", "name") } options := github.ListOptions{ - PerPage: 100, + PerPage: maxPerPage, } var all_variables []map[string]string for { - variables, resp, err := client.Actions.ListEnvVariables(ctx, owner, repoName, escapedEnvName, &options) + variables, resp, err := client.Actions.ListEnvVariables(ctx, owner, repoName, url.PathEscape(envName), &options) if err != nil { - return err + return diag.FromErr(err) } for _, variable := range variables.Variables { new_variable := map[string]string{ @@ -109,8 +107,15 @@ func dataSourceGithubActionsEnvironmentVariablesRead(d *schema.ResourceData, met options.Page = resp.NextPage } - d.SetId(buildTwoPartID(repoName, envName)) - _ = d.Set("variables", all_variables) + if id, err := buildID(repoName, escapeIDPart(envName)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + + if err := d.Set("variables", all_variables); err != nil { + return diag.FromErr(err) + } return nil } diff --git a/github/data_source_github_repository_environment_deployment_policies.go b/github/data_source_github_repository_environment_deployment_policies.go index a7090a6a37..2d26e2bbd5 100644 --- a/github/data_source_github_repository_environment_deployment_policies.go +++ b/github/data_source_github_repository_environment_deployment_policies.go @@ -2,7 +2,7 @@ package github import ( "context" - "fmt" + "net/url" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -49,9 +49,9 @@ func dataSourceGithubRepositoryEnvironmentDeploymentPoliciesRead(ctx context.Con client := meta.(*Owner).v3client owner := meta.(*Owner).name repoName := d.Get("repository").(string) - environmentName := d.Get("environment").(string) + envName := d.Get("environment").(string) - policies, _, err := client.Repositories.ListDeploymentBranchPolicies(ctx, owner, repoName, environmentName) + policies, _, err := client.Repositories.ListDeploymentBranchPolicies(ctx, owner, repoName, url.PathEscape(envName)) if err != nil { return diag.FromErr(err) } @@ -65,9 +65,13 @@ func dataSourceGithubRepositoryEnvironmentDeploymentPoliciesRead(ctx context.Con results = append(results, policyMap) } - d.SetId(fmt.Sprintf("%s:%s", repoName, environmentName)) - err = d.Set("policies", results) - if err != nil { + if id, err := buildID(repoName, escapeIDPart(envName)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + + if err = d.Set("policies", results); err != nil { return diag.FromErr(err) } diff --git a/github/data_source_github_repository_environments.go b/github/data_source_github_repository_environments.go index 3acdd854a6..415d442cdf 100644 --- a/github/data_source_github_repository_environments.go +++ b/github/data_source_github_repository_environments.go @@ -4,12 +4,13 @@ import ( "context" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceGithubRepositoryEnvironments() *schema.Resource { return &schema.Resource{ - Read: dataSourceGithubRepositoryEnvironmentsRead, + ReadContext: dataSourceGithubRepositoryEnvironmentsRead, Schema: map[string]*schema.Schema{ "repository": { @@ -36,7 +37,7 @@ func dataSourceGithubRepositoryEnvironments() *schema.Resource { } } -func dataSourceGithubRepositoryEnvironmentsRead(d *schema.ResourceData, meta any) error { +func dataSourceGithubRepositoryEnvironmentsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client orgName := meta.(*Owner).name repoName := d.Get("repository").(string) @@ -45,9 +46,9 @@ func dataSourceGithubRepositoryEnvironmentsRead(d *schema.ResourceData, meta any for { listOptions := &github.EnvironmentListOptions{} - environments, resp, err := client.Repositories.ListEnvironments(context.Background(), orgName, repoName, listOptions) + environments, resp, err := client.Repositories.ListEnvironments(ctx, orgName, repoName, listOptions) if err != nil { - return err + return diag.FromErr(err) } results = append(results, flattenEnvironments(environments)...) @@ -60,9 +61,8 @@ func dataSourceGithubRepositoryEnvironmentsRead(d *schema.ResourceData, meta any } d.SetId(repoName) - err := d.Set("environments", results) - if err != nil { - return err + if err := d.Set("environments", results); err != nil { + return diag.FromErr(err) } return nil diff --git a/github/resource_github_actions_environment_secret.go b/github/resource_github_actions_environment_secret.go index d0b9f4c3a1..355e16df7e 100644 --- a/github/resource_github_actions_environment_secret.go +++ b/github/resource_github_actions_environment_secret.go @@ -9,15 +9,16 @@ import ( "net/url" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceGithubActionsEnvironmentSecret() *schema.Resource { return &schema.Resource{ - Create: resourceGithubActionsEnvironmentSecretCreateOrUpdate, - Read: resourceGithubActionsEnvironmentSecretRead, - Delete: resourceGithubActionsEnvironmentSecretDelete, + CreateContext: resourceGithubActionsEnvironmentSecretCreateOrUpdate, + ReadContext: resourceGithubActionsEnvironmentSecretRead, + DeleteContext: resourceGithubActionsEnvironmentSecretDelete, Schema: map[string]*schema.Schema{ "repository": { @@ -70,26 +71,24 @@ func resourceGithubActionsEnvironmentSecret() *schema.Resource { } } -func resourceGithubActionsEnvironmentSecretCreateOrUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentSecretCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.Background() repoName := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) secretName := d.Get("secret_name").(string) plaintextValue := d.Get("plaintext_value").(string) var encryptedValue string repo, _, err := client.Repositories.Get(ctx, owner, repoName) if err != nil { - return err + return diag.FromErr(err) } - keyId, publicKey, err := getEnvironmentPublicKeyDetails(repo.GetID(), escapedEnvName, meta) + keyId, publicKey, err := getEnvironmentPublicKeyDetails(ctx, repo.GetID(), url.PathEscape(envName), meta) if err != nil { - return err + return diag.FromErr(err) } if encryptedText, ok := d.GetOk("encrypted_value"); ok { @@ -97,7 +96,7 @@ func resourceGithubActionsEnvironmentSecretCreateOrUpdate(d *schema.ResourceData } else { encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey) if err != nil { - return err + return diag.FromErr(err) } encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes) } @@ -109,25 +108,31 @@ func resourceGithubActionsEnvironmentSecretCreateOrUpdate(d *schema.ResourceData EncryptedValue: encryptedValue, } - _, err = client.Actions.CreateOrUpdateEnvSecret(ctx, int(repo.GetID()), escapedEnvName, eSecret) + _, err = client.Actions.CreateOrUpdateEnvSecret(ctx, int(repo.GetID()), url.PathEscape(envName), eSecret) if err != nil { - return err + return diag.FromErr(err) } - d.SetId(buildThreePartID(repoName, envName, secretName)) - return resourceGithubActionsEnvironmentSecretRead(d, meta) + id, err := buildID(repoName, escapeIDPart(envName), secretName) + if err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + + return resourceGithubActionsEnvironmentSecretRead(ctx, d, meta) } -func resourceGithubActionsEnvironmentSecretRead(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentSecretRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.Background() - repoName, envName, secretName, err := parseThreePartID(d.Id(), "repository", "environment", "secret_name") + repoName, envNamePart, secretName, err := parseID3(d.Id()) if err != nil { - return err + return diag.FromErr(err) } - escapedEnvName := url.PathEscape(envName) + + envName := unescapeIDPart(envNamePart) repo, _, err := client.Repositories.Get(ctx, owner, repoName) if err != nil { @@ -140,10 +145,10 @@ func resourceGithubActionsEnvironmentSecretRead(d *schema.ResourceData, meta any return nil } } - return err + return diag.FromErr(err) } - secret, _, err := client.Actions.GetEnvSecret(ctx, int(repo.GetID()), escapedEnvName, secretName) + secret, _, err := client.Actions.GetEnvSecret(ctx, int(repo.GetID()), url.PathEscape(envName), secretName) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { @@ -154,17 +159,17 @@ func resourceGithubActionsEnvironmentSecretRead(d *schema.ResourceData, meta any return nil } } - return err + return diag.FromErr(err) } if err = d.Set("encrypted_value", d.Get("encrypted_value")); err != nil { - return err + return diag.FromErr(err) } if err = d.Set("plaintext_value", d.Get("plaintext_value")); err != nil { - return err + return diag.FromErr(err) } if err = d.Set("created_at", secret.CreatedAt.String()); err != nil { - return err + return diag.FromErr(err) } // This is a drift detection mechanism based on timestamps. @@ -186,41 +191,44 @@ func resourceGithubActionsEnvironmentSecretRead(d *schema.ResourceData, meta any _ = d.Set("plaintext_value", "") } else if !ok { if err = d.Set("updated_at", secret.UpdatedAt.String()); err != nil { - return err + return diag.FromErr(err) } } return nil } -func resourceGithubActionsEnvironmentSecretDelete(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentSecretDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.WithValue(context.Background(), ctxId, d.Id()) - repoName, envName, secretName, err := parseThreePartID(d.Id(), "repository", "environment", "secret_name") + repoName, envNamePart, secretName, err := parseID3(d.Id()) if err != nil { - return err + return diag.FromErr(err) } - escapedEnvName := url.PathEscape(envName) + + envName := unescapeIDPart(envNamePart) + repo, _, err := client.Repositories.Get(ctx, owner, repoName) if err != nil { - return err + return diag.FromErr(err) } log.Printf("[INFO] Deleting environment secret: %s", d.Id()) - _, err = client.Actions.DeleteEnvSecret(ctx, int(repo.GetID()), escapedEnvName, secretName) + _, err = client.Actions.DeleteEnvSecret(ctx, int(repo.GetID()), url.PathEscape(envName), secretName) + if err != nil { + return diag.FromErr(err) + } - return err + return nil } -func getEnvironmentPublicKeyDetails(repoID int64, envName string, meta any) (keyId, pkValue string, err error) { +func getEnvironmentPublicKeyDetails(ctx context.Context, repoID int64, envNameEscaped string, meta any) (string, string, error) { client := meta.(*Owner).v3client - ctx := context.Background() - publicKey, _, err := client.Actions.GetEnvPublicKey(ctx, int(repoID), envName) + publicKey, _, err := client.Actions.GetEnvPublicKey(ctx, int(repoID), envNameEscaped) if err != nil { - return keyId, pkValue, err + return "", "", err } - return publicKey.GetKeyID(), publicKey.GetKey(), err + return publicKey.GetKeyID(), publicKey.GetKey(), nil } diff --git a/github/resource_github_actions_environment_secret_test.go b/github/resource_github_actions_environment_secret_test.go index 3164547ae4..73968b4416 100644 --- a/github/resource_github_actions_environment_secret_test.go +++ b/github/resource_github_actions_environment_secret_test.go @@ -130,6 +130,9 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) { PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ + { + Config: config, + }, { Config: config, Destroy: true, diff --git a/github/resource_github_actions_environment_variable.go b/github/resource_github_actions_environment_variable.go index ac6990ce53..b3dff7eb51 100644 --- a/github/resource_github_actions_environment_variable.go +++ b/github/resource_github_actions_environment_variable.go @@ -8,17 +8,18 @@ import ( "net/url" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceGithubActionsEnvironmentVariable() *schema.Resource { return &schema.Resource{ - Create: resourceGithubActionsEnvironmentVariableCreateOrUpdate, - Read: resourceGithubActionsEnvironmentVariableRead, - Update: resourceGithubActionsEnvironmentVariableCreateOrUpdate, - Delete: resourceGithubActionsEnvironmentVariableDelete, + CreateContext: resourceGithubActionsEnvironmentVariableCreateOrUpdate, + ReadContext: resourceGithubActionsEnvironmentVariableRead, + UpdateContext: resourceGithubActionsEnvironmentVariableCreateOrUpdate, + DeleteContext: resourceGithubActionsEnvironmentVariableDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: resourceGithubActionsEnvironmentVariableImport, }, Schema: map[string]*schema.Schema{ @@ -59,14 +60,12 @@ func resourceGithubActionsEnvironmentVariable() *schema.Resource { } } -func resourceGithubActionsEnvironmentVariableCreateOrUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentVariableCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.Background() repoName := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) name := d.Get("variable_name").(string) variable := &github.ActionsVariable{ @@ -75,37 +74,42 @@ func resourceGithubActionsEnvironmentVariableCreateOrUpdate(d *schema.ResourceDa } // Try to create the variable first - _, err := client.Actions.CreateEnvVariable(ctx, owner, repoName, escapedEnvName, variable) + _, err := client.Actions.CreateEnvVariable(ctx, owner, repoName, url.PathEscape(envName), variable) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusConflict { // Variable already exists, try to update instead // If it fails here, we want to return the error otherwise continue - _, err = client.Actions.UpdateEnvVariable(ctx, owner, repoName, escapedEnvName, variable) + _, err = client.Actions.UpdateEnvVariable(ctx, owner, repoName, url.PathEscape(envName), variable) if err != nil { - return err + return diag.FromErr(err) } } else { - return err + return diag.FromErr(err) } } - d.SetId(buildThreePartID(repoName, envName, name)) - return resourceGithubActionsEnvironmentVariableRead(d, meta) + if id, err := buildID(repoName, escapeIDPart(envName), name); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + + return resourceGithubActionsEnvironmentVariableRead(ctx, d, meta) } -func resourceGithubActionsEnvironmentVariableRead(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentVariableRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.Background() - repoName, envName, name, err := parseThreePartID(d.Id(), "repository", "environment", "variable_name") + repoName, envNamePart, name, err := parseID3(d.Id()) if err != nil { - return err + return diag.FromErr(err) } - escapedEnvName := url.PathEscape(envName) - variable, _, err := client.Actions.GetEnvVariable(ctx, owner, repoName, escapedEnvName, name) + envName := unescapeIDPart(envNamePart) + + variable, _, err := client.Actions.GetEnvVariable(ctx, owner, repoName, url.PathEscape(envName), name) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { @@ -116,7 +120,7 @@ func resourceGithubActionsEnvironmentVariableRead(d *schema.ResourceData, meta a return nil } } - return err + return diag.FromErr(err) } _ = d.Set("repository", repoName) @@ -129,18 +133,40 @@ func resourceGithubActionsEnvironmentVariableRead(d *schema.ResourceData, meta a return nil } -func resourceGithubActionsEnvironmentVariableDelete(d *schema.ResourceData, meta any) error { +func resourceGithubActionsEnvironmentVariableDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name - ctx := context.WithValue(context.Background(), ctxId, d.Id()) - repoName, envName, name, err := parseThreePartID(d.Id(), "repository", "environment", "variable_name") + repoName, envNamePart, name, err := parseID3(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + envName := unescapeIDPart(envNamePart) + + _, err = client.Actions.DeleteEnvVariable(ctx, owner, repoName, url.PathEscape(envName), name) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceGithubActionsEnvironmentVariableImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + repoName, envNamePart, name, err := parseID3(d.Id()) if err != nil { - return err + return nil, err } - escapedEnvName := url.PathEscape(envName) - _, err = client.Actions.DeleteEnvVariable(ctx, owner, repoName, escapedEnvName, name) + if err := d.Set("repository", repoName); err != nil { + return nil, err + } + if err := d.Set("environment", unescapeIDPart(envNamePart)); err != nil { + return nil, err + } + if err := d.Set("variable_name", name); err != nil { + return nil, err + } - return err + return []*schema.ResourceData{d}, nil } diff --git a/github/resource_github_actions_environment_variable_test.go b/github/resource_github_actions_environment_variable_test.go index 2a2325eacc..96b667e8f6 100644 --- a/github/resource_github_actions_environment_variable_test.go +++ b/github/resource_github_actions_environment_variable_test.go @@ -150,7 +150,6 @@ func TestAccGithubActionsEnvironmentVariable(t *testing.T) { }, { ResourceName: "github_actions_environment_variable.variable", - ImportStateId: fmt.Sprintf(`%s:%s:%s`, repoName, envName, varName), ImportState: true, ImportStateVerify: true, }, @@ -208,13 +207,12 @@ func TestAccGithubActionsEnvironmentVariable_alreadyExists(t *testing.T) { client := testAccProvider.Meta().(*Owner).v3client owner := testAccProvider.Meta().(*Owner).name ctx := context.Background() - escapedEnvName := url.PathEscape(envName) variable := &github.ActionsVariable{ Name: varName, Value: value, } - _, err := client.Actions.CreateEnvVariable(ctx, owner, repoName, escapedEnvName, variable) + _, err := client.Actions.CreateEnvVariable(ctx, owner, repoName, url.PathEscape(envName), variable) return err }, ), diff --git a/github/resource_github_repository_environment.go b/github/resource_github_repository_environment.go index f4fa9fda79..bf2fccf6f0 100644 --- a/github/resource_github_repository_environment.go +++ b/github/resource_github_repository_environment.go @@ -8,18 +8,19 @@ import ( "net/url" "github.com/google/go-github/v81/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceGithubRepositoryEnvironment() *schema.Resource { return &schema.Resource{ - Create: resourceGithubRepositoryEnvironmentCreate, - Read: resourceGithubRepositoryEnvironmentRead, - Update: resourceGithubRepositoryEnvironmentUpdate, - Delete: resourceGithubRepositoryEnvironmentDelete, + CreateContext: resourceGithubRepositoryEnvironmentCreate, + ReadContext: resourceGithubRepositoryEnvironmentRead, + UpdateContext: resourceGithubRepositoryEnvironmentUpdate, + DeleteContext: resourceGithubRepositoryEnvironmentDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: resourceGithubRepositoryEnvironmentImport, }, Schema: map[string]*schema.Schema{ "repository": { @@ -98,40 +99,40 @@ func resourceGithubRepositoryEnvironment() *schema.Resource { } } -func resourceGithubRepositoryEnvironmentCreate(d *schema.ResourceData, meta any) error { +func resourceGithubRepositoryEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client - owner := meta.(*Owner).name + repoName := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) updateData := createUpdateEnvironmentData(d) - ctx := context.Background() - - _, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, escapedEnvName, &updateData) + _, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, url.PathEscape(envName), &updateData) if err != nil { - return err + return diag.FromErr(err) } - d.SetId(buildTwoPartID(repoName, envName)) + if id, err := buildID(repoName, escapeIDPart(envName)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } - return resourceGithubRepositoryEnvironmentRead(d, meta) + return nil } -func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) error { +func resourceGithubRepositoryEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client - owner := meta.(*Owner).name - repoName, envName, err := parseTwoPartID(d.Id(), "repository", "environment") - escapedEnvName := url.PathEscape(envName) + + repoName, envNamePart, err := parseID2(d.Id()) if err != nil { - return err + return diag.FromErr(err) } - ctx := context.WithValue(context.Background(), ctxId, d.Id()) + envName := unescapeIDPart(envNamePart) - env, _, err := client.Repositories.GetEnvironment(ctx, owner, repoName, escapedEnvName) + env, _, err := client.Repositories.GetEnvironment(ctx, owner, repoName, url.PathEscape(envName)) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { @@ -142,7 +143,7 @@ func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) e return nil } } - return err + return diag.FromErr(err) } _ = d.Set("repository", repoName) @@ -154,7 +155,7 @@ func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) e switch *pr.Type { case "wait_timer": if err = d.Set("wait_timer", pr.WaitTimer); err != nil { - return err + return diag.FromErr(err) } case "required_reviewers": @@ -179,11 +180,11 @@ func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) e "users": users, }, }); err != nil { - return err + return diag.FromErr(err) } if err = d.Set("prevent_self_review", pr.PreventSelfReview); err != nil { - return err + return diag.FromErr(err) } } } @@ -195,7 +196,7 @@ func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) e "custom_branch_policies": env.DeploymentBranchPolicy.CustomBranchPolicies, }, }); err != nil { - return err + return diag.FromErr(err) } } else { _ = d.Set("deployment_branch_policy", []any{}) @@ -204,41 +205,61 @@ func resourceGithubRepositoryEnvironmentRead(d *schema.ResourceData, meta any) e return nil } -func resourceGithubRepositoryEnvironmentUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubRepositoryEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client - owner := meta.(*Owner).name + repoName := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) updateData := createUpdateEnvironmentData(d) - ctx := context.Background() - - resultKey, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, escapedEnvName, &updateData) + _, _, err := client.Repositories.CreateUpdateEnvironment(ctx, owner, repoName, url.PathEscape(envName), &updateData) if err != nil { - return err + return diag.FromErr(err) } - d.SetId(buildTwoPartID(repoName, resultKey.GetName())) + if id, err := buildID(repoName, escapeIDPart(envName)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } - return resourceGithubRepositoryEnvironmentRead(d, meta) + return nil } -func resourceGithubRepositoryEnvironmentDelete(d *schema.ResourceData, meta any) error { +func resourceGithubRepositoryEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client - owner := meta.(*Owner).name - repoName, envName, err := parseTwoPartID(d.Id(), "repository", "environment") - escapedEnvName := url.PathEscape(envName) + + repoName, envNamePart, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + envName := unescapeIDPart(envNamePart) + + _, err = client.Repositories.DeleteEnvironment(ctx, owner, repoName, url.PathEscape(envName)) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceGithubRepositoryEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + repoName, envNamePart, err := parseID2(d.Id()) if err != nil { - return err + return nil, err } - ctx := context.WithValue(context.Background(), ctxId, d.Id()) + if err := d.Set("repository", repoName); err != nil { + return nil, err + } + if err := d.Set("environment", unescapeIDPart(envNamePart)); err != nil { + return nil, err + } - _, err = client.Repositories.DeleteEnvironment(ctx, owner, repoName, escapedEnvName) - return err + return []*schema.ResourceData{d}, nil } func createUpdateEnvironmentData(d *schema.ResourceData) github.CreateUpdateEnvironment { diff --git a/github/resource_github_repository_environment_deployment_policy.go b/github/resource_github_repository_environment_deployment_policy.go index 0e408766c7..8726a02663 100644 --- a/github/resource_github_repository_environment_deployment_policy.go +++ b/github/resource_github_repository_environment_deployment_policy.go @@ -16,12 +16,13 @@ import ( func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource { return &schema.Resource{ + CustomizeDiff: resourceGithubRepositoryEnvironmentDeploymentPolicyDiff, CreateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyCreate, ReadContext: resourceGithubRepositoryEnvironmentDeploymentPolicyRead, UpdateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate, DeleteContext: resourceGithubRepositoryEnvironmentDeploymentPolicyDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: resourceGithubRepositoryEnvironmentDeploymentPolicyImport, }, Schema: map[string]*schema.Schema{ "repository": { @@ -65,17 +66,32 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicy() *schema.Resource { }, }, }, - CustomizeDiff: customDeploymentPolicyDiffFunction, } } +func resourceGithubRepositoryEnvironmentDeploymentPolicyDiff(_ context.Context, diff *schema.ResourceDiff, v any) error { + if diff.Id() == "" { + return nil + } + + if diff.HasChange("branch_pattern") && diff.HasChange("tag_pattern") { + if err := diff.ForceNew("branch_pattern"); err != nil { + return err + } + if err := diff.ForceNew("tag_pattern"); err != nil { + return err + } + } + + return nil +} + func resourceGithubRepositoryEnvironmentDeploymentPolicyCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client owner := meta.(*Owner).name repoName := d.Get("repository").(string) envName := d.Get("environment").(string) - escapedEnvName := url.PathEscape(envName) var createData github.DeploymentBranchPolicyRequest if v, ok := d.GetOk("branch_pattern"); ok { @@ -92,30 +108,37 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyCreate(ctx context.Conte return diag.Errorf("only one of 'branch_pattern' or 'tag_pattern' must be specified") } - resultKey, _, err := client.Repositories.CreateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, &createData) + resultKey, _, err := client.Repositories.CreateDeploymentBranchPolicy(ctx, owner, repoName, url.PathEscape(envName), &createData) if err != nil { return diag.FromErr(err) } - d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10))) + if id, err := buildID(repoName, escapeIDPart(envName), strconv.FormatInt(resultKey.GetID(), 10)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + return nil } func resourceGithubRepositoryEnvironmentDeploymentPolicyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client - owner := meta.(*Owner).name - repoName, envName, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + + repoName, envNamePart, branchPolicyIdString, err := parseID3(d.Id()) if err != nil { return diag.FromErr(err) } + envName := unescapeIDPart(envNamePart) + branchPolicyId, err := strconv.ParseInt(branchPolicyIdString, 10, 64) if err != nil { return diag.FromErr(err) } - branchPolicy, _, err := client.Repositories.GetDeploymentBranchPolicy(ctx, owner, repoName, envName, branchPolicyId) + branchPolicy, _, err := client.Repositories.GetDeploymentBranchPolicy(ctx, owner, repoName, url.PathEscape(envName), branchPolicyId) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) { @@ -148,8 +171,7 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate(ctx context.Conte envName := d.Get("environment").(string) branchPattern := d.Get("branch_pattern").(string) tagPattern := d.Get("tag_pattern").(string) - escapedEnvName := url.PathEscape(envName) - _, _, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + _, _, branchPolicyIdString, err := parseID3(d.Id()) if err != nil { return diag.FromErr(err) } @@ -168,11 +190,17 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyUpdate(ctx context.Conte Name: github.Ptr(pattern), } - resultKey, _, err := client.Repositories.UpdateDeploymentBranchPolicy(ctx, owner, repoName, escapedEnvName, branchPolicyId, &updateData) + resultKey, _, err := client.Repositories.UpdateDeploymentBranchPolicy(ctx, owner, repoName, url.PathEscape(envName), branchPolicyId, &updateData) if err != nil { return diag.FromErr(err) } - d.SetId(buildThreePartID(repoName, escapedEnvName, strconv.FormatInt(resultKey.GetID(), 10))) + + if id, err := buildID(repoName, escapeIDPart(envName), strconv.FormatInt(resultKey.GetID(), 10)); err != nil { + return diag.FromErr(err) + } else { + d.SetId(id) + } + return nil } @@ -180,17 +208,19 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyDelete(ctx context.Conte client := meta.(*Owner).v3client owner := meta.(*Owner).name - repoName, envName, branchPolicyIdString, err := parseThreePartID(d.Id(), "repository", "environment", "branchPolicyId") + repoName, envNamePart, branchPolicyIdString, err := parseID3(d.Id()) if err != nil { return diag.FromErr(err) } + envName := unescapeIDPart(envNamePart) + branchPolicyId, err := strconv.ParseInt(branchPolicyIdString, 10, 64) if err != nil { return diag.FromErr(err) } - _, err = client.Repositories.DeleteDeploymentBranchPolicy(ctx, owner, repoName, envName, branchPolicyId) + _, err = client.Repositories.DeleteDeploymentBranchPolicy(ctx, owner, repoName, url.PathEscape(envName), branchPolicyId) if err != nil { return diag.FromErr(err) } @@ -198,15 +228,18 @@ func resourceGithubRepositoryEnvironmentDeploymentPolicyDelete(ctx context.Conte return nil } -func customDeploymentPolicyDiffFunction(_ context.Context, diff *schema.ResourceDiff, v any) error { - if diff.HasChange("branch_pattern") && diff.HasChange("tag_pattern") { - if err := diff.ForceNew("branch_pattern"); err != nil { - return err - } - if err := diff.ForceNew("tag_pattern"); err != nil { - return err - } +func resourceGithubRepositoryEnvironmentDeploymentPolicyImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + repoName, envNamePart, _, err := parseID3(d.Id()) + if err != nil { + return nil, err } - return nil + if err := d.Set("repository", repoName); err != nil { + return nil, err + } + if err := d.Set("environment", unescapeIDPart(envNamePart)); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil } diff --git a/github/resource_github_repository_environment_deployment_policy_test.go b/github/resource_github_repository_environment_deployment_policy_test.go index d3cc2a1592..223f6f7b73 100644 --- a/github/resource_github_repository_environment_deployment_policy_test.go +++ b/github/resource_github_repository_environment_deployment_policy_test.go @@ -10,10 +10,11 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { +func TestAccGithubRepositoryEnvironmentDeploymentPolicy(t *testing.T) { t.Run("creates a repository environment with branch-based deployment policy", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + envName := "environment / test" config := fmt.Sprintf(` data "github_user" "current" { @@ -27,7 +28,7 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { resource "github_repository_environment" "test" { repository = github_repository.test.name - environment = "environment / test" + environment = "%s" wait_timer = 10000 reviewers { users = [data.github_user.current.id] @@ -44,7 +45,7 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { branch_pattern = "releases/*" } - `, repoName) + `, repoName, envName) check := resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( @@ -65,8 +66,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, @@ -75,9 +76,7 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranch(t *testing.T) { }, }) }) -} -func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchUpdate(t *testing.T) { t.Run("updates the pattern for a branch-based deployment policy", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) @@ -178,8 +177,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchUpdate(t *testing.T ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config1, @@ -192,9 +191,7 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchUpdate(t *testing.T }, }) }) -} -func TestAccGithubRepositoryEnvironmentDeploymentPolicyTag(t *testing.T) { t.Run("creates a repository environment with tag-based deployment policy", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) @@ -249,8 +246,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTag(t *testing.T) { ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, @@ -259,13 +256,11 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTag(t *testing.T) { }, }) }) -} -func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagUpdate(t *testing.T) { t.Run("updates the pattern for a tag-based deployment policy", func(t *testing.T) { + var deploymentPolicyId string randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - var deploymentPolicyId string config1 := fmt.Sprintf(` @@ -373,8 +368,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagUpdate(t *testing.T) { ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config1, @@ -387,13 +382,11 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagUpdate(t *testing.T) { }, }) }) -} -func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchToTagUpdate(t *testing.T) { t.Run("recreates deployment policy when pattern type changes from branch to tag", func(t *testing.T) { + var deploymentPolicyId string randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - var deploymentPolicyId string config1 := fmt.Sprintf(` @@ -501,8 +494,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchToTagUpdate(t *test ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config1, @@ -515,13 +508,11 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyBranchToTagUpdate(t *test }, }) }) -} -func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagToBranchUpdate(t *testing.T) { t.Run("recreates deployment policy when pattern type changes from tag to branch", func(t *testing.T) { + var deploymentPolicyId string randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - var deploymentPolicyId string config1 := fmt.Sprintf(` @@ -629,8 +620,8 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagToBranchUpdate(t *test ) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config1, @@ -643,9 +634,57 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyTagToBranchUpdate(t *test }, }) }) + + t.Run("import", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + envName := "environment / test" + config := fmt.Sprintf(` +data "github_user" "current" { + username = "" +} + +resource "github_repository" "test" { + name = "%s" +} + +resource "github_repository_environment" "test" { + repository = github_repository.test.name + environment = "%s" + wait_timer = 10000 + reviewers { + users = [data.github_user.current.id] + } + deployment_branch_policy { + protected_branches = false + custom_branch_policies = true + } +} + +resource "github_repository_environment_deployment_policy" "test" { + repository = github_repository.test.name + environment = github_repository_environment.test.environment + branch_pattern = "main" } +`, repoName, envName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.TestCheckResourceAttr("github_repository_environment_deployment_policy.test", "environment", envName), + }, + { + ResourceName: "github_repository_environment_deployment_policy.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) -func TestAccGithubRepositoryEnvironmentDeploymentPolicyErrors(t *testing.T) { t.Run("errors when no patterns are set", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) @@ -795,631 +834,6 @@ func TestAccGithubRepositoryEnvironmentDeploymentPolicyErrors(t *testing.T) { }) } -func TestAccGithubRepositoryEnvironmentDeploymentPolicy(t *testing.T) { - t.Run("creates a repository environment with branch-based deployment policy", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment / test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - branch_pattern = "releases/*" - } - - `, repoName) - - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment / test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - "releases/*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config, - Check: check, - }, - }, - }) - }) - - t.Run("updates the pattern for a branch-based deployment policy", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - var deploymentPolicyId string - - config1 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - branch_pattern = "main" - } - - `, repoName) - - check1 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - "main", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - ), - testDeploymentPolicyId("github_repository_environment_deployment_policy.test", &deploymentPolicyId), - ) - - config2 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - branch_pattern = "release/*" - } - - `, repoName) - - check2 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository_environment_deployment_policy.test", "repository", repoName), - resource.TestCheckResourceAttr("github_repository_environment_deployment_policy.test", "environment", "environment/test"), - resource.TestCheckResourceAttr("github_repository_environment_deployment_policy.test", "branch_pattern", "release/*"), - resource.TestCheckNoResourceAttr("github_repository_environment_deployment_policy.test", "tag_pattern"), - testSameDeploymentPolicyId( - "github_repository_environment_deployment_policy.test", - &deploymentPolicyId, - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config1, - Check: check1, - }, - { - Config: config2, - Check: check2, - }, - }, - }) - }) - - t.Run("creates a repository environment with tag-based deployment policy", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - tag_pattern = "v*" - } - - `, repoName) - - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - "v*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config, - Check: check, - }, - }, - }) - }) - - t.Run("updates the pattern for a tag-based deployment policy", func(t *testing.T) { - var deploymentPolicyId string - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - - config1 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - tag_pattern = "v*" - } - - `, repoName) - - check1 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - "v*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - ), - testDeploymentPolicyId("github_repository_environment_deployment_policy.test", &deploymentPolicyId), - ) - - config2 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - tag_pattern = "version*" - } - - `, repoName) - - check2 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - "version*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - ), - testSameDeploymentPolicyId( - "github_repository_environment_deployment_policy.test", - &deploymentPolicyId, - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config1, - Check: check1, - }, - { - Config: config2, - Check: check2, - }, - }, - }) - }) - - t.Run("recreates deployment policy when pattern type changes from branch to tag", func(t *testing.T) { - var deploymentPolicyId string - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - - config1 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - branch_pattern = "release/*" - } - - `, repoName) - - check1 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - "release/*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - ), - testDeploymentPolicyId("github_repository_environment_deployment_policy.test", &deploymentPolicyId), - ) - - config2 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - tag_pattern = "v*" - } - - `, repoName) - - check2 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - "v*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - ), - testNewDeploymentPolicyId( - "github_repository_environment_deployment_policy.test", - &deploymentPolicyId, - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config1, - Check: check1, - }, - { - Config: config2, - Check: check2, - }, - }, - }) - }) - - t.Run("recreates deployment policy when pattern type changes from tag to branch", func(t *testing.T) { - var deploymentPolicyId string - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-deploy-%s", testResourcePrefix, randomID) - - config1 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - tag_pattern = "v*" - } - - `, repoName) - - check1 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - "v*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - ), - testDeploymentPolicyId("github_repository_environment_deployment_policy.test", &deploymentPolicyId), - ) - - config2 := fmt.Sprintf(` - - data "github_user" "current" { - username = "" - } - - resource "github_repository" "test" { - name = "%s" - ignore_vulnerability_alerts_during_read = true - } - - resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment/test" - wait_timer = 10000 - reviewers { - users = [data.github_user.current.id] - } - deployment_branch_policy { - protected_branches = false - custom_branch_policies = true - } - } - - resource "github_repository_environment_deployment_policy" "test" { - repository = github_repository.test.name - environment = github_repository_environment.test.environment - branch_pattern = "release/*" - } - - `, repoName) - - check2 := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "repository", - repoName, - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "environment", - "environment/test", - ), - resource.TestCheckResourceAttr( - "github_repository_environment_deployment_policy.test", "branch_pattern", - "release/*", - ), - resource.TestCheckNoResourceAttr( - "github_repository_environment_deployment_policy.test", "tag_pattern", - ), - testNewDeploymentPolicyId( - "github_repository_environment_deployment_policy.test", - &deploymentPolicyId, - ), - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config1, - Check: check1, - }, - { - Config: config2, - Check: check2, - }, - }, - }) - }) -} - func testDeploymentPolicyId(resourceName string, id *string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] diff --git a/github/resource_github_repository_environment_test.go b/github/resource_github_repository_environment_test.go index 16d2beb148..db4ee9cdea 100644 --- a/github/resource_github_repository_environment_test.go +++ b/github/resource_github_repository_environment_test.go @@ -11,7 +11,8 @@ import ( func TestAccGithubRepositoryEnvironment(t *testing.T) { t.Run("creates a repository environment", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-env-%s", testResourcePrefix, randomID) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + envName := "environment / test" config := fmt.Sprintf(` data "github_user" "current" { @@ -24,28 +25,109 @@ func TestAccGithubRepositoryEnvironment(t *testing.T) { } resource "github_repository_environment" "test" { - repository = github_repository.test.name - environment = "environment / test" - can_admins_bypass = false - wait_timer = 10000 - prevent_self_review = true + repository = github_repository.test.name + environment = "%s" + + can_admins_bypass = false + wait_timer = 10000 + prevent_self_review = true + + reviewers { + users = [data.github_user.current.id] + } + + deployment_branch_policy { + protected_branches = true + custom_branch_policies = false + } + } + + `, repoName, envName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("github_repository_environment.test", "environment", envName), + resource.TestCheckResourceAttr("github_repository_environment.test", "can_admins_bypass", "false"), + resource.TestCheckResourceAttr("github_repository_environment.test", "prevent_self_review", "true"), + resource.TestCheckResourceAttr("github_repository_environment.test", "wait_timer", "10000"), + ), + }, + }, + }) + }) + + t.Run("creates a repository environment with id separator", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + envName := "environment:test" + config := fmt.Sprintf(` + + data "github_user" "current" { + username = "" + } + + resource "github_repository" "test" { + name = "%s" + visibility = "public" + } + + resource "github_repository_environment" "test" { + repository = github_repository.test.name + environment = "%s" + + can_admins_bypass = false + wait_timer = 10000 + prevent_self_review = true + reviewers { users = [data.github_user.current.id] } + deployment_branch_policy { protected_branches = true custom_branch_policies = false } } - `, repoName) + `, repoName, envName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("github_repository_environment.test", "environment", envName), + resource.TestCheckResourceAttr("github_repository_environment.test", "can_admins_bypass", "false"), + resource.TestCheckResourceAttr("github_repository_environment.test", "prevent_self_review", "true"), + resource.TestCheckResourceAttr("github_repository_environment.test", "wait_timer", "10000"), + ), + }, + }, + }) + }) + + t.Run("import", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + envName := "environment / test" + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "public" + } - check := resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("github_repository_environment.test", "environment", "environment / test"), - resource.TestCheckResourceAttr("github_repository_environment.test", "can_admins_bypass", "false"), - resource.TestCheckResourceAttr("github_repository_environment.test", "prevent_self_review", "true"), - resource.TestCheckResourceAttr("github_repository_environment.test", "wait_timer", "10000"), - ) + resource "github_repository_environment" "test" { + repository = github_repository.test.name + environment = "%s" + } + `, repoName, envName) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, @@ -53,7 +135,12 @@ func TestAccGithubRepositoryEnvironment(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: check, + }, + { + ResourceName: "github_repository_environment.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"can_admins_bypass", "prevent_self_review", "reviewers", "wait_timer", "deployment_branch_policy"}, }, }, }) diff --git a/github/util.go b/github/util.go index 693bf55d0f..a84be7735f 100644 --- a/github/util.go +++ b/github/util.go @@ -19,9 +19,74 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +const ( + idSeparator = ":" + idSeperatorEscaped = `??` +) + // https://developer.github.com/guides/traversing-with-pagination/#basics-of-pagination var maxPerPage = 100 +// escapeIDPart escapes any idSeparator characters in a string. +func escapeIDPart(part string) string { + return strings.ReplaceAll(part, idSeparator, idSeperatorEscaped) +} + +// unescapeIDPart unescapes any escaped idSeparator characters in a string. +func unescapeIDPart(part string) string { + return strings.ReplaceAll(part, idSeperatorEscaped, idSeparator) +} + +// buildID joins the parts with the idSeparator. +func buildID(parts ...string) (string, error) { + l := len(parts) + if l == 0 { + return "", fmt.Errorf("no parts provided to build id") + } + + id := strings.Join(parts, idSeparator) + + if p := strings.Split(id, idSeparator); len(p) != l { + return "", fmt.Errorf("unescaped seperators in id parts %v", parts) + } + + return id, nil +} + +// parseID splits the id by the idSeparator checking the count. +func parseID(id string, count int) ([]string, error) { + if len(id) == 0 { + return nil, fmt.Errorf("id is empty") + } + + parts := strings.Split(id, idSeparator) + if len(parts) != count { + return nil, fmt.Errorf("unexpected ID format (%q); expected %d parts separated by %q", id, count, idSeparator) + } + + return parts, nil +} + +// parseID2 splits the id by the idSeparator into two parts. +func parseID2(id string) (string, string, error) { + parts, err := parseID(id, 2) + if err != nil { + return "", "", err + } + + return parts[0], parts[1], nil +} + +// parseID3 splits the id by the idSeparator into three parts. +func parseID3(id string) (string, string, string, error) { + parts, err := parseID(id, 3) + if err != nil { + return "", "", "", err + } + + return parts[0], parts[1], parts[2], nil +} + func checkOrganization(meta any) error { if !meta.(*Owner).IsOrganization { return fmt.Errorf("this resource can only be used in the context of an organization, %q is a user", meta.(*Owner).name) diff --git a/github/util_test.go b/github/util_test.go index 5dbd9374fe..bd2b4fb9c9 100644 --- a/github/util_test.go +++ b/github/util_test.go @@ -7,6 +7,321 @@ import ( "github.com/hashicorp/go-cty/cty" ) +func Test_escapeIDPart(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + input string + expected string + }{ + { + testName: "no_separator", + input: "part1", + expected: "part1", + }, + { + testName: "with_separator", + input: "part:1", + expected: "part??1", + }, + { + testName: "multiple_separators", + input: "part:1:subpart:2", + expected: "part??1??subpart??2", + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got := escapeIDPart(d.input) + + if got != d.expected { + t.Fatalf("expected escaped part %q but got %q", d.expected, got) + } + }) + } +} + +func Test_unescapeIDPart(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + input string + expected string + }{ + { + testName: "no_escaped_separator", + input: "part1", + expected: "part1", + }, + { + testName: "with_escaped_separator", + input: "part??1", + expected: "part:1", + }, + { + testName: "multiple_escaped_separators", + input: "part??1??subpart??2", + expected: "part:1:subpart:2", + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got := unescapeIDPart(d.input) + + if got != d.expected { + t.Fatalf("expected unescaped part %q but got %q", d.expected, got) + } + }) + } +} + +func Test_buildID(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + parts []string + expect string + hasError bool + }{ + { + testName: "one_part", + parts: []string{"part1"}, + expect: "part1", + hasError: false, + }, + { + testName: "two_parts", + parts: []string{"part1", "part2"}, + expect: "part1:part2", + hasError: false, + }, + { + testName: "three_parts", + parts: []string{"part1", "part2", "part3"}, + expect: "part1:part2:part3", + hasError: false, + }, + { + testName: "part_with_unescaped_separator", + parts: []string{"part1", "part:2", "part3"}, + expect: "", + hasError: true, + }, + { + testName: "no_parts", + parts: []string{}, + expect: "", + hasError: true, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got, err := buildID(d.parts...) + + if d.hasError && err == nil { + t.Fatalf("expected error but got none") + } + if !d.hasError && err != nil { + t.Fatalf("did not expect error but got: %v", err) + } + if got != d.expect { + t.Fatalf("expected id %q but got %q", d.expect, got) + } + }) + } +} + +func Test_parseID(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + id string + count int + expect []string + hasError bool + }{ + { + testName: "two_parts_expected_two", + id: "part1:part2", + count: 2, + expect: []string{"part1", "part2"}, + hasError: false, + }, + { + testName: "three_parts_expected_three", + id: "part1:part2:part3", + count: 3, + expect: []string{"part1", "part2", "part3"}, + hasError: false, + }, + { + testName: "two_parts_expected_three", + id: "part1:part2", + count: 3, + expect: nil, + hasError: true, + }, + { + testName: "three_parts_expected_two", + id: "part1:part2:part3", + count: 2, + expect: nil, + hasError: true, + }, + { + testName: "empty_id", + id: "", + count: 0, + expect: nil, + hasError: true, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got, err := parseID(d.id, d.count) + + if d.hasError && err == nil { + t.Fatalf("expected error but got none") + } + if !d.hasError && err != nil { + t.Fatalf("did not expect error but got: %v", err) + } + if !d.hasError { + for i, part := range d.expect { + if got[i] != part { + t.Fatalf("expected part %d to be %q but got %q", i, part, got[i]) + } + } + } + }) + } +} + +func Test_parseID2(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + id string + expect1 string + expect2 string + hasError bool + }{ + { + testName: "valid_two_parts", + id: "part1:part2", + expect1: "part1", + expect2: "part2", + hasError: false, + }, + { + testName: "invalid_three_parts", + id: "part1:part2:part3", + expect1: "", + expect2: "", + hasError: true, + }, + { + testName: "invalid_one_part", + id: "part1", + expect1: "", + expect2: "", + hasError: true, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got1, got2, err := parseID2(d.id) + + if d.hasError && err == nil { + t.Fatalf("expected error but got none") + } + if !d.hasError && err != nil { + t.Fatalf("did not expect error but got: %v", err) + } + if !d.hasError { + if got1 != d.expect1 { + t.Fatalf("expected part 1 to be %q but got %q", d.expect1, got1) + } + if got2 != d.expect2 { + t.Fatalf("expected part 2 to be %q but got %q", d.expect2, got2) + } + } + }) + } +} + +func Test_parseID3(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + id string + expect1 string + expect2 string + expect3 string + hasError bool + }{ + { + testName: "valid_three_parts", + id: "part1:part2:part3", + expect1: "part1", + expect2: "part2", + expect3: "part3", + hasError: false, + }, + { + testName: "invalid_two_parts", + id: "part1:part2", + expect1: "", + expect2: "", + expect3: "", + hasError: true, + }, + { + testName: "invalid_four_parts", + id: "part1:part2:part3:part4", + expect1: "", + expect2: "", + expect3: "", + hasError: true, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got1, got2, got3, err := parseID3(d.id) + + if d.hasError && err == nil { + t.Fatalf("expected error but got none") + } + if !d.hasError && err != nil { + t.Fatalf("did not expect error but got: %v", err) + } + if !d.hasError { + if got1 != d.expect1 { + t.Fatalf("expected part 1 to be %q but got %q", d.expect1, got1) + } + if got2 != d.expect2 { + t.Fatalf("expected part 2 to be %q but got %q", d.expect2, got2) + } + if got3 != d.expect3 { + t.Fatalf("expected part 3 to be %q but got %q", d.expect3, got3) + } + } + }) + } +} + func TestGithubUtilRole_validation(t *testing.T) { cases := []struct { Value string diff --git a/website/docs/r/actions_environment_variable.html.markdown b/website/docs/r/actions_environment_variable.html.markdown index 32079c86b1..a6ddc70277 100644 --- a/website/docs/r/actions_environment_variable.html.markdown +++ b/website/docs/r/actions_environment_variable.html.markdown @@ -42,21 +42,20 @@ resource "github_actions_environment_variable" "example_variable" { The following arguments are supported: - -* `repository` - (Required) Name of the repository. -* `environment` - (Required) Name of the environment. -* `variable_name` - (Required) Name of the variable. -* `value` - (Required) Value of the variable +* `repository` - (Required) Name of the repository. +* `environment` - (Required) Name of the environment. +* `variable_name` - (Required) Name of the variable. +* `value` - (Required) Value of the variable ## Attributes Reference -* `created_at` - Date of actions_environment_secret creation. -* `updated_at` - Date of actions_environment_secret update. +* `created_at` - Date of actions_environment_secret creation. +* `updated_at` - Date of actions_environment_secret update. ## Import -This resource can be imported using an ID made up of the repository name, environment name, and variable name: +This resource can be imported using an ID made of the repository name, environment name (any `:` in the name need to be escaped as `??`), and variable name all separated by a `:`. -``` -$ terraform import github_actions_environment_variable.test_variable myrepo:myenv:myvariable +```shell +terraform import github_actions_environment_variable.example myrepo:myenv:myvariable ``` diff --git a/website/docs/r/repository_environment.html.markdown b/website/docs/r/repository_environment.html.markdown index 69b3cd2474..1a07db2e15 100644 --- a/website/docs/r/repository_environment.html.markdown +++ b/website/docs/r/repository_environment.html.markdown @@ -45,7 +45,7 @@ The following arguments are supported: * `wait_timer` - (Optional) Amount of time to delay a job after the job is initially triggered. -* `can_admins_bypass` - (Optional) Can repository admins bypass the environment protections. Defaults to `true`. +* `can_admins_bypass` - (Optional) Can repository admins bypass the environment protections. Defaults to `true`. * `prevent_self_review` - (Optional) Whether or not a user who created the job is prevented from approving their own job. Defaults to `false`. @@ -57,7 +57,7 @@ The `reviewers` block supports the following: * `users` - (Optional) Up to 6 IDs for users who may review jobs that reference the environment. Reviewers must have at least read access to the repository. Only one of the required reviewers needs to approve the job for it to proceed. -#### Deployment Branch Policy #### +#### Deployment Branch Policy The `deployment_branch_policy` block supports the following: @@ -65,11 +65,10 @@ The `deployment_branch_policy` block supports the following: * `custom_branch_policies` - (Required) Whether only branches that match the specified name patterns can deploy to this environment. - ## Import -GitHub Repository Environment can be imported using an ID made up of `name` of the repository combined with the `environment` name of the environment, separated by a `:` character, e.g. +This resource can be imported using an ID made of the repository name, and environment name (any `:` in the name need to be escaped as `??`) separated by a `:`. -``` -$ terraform import github_repository_environment.daily terraform:daily +```shell +terraform import github_repository_environment.example myrepo:myenv ``` diff --git a/website/docs/r/repository_environment_deployment_policy.html.markdown b/website/docs/r/repository_environment_deployment_policy.html.markdown index cafc093ddc..028e1c1268 100644 --- a/website/docs/r/repository_environment_deployment_policy.html.markdown +++ b/website/docs/r/repository_environment_deployment_policy.html.markdown @@ -74,7 +74,6 @@ resource "github_repository_environment_deployment_policy" "test" { } ``` - ## Argument Reference The following arguments are supported: @@ -87,11 +86,10 @@ The following arguments are supported: * `tag_pattern` - (Optional) The name pattern that tags must match in order to deploy to the environment. If not specified, `branch_pattern` must be specified. - ## Import -GitHub Repository Environment Deployment Policy can be imported using an ID made up of `name` of the repository combined with the `environment` name of the environment with the `Id` of the deployment policy, separated by a `:` character, e.g. +This resource can be imported using an ID made of the repository name, environment name (any `:` in the name need to be escaped as `??`), and deployment policy ID all separated by a `:`. -``` -$ terraform import github_repository_environment.daily terraform:daily:123456 +```shell +terraform import github_repository_environment.example myrepo:myenv:123456 ```