From 37ab4365b297ecbea41ba4e628e3839895ba9761 Mon Sep 17 00:00:00 2001 From: Ryan Nixon <902500+taiidani@users.noreply.github.com> Date: Thu, 12 Oct 2023 00:29:03 -0700 Subject: [PATCH] Migrate Azure Service Principal to Framework (#168) * Migrate Azure Service Principal resource to Framework * Set scope to be Optional * Use docs generation for Vault Approle --- docs/data-sources/credential_vault_approle.md | 45 +- .../credential_azure_service_principal.md | 76 +-- docs/resources/credential_username.md | 2 +- docs/resources/credential_vault_approle.md | 2 +- .../resource.tf | 7 + .../credentials_azure_service_principal.tf | 21 +- integration/main.tftest.hcl | 4 + jenkins/provider.go | 13 +- jenkins/provider_framework.go | 1 + jenkins/resource.go | 1 + ...kins_credential_azure_service_principal.go | 448 +++++++++++------- .../credential_vault_approle.md.tmpl | 32 -- ...credential_azure_service_principal.md.tmpl | 43 -- 13 files changed, 370 insertions(+), 325 deletions(-) create mode 100644 examples/resources/jenkins_credential_azure_service_principal/resource.tf delete mode 100644 templates/data-sources/credential_vault_approle.md.tmpl delete mode 100644 templates/resources/credential_azure_service_principal.md.tmpl diff --git a/docs/data-sources/credential_vault_approle.md b/docs/data-sources/credential_vault_approle.md index d0a7fec..bac4f3b 100644 --- a/docs/data-sources/credential_vault_approle.md +++ b/docs/data-sources/credential_vault_approle.md @@ -1,32 +1,41 @@ -# jenkins_credential_vault_approle Data Source +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "jenkins_credential_vault_approle Data Source - terraform-provider-jenkins" +subcategory: "" +description: |- + Get the attributes of a vault approle credential within Jenkins. +--- -Get the attributes of a Vault AppRole credential within Jenkins. +# jenkins_credential_vault_approle (Data Source) -~> The Jenkins installation that uses this resource is expected to have the [Hashicorp Vault Plugin](https://plugins.jenkins.io/hashicorp-vault-plugin/) installed in their system. +Get the attributes of a vault approle credential within Jenkins. ## Example Usage -```hcl +```terraform data "jenkins_credential_vault_approle" "example" { - name = "job-name" + name = "name" + folder = jenkins_folder.example.id } ``` -## Argument Reference + +## Schema -The following arguments are supported: +### Required -* `name` - (Required) The name of the resource being read. -* `domain` - (Optional) The domain store to place the credentials into. If not set will default to the global credentials store. -* `folder` - (Optional) The folder namespace containing this resource. +- `name` (String) The name of the resource being read. -## Attribute Reference +### Optional -In addition to all arguments above, the following attributes are exported: +- `domain` (String) The domain store containing this resource. +- `folder` (String) The folder namespace containing this resource. -* `id` - The full canonical job path, E.G. `/job/job-name`. -* `description` - A human readable description of the credentials being stored. -* `scope` - The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". -* `namespace` - The Vault namespace of the approle credential. -* `path` - The unique name of the approle auth backend. Defaults to `approle`. -* `role_id` - The role_id to be associated with the credentials. +### Read-Only + +- `description` (String) A human readable description of the credentials being stored. +- `id` (String) The full canonical job path, e.g. `/job/job-name` +- `namespace` (String) The Vault namespace of the approle credential. +- `path` (String) The unique name of the approle auth backend. +- `role_id` (String) The role_id associated with the credentials. +- `scope` (String) The visibility of the credentials to Jenkins agents. This will be either "GLOBAL" or "SYSTEM". diff --git a/docs/resources/credential_azure_service_principal.md b/docs/resources/credential_azure_service_principal.md index 0c0a2c8..d01c274 100644 --- a/docs/resources/credential_azure_service_principal.md +++ b/docs/resources/credential_azure_service_principal.md @@ -1,4 +1,14 @@ -# resource_jenkins_credential_azure_service_principal Resource +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "jenkins_credential_azure_service_principal Resource - terraform-provider-jenkins" +subcategory: "" +description: |- + Manages an Azure Service Principal credential within Jenkins. This credential may then be referenced within jobs that are created. + ~> The "client_secret" property may leave plain-text secret id in your state file. If using the property to manage the secret id in Terraform, ensure that your state file is properly secured and encrypted at rest. + ~> The Jenkins installation that uses this resource is expected to have the Azure Credentials Plugin https://plugins.jenkins.io/azure-credentials/ installed in their system. +--- + +# jenkins_credential_azure_service_principal (Resource) Manages an Azure Service Principal credential within Jenkins. This credential may then be referenced within jobs that are created. @@ -8,36 +18,40 @@ Manages an Azure Service Principal credential within Jenkins. This credential ma ## Example Usage -```hcl -resource jenkins_credential_azure_service_principal foo { - name = "example-secret" - subscription_id = "01234567-89ab-cdef-0123-456789abcdef" - client_id = "abcdef01-2345-6789-0123-456789abcdef" - client_secret = "super-secret" - tenant = "01234567-89ab-cdef-abcd-456789abcdef" +```terraform +resource "jenkins_credential_azure_service_principal" "foo" { + name = "example-secret" + subscription_id = "01234567-89ab-cdef-0123-456789abcdef" + client_id = "abcdef01-2345-6789-0123-456789abcdef" + client_secret = "super-secret" + tenant = "01234567-89ab-cdef-abcd-456789abcdef" } ``` -## Argument Reference - -The following arguments are supported: - -* `name` - (Required) The name of the credentials being created. This maps to the ID property within Jenkins, and cannot be changed once set. -* `domain` - (Optional) The domain store to place the credentials into. If not set will default to the global credentials store. -* `folder` - (Optional) The folder namespace to store the credentials in. If not set will default to global Jenkins credentials. -* `scope` - (Optional) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". -* `description` - (Optional) A human readable description of the credentials being stored. -* `subscription_id` - (Required) The Azure subscription id mapped to the Azure Service Principal. -* `client_id` - (Required) The client id (application id) of the Azure Service Principal. -* `client_secret` - (Optional) The client secret of the Azure Service Principal. Cannot be used with `certificate_id`. Has to be specified, if `certificate_id` is not specified. -* `certificate_id` - (Optional) The certificate reference of the Azure Service Principal, pointing to a Jenkins certificate credential. Cannot be used with `client_secret`. Has to be specified, if `client_secret` is not specified. -* `tenant` - (Required) The Azure Tenant ID of the Azure Service Principal. -* `azure_environment_name` - (Optional) The Azure Cloud enviroment name. Allowed values are "Azure", "Azure China", "Azure Germany", "Azure US Government". -* `service_management_url` - (Optional) Override the Azure management endpoint URL for the selected Azure environment. -* `authentication_endpoint` - (Optional) Override the Azure Active Directory endpoint for the selected Azure environment. -* `resource_manager_endpoint` - (Optional) Override the Azure resource manager endpoint URL for the selected Azure environment. -* `graph_endpoint` - (Optional) Override the Azure graph endpoint URL for the selected Azure environment. - -## Attribute Reference - -All arguments above are exported. + +## Schema + +### Required + +- `client_id` (String) The client id (application id) of the Azure Service Principal. +- `name` (String) The name of the credentials being created. This maps to the ID property within Jenkins, and cannot be changed once set. +- `subscription_id` (String) The Azure subscription id mapped to the Azure Service Principal. +- `tenant` (String) The Azure Tenant ID of the Azure Service Principal. + +### Optional + +- `authentication_endpoint` (String) Override the Azure Active Directory endpoint for the selected Azure environment. +- `azure_environment_name` (String) The Azure Cloud enviroment name. Allowed values are "Azure", "Azure China", "Azure Germany", "Azure US Government". +- `certificate_id` (String, Sensitive) The certificate reference of the Azure Service Principal, pointing to a Jenkins certificate credential. Cannot be used with `client_secret`. Has to be specified, if `client_secret` is not specified. +- `client_secret` (String, Sensitive) The client secret of the Azure Service Principal. Cannot be used with `certificate_id`. Has to be specified, if `certificate_id` is not specified. +- `description` (String) A human readable description of the credentials being stored. +- `domain` (String) The domain store to place the credentials into. If not set will default to the global credentials store. +- `folder` (String) The folder namespace to store the credentials in. If not set will default to global Jenkins credentials. +- `graph_endpoint` (String) Override the Azure graph endpoint URL for the selected Azure environment. +- `resource_manager_endpoint` (String) Override the Azure resource manager endpoint URL for the selected Azure environment. +- `scope` (String) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". +- `service_management_url` (String) Override the Azure management endpoint URL for the selected Azure environment. + +### Read-Only + +- `id` (String) The full canonical job path, e.g. `/job/job-name` diff --git a/docs/resources/credential_username.md b/docs/resources/credential_username.md index 190a0d7..46ecba1 100644 --- a/docs/resources/credential_username.md +++ b/docs/resources/credential_username.md @@ -38,8 +38,8 @@ resource "jenkins_credential_username" "example" { - `domain` (String) The domain store to place the credentials into. If not set will default to the global credentials store. - `folder` (String) The folder namespace to store the credentials in. If not set will default to global Jenkins credentials. - `password` (String, Sensitive) The password to be associated with the credentials. If empty then the password property will become unmanaged and expected to be set manually within Jenkins. If set then the password will be updated only upon changes -- if the password is set manually within Jenkins then it will not reconcile this drift until the next time the password property is changed. +- `scope` (String) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". ### Read-Only - `id` (String) The full canonical job path, e.g. `/job/job-name` -- `scope` (String) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". diff --git a/docs/resources/credential_vault_approle.md b/docs/resources/credential_vault_approle.md index 3b58dcb..ffb6053 100644 --- a/docs/resources/credential_vault_approle.md +++ b/docs/resources/credential_vault_approle.md @@ -41,9 +41,9 @@ resource "jenkins_credential_vault_approle" "example" { - `folder` (String) The folder namespace to store the credentials in. If not set will default to global Jenkins credentials. - `namespace` (String) The Vault namespace of the approle credential. - `path` (String) The unique name of the approle auth backend. Defaults to `approle`. +- `scope` (String) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". - `secret_id` (String, Sensitive) The secret_id to be associated with the credentials. If empty then the secret_id property will become unmanaged and expected to be set manually within Jenkins. If set then the secret_id will be updated only upon changes -- if the secret_id is set manually within Jenkins then it will not reconcile this drift until the next time the secret_id property is changed. ### Read-Only - `id` (String) The full canonical job path, e.g. `/job/job-name` -- `scope` (String) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". diff --git a/examples/resources/jenkins_credential_azure_service_principal/resource.tf b/examples/resources/jenkins_credential_azure_service_principal/resource.tf new file mode 100644 index 0000000..4611fec --- /dev/null +++ b/examples/resources/jenkins_credential_azure_service_principal/resource.tf @@ -0,0 +1,7 @@ +resource "jenkins_credential_azure_service_principal" "foo" { + name = "example-secret" + subscription_id = "01234567-89ab-cdef-0123-456789abcdef" + client_id = "abcdef01-2345-6789-0123-456789abcdef" + client_secret = "super-secret" + tenant = "01234567-89ab-cdef-abcd-456789abcdef" +} diff --git a/integration/credentials/credentials_azure_service_principal.tf b/integration/credentials/credentials_azure_service_principal.tf index f54c31d..3f5e858 100644 --- a/integration/credentials/credentials_azure_service_principal.tf +++ b/integration/credentials/credentials_azure_service_principal.tf @@ -1,9 +1,14 @@ - resource "jenkins_credential_azure_service_principal" "azure_service_principal_test_credential" { - name = "bla" - folder = jenkins_folder.example.id - description = "blabla" - subscription_id = "123" - client_id = "123" - client_secret = "super-secret" - tenant = "456" +resource "jenkins_credential_azure_service_principal" "azure_service_principal_test_credential" { + name = "bla" + folder = jenkins_folder.example.id + description = "blabla" + subscription_id = "123" + client_id = "123" + client_secret = "super-secret" + tenant = "456" +} + +output "azure_service_principal" { + value = jenkins_credential_azure_service_principal.azure_service_principal_test_credential + sensitive = true } diff --git a/integration/main.tftest.hcl b/integration/main.tftest.hcl index 32f9bf8..74a91c0 100644 --- a/integration/main.tftest.hcl +++ b/integration/main.tftest.hcl @@ -54,6 +54,10 @@ run "credentials" { random = random } + assert { + condition = output.azure_service_principal.client_id == "123" + error_message = "${nonsensitive(output.azure_service_principal.client_id)} did not contain expected \"123\" value" + } assert { condition = output.username.username == jenkins_credential_username.global.username error_message = "${output.username.username} data value did not match resource value" diff --git a/jenkins/provider.go b/jenkins/provider.go index 301cab1..773c02a 100644 --- a/jenkins/provider.go +++ b/jenkins/provider.go @@ -50,13 +50,12 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ - "jenkins_credential_secret_file": resourceJenkinsCredentialSecretFile(), - "jenkins_credential_secret_text": resourceJenkinsCredentialSecretText(), - "jenkins_credential_ssh": resourceJenkinsCredentialSSH(), - "jenkins_folder": resourceJenkinsFolder(), - "jenkins_job": resourceJenkinsJob(), - "jenkins_credential_azure_service_principal": resourceJenkinsCredentialAzureServicePrincipal(), - "jenkins_view": resourceJenkinsView(), + "jenkins_credential_secret_file": resourceJenkinsCredentialSecretFile(), + "jenkins_credential_secret_text": resourceJenkinsCredentialSecretText(), + "jenkins_credential_ssh": resourceJenkinsCredentialSSH(), + "jenkins_folder": resourceJenkinsFolder(), + "jenkins_job": resourceJenkinsJob(), + "jenkins_view": resourceJenkinsView(), }, ConfigureContextFunc: configureProvider, diff --git a/jenkins/provider_framework.go b/jenkins/provider_framework.go index 0eabba0..37bf75a 100644 --- a/jenkins/provider_framework.go +++ b/jenkins/provider_framework.go @@ -130,6 +130,7 @@ func (p *JenkinsProvider) DataSources(ctx context.Context) []func() datasource.D // Resources satisfies the provider.Provider interface for JenkinsProvider. func (p *JenkinsProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ + newCredentialAzureServicePrincipalResource, newCredentialUsernameResource, newCredentialVaultAppRoleResource, } diff --git a/jenkins/resource.go b/jenkins/resource.go index 62afea0..6493ca7 100644 --- a/jenkins/resource.go +++ b/jenkins/resource.go @@ -90,6 +90,7 @@ func (r *resourceHelper) schemaCredential(s map[string]schema.Attribute) map[str } s["scope"] = schema.StringAttribute{ MarkdownDescription: `The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL".`, + Optional: true, Computed: true, Default: stringdefault.StaticString("GLOBAL"), Validators: []validator.String{ diff --git a/jenkins/resource_jenkins_credential_azure_service_principal.go b/jenkins/resource_jenkins_credential_azure_service_principal.go index 60eb0e2..d2a77b8 100644 --- a/jenkins/resource_jenkins_credential_azure_service_principal.go +++ b/jenkins/resource_jenkins_credential_azure_service_principal.go @@ -6,15 +6,19 @@ import ( "fmt" "strings" - "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" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" ) -// VaultAppRoleCredentials struct representing credential for storing Vault AppRole role id and secret id +// AzureServicePrincipalCredentials struct representing credential for storing Azure service credentials type AzureServicePrincipalCredentials struct { XMLName xml.Name `xml:"com.microsoft.azure.util.AzureCredentials"` - Id string `xml:"id"` + ID string `xml:"id"` Scope string `xml:"scope"` Description string `xml:"description"` Data AzureServicePrincipalCredentialsData `xml:"data"` @@ -33,259 +37,335 @@ type AzureServicePrincipalCredentialsData struct { GraphEndpoint string `xml:"graphEndpoint"` } -func resourceJenkinsCredentialAzureServicePrincipal() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceJenkinsCredentialAzureServicePrincipalCreate, - ReadContext: resourceJenkinsCredentialAzureServicePrincipalRead, - UpdateContext: resourceJenkinsCredentialAzureServicePrincipalUpdate, - DeleteContext: resourceJenkinsCredentialAzureServicePrincipalDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceJenkinsCredentialAzureServicePrincipalImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Description: "The credential id of the Azure serivce principal credential created in Jenkins.", - Required: true, - ForceNew: true, - }, - "domain": { - Type: schema.TypeString, - Description: "The Jenkins domain that the credentials will be added to.", - Optional: true, - Default: defaultCredentialDomain, - // In-place updates should be possible, but gojenkins does not support move operations - ForceNew: true, - }, - "folder": { - Type: schema.TypeString, - Description: "The Jenkins folder that the credentials will be added to.", - Optional: true, - ForceNew: true, - }, - "scope": { - Type: schema.TypeString, - Description: "The Jenkins scope assigned to the credentials.", - Optional: true, - Default: "GLOBAL", - ValidateDiagFunc: validateCredentialScope, - }, - "description": { - Type: schema.TypeString, - Description: "An optional description to help tell similar credentials apart.", - Optional: true, - }, - "subscription_id": { - Type: schema.TypeString, - Description: "The Azure subscription id.", - Required: true, +type credentialAzureServicePrincipalResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Folder types.String `tfsdk:"folder"` + Description types.String `tfsdk:"description"` + Domain types.String `tfsdk:"domain"` + Scope types.String `tfsdk:"scope"` + SubscriptionId types.String `tfsdk:"subscription_id"` + ClientId types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + CertificateId types.String `tfsdk:"certificate_id"` + Tenant types.String `tfsdk:"tenant"` + AzureEnvironmentName types.String `tfsdk:"azure_environment_name"` + ServiceManagementURL types.String `tfsdk:"service_management_url"` + AuthenticationEndpoint types.String `tfsdk:"authentication_endpoint"` + ResourceManagerEndpoint types.String `tfsdk:"resource_manager_endpoint"` + GraphEndpoint types.String `tfsdk:"graph_endpoint"` +} + +type credentialAzureServicePrincipalResource struct { + *resourceHelper +} + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.ResourceWithConfigure = &credentialAzureServicePrincipalResource{} + +func newCredentialAzureServicePrincipalResource() resource.Resource { + return &credentialAzureServicePrincipalResource{ + resourceHelper: newResourceHelper(), + } +} + +// Metadata should return the full name of the resource. +func (r *credentialAzureServicePrincipalResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_credential_azure_service_principal" +} + +// Schema should return the schema for this resource. +func (r *credentialAzureServicePrincipalResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: ` +Manages an Azure Service Principal credential within Jenkins. This credential may then be referenced within jobs that are created. + +~> The "client_secret" property may leave plain-text secret id in your state file. If using the property to manage the secret id in Terraform, ensure that your state file is properly secured and encrypted at rest. + +~> The Jenkins installation that uses this resource is expected to have the [Azure Credentials Plugin](https://plugins.jenkins.io/azure-credentials/) installed in their system.`, + Attributes: r.schemaCredential(map[string]schema.Attribute{ + "subscription_id": schema.StringAttribute{ + MarkdownDescription: "The Azure subscription id mapped to the Azure Service Principal.", + Required: true, }, - "client_id": { - Type: schema.TypeString, - Description: "The client id (application id) of the Azure Service Principal.", - Required: true, + "client_id": schema.StringAttribute{ + MarkdownDescription: "The client id (application id) of the Azure Service Principal.", + Required: true, }, - "client_secret": { - Type: schema.TypeString, - Description: "The client secret of the Azure Service Principal. Cannot be used with certificate_id.", - Sensitive: true, - Optional: true, - ExactlyOneOf: []string{"client_secret", "certificate_id"}, + "client_secret": schema.StringAttribute{ + MarkdownDescription: "The client secret of the Azure Service Principal. Cannot be used with `certificate_id`. Has to be specified, if `certificate_id` is not specified.", + Sensitive: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("client_secret"), path.MatchRoot("certificate_id")), + }, }, - "certificate_id": { - Type: schema.TypeString, - Description: "The certificate reference of the Azure Service Principal, pointing to a Jenkins certificate credential. Cannot be used with client_secret.", - Sensitive: true, - Optional: true, - ExactlyOneOf: []string{"client_secret", "certificate_id"}, + "certificate_id": schema.StringAttribute{ + MarkdownDescription: "The certificate reference of the Azure Service Principal, pointing to a Jenkins certificate credential. Cannot be used with `client_secret`. Has to be specified, if `client_secret` is not specified.", + Sensitive: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("client_secret"), path.MatchRoot("certificate_id")), + }, }, - "tenant": { - Type: schema.TypeString, - Description: "The Azure Tenant ID of the Azure Service Principal.", - Required: true, + "tenant": schema.StringAttribute{ + MarkdownDescription: "The Azure Tenant ID of the Azure Service Principal.", + Required: true, }, - "azure_environment_name": { - Type: schema.TypeString, - Description: `The Azure Cloud enviroment name. Allowed values are "Azure", "Azure China", "Azure Germany", "Azure US Government".`, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"Azure", "Azure China", "Azure Germany", "Azure US Government"}, false), - Default: "Azure", + "azure_environment_name": schema.StringAttribute{ + MarkdownDescription: `The Azure Cloud enviroment name. Allowed values are "Azure", "Azure China", "Azure Germany", "Azure US Government".`, + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Azure"), + Validators: []validator.String{ + stringvalidator.OneOf("Azure", "Azure China", "Azure Germany", "Azure US Government"), + }, }, - "service_management_url": { - Type: schema.TypeString, - Description: "Override the Azure management endpoint URL for the selected Azure environment.", - Optional: true, - Default: "", + "service_management_url": schema.StringAttribute{ + MarkdownDescription: "Override the Azure management endpoint URL for the selected Azure environment.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "authentication_endpoint": { - Type: schema.TypeString, - Description: "Override the Azure Active Directory endpoint for the selected Azure environment.", - Optional: true, - Default: "", + "authentication_endpoint": schema.StringAttribute{ + MarkdownDescription: "Override the Azure Active Directory endpoint for the selected Azure environment.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "resource_manager_endpoint": { - Type: schema.TypeString, - Description: "Override the Azure resource manager endpoint URL for the selected Azure environment.", - Optional: true, - Default: "", + "resource_manager_endpoint": schema.StringAttribute{ + MarkdownDescription: "Override the Azure resource manager endpoint URL for the selected Azure environment.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "graph_endpoint": { - Type: schema.TypeString, - Description: "Override the Azure graph endpoint URL for the selected Azure environment.", - Optional: true, - Default: "", + "graph_endpoint": schema.StringAttribute{ + MarkdownDescription: "Override the Azure graph endpoint URL for the selected Azure environment.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - }, + }), } } -func resourceJenkinsCredentialAzureServicePrincipalCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(jenkinsClient) - cm := client.Credentials() - cm.Folder = formatFolderName(d.Get("folder").(string)) - // return diag.FromErr(fmt.Errorf("invalid folder name '%s', '%s'", cm.Folder, d.Get("folder").(string))) +// Create is called when the provider must create a new resource. Config +// and planned state values should be read from the +// CreateRequest and new state values set on the CreateResponse. +func (r *credentialAzureServicePrincipalResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data credentialAzureServicePrincipalResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + cm := r.client.Credentials() + cm.Folder = formatFolderName(data.Folder.ValueString()) + // Validate that the folder exists - if err := folderExists(ctx, client, cm.Folder); err != nil { - return diag.FromErr(fmt.Errorf("invalid folder name '%s' specified: %w", cm.Folder, err)) + if err := folderExists(ctx, r.client, cm.Folder); err != nil { + resp.Diagnostics.AddError( + "Invalid Folder", + fmt.Sprintf("An invalid folder name %q was specified. ", cm.Folder)+ + "Please report this issue to the provider developers.\n\n"+ + "Error: "+err.Error(), + ) + + return } credData := AzureServicePrincipalCredentialsData{ - SubscriptionId: d.Get("subscription_id").(string), - ClientId: d.Get("client_id").(string), - ClientSecret: d.Get("client_secret").(string), - CertificateId: d.Get("certificate_id").(string), - Tenant: d.Get("tenant").(string), - AzureEnvironmentName: d.Get("azure_environment_name").(string), - ServiceManagementURL: d.Get("service_management_url").(string), - AuthenticationEndpoint: d.Get("authentication_endpoint").(string), - ResourceManagerEndpoint: d.Get("resource_manager_endpoint").(string), - GraphEndpoint: d.Get("graph_endpoint").(string), + SubscriptionId: data.SubscriptionId.ValueString(), + ClientId: data.ClientId.ValueString(), + ClientSecret: data.ClientSecret.ValueString(), + CertificateId: data.CertificateId.ValueString(), + Tenant: data.Tenant.ValueString(), + AzureEnvironmentName: data.AzureEnvironmentName.ValueString(), + ServiceManagementURL: data.ServiceManagementURL.ValueString(), + AuthenticationEndpoint: data.AuthenticationEndpoint.ValueString(), + ResourceManagerEndpoint: data.ResourceManagerEndpoint.ValueString(), + GraphEndpoint: data.GraphEndpoint.ValueString(), } cred := AzureServicePrincipalCredentials{ - Id: d.Get("name").(string), - Scope: d.Get("scope").(string), - Description: d.Get("description").(string), + ID: data.Name.ValueString(), + Scope: data.Scope.ValueString(), + Description: data.Description.ValueString(), Data: credData, } - domain := d.Get("domain").(string) - err := cm.Add(ctx, domain, cred) + err := cm.Add(ctx, data.Domain.ValueString(), cred) if err != nil { - return diag.Errorf("Could not create Azure service principal credentials: %s", err) + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while creating the resource. "+ + "Please report this issue to the provider developers.\n\n"+ + "Error: "+err.Error(), + ) + + return } - d.SetId(generateCredentialID(d.Get("folder").(string), cred.Id)) - return resourceJenkinsCredentialAzureServicePrincipalRead(ctx, d, meta) + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.ID = types.StringValue(generateCredentialID(data.Folder.ValueString(), cred.ID)) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func resourceJenkinsCredentialAzureServicePrincipalRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - cm := meta.(jenkinsClient).Credentials() - cm.Folder = formatFolderName(d.Get("folder").(string)) +// Read is called when the provider must read resource values in order +// to update state. Planned state values should be read from the +// ReadRequest and new state values set on the ReadResponse. +func (r *credentialAzureServicePrincipalResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data credentialAzureServicePrincipalResourceModel - cred := AzureServicePrincipalCredentials{} - err := cm.GetSingle( - ctx, - d.Get("domain").(string), - d.Get("name").(string), - &cred, - ) + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + cm := r.client.Credentials() + cm.Folder = formatFolderName(data.Folder.ValueString()) + cred := AzureServicePrincipalCredentials{} + err := cm.GetSingle(ctx, data.Domain.ValueString(), data.Name.ValueString(), &cred) if err != nil { if strings.HasSuffix(err.Error(), "404") { // Job does not exist - d.SetId("") - return nil + resp.State.RemoveResource(ctx) + return } - return diag.Errorf("Could not read Azure service principal credentials: %s", err) + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while parsing the resource read response. "+ + "Please report this issue to the provider developers.\n\n"+ + "Error: "+err.Error(), + ) + + return } - d.SetId(generateCredentialID(d.Get("folder").(string), cred.Id)) - _ = d.Set("description", cred.Description) - _ = d.Set("scope", cred.Scope) + data.ID = types.StringValue(generateCredentialID(data.Folder.ValueString(), cred.ID)) + data.Scope = types.StringValue(cred.Scope) + data.Description = types.StringValue(cred.Description) // NOTE: We are NOT setting the password here, as the password returned by GetSingle is garbage // Password only applies to Create/Update operations if the "password" property is non-empty - return nil + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func resourceJenkinsCredentialAzureServicePrincipalUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - cm := meta.(jenkinsClient).Credentials() - cm.Folder = formatFolderName(d.Get("folder").(string)) +// Update is called to update the state of the resource. Config, planned +// state, and prior state values should be read from the +// UpdateRequest and new state values set on the UpdateResponse. +func (r *credentialAzureServicePrincipalResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data credentialAzureServicePrincipalResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } - domain := d.Get("domain").(string) + cm := r.client.Credentials() + cm.Folder = formatFolderName(data.Folder.ValueString()) credData := AzureServicePrincipalCredentialsData{ - SubscriptionId: d.Get("subscription_id").(string), - ClientId: d.Get("client_id").(string), - Tenant: d.Get("tenant").(string), - AzureEnvironmentName: d.Get("azure_environment_name").(string), - ServiceManagementURL: d.Get("service_management_url").(string), - AuthenticationEndpoint: d.Get("authentication_endpoint").(string), - ResourceManagerEndpoint: d.Get("resource_manager_endpoint").(string), - GraphEndpoint: d.Get("graph_endpoint").(string), + SubscriptionId: data.SubscriptionId.ValueString(), + ClientId: data.ClientId.ValueString(), + Tenant: data.Tenant.ValueString(), + AzureEnvironmentName: data.AzureEnvironmentName.ValueString(), + ServiceManagementURL: data.ServiceManagementURL.ValueString(), + AuthenticationEndpoint: data.AuthenticationEndpoint.ValueString(), + ResourceManagerEndpoint: data.ResourceManagerEndpoint.ValueString(), + GraphEndpoint: data.GraphEndpoint.ValueString(), } cred := AzureServicePrincipalCredentials{ - Id: d.Get("name").(string), - Scope: d.Get("scope").(string), - Description: d.Get("description").(string), + ID: data.Name.ValueString(), + Scope: data.Scope.ValueString(), + Description: data.Description.ValueString(), Data: credData, } // Only enforce the password if it is non-empty - if d.Get("client_secret").(string) != "" { - cred.Data.ClientSecret = d.Get("client_secret").(string) + if data.ClientSecret.ValueString() != "" { + cred.Data.ClientSecret = data.ClientSecret.ValueString() } - if d.Get("certificate_id").(string) != "" { - cred.Data.ClientId = d.Get("certificate_id").(string) + if data.CertificateId.ValueString() != "" { + cred.Data.ClientId = data.CertificateId.ValueString() } - err := cm.Update(ctx, domain, d.Get("name").(string), &cred) + err := cm.Update(ctx, data.Domain.ValueString(), data.Name.ValueString(), &cred) if err != nil { - return diag.Errorf("Could not update Azure Service Principal credentials: %s", err) + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "Error: "+err.Error(), + ) + + return } - d.SetId(generateCredentialID(d.Get("folder").(string), cred.Id)) - return resourceJenkinsCredentialAzureServicePrincipalRead(ctx, d, meta) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func resourceJenkinsCredentialAzureServicePrincipalDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - cm := meta.(jenkinsClient).Credentials() - cm.Folder = formatFolderName(d.Get("folder").(string)) +// Delete is called when the provider must delete the resource. Config +// values may be read from the DeleteRequest. +// +// If execution completes without error, the framework will automatically +// call DeleteResponse.State.RemoveResource(), so it can be omitted +// from provider logic. +func (r *credentialAzureServicePrincipalResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data credentialAzureServicePrincipalResourceModel - err := cm.Delete( - ctx, - d.Get("domain").(string), - d.Get("name").(string), - ) + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + cm := r.client.Credentials() + cm.Folder = formatFolderName(data.Folder.ValueString()) + + err := cm.Delete(ctx, data.Domain.ValueString(), data.Name.ValueString()) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while deleting the resource. "+ + "Please report this issue to the provider developers.\n\n"+ + "Error: "+err.Error(), + ) + + return } - - return nil } -func resourceJenkinsCredentialAzureServicePrincipalImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { - ret := []*schema.ResourceData{d} - - splitID := strings.Split(d.Id(), "/") +// ImportState is called when performing import operations of existing resources. +func (r *credentialAzureServicePrincipalResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + splitID := strings.Split(req.ID, "/") if len(splitID) < 2 { - return ret, fmt.Errorf("Import ID was improperly formatted. Imports need to be in the format \"[/]/\"") + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: \"[/]/\". Got: %q", req.ID), + ) + return } name := splitID[len(splitID)-1] - _ = d.Set("name", name) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), name)...) domain := splitID[len(splitID)-2] - _ = d.Set("domain", domain) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("domain"), domain)...) folder := strings.Trim(strings.Join(splitID[0:len(splitID)-2], "/"), "/") - _ = d.Set("folder", folder) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("folder"), folder)...) - d.SetId(generateCredentialID(folder, name)) - return ret, nil + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), generateCredentialID(folder, name))...) } diff --git a/templates/data-sources/credential_vault_approle.md.tmpl b/templates/data-sources/credential_vault_approle.md.tmpl deleted file mode 100644 index d0a7fec..0000000 --- a/templates/data-sources/credential_vault_approle.md.tmpl +++ /dev/null @@ -1,32 +0,0 @@ -# jenkins_credential_vault_approle Data Source - -Get the attributes of a Vault AppRole credential within Jenkins. - -~> The Jenkins installation that uses this resource is expected to have the [Hashicorp Vault Plugin](https://plugins.jenkins.io/hashicorp-vault-plugin/) installed in their system. - -## Example Usage - -```hcl -data "jenkins_credential_vault_approle" "example" { - name = "job-name" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `name` - (Required) The name of the resource being read. -* `domain` - (Optional) The domain store to place the credentials into. If not set will default to the global credentials store. -* `folder` - (Optional) The folder namespace containing this resource. - -## Attribute Reference - -In addition to all arguments above, the following attributes are exported: - -* `id` - The full canonical job path, E.G. `/job/job-name`. -* `description` - A human readable description of the credentials being stored. -* `scope` - The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". -* `namespace` - The Vault namespace of the approle credential. -* `path` - The unique name of the approle auth backend. Defaults to `approle`. -* `role_id` - The role_id to be associated with the credentials. diff --git a/templates/resources/credential_azure_service_principal.md.tmpl b/templates/resources/credential_azure_service_principal.md.tmpl deleted file mode 100644 index 0c0a2c8..0000000 --- a/templates/resources/credential_azure_service_principal.md.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -# resource_jenkins_credential_azure_service_principal Resource - -Manages an Azure Service Principal credential within Jenkins. This credential may then be referenced within jobs that are created. - -~> The "client_secret" property may leave plain-text secret id in your state file. If using the property to manage the secret id in Terraform, ensure that your state file is properly secured and encrypted at rest. - -~> The Jenkins installation that uses this resource is expected to have the [Azure Credentials Plugin](https://plugins.jenkins.io/azure-credentials/) installed in their system. - -## Example Usage - -```hcl -resource jenkins_credential_azure_service_principal foo { - name = "example-secret" - subscription_id = "01234567-89ab-cdef-0123-456789abcdef" - client_id = "abcdef01-2345-6789-0123-456789abcdef" - client_secret = "super-secret" - tenant = "01234567-89ab-cdef-abcd-456789abcdef" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `name` - (Required) The name of the credentials being created. This maps to the ID property within Jenkins, and cannot be changed once set. -* `domain` - (Optional) The domain store to place the credentials into. If not set will default to the global credentials store. -* `folder` - (Optional) The folder namespace to store the credentials in. If not set will default to global Jenkins credentials. -* `scope` - (Optional) The visibility of the credentials to Jenkins agents. This must be set to either "GLOBAL" or "SYSTEM". If not set will default to "GLOBAL". -* `description` - (Optional) A human readable description of the credentials being stored. -* `subscription_id` - (Required) The Azure subscription id mapped to the Azure Service Principal. -* `client_id` - (Required) The client id (application id) of the Azure Service Principal. -* `client_secret` - (Optional) The client secret of the Azure Service Principal. Cannot be used with `certificate_id`. Has to be specified, if `certificate_id` is not specified. -* `certificate_id` - (Optional) The certificate reference of the Azure Service Principal, pointing to a Jenkins certificate credential. Cannot be used with `client_secret`. Has to be specified, if `client_secret` is not specified. -* `tenant` - (Required) The Azure Tenant ID of the Azure Service Principal. -* `azure_environment_name` - (Optional) The Azure Cloud enviroment name. Allowed values are "Azure", "Azure China", "Azure Germany", "Azure US Government". -* `service_management_url` - (Optional) Override the Azure management endpoint URL for the selected Azure environment. -* `authentication_endpoint` - (Optional) Override the Azure Active Directory endpoint for the selected Azure environment. -* `resource_manager_endpoint` - (Optional) Override the Azure resource manager endpoint URL for the selected Azure environment. -* `graph_endpoint` - (Optional) Override the Azure graph endpoint URL for the selected Azure environment. - -## Attribute Reference - -All arguments above are exported.