diff --git a/argocd/resource_argocd_repository.go b/argocd/resource_argocd_repository.go index 55b67bfd..9eb4711a 100644 --- a/argocd/resource_argocd_repository.go +++ b/argocd/resource_argocd_repository.go @@ -39,7 +39,16 @@ func resourceArgoCDRepositoryCreate(ctx context.Context, d *schema.ResourceData, } c := *server.RepositoryClient - repo := expandRepository(d) + repo, err := expandRepository(d) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("could not expand repository attributes: %s", err), + Detail: err.Error(), + }, + } + } featureProjectScopedRepositoriesSupported, err := server.isFeatureSupported(featureProjectScopedRepositories) if err != nil { @@ -211,7 +220,16 @@ func resourceArgoCDRepositoryUpdate(ctx context.Context, d *schema.ResourceData, } } c := *server.RepositoryClient - repo := expandRepository(d) + repo, err := expandRepository(d) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("could not expand repository attributes: %s", err), + Detail: err.Error(), + }, + } + } featureProjectScopedRepositoriesSupported, err := server.isFeatureSupported(featureProjectScopedRepositories) if err != nil { diff --git a/argocd/resource_argocd_repository_credentials.go b/argocd/resource_argocd_repository_credentials.go index 6ebf611d..6b27385a 100644 --- a/argocd/resource_argocd_repository_credentials.go +++ b/argocd/resource_argocd_repository_credentials.go @@ -36,7 +36,16 @@ func resourceArgoCDRepositoryCredentialsCreate(ctx context.Context, d *schema.Re } } c := *server.RepoCredsClient - repoCreds := expandRepositoryCredentials(d) + repoCreds, err := expandRepositoryCredentials(d) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("could not expand repository credential attributes: %s", err), + Detail: err.Error(), + }, + } + } tokenMutexConfiguration.Lock() rc, err := c.CreateRepositoryCredentials( @@ -122,7 +131,16 @@ func resourceArgoCDRepositoryCredentialsUpdate(ctx context.Context, d *schema.Re } } c := *server.RepoCredsClient - repoCreds := expandRepositoryCredentials(d) + repoCreds, err := expandRepositoryCredentials(d) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("could not expand repository credential attributes: %s", err), + Detail: err.Error(), + }, + } + } tokenMutexConfiguration.Lock() r, err := c.UpdateRepositoryCredentials( diff --git a/argocd/resource_argocd_repository_credentials_test.go b/argocd/resource_argocd_repository_credentials_test.go index dccd09f6..1d4101d2 100644 --- a/argocd/resource_argocd_repository_credentials_test.go +++ b/argocd/resource_argocd_repository_credentials_test.go @@ -51,6 +51,44 @@ func TestAccArgoCDRepositoryCredentials(t *testing.T) { }) } +func TestAccArgoCDRepositoryCredentials_GitHubApp(t *testing.T) { + sshPrivateKey, err := generateSSHPrivateKey() + assert.NoError(t, err) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccArgoCDRepositoryCredentialsGitHubApp( + "https://private-git-repository.argocd.svc.cluster.local/project-1.git", + "123456", + "987654321", + "https://ghe.example.com/api/v3", + sshPrivateKey, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "argocd_repository_credentials.githubapp", + "githubapp_id", + "123456", + ), + resource.TestCheckResourceAttr( + "argocd_repository_credentials.githubapp", + "githubapp_installation_id", + "987654321", + ), + resource.TestCheckResourceAttr( + "argocd_repository_credentials.githubapp", + "githubapp_enterprise_base_url", + "https://ghe.example.com/api/v3", + ), + ), + }, + }, + }) +} + func testAccArgoCDRepositoryCredentialsSimple(repoUrl, username, sshPrivateKey string) string { return fmt.Sprintf(` resource "argocd_repository_credentials" "simple" { @@ -80,6 +118,20 @@ resource "argocd_repository_credentials" "private" { `) } +func testAccArgoCDRepositoryCredentialsGitHubApp(repoUrl, id, installID, enterpriseBaseURL, appKey string) string { + return fmt.Sprintf(` +resource "argocd_repository_credentials" "githubapp" { + url = "%s" + githubapp_id = "%s" + githubapp_installation_id = "%s" + githubapp_enterprise_base_url = "%s" + githubapp_private_key = < 0 { + r["githubapp_id"] = convertInt64ToString(repository.GithubAppId) + } + if repository.GithubAppInstallationId > 0 { + r["githubapp_installation_id"] = convertInt64ToString(repository.GithubAppInstallationId) } for k, v := range r { if err := persistToState(k, v, d); err != nil { diff --git a/argocd/structure_repository_credentials.go b/argocd/structure_repository_credentials.go index 47768a08..f85bbc99 100644 --- a/argocd/structure_repository_credentials.go +++ b/argocd/structure_repository_credentials.go @@ -11,7 +11,9 @@ import ( // Expand -func expandRepositoryCredentials(d *schema.ResourceData) *application.RepoCreds { +func expandRepositoryCredentials(d *schema.ResourceData) (*application.RepoCreds, error) { + var err error + repoCreds := &application.RepoCreds{} if v, ok := d.GetOk("url"); ok { repoCreds.URL = v.(string) @@ -34,27 +36,45 @@ func expandRepositoryCredentials(d *schema.ResourceData) *application.RepoCreds if v, ok := d.GetOk("enable_oci"); ok { repoCreds.EnableOCI = v.(bool) } - return repoCreds + if v, ok := d.GetOk("githubapp_id"); ok { + repoCreds.GithubAppId, err = convertStringToInt64(v.(string)) + if err != nil { + return nil, err + } + } + if v, ok := d.GetOk("githubapp_installation_id"); ok { + repoCreds.GithubAppInstallationId, err = convertStringToInt64(v.(string)) + if err != nil { + return nil, err + } + } + if v, ok := d.GetOk("githubapp_enterprise_base_url"); ok { + repoCreds.GitHubAppEnterpriseBaseURL = v.(string) + } + if v, ok := d.GetOk("githubapp_private_key"); ok { + repoCreds.GithubAppPrivateKey = v.(string) + } + return repoCreds, nil } // Flatten - -func flattenRepositoryCredentials(repository application.RepoCreds, d *schema.ResourceData) diag.Diagnostics { +func flattenRepositoryCredentials(repoCreds application.RepoCreds, d *schema.ResourceData) diag.Diagnostics { r := map[string]interface{}{ - "url": repository.URL, - "username": repository.Username, - // TODO: ArgoCD API does not return sensitive data! - //"password": repository.Password, - //"ssh_private_key": repository.SSHPrivateKey, - //"tls_client_cert_key": repository.TLSClientCertKey, - "tls_client_cert_data": repository.TLSClientCertData, + "url": repoCreds.URL, + "username": repoCreds.Username, } + + // Note: We are only able to retrieve URL and Username from the ArgoCD API + // at this point since `repocreds` does not implement a `Get` endpoint which + // would alow us to retrieve additional details. See + // https://github.com/argoproj/argo-cd/blob/7be094f38d06859b594b98eb75c7c70d39b80b1e/server/repocreds/repocreds.go#L58-L61 + for k, v := range r { if err := persistToState(k, v, d); err != nil { return []diag.Diagnostic{ { Severity: diag.Error, - Summary: fmt.Sprintf("credentials key (%s) and value for repository %s could not be persisted to state", k, repository.URL), + Summary: fmt.Sprintf("credentials key (%s) and value for repository %s could not be persisted to state", k, repoCreds.URL), Detail: err.Error(), }, } diff --git a/argocd/validators.go b/argocd/validators.go index 3c28eb58..bbb89455 100644 --- a/argocd/validators.go +++ b/argocd/validators.go @@ -117,3 +117,12 @@ func validateSSHPrivateKey(value interface{}, key string) (ws []string, es []err } return } + +func validatePositiveInteger(value interface{}, key string) (ws []string, es []error) { + v := value.(string) + positiveIntegerRegexp := regexp.MustCompile(`^[+]?\d+?$`) + if !positiveIntegerRegexp.MatchString(v) { + es = append(es, fmt.Errorf("%s: invalid input '%s'. String input must match a positive integer, e.g.'12345'", key, v)) + } + return +} diff --git a/docs/resources/repository.md b/docs/resources/repository.md index 33da0744..15037c23 100644 --- a/docs/resources/repository.md +++ b/docs/resources/repository.md @@ -40,6 +40,10 @@ resource "argocd_repository" "private" { * `tls_client_cert_data` - (Optional), TLS client cert data to authenticate against the repository server. * `tls_client_cert_key` - (Optional), TLS client cert key to authenticate against the repository server. * `enable_oci` - (Optional), specify whether the repository server should be viewed as OCI compliant. +* `githubapp_id` - (Optional), string, GitHub App id for authenticating at the repo server only for GitHub repos +* `githubapp_installation_id` - (Optional), string, GitHub App installation id for authenticating at the repo server only for GitHub repos +* `githubapp_enterprise_base_url` - (Optional), string, If using GitHub App for a GitHub Enterprise repository the host url is required +* `githubapp_private_key` - (Optional), string, SSH private key data for GitHub App authentication. # Exported Attributes diff --git a/docs/resources/repository_credentials.md b/docs/resources/repository_credentials.md index 1072630d..5436579e 100644 --- a/docs/resources/repository_credentials.md +++ b/docs/resources/repository_credentials.md @@ -2,6 +2,11 @@ Creates ArgoCD repository credentials, for use with future or existing private repositories. +**Note**: due to restrictions in the ArgoCD API the provider is unable to track +drift in this resource to fields other than `username`. I.e. the provider is +unable to detect changes to repository credentials that are made outside of +Terraform (e.g. manual updates to the underlying Kubernetes Secrets). + ## Example Usage ```hcl @@ -27,6 +32,10 @@ resource "argocd_repository" "private" { * `tls_client_cert_data` - (Optional), TLS client cert data to authenticate against the repository server. * `tls_client_cert_key` - (Optional), TLS client cert key to authenticate against the repository server. * `enable_oci` - (Optional), specify whether the repository server should be viewed as OCI compliant. +* `githubapp_id` - (Optional), string, GitHub App id for authenticating at the repo server only for GitHub repos +* `githubapp_installation_id` - (Optional), string, GitHub App installation id for authenticating at the repo server only for GitHub repos +* `githubapp_enterprise_base_url` - (Optional), string, If using GitHub App for a GitHub Enterprise repository the host url is required +* `githubapp_private_key` - (Optional), string, SSH private key data for GitHub App authentication. ## Import