-
Notifications
You must be signed in to change notification settings - Fork 741
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add github_codespaces_organization_secret
- Loading branch information
Showing
5 changed files
with
560 additions
and
0 deletions.
There are no files selected for viewing
78 changes: 78 additions & 0 deletions
78
github/data_source_github_codespaces_organization_secrets.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/google/go-github/v53/github" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
) | ||
|
||
func dataSourceGithubDependabotOrganizationSecrets() *schema.Resource { | ||
return &schema.Resource{ | ||
Read: dataSourceGithubDependabotOrganizationSecretsRead, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"secrets": { | ||
Type: schema.TypeList, | ||
Computed: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"name": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"visibility": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"created_at": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"updated_at": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func dataSourceGithubDependabotOrganizationSecretsRead(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*Owner).v3client | ||
owner := meta.(*Owner).name | ||
|
||
options := github.ListOptions{ | ||
PerPage: 100, | ||
} | ||
|
||
var all_secrets []map[string]string | ||
for { | ||
secrets, resp, err := client.Dependabot.ListOrgSecrets(context.TODO(), owner, &options) | ||
if err != nil { | ||
return err | ||
} | ||
for _, secret := range secrets.Secrets { | ||
new_secret := map[string]string{ | ||
"name": secret.Name, | ||
"visibility": secret.Visibility, | ||
"created_at": secret.CreatedAt.String(), | ||
"updated_at": secret.UpdatedAt.String(), | ||
} | ||
all_secrets = append(all_secrets, new_secret) | ||
|
||
} | ||
if resp.NextPage == 0 { | ||
break | ||
} | ||
options.Page = resp.NextPage | ||
} | ||
|
||
d.SetId(owner) | ||
d.Set("secrets", all_secrets) | ||
|
||
return nil | ||
} |
59 changes: 59 additions & 0 deletions
59
github/data_source_github_codespaces_organization_secrets_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package github | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/resource" | ||
) | ||
|
||
func TestAccGithubCodespacesOrganizationSecretsDataSource(t *testing.T) { | ||
|
||
t.Run("queries organization codespaces secrets from a repository", func(t *testing.T) { | ||
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) | ||
|
||
config := fmt.Sprintf(` | ||
resource "github_codespaces_organization_secret" "test" { | ||
secret_name = "org_dep_secret_1_%s" | ||
plaintext_value = "foo" | ||
visibility = "private" | ||
} | ||
`, randomID) | ||
|
||
config2 := config + ` | ||
data "github_codespaces_organization_secrets" "test" { | ||
} | ||
` | ||
|
||
check := resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.github_codespaces_organization_secrets.test", "secrets.#", "1"), | ||
resource.TestCheckResourceAttr("data.github_codespaces_organization_secrets.test", "secrets.0.name", strings.ToUpper(fmt.Sprintf("ORG_DEP_SECRET_1_%s", randomID))), | ||
resource.TestCheckResourceAttr("data.github_codespaces_organization_secrets.test", "secrets.0.visibility", "private"), | ||
resource.TestCheckResourceAttrSet("data.github_codespaces_organization_secrets.test", "secrets.0.created_at"), | ||
resource.TestCheckResourceAttrSet("data.github_codespaces_organization_secrets.test", "secrets.0.updated_at"), | ||
) | ||
|
||
testCase := func(t *testing.T, mode string) { | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { skipUnlessMode(t, mode) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: config, | ||
Check: resource.ComposeTestCheckFunc(), | ||
}, | ||
{ | ||
Config: config2, | ||
Check: check, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
t.Run("with an organization account", func(t *testing.T) { | ||
testCase(t, organization) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
235 changes: 235 additions & 0 deletions
235
github/resource_github_codespaces_organization_secret.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/google/go-github/v53/github" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/validation" | ||
) | ||
|
||
func resourceGithubCodespacesOrganizationSecret() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceGithubCodespacesOrganizationSecretCreateOrUpdate, | ||
Read: resourceGithubCodespacesOrganizationSecretRead, | ||
Update: resourceGithubCodespacesOrganizationSecretCreateOrUpdate, | ||
Delete: resourceGithubCodespacesOrganizationSecretDelete, | ||
Importer: &schema.ResourceImporter{ | ||
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
d.Set("secret_name", d.Id()) | ||
return []*schema.ResourceData{d}, nil | ||
}, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"secret_name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
Description: "Name of the secret.", | ||
ValidateFunc: validateSecretNameFunc, | ||
}, | ||
"encrypted_value": { | ||
Type: schema.TypeString, | ||
ForceNew: true, | ||
Optional: true, | ||
Sensitive: true, | ||
ConflictsWith: []string{"plaintext_value"}, | ||
Description: "Encrypted value of the secret using the GitHub public key in Base64 format.", | ||
ValidateFunc: validation.StringIsBase64, | ||
}, | ||
"plaintext_value": { | ||
Type: schema.TypeString, | ||
ForceNew: true, | ||
Optional: true, | ||
Sensitive: true, | ||
Description: "Plaintext value of the secret to be encrypted.", | ||
ConflictsWith: []string{"encrypted_value"}, | ||
}, | ||
"visibility": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "Configures the access that repositories have to the organization secret. Must be one of 'all', 'private' or 'selected'. 'selected_repository_ids' is required if set to 'selected'.", | ||
ValidateFunc: validateValueFunc([]string{"all", "private", "selected"}), | ||
ForceNew: true, | ||
}, | ||
"selected_repository_ids": { | ||
Type: schema.TypeSet, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeInt, | ||
}, | ||
Set: schema.HashInt, | ||
Optional: true, | ||
Description: "An array of repository ids that can access the organization secret.", | ||
}, | ||
"created_at": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Date of 'codespaces_secret' creation.", | ||
}, | ||
"updated_at": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Date of 'codespaces_secret' update.", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceGithubCodespacesOrganizationSecretCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*Owner).v3client | ||
owner := meta.(*Owner).name | ||
ctx := context.Background() | ||
|
||
secretName := d.Get("secret_name").(string) | ||
plaintextValue := d.Get("plaintext_value").(string) | ||
var encryptedValue string | ||
|
||
visibility := d.Get("visibility").(string) | ||
selectedRepositories, hasSelectedRepositories := d.GetOk("selected_repository_ids") | ||
|
||
if visibility != "selected" && hasSelectedRepositories { | ||
return fmt.Errorf("cannot use selected_repository_ids without visibility being set to selected") | ||
} | ||
|
||
selectedRepositoryIDs := github.SelectedRepoIDs{} | ||
|
||
if hasSelectedRepositories { | ||
ids := selectedRepositories.(*schema.Set).List() | ||
|
||
for _, id := range ids { | ||
selectedRepositoryIDs = append(selectedRepositoryIDs, id.(int64)) | ||
} | ||
} | ||
|
||
keyId, publicKey, err := getCodespacesOrganizationPublicKeyDetails(owner, meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if encryptedText, ok := d.GetOk("encrypted_value"); ok { | ||
encryptedValue = encryptedText.(string) | ||
} else { | ||
encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey) | ||
if err != nil { | ||
return err | ||
} | ||
encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes) | ||
} | ||
|
||
// Create an EncryptedSecret and encrypt the plaintext value into it | ||
eSecret := &github.EncryptedSecret{ | ||
Name: secretName, | ||
KeyID: keyId, | ||
Visibility: visibility, | ||
SelectedRepositoryIDs: selectedRepositoryIDs, | ||
EncryptedValue: encryptedValue, | ||
} | ||
|
||
_, err = client.Codespaces.CreateOrUpdateOrgSecret(ctx, owner, eSecret) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId(secretName) | ||
return resourceGithubCodespacesOrganizationSecretRead(d, meta) | ||
} | ||
|
||
func resourceGithubCodespacesOrganizationSecretRead(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*Owner).v3client | ||
owner := meta.(*Owner).name | ||
ctx := context.Background() | ||
|
||
secret, _, err := client.Codespaces.GetOrgSecret(ctx, owner, d.Id()) | ||
if err != nil { | ||
if ghErr, ok := err.(*github.ErrorResponse); ok { | ||
if ghErr.Response.StatusCode == http.StatusNotFound { | ||
log.Printf("[WARN] Removing actions secret %s from state because it no longer exists in GitHub", | ||
d.Id()) | ||
d.SetId("") | ||
return nil | ||
} | ||
} | ||
return err | ||
} | ||
|
||
d.Set("encrypted_value", d.Get("encrypted_value")) | ||
d.Set("plaintext_value", d.Get("plaintext_value")) | ||
d.Set("created_at", secret.CreatedAt.String()) | ||
d.Set("visibility", secret.Visibility) | ||
|
||
selectedRepositoryIDs := []int64{} | ||
|
||
if secret.Visibility == "selected" { | ||
opt := &github.ListOptions{ | ||
PerPage: 30, | ||
} | ||
for { | ||
results, resp, err := client.Codespaces.ListSelectedReposForOrgSecret(ctx, owner, d.Id(), opt) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, repo := range results.Repositories { | ||
selectedRepositoryIDs = append(selectedRepositoryIDs, repo.GetID()) | ||
} | ||
|
||
if resp.NextPage == 0 { | ||
break | ||
} | ||
opt.Page = resp.NextPage | ||
} | ||
} | ||
|
||
d.Set("selected_repository_ids", selectedRepositoryIDs) | ||
|
||
// This is a drift detection mechanism based on timestamps. | ||
// | ||
// If we do not currently store the "updated_at" field, it means we've only | ||
// just created the resource and the value is most likely what we want it to | ||
// be. | ||
// | ||
// If the resource is changed externally in the meantime then reading back | ||
// the last update timestamp will return a result different than the | ||
// timestamp we've persisted in the state. In that case, we can no longer | ||
// trust that the value (which we don't see) is equal to what we've declared | ||
// previously. | ||
// | ||
// The only solution to enforce consistency between is to mark the resource | ||
// as deleted (unset the ID) in order to fix potential drift by recreating | ||
// the resource. | ||
if updatedAt, ok := d.GetOk("updated_at"); ok && updatedAt != secret.UpdatedAt.String() { | ||
log.Printf("[WARN] The secret %s has been externally updated in GitHub", d.Id()) | ||
d.SetId("") | ||
} else if !ok { | ||
d.Set("updated_at", secret.UpdatedAt.String()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceGithubCodespacesOrganizationSecretDelete(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*Owner).v3client | ||
orgName := meta.(*Owner).name | ||
ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
||
log.Printf("[DEBUG] Deleting secret: %s", d.Id()) | ||
_, err := client.Codespaces.DeleteOrgSecret(ctx, orgName, d.Id()) | ||
return err | ||
} | ||
|
||
func getCodespacesOrganizationPublicKeyDetails(owner string, meta interface{}) (keyId, pkValue string, err error) { | ||
client := meta.(*Owner).v3client | ||
ctx := context.Background() | ||
|
||
publicKey, _, err := client.Codespaces.GetOrgPublicKey(ctx, owner) | ||
if err != nil { | ||
return keyId, pkValue, err | ||
} | ||
|
||
return publicKey.GetKeyID(), publicKey.GetKey(), err | ||
} |
Oops, something went wrong.