Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pub/terra/terraform-provider-terrareg
Show changes
Commits on Source (38)
Showing
with 1030 additions and 8 deletions
......@@ -72,6 +72,7 @@ jobs:
env:
ADMIN_AUTHENTICATION_TOKEN: unittest-api-key
MIGRATE_DATABASE: "True"
GIT_PROVIDER_CONFIG: '[{"name": "Github", "base_url": "https://github.com/{namespace}/{module}", "clone_url": "ssh://git@github.com:{namespace}/{module}.git", "browse_url": "https://github.com/{namespace}/{module}/tree/{tag}/{path}"}, {"name": "Bitbucket", "base_url": "https://bitbucket.org/{namespace}/{module}", "clone_url": "ssh://git@bitbucket.org:{namespace}/{module}-{provider}.git", "browse_url": "https://bitbucket.org/{namespace}/{module}-{provider}/src/{tag_uri_encoded}/{path}"}, {"name": "Gitlab", "base_url": "https://gitlab.com/{namespace}/{module}", "clone_url": "ssh://git@gitlab.com:{namespace}/{module}-{provider}.git", "browse_url": "https://gitlab.com/{namespace}/{module}-{provider}/-/tree/{tag}/{path}"}]'
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
......
......@@ -29,6 +29,7 @@ test:
TF_ACC: "1"
TERRAREG_URL: "http://terrareg:5000"
TERRAREG_API_KEY: unittest-api-key
GIT_PROVIDER_CONFIG: '[{"name": "Github", "base_url": "https://github.com/{namespace}/{module}", "clone_url": "ssh://git@github.com:{namespace}/{module}.git", "browse_url": "https://github.com/{namespace}/{module}/tree/{tag}/{path}"}, {"name": "Bitbucket", "base_url": "https://bitbucket.org/{namespace}/{module}", "clone_url": "ssh://git@bitbucket.org:{namespace}/{module}-{provider}.git", "browse_url": "https://bitbucket.org/{namespace}/{module}-{provider}/src/{tag_uri_encoded}/{path}"}, {"name": "Gitlab", "base_url": "https://gitlab.com/{namespace}/{module}", "clone_url": "ssh://git@gitlab.com:{namespace}/{module}-{provider}.git", "browse_url": "https://gitlab.com/{namespace}/{module}-{provider}/-/tree/{tag}/{path}"}]'
script:
- unset http_proxy
- unset https_proxy
......
# Changelog
# [1.1.0](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/compare/v1.0.0...v1.1.0) (2024-02-03)
### Bug Fixes
* Add validation to ensure that either name or id is passed to git_provider data source ([028ef7a](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/028ef7a74fa7b186f49688873f5a6bed69670a9b)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Avoid unknown ID values during plan ([8eff463](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/8eff46327e2cad16d3e47fdb2aecc44a02403f8c)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Correctly mark object as deleted in READ when API returns 404 ([fe5b91e](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/fe5b91eab8f8cbdc5de4bd05bfcc511c29228a65)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Only update attributes during Read if the value has actually changed, to avoid showing plan changes ([fcb74ec](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/fcb74ecdb2109aef2d2cafa2c2a61981a298cb69)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Update namespace ID only in Read operation, using ID for obtaining information from API and updating, if it differs from name ([901a360](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/901a360482123faa243ff5aed87d524c19464808))
### Features
* Add data source for git_provider. Update ID type of git provider to int64 ([b579953](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/b5799535d6cc47f2a92531c95dfe561d5453bb79)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Add module resource ([a54c3c0](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/a54c3c0298d9fd0866dba8e01b649262ddadc2dc)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
* Add terrareg_git_providers data source for obtaining all git providers ([a34887d](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/commit/a34887d42cbe6dceddd9a5484ed525ceef513cf6)), closes [#2](https://gitlab.dockstudios.co.uk/pub/terra/terraform-provider-terrareg/issues/2)
# 1.0.0 (2024-01-29)
......
GO_FILES = $(shell find internal -type f -name '*.go' ! -name '*_test.go')
default: testacc
# Run acceptance tests
.PHONY: testacc
testacc:
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
terraform-provider-terrareg: $(GO_FILES)
go build
~/.terraform.d/plugins/terraform-provider-terrareg: terraform-provider-terrareg
cp terraform-provider-terrareg ~/.terraform.d/plugins/
dev: ~/.terraform.d/plugins/terraform-provider-terrareg
......@@ -33,7 +33,7 @@ go test $(go list ./...) -count=1 -v
To run acceptance tests, run an instance of terrareg (https://github.com/matthewjohn/terrareg) and run acceptance tests:
```
docker run -d -p 5000:5000 -e MIGRATE_DATABASE=true -e ADMIN_AUTHENTICATION_TOKEN=password
docker run -d -p 5000:5000 -e GIT_PROVIDER_CONFIG='[{"name": "Github", "base_url": "https://github.com/{namespace}/{module}", "clone_url": "ssh://git@github.com:{namespace}/{module}.git", "browse_url": "https://github.com/{namespace}/{module}/tree/{tag}/{path}"}, {"name": "Bitbucket", "base_url": "https://bitbucket.org/{namespace}/{module}", "clone_url": "ssh://git@bitbucket.org:{namespace}/{module}-{provider}.git", "browse_url": "https://bitbucket.org/{namespace}/{module}-{provider}/src/{tag_uri_encoded}/{path}"}, {"name": "Gitlab", "base_url": "https://gitlab.com/{namespace}/{module}", "clone_url": "ssh://git@gitlab.com:{namespace}/{module}-{provider}.git", "browse_url": "https://gitlab.com/{namespace}/{module}-{provider}/-/tree/{tag}/{path}"}]' -e MIGRATE_DATABASE=true -e ADMIN_AUTHENTICATION_TOKEN=password ghcr.io/matthewjohn/terrareg:latest
TF_ACC=1 TERRAREG_URL=http://localhost:5000 TERRAREG_API_KEY=password go test $(go list ./...) -count=1 -v
```
......
data "terrareg_git_provider" "example-by-name" {
name = "Github"
}
data "terrareg_git_provider" "example-by-id" {
id = 1
}
data "terrareg_git_providers" "this" { }
resource "terrareg_namespace" "this" {
name = "example-namespace"
}
data "terrareg_git_provider" "this" {
name = "Gitlab"
}
resource "terrareg_module" "example" {
namespace = terrareg_namespace.this.name
name = "example"
provider_name = "aws"
git_provider_id = data.terrareg_git_provider.this.id
git_tag_format = "v{version}"
}
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/matthewjohn/terraform-provider-terrareg/internal/terrareg"
)
// Ensure provider defined types fully satisfy framework interfaces.
var _ datasource.DataSource = &GitProviderDataSource{}
func NewGitProviderDataSource() datasource.DataSource {
return &GitProviderDataSource{}
}
// GitProviderDataSource defines the data source implementation.
type GitProviderDataSource struct {
client *terrareg.TerraregClient
}
// GitProviderDataSourceModel describes the data source data model.
type GitProviderDataSourceModel struct {
Id types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
}
func (d *GitProviderDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_git_provider"
}
func (d *GitProviderDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Data source for obtaining git provider",
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
// Mark as computed, but also Optional, to allow the user to define the value
Computed: true,
MarkdownDescription: "Internal ID",
Optional: true,
},
"name": schema.StringAttribute{
// Mark as computed, but also Optional, to allow the user to define the value
Computed: true,
Optional: true,
MarkdownDescription: "Internal ID",
},
},
}
}
func (d GitProviderDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator {
return []datasource.ConfigValidator{
datasourcevalidator.Conflicting(
path.MatchRoot("id"),
path.MatchRoot("name"),
),
}
}
func (d *GitProviderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*terrareg.TerraregClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *terrareg.TerraregClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
d.client = client
}
func (d *GitProviderDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data GitProviderDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
if data.Id.IsNull() && data.Name.IsNull() {
resp.Diagnostics.AddError("Client Error", "Either 'id' or 'name' must be provided to terrareg_git_provider data source.")
return
}
gitProviders, err := d.client.GetGitProviders()
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err))
return
}
foundMatch := false
for _, v := range gitProviders {
if !data.Name.IsNull() {
if data.Name.Equal(types.StringValue(v.Name)) {
data.Id = types.Int64Value(v.ID)
foundMatch = true
break
}
}
if !data.Id.IsNull() {
if data.Id.Equal(types.Int64Value(v.ID)) {
data.Name = types.StringValue(v.Name)
foundMatch = true
break
}
}
}
if !foundMatch {
resp.Diagnostics.AddError("Client Error", "Unable to find git provider with matching details")
return
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
package provider
import (
"regexp"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccGitProviderDataSource_by_id(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Read testing
{
Config: buildTestProviderConfig(testAccGitProviderDataSourceConfig_by_id),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.terrareg_git_provider.this", "id", "3"),
resource.TestCheckResourceAttr("data.terrareg_git_provider.this", "name", "Gitlab"),
),
},
},
})
}
func TestAccGitProviderDataSource_by_name(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Read testing
{
Config: buildTestProviderConfig(testAccGitProviderDataSourceConfig_by_name),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.terrareg_git_provider.this", "id", "2"),
resource.TestCheckResourceAttr("data.terrareg_git_provider.this", "name", "Bitbucket"),
),
},
},
})
}
func TestAccGitProviderDataSource_id_and_name(t *testing.T) {
errorRegex, err := regexp.Compile(".*These attributes cannot be configured together: \\[id,name\\].*")
if err != nil {
t.Error(err)
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Read testing
{
Config: buildTestProviderConfig(testAccGitProviderDataSourceConfig_id_and_name),
ExpectError: errorRegex,
},
},
})
}
func TestAccGitProviderDataSource_no_arguments(t *testing.T) {
errorRegex, err := regexp.Compile(".*Either 'id' or 'name' must be provided to terrareg_git_provider data source.*")
if err != nil {
t.Error(err)
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Read testing
{
Config: buildTestProviderConfig(testAccGitProviderDataSourceConfig_no_arguments),
ExpectError: errorRegex,
},
},
})
}
const testAccGitProviderDataSourceConfig_by_id = `
data "terrareg_git_provider" "this" {
id = 3
}
`
const testAccGitProviderDataSourceConfig_by_name = `
data "terrareg_git_provider" "this" {
name = "Bitbucket"
}
`
const testAccGitProviderDataSourceConfig_id_and_name = `
data "terrareg_git_provider" "this" {
id = 3
name = "Bitbucket"
}
`
const testAccGitProviderDataSourceConfig_no_arguments = `
data "terrareg_git_provider" "this" {
}
`
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/matthewjohn/terraform-provider-terrareg/internal/terrareg"
)
// Ensure provider defined types fully satisfy framework interfaces.
var _ datasource.DataSource = &GitProvidersDataSource{}
func NewGitProvidersDataSource() datasource.DataSource {
return &GitProvidersDataSource{}
}
// GitProvidersDataSource defines the data source implementation.
type GitProvidersDataSource struct {
client *terrareg.TerraregClient
}
// GitProvidersDataSourceModel describes the data source data model.
type GitProvidersDataSourceModel struct {
Id types.String `tfsdk:"id"`
GitProviders []terrareg.GitProviderModel `tfsdk:"git_providers"`
}
func (d *GitProvidersDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_git_providers"
}
func (d *GitProvidersDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Data source for obtaining all git providers",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Internal ID",
},
"git_providers": schema.ListAttribute{
ElementType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"id": types.Int64Type,
"name": types.StringType,
},
},
MarkdownDescription: "List of Git Providers, including id and name",
Computed: true,
},
},
}
}
func (d *GitProvidersDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*terrareg.TerraregClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *terrareg.TerraregClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
d.client = client
}
func (d *GitProvidersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data GitProvidersDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
gitProviders, err := d.client.GetGitProviders()
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err))
return
}
data.GitProviders = gitProviders
// Create fake ID, required by Terraform
data.Id = types.StringValue("this")
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccGitProvidersDataSource(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Read testing
{
Config: buildTestProviderConfig(testAccGitProvidersDataSourceConfig),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.0.id", "1"),
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.0.name", "Github"),
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.1.id", "2"),
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.1.name", "Bitbucket"),
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.2.id", "3"),
resource.TestCheckResourceAttr("data.terrareg_git_providers.this", "git_providers.2.name", "Gitlab"),
),
},
},
})
}
const testAccGitProvidersDataSourceConfig = `
data "terrareg_git_providers" "this" { }
`
package provider
import (
"context"
"fmt"
"strings"
"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/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/matthewjohn/terraform-provider-terrareg/internal/terrareg"
)
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &ModuleResource{}
var _ resource.ResourceWithImportState = &ModuleResource{}
var _ resource.ResourceWithModifyPlan = &ModuleResource{}
func NewModuleResource() resource.Resource {
return &ModuleResource{}
}
// ModuleResource defines the resource implementation.
type ModuleResource struct {
client *terrareg.TerraregClient
}
// ModuleResourceModel describes the resource data model.
type ModuleResourceModel struct {
ID types.String `tfsdk:"id"`
Namespace types.String `tfsdk:"namespace"`
Name types.String `tfsdk:"name"`
Provider types.String `tfsdk:"provider_name"`
GitProviderID types.Int64 `tfsdk:"git_provider_id"`
RepoBaseUrlTemplate types.String `tfsdk:"repo_base_url_template"`
RepoCloneUrlTemplate types.String `tfsdk:"repo_clone_url_template"`
RepoBrowseUrlTemplate types.String `tfsdk:"repo_browse_url_template"`
GitTagFormat types.String `tfsdk:"git_tag_format"`
GitPath types.String `tfsdk:"git_path"`
}
func (r *ModuleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_module"
}
func (r *ModuleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Module resource",
Attributes: map[string]schema.Attribute{
// ID attribute required for unit testing
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Full ID of the module",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"namespace": schema.StringAttribute{
Required: true,
MarkdownDescription: "Namespace of the module",
},
"name": schema.StringAttribute{
Required: true,
MarkdownDescription: "Module name",
},
"provider_name": schema.StringAttribute{
Required: true,
MarkdownDescription: "Module provider",
},
"git_provider_id": schema.Int64Attribute{
Optional: true,
MarkdownDescription: `Id of the Git Repository Provider to use for the module.
Set to ` + "`null`" + `for Custom.
(See https://github.com/MatthewJohn/terrareg/blob/main/docs/USER_GUIDE.md#git-providers)`,
},
"repo_base_url_template": schema.StringAttribute{
Optional: true,
MarkdownDescription: `This URL must be valid for browsing the base of the repository.
It may include templated values, such as: {namespace}, {module}, {provider}.
E.g. https://github.com/{namespace}/{module}-{provider}
NOTE: Setting this field will override the repository provider configuration.`,
},
"repo_clone_url_template": schema.StringAttribute{
Optional: true,
MarkdownDescription: `This URL must be valid for cloning the repository.
It may include templated values, such as: {namespace}, {module}, {provider}.
E.g. ssh://git@github.com/{namespace}/{module}-{provider}.git
NOTE: Setting this field will override the repository provider configuration.`,
},
"repo_browse_url_template": schema.StringAttribute{
Optional: true,
MarkdownDescription: `This URL must be valid for browsing the source code of the repository at a particular tag/path.
It may include templated values, such as: {namespace}, {module}, {provider}.
It must include the following template values: {tag} and {path}
E.g. https://github.com/{namespace}/{module}-{provider}/tree/{tag}/{path}
NOTE: Setting this field will override the repository provider configuration.`,
},
"git_tag_format": schema.StringAttribute{
Required: true,
MarkdownDescription: `This value will be converted to the expected git tag for a module version.
The {version} placeholder will be used to generated the git tag when translating the module version to a git tag.
For example, using v{version} will translate to a git tag 'v1.1.1' for module version '1.1.1'
If the git tagging format in use does not contain a full semantic version, use placeholders {major}, {minor} and {patch}
to indicate which values are present in the tag - any missing values will be assumed to be '0'.
For example a git tag format of v{major}.{minor} would interpret a tag v1.2 as a module version 1.2.0,
where as a git tag format v{major}.{patch} would generate a version v1.0.2.
Note that if the {version} placeholder is not used, the module version import API must be provided with the git_tag argument and indexing with version argument is disabled.`,
},
"git_path": schema.StringAttribute{
Optional: true,
MarkdownDescription: "Set the path within the repository that the module exists. Defaults to the root of the repository.",
},
},
}
}
func (r *ModuleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*terrareg.TerraregClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *terrareg.TerraregClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
r.client = client
}
func (r *ModuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data ModuleResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
id, err := r.client.CreateModule(
data.Namespace.ValueString(),
data.Name.ValueString(),
data.Provider.ValueString(),
terrareg.ModuleModel{
GitProviderID: data.GitProviderID.ValueInt64(),
RepoBaseUrlTemplate: data.RepoBaseUrlTemplate.ValueString(),
RepoCloneUrlTemplate: data.RepoCloneUrlTemplate.ValueString(),
RepoBrowseUrlTemplate: data.RepoBrowseUrlTemplate.ValueString(),
GitTagFormat: data.GitTagFormat.ValueString(),
GitPath: data.GitPath.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create module, got error: %s", err))
return
}
// Set ID attribute
data.ID = types.StringValue(id)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *ModuleResource) generateId(namespace string, name string, provider string) string {
return fmt.Sprintf("%s/%s/%s", namespace, name, provider)
}
func (r *ModuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data ModuleResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Use existing ID, if state is not available for namespace, name or provider
var namespace, name, provider string
if data.Namespace.IsUnknown() ||
data.Namespace.IsNull() ||
data.Name.IsUnknown() ||
data.Name.IsNull() ||
data.Provider.IsUnknown() ||
data.Provider.IsNull() {
splitId := strings.Split(data.ID.ValueString(), "/")
if len(splitId) != 3 {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("ID is an invalid format: %s", data.ID.ValueString()))
return
}
namespace, name, provider = splitId[0], splitId[1], splitId[2]
} else {
namespace = data.Namespace.ValueString()
name = data.Name.ValueString()
provider = data.Provider.ValueString()
}
module, err := r.client.GetModule(namespace, name, provider)
// If module was not found, set ID to empty value
if err == terrareg.ErrNotFound {
resp.State.RemoveResource(ctx)
return
} else if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read module, got error: %s", err))
return
}
// Update attributes, if they've modified
if data.Namespace.ValueString() != namespace {
data.Namespace = types.StringValue(namespace)
}
if data.Name.ValueString() != name {
data.Name = types.StringValue(name)
}
if data.Provider.ValueString() != provider {
data.Provider = types.StringValue(provider)
}
if data.GitProviderID.ValueInt64() != module.GitProviderID {
data.GitProviderID = types.Int64Value(module.GitProviderID)
}
if data.RepoBaseUrlTemplate.ValueString() != module.RepoBaseUrlTemplate {
data.RepoBaseUrlTemplate = types.StringValue(module.RepoBaseUrlTemplate)
}
if data.RepoCloneUrlTemplate.ValueString() != module.RepoCloneUrlTemplate {
data.RepoCloneUrlTemplate = types.StringValue(module.RepoCloneUrlTemplate)
}
if data.RepoBrowseUrlTemplate.ValueString() != module.RepoBrowseUrlTemplate {
data.RepoBrowseUrlTemplate = types.StringValue(module.RepoBrowseUrlTemplate)
}
if data.GitTagFormat.ValueString() != module.GitTagFormat {
data.GitTagFormat = types.StringValue(module.GitTagFormat)
}
if data.GitPath.ValueString() != module.GitPath {
data.GitPath = types.StringValue(module.GitPath)
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *ModuleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan ModuleResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
var state ModuleResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Only provide namespace, name and provider, if one of the attributes
// has been changed
var newNamespace string
var newName string
var newProvider string
if !state.Namespace.Equal(plan.Namespace) ||
!state.Name.Equal(plan.Name) ||
!state.Provider.Equal(plan.Provider) {
newNamespace = plan.Namespace.ValueString()
newName = plan.Name.ValueString()
newProvider = plan.Provider.ValueString()
}
_, err := r.client.UpdateModule(
state.Namespace.ValueString(),
state.Name.ValueString(),
state.Provider.ValueString(),
terrareg.ModuleUpdateModel{
Namespace: newNamespace,
Name: newName,
Provider: newProvider,
ModuleModel: &terrareg.ModuleModel{
GitProviderID: plan.GitProviderID.ValueInt64(),
RepoBaseUrlTemplate: plan.RepoBaseUrlTemplate.ValueString(),
RepoCloneUrlTemplate: plan.RepoCloneUrlTemplate.ValueString(),
RepoBrowseUrlTemplate: plan.RepoBrowseUrlTemplate.ValueString(),
GitTagFormat: plan.GitTagFormat.ValueString(),
GitPath: plan.GitPath.ValueString(),
},
},
)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update module, got error: %s", err))
return
}
newId := types.StringValue(r.generateId(plan.Namespace.ValueString(), plan.Name.ValueString(), plan.Provider.ValueString()))
if !plan.ID.Equal(newId) {
plan.ID = newId
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *ModuleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state ModuleResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
err := r.client.DeleteModule(state.Namespace.ValueString(), state.Name.ValueString(), state.Provider.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete module, got error: %s", err))
return
}
}
func (r *ModuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
func (r ModuleResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
var plan ModuleResourceModel
diags := req.Plan.Get(ctx, &plan)
// If unable to obtain plan (generally during a destroy),
// exit early without erroring
if diags.HasError() {
return
}
if !plan.Namespace.IsNull() && !plan.Namespace.IsUnknown() && !plan.Name.IsNull() && !plan.Name.IsUnknown() && !plan.Provider.IsNull() && !plan.Provider.IsUnknown() {
newId := r.generateId(plan.Namespace.ValueString(), plan.Name.ValueString(), plan.Provider.ValueString())
// If plan value of ID is not unknown and needs to be modified,
// update it.
if !plan.ID.IsUnknown() && plan.ID.ValueString() != newId {
resp.Diagnostics.Append(resp.Plan.SetAttribute(ctx, path.Root("id"), types.StringValue(newId))...)
}
}
}
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccModuleResource_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: buildTestProviderConfig(testAccNamespaceResourceConfig_basic),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("terrareg_module.example", "id", "module-basic-example/basic-example/aws"),
resource.TestCheckResourceAttr("terrareg_module.example", "namespace", "module-basic-example"),
resource.TestCheckResourceAttr("terrareg_module.example", "name", "basic-example"),
resource.TestCheckResourceAttr("terrareg_module.example", "provider_name", "aws"),
),
},
// ImportState testing
{
ResourceName: "terrareg_module.example",
ImportState: true,
ImportStateVerify: true,
// This is not normally necessary, but is here because this
// example code does not have an actual upstream service.
// Once the Read method is able to refresh information from
// the upstream service, this can be removed.
// ImportStateVerifyIgnore: []string{"configurable_attribute", "defaulted"},
},
// Update and Read testing
{
Config: buildTestProviderConfig(testAccNamespaceResourceConfig_basic_read),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("terrareg_module.example", "id", "module-basic-example/basic-example2/awsnew"),
resource.TestCheckResourceAttr("terrareg_module.example", "namespace", "module-basic-example"),
resource.TestCheckResourceAttr("terrareg_module.example", "name", "basic-example2"),
resource.TestCheckResourceAttr("terrareg_module.example", "provider_name", "awsnew"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}
func TestAccModuleResource_full(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: buildTestProviderConfig(testAccNamespaceResourceConfig_full),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("terrareg_module.example2", "id", "module-basic-example2/basic-example3/aws"),
resource.TestCheckResourceAttr("terrareg_module.example2", "namespace", "module-basic-example2"),
resource.TestCheckResourceAttr("terrareg_module.example2", "name", "basic-example3"),
resource.TestCheckResourceAttr("terrareg_module.example2", "provider_name", "aws"),
resource.TestCheckResourceAttr("terrareg_module.example2", "git_tag_format", "v{version}3"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_base_url_template", "https://somecustom-domain.com/{namespace}/{module}-{provider}"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_clone_url_template", "ssh://git@some-custom-domain.com/{namespace}/{module}-{provider}.git"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_browse_url_template", "https://some-custom-domain.com/{namespace}/{module}-{provider}/tree/{tag}/{path}"),
),
},
// ImportState testing
{
ResourceName: "terrareg_module.example2",
ImportState: true,
ImportStateVerify: true,
// This is not normally necessary, but is here because this
// example code does not have an actual upstream service.
// Once the Read method is able to refresh information from
// the upstream service, this can be removed.
// ImportStateVerifyIgnore: []string{"configurable_attribute", "defaulted"},
},
// Update and Read testing
{
Config: buildTestProviderConfig(testAccNamespaceResourceConfig_full_read),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("terrareg_module.example2", "id", "module-basic-example2/basic-example3/aws"),
resource.TestCheckResourceAttr("terrareg_module.example2", "namespace", "module-basic-example2"),
resource.TestCheckResourceAttr("terrareg_module.example2", "name", "basic-example3"),
resource.TestCheckResourceAttr("terrareg_module.example2", "provider_name", "aws"),
resource.TestCheckResourceAttr("terrareg_module.example2", "git_tag_format", "v{version}4"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_base_url_template", "https://somecustom-domain2.com/{namespace}/{module}-{provider}"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_clone_url_template", "ssh://git@some-custom-domain2.com/{namespace}/{module}-{provider}.git"),
resource.TestCheckResourceAttr("terrareg_module.example2", "repo_browse_url_template", "https://some-custom-domain2.com/{namespace}/{module}-{provider}/tree/{tag}/{path}"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}
const testAccNamespaceResourceConfig_basic = `
resource "terrareg_namespace" "this" {
name = "module-basic-example"
}
data "terrareg_git_provider" "this" {
name = "Gitlab"
}
resource "terrareg_module" "example" {
namespace = terrareg_namespace.this.name
name = "basic-example"
provider_name = "aws"
git_provider_id = data.terrareg_git_provider.this.id
git_tag_format = "v{version}"
}
`
const testAccNamespaceResourceConfig_basic_read = `
resource "terrareg_namespace" "this" {
name = "module-basic-example"
}
data "terrareg_git_provider" "this" {
name = "Gitlab"
}
resource "terrareg_module" "example" {
namespace = terrareg_namespace.this.name
name = "basic-example2"
provider_name = "awsnew"
git_provider_id = data.terrareg_git_provider.this.id
git_tag_format = "v{version}-new"
}
`
const testAccNamespaceResourceConfig_full = `
resource "terrareg_namespace" "this" {
name = "module-basic-example2"
}
data "terrareg_git_provider" "this" {
name = "Gitlab"
}
resource "terrareg_module" "example2" {
namespace = terrareg_namespace.this.name
name = "basic-example3"
provider_name = "aws"
git_provider_id = data.terrareg_git_provider.this.id
git_tag_format = "v{version}3"
repo_base_url_template = "https://somecustom-domain.com/{namespace}/{module}-{provider}"
repo_clone_url_template = "ssh://git@some-custom-domain.com/{namespace}/{module}-{provider}.git"
repo_browse_url_template = "https://some-custom-domain.com/{namespace}/{module}-{provider}/tree/{tag}/{path}"
}
`
const testAccNamespaceResourceConfig_full_read = `
resource "terrareg_namespace" "this" {
name = "module-basic-example2"
}
data "terrareg_git_provider" "this" {
name = "Gitlab"
}
resource "terrareg_module" "example2" {
namespace = terrareg_namespace.this.name
name = "basic-example3"
provider_name = "aws"
git_provider_id = data.terrareg_git_provider.this.id
git_tag_format = "v{version}4"
repo_base_url_template = "https://somecustom-domain2.com/{namespace}/{module}-{provider}"
repo_clone_url_template = "ssh://git@some-custom-domain2.com/{namespace}/{module}-{provider}.git"
repo_browse_url_template = "https://some-custom-domain2.com/{namespace}/{module}-{provider}/tree/{tag}/{path}"
}
`
......@@ -112,10 +112,16 @@ func (r *NamespaceResource) Read(ctx context.Context, req resource.ReadRequest,
return
}
namespace, err := r.client.GetNamespace(data.Name.ValueString())
// Update ID, if it does not match
if data.ID.IsUnknown() || data.ID.ValueString() != data.Name.ValueString() {
data.ID = data.Name
}
namespace, err := r.client.GetNamespace(data.ID.ValueString())
// If namespace was not found, set Name to empty value
if err == terrareg.ErrNotFound {
data.Name = types.StringValue("")
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read namespace, got error: %s", err))
......@@ -123,8 +129,9 @@ func (r *NamespaceResource) Read(ctx context.Context, req resource.ReadRequest,
}
// Update attributes
data.ID = data.Name
data.DisplayName = types.StringValue(namespace.DisplayName)
if data.DisplayName.ValueString() != namespace.DisplayName {
data.DisplayName = types.StringValue(namespace.DisplayName)
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
......@@ -157,8 +164,9 @@ func (r *NamespaceResource) Update(ctx context.Context, req resource.UpdateReque
return
}
// Update ID attribute
data.ID = data.Name
if data.ID.IsUnknown() || data.ID.ValueString() != data.Name.ValueString() {
data.ID = data.Name
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
......
......@@ -102,12 +102,14 @@ func (p *TerraregProvider) Configure(ctx context.Context, req provider.Configure
func (p *TerraregProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewNamespaceResource,
NewModuleResource,
}
}
func (p *TerraregProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
// NewExampleDataSource,
NewGitProvidersDataSource,
NewGitProviderDataSource,
}
}
......
......@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
)
type TerraregClient struct {
......@@ -45,6 +46,15 @@ func (c *TerraregClient) getTerraregApiUrl(apiEndpoint string) string {
return fmt.Sprintf("%s/v1/terrareg/%s", c.Url, apiEndpoint)
}
func (c *TerraregClient) printBody(resp *http.Response) {
respDump, err := httputil.DumpResponse(resp, true)
if err != nil {
fmt.Printf("[terrareg] Failed to dump repsonse")
return
}
fmt.Printf("[terrareg] Got body repsonse: %s\n", string(respDump))
}
func (c *TerraregClient) makeRequest(url string, requestMethod string, jsonData any) (*http.Response, error) {
body := new(bytes.Buffer)
if jsonData != nil {
......
package terrareg
import (
"encoding/json"
"fmt"
)
type GitProviderModel struct {
ID int64 `json:"id" tfsdk:"id"`
Name string `json:"name" tfsdk:"name"`
}
func (c *TerraregClient) GetGitProviders() ([]GitProviderModel, error) {
url := c.getTerraregApiUrl("git_providers")
res, err := c.makeRequest(url, "GET", nil)
if err != nil {
return nil, err
}
err = c.handleCommonStatusCode(res.StatusCode)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, ErrUnknownError
}
// Body is 200
if res.Body == nil {
return nil, ErrUnknownError
}
dec := json.NewDecoder(res.Body)
// dec.DisallowUnknownFields()
var data []GitProviderModel
err = dec.Decode(&data)
if err != nil {
fmt.Printf("Terrareg Client: Unable to decode git providers JSON from response body")
return nil, err
}
return data, nil
}