diff --git a/docs/resources/association_type.md b/docs/resources/association_type.md new file mode 100644 index 0000000..ba9981d --- /dev/null +++ b/docs/resources/association_type.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "akeneo_association_type Resource - terraform-provider-akeneo" +subcategory: "" +description: |- + Akeneo association type resource +--- + +# akeneo_association_type (Resource) + +Akeneo association type resource + + + + +## Schema + +### Required + +- `code` (String) Association type code + +### Optional + +- `is_quantified` (Boolean) Whether the association type is a quantified association +- `is_two_way` (Boolean) Whether the association type is a two-way association +- `labels` (Map of String) Label definition per locale diff --git a/go.mod b/go.mod index 0c830eb..f1086e1 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,8 @@ module github.com/0xfrej/terraform-provider-akeneo -go 1.22.0 -toolchain go1.22.9 +go 1.22.7 + +toolchain go1.23.2 require ( github.com/ezifyio/go-akeneo v1.0.28 diff --git a/internal/akeneox/association.go b/internal/akeneox/association.go new file mode 100644 index 0000000..840b9c6 --- /dev/null +++ b/internal/akeneox/association.go @@ -0,0 +1,48 @@ +package akeneox + +import ( + "fmt" + + goakeneo "github.com/ezifyio/go-akeneo" +) + +const ( + associationTypesSinglePath = "/api/rest/v1/association-types/%s" +) + +type AssociationTypeService struct { + client *goakeneo.Client +} + +func NewAssociationTypeClient(client *goakeneo.Client) *AssociationTypeService { + return &AssociationTypeService{ + client: client, + } +} + +func (a *AssociationTypeService) UpdateAssociationTypes(association AssociationType) error { + err := a.client.PATCH( + fmt.Sprintf(associationTypesSinglePath, association.Code), + nil, + association, + nil, + ) + if err != nil { + return err + } + return nil +} + +func (a *AssociationTypeService) GetAssociationType(code string) (*AssociationType, error) { + response := new(AssociationType) + err := a.client.GET( + fmt.Sprintf(associationTypesSinglePath, code), + nil, + nil, + response, + ) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/internal/akeneox/entity.go b/internal/akeneox/entity.go index e7c38a6..1b37a2d 100644 --- a/internal/akeneox/entity.go +++ b/internal/akeneox/entity.go @@ -36,3 +36,10 @@ type MeasurementFamilyPatchResponse struct { Message string `json:"message,omitempty"` Errors []goakeneo.ValidationError `json:"errors,omitempty"` } + +type AssociationType struct { + Code string `json:"code,omitempty" mapstructure:"code"` + Labels map[string]string `json:"labels,omitempty" mapstructure:"labels"` + IsQuantified *bool `json:"is_quantified,omitempty" mapstructure:"is_quantified"` + IsTwoWay *bool `json:"is_two_way,omitempty" mapstructure:"is_two_way"` +} diff --git a/internal/provider/association_type_resource.go b/internal/provider/association_type_resource.go new file mode 100644 index 0000000..45a5724 --- /dev/null +++ b/internal/provider/association_type_resource.go @@ -0,0 +1,278 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/0xfrej/terraform-provider-akeneo/internal/akeneox" + "github.com/0xfrej/terraform-provider-akeneo/internal/validator/stringvalidatorx" + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &AssociationTypeResource{} +var _ resource.ResourceWithImportState = &AssociationTypeResource{} +var _ resource.ResourceWithConfigure = &AssociationTypeResource{} + +func NewAssociationTypeResource() resource.Resource { + return &AssociationTypeResource{} +} + +// AssociationTypeResource defines the resource implementation. +type AssociationTypeResource struct { + client *akeneox.AssociationTypeService +} + +// AssociationTypeResourceModel describes the resource data model. +type AssociationTypeResourceModel struct { + Code types.String `tfsdk:"code"` + Labels types.Map `tfsdk:"labels"` + IsQuantified types.Bool `tfsdk:"is_quantified"` + IsTwoWay types.Bool `tfsdk:"is_two_way"` +} + +func (r *AssociationTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_association_type" +} + +func (r *AssociationTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Akeneo association type resource", + + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Association type code", + Required: true, + }, + "labels": schema.MapAttribute{ + Description: "Label definition per locale", + Optional: true, + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.KeysAre(stringvalidatorx.IsLocaleCode()), + }, + }, + "is_quantified": schema.BoolAttribute{ + Description: "Whether the association type is a quantified association", + Optional: true, + }, + "is_two_way": schema.BoolAttribute{ + Description: "Whether the association type is a two-way association", + Optional: true, + }, + }, + } +} + +func (r *AssociationTypeResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(*ResourceData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *ResourceData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + if data.Client == nil { + resp.Diagnostics.AddError( + "Missing client instance", + "Client instance pointer passed to Configure is required, got nil", + ) + return + } + + r.client = akeneox.NewAssociationTypeClient(data.Client) +} + +func (r *AssociationTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data AssociationTypeResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + apiData := r.mapToApiObject(ctx, &resp.Diagnostics, &data) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.UpdateAssociationTypes(*apiData) + if err != nil { + resp.Diagnostics.AddError( + "Error while creating a association type", + "An unexpected error occurred when creating association type. \n\n"+ + "Akeneo API Error: "+err.Error(), + ) + return + } + + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AssociationTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AssociationTypeResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + attrData, err := r.client.GetAssociationType(data.Code.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error while reading an association type", + "An unexpected error occurred when reading association type. \n\n"+ + "Akeneo API Error: "+err.Error(), + ) + return + } + + if resp.Diagnostics.HasError() { + return + } + + r.mapToTfObject(&resp.Diagnostics, &data, attrData) + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AssociationTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data AssociationTypeResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + apiData := r.mapToApiObject(ctx, &resp.Diagnostics, &data) + + if resp.Diagnostics.HasError() { + return + } + err := r.client.UpdateAssociationTypes(*apiData) + if err != nil { + resp.Diagnostics.AddError( + "Error while creating a association type", + "An unexpected error occurred when creating association type. \n\n"+ + "Akeneo API Error: "+err.Error(), + ) + return + } + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AssociationTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + //var data AssociationTypeResourceModel + // + //// Read Terraform prior state data into the model + //resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + // + //if resp.Diagnostics.HasError() { + // return + //} + + resp.Diagnostics.AddError( + "This resource does not support deletes", + "This resource does not support deletes. The Akeneo API does not support deletes for association types.", + ) +} + +func (r *AssociationTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("code"), req, resp) +} + +func (r *AssociationTypeResource) mapToApiObject(ctx context.Context, diags *diag.Diagnostics, data *AssociationTypeResourceModel) *akeneox.AssociationType { + a := akeneox.AssociationType{ + Code: data.Code.ValueString(), + } + + if !(data.Labels.IsNull() || data.Labels.IsUnknown()) { + elements := make(map[string]types.String, len(data.Labels.Elements())) + diags.Append(data.Labels.ElementsAs(ctx, &elements, false)...) + labels := make(map[string]string) + for locale, label := range elements { + labels[locale] = label.ValueString() + } + a.Labels = labels + } + + if !(data.IsQuantified.IsNull() || data.IsQuantified.IsUnknown()) { + v := data.IsQuantified.ValueBool() + a.IsQuantified = &v + } + + if !(data.IsTwoWay.IsNull() || data.IsTwoWay.IsUnknown()) { + v := data.IsTwoWay.ValueBool() + a.IsTwoWay = &v + } + + if diags.HasError() { + return nil + } + + return &a +} + +func (r *AssociationTypeResource) mapToTfObject(respDiags *diag.Diagnostics, data *AssociationTypeResourceModel, apiData *akeneox.AssociationType) { + data.Code = types.StringValue(apiData.Code) + + if len(apiData.Labels) > 0 { + elements := make(map[string]attr.Value, len(apiData.Labels)) + + for k, v := range apiData.Labels { + elements[k] = types.StringValue(v) + } + + mapVal, diags := types.MapValue(types.StringType, elements) + if diags.HasError() { + respDiags.Append(diags...) + } + data.Labels = mapVal + } + + if apiData.IsQuantified != nil { + data.IsQuantified = types.BoolValue(*apiData.IsQuantified) + } + + if apiData.IsTwoWay != nil { + data.IsQuantified = types.BoolValue(*apiData.IsTwoWay) + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 237c78f..2f3bb7f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -150,6 +150,7 @@ func (p *AkeneoProvider) Resources(ctx context.Context) []func() resource.Resour NewMeasurementFamilyResource, NewChannelResource, NewCategoryResource, + NewAssociationTypeResource, } }