From fd6dc09ffac1ac33316ebc6812974a208b290d45 Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Thu, 10 Aug 2023 13:23:15 +0300 Subject: [PATCH 1/7] WIP - add webhook support --- internal/cli/models.go | 48 ++++++- internal/cli/webhook.go | 87 ++++++++++++ port/webhook/model.go | 48 +++++++ port/webhook/refreshWebhookState.go | 35 +++++ port/webhook/resource.go | 154 +++++++++++++++++++++ port/webhook/schema.go | 160 ++++++++++++++++++++++ port/webhook/webhookResourceToPortBody.go | 61 +++++++++ provider/provider.go | 2 + 8 files changed, 591 insertions(+), 4 deletions(-) create mode 100644 internal/cli/webhook.go create mode 100644 port/webhook/model.go create mode 100644 port/webhook/refreshWebhookState.go create mode 100644 port/webhook/resource.go create mode 100644 port/webhook/schema.go create mode 100644 port/webhook/webhookResourceToPortBody.go diff --git a/internal/cli/models.go b/internal/cli/models.go index 4435f8c1..41650963 100644 --- a/internal/cli/models.go +++ b/internal/cli/models.go @@ -195,13 +195,53 @@ type ( Required *bool `json:"required,omitempty"` Many *bool `json:"many,omitempty"` } + + Webhook struct { + Meta + Identifier string `json:"identifier,omitempty"` + Title *string `json:"title,omitempty"` + Icon *string `json:"icon,omitempty"` + Description *string `json:"description,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Security *Security `json:"security,omitempty"` + Mappings []Mappings `json:"mappings,omitempty"` + } + + Security struct { + Secret *string `json:"secret,omitempty"` + SignatureHeaderName *string `json:"signatureHeaderName,omitempty"` + SignatureAlgorithm *string `json:"signatureAlgorithm,omitempty"` + SignaturePrefix *string `json:"signaturePrefix,omitempty"` + RequestIdentifierPath *string `json:"requestIdentifierPath,omitempty"` + } + + EntityProperty struct { + Identifier *string `json:"identifier,omitempty"` + Title *string `json:"title,omitempty"` + Icon *string `json:"icon,omitempty"` + Team *string `json:"team,omitempty"` + Properties map[string]string `json:"properties,omitempty"` + Relations map[string]string `json:"relations,omitempty"` + } + + WebhookProperty struct { + Blueprint *string `json:"blueprint,omitempty"` + Filter *string `json:"filter,omitempty"` + ItemsToParse *string `json:"items_to_parse,omitempty"` + Entity *EntityProperty `json:"entity,omitempty"` + } + + Mappings struct { + Properties *WebhookProperty `json:"properties,omitempty"` + } ) type PortBody struct { - OK bool `json:"ok"` - Entity Entity `json:"entity"` - Blueprint Blueprint `json:"blueprint"` - Action Action `json:"action"` + OK bool `json:"ok"` + Entity Entity `json:"entity"` + Blueprint Blueprint `json:"blueprint"` + Action Action `json:"action"` + Integration Webhook `json:"integration"` } type PortProviderModel struct { diff --git a/internal/cli/webhook.go b/internal/cli/webhook.go new file mode 100644 index 00000000..59560996 --- /dev/null +++ b/internal/cli/webhook.go @@ -0,0 +1,87 @@ +package cli + +import ( + "context" + "encoding/json" + "fmt" +) + +func (c *PortClient) ReadWebhook(ctx context.Context, webhookID string) (*Webhook, int, error) { + pb := &PortBody{} + url := "v1/webhooks/{webhook_identifier}" + resp, err := c.Client.R(). + SetContext(ctx). + SetHeader("Accept", "application/json"). + SetResult(pb). + SetPathParam("webhook_identifier", webhookID). + Get(url) + if err != nil { + return nil, resp.StatusCode(), err + } + if !pb.OK { + return nil, resp.StatusCode(), fmt.Errorf("failed to read webhook, got: %s", resp.Body()) + } + return &pb.Integration, resp.StatusCode(), nil +} + +func (c *PortClient) CreateWebhook(ctx context.Context, webhook *Webhook) (*Webhook, error) { + url := "v1/webhooks" + resp, err := c.Client.R(). + SetBody(webhook). + SetContext(ctx). + Post(url) + if err != nil { + return nil, err + } + var pb PortBody + err = json.Unmarshal(resp.Body(), &pb) + if err != nil { + return nil, err + } + if !pb.OK { + return nil, fmt.Errorf("failed to create webhook, got: %s", resp.Body()) + } + return &pb.Integration, nil +} + +func (c *PortClient) UpdateWebhook(ctx context.Context, webhookID string, webhook *Webhook) (*Webhook, error) { + url := "v1/webhooks/{webhook_identifier}" + resp, err := c.Client.R(). + SetBody(webhook). + SetContext(ctx). + SetPathParam("webhook_identifier", webhookID). + Put(url) + if err != nil { + return nil, err + } + var pb PortBody + err = json.Unmarshal(resp.Body(), &pb) + if err != nil { + return nil, err + } + if !pb.OK { + return nil, fmt.Errorf("failed to create webhook, got: %s", resp.Body()) + } + return &pb.Integration, nil +} + +func (c *PortClient) DeleteWebhook(ctx context.Context, webhookID string) error { + url := "v1/webhooks/{webhook_identifier}" + resp, err := c.Client.R(). + SetContext(ctx). + SetHeader("Accept", "application/json"). + SetPathParam("webhook_identifier", webhookID). + Delete(url) + if err != nil { + return err + } + responseBody := make(map[string]interface{}) + err = json.Unmarshal(resp.Body(), &responseBody) + if err != nil { + return err + } + if !(responseBody["ok"].(bool)) { + return fmt.Errorf("failed to delete webhook. got:\n%s", string(resp.Body())) + } + return nil +} diff --git a/port/webhook/model.go b/port/webhook/model.go new file mode 100644 index 00000000..e24ab63c --- /dev/null +++ b/port/webhook/model.go @@ -0,0 +1,48 @@ +package webhook + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type SecurityModel struct { + Secret types.String `tfsdk:"secret"` + SignatureHeaderName types.String `tfsdk:"signature_header_name"` + SignatureAlgorithm types.String `tfsdk:"signature_algorithm"` + SignaturePrefix types.String `tfsdk:"signature_prefix"` + RequestIdentifierPath types.String `tfsdk:"request_identifier_path"` +} + +type EntityModel struct { + Identifier types.String `tfsdk:"identifier"` + Title types.String `tfsdk:"title"` + Icon types.String `tfsdk:"icon"` + Team types.String `tfsdk:"team"` + Properties map[string]string `tfsdk:"properties"` + Relations map[string]string `tfsdk:"relations"` +} + +type PropertiesModel struct { + Identifier types.String `tfsdk:"identifier"` + Filter types.String `tfsdk:"filter"` + ItemsToParse types.String `tfsdk:"items_to_parse"` + Entity *EntityModel `tfsdk:"entity"` +} + +type MappingsModel struct { + Properties *PropertiesModel `tfsdk:"properties"` +} + +type WebhookModel struct { + ID types.String `tfsdk:"id"` + Icon types.String `tfsdk:"icon"` + Identifier types.String `tfsdk:"identifier"` + Title types.String `tfsdk:"title"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + Security *SecurityModel `tfsdk:"security"` + Mappings []MappingsModel `tfsdk:"mappings"` + CreatedAt types.String `tfsdk:"created_at"` + CreatedBy types.String `tfsdk:"created_by"` + UpdatedAt types.String `tfsdk:"updated_at"` + UpdatedBy types.String `tfsdk:"updated_by"` +} diff --git a/port/webhook/refreshWebhookState.go b/port/webhook/refreshWebhookState.go new file mode 100644 index 00000000..bfd3c3cf --- /dev/null +++ b/port/webhook/refreshWebhookState.go @@ -0,0 +1,35 @@ +package webhook + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/port-labs/terraform-provider-port-labs/internal/cli" +) + +func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhook) error { + state.ID = types.StringValue(w.Identifier) + state.Identifier = types.StringValue(w.Identifier) + state.CreatedAt = types.StringValue(w.CreatedAt.String()) + state.CreatedBy = types.StringValue(w.CreatedBy) + state.UpdatedAt = types.StringValue(w.UpdatedAt.String()) + state.UpdatedBy = types.StringValue(w.UpdatedBy) + + if w.Icon != nil { + state.Icon = types.StringValue(*w.Icon) + } + + if w.Title != nil { + state.Title = types.StringValue(*w.Title) + } + + if w.Description != nil { + state.Description = types.StringValue(*w.Description) + } + + if w.Enabled != nil { + state.Enabled = types.BoolValue(*w.Enabled) + } + + return nil +} diff --git a/port/webhook/resource.go b/port/webhook/resource.go new file mode 100644 index 00000000..eca8cbd9 --- /dev/null +++ b/port/webhook/resource.go @@ -0,0 +1,154 @@ +package webhook + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/port-labs/terraform-provider-port-labs/internal/cli" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &WebhookResource{} +var _ resource.ResourceWithImportState = &WebhookResource{} + +func NewWebhookResource() resource.Resource { + return &WebhookResource{} +} + +type WebhookResource struct { + portClient *cli.PortClient +} + +func (r *WebhookResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_webhook" +} + +func (r *WebhookResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.portClient = req.ProviderData.(*cli.PortClient) +} + +func (r *WebhookResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state *WebhookModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + identifier := state.Identifier.ValueString() + w, statusCode, err := r.portClient.ReadWebhook(ctx, identifier) + if err != nil { + if statusCode == 404 { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("failed to read webhook", err.Error()) + return + } + + err = refreshWebhookState(ctx, state, w) + if err != nil { + resp.Diagnostics.AddError("failed writing webhook fields to resource", err.Error()) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *WebhookResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var state *WebhookModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + w, err := webhookResourceToBody(ctx, state) + if err != nil { + resp.Diagnostics.AddError("failed to convert webhook resource to body", err.Error()) + return + } + + wp, err := r.portClient.CreateWebhook(ctx, w) + if err != nil { + resp.Diagnostics.AddError("failed to create webhook", err.Error()) + return + } + + writeEntityComputedFieldsToState(state, wp) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func writeEntityComputedFieldsToState(state *WebhookModel, wp *cli.Webhook) { + state.ID = types.StringValue(wp.Identifier) + state.Identifier = types.StringValue(wp.Identifier) + state.CreatedAt = types.StringValue(wp.CreatedAt.String()) + state.CreatedBy = types.StringValue(wp.CreatedBy) + state.UpdatedAt = types.StringValue(wp.UpdatedAt.String()) + state.UpdatedBy = types.StringValue(wp.UpdatedBy) +} + +func (r *WebhookResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var state *WebhookModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + // bp, _, err := r.portClient.ReadBlueprint(ctx, state.Blueprint.ValueString()) + // if err != nil { + // resp.Diagnostics.AddError("failed to read blueprint", err.Error()) + // return + // } + + // e, err := entityResourceToBody(ctx, state, bp) + // if err != nil { + // resp.Diagnostics.AddError("failed to convert entity resource to body", err.Error()) + // return + // } + + // runID := "" + // if !state.RunID.IsNull() { + // runID = state.RunID.ValueString() + // } + + // en, err := r.portClient.CreateEntity(ctx, e, runID) + // if err != nil { + // resp.Diagnostics.AddError("failed to create entity", err.Error()) + // return + // } + + // writeEntityComputedFieldsToState(state, en) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *WebhookResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state *WebhookModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.portClient.DeleteWebhook(ctx, state.Identifier.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("failed to delete webhook", err.Error()) + return + } + +} + +func (r *WebhookResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("identifier"), req.ID, + )...) +} diff --git a/port/webhook/schema.go b/port/webhook/schema.go new file mode 100644 index 00000000..ff8c8e1c --- /dev/null +++ b/port/webhook/schema.go @@ -0,0 +1,160 @@ +package webhook + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "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" +) + +func WebhookSchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "identifier": schema.StringAttribute{ + MarkdownDescription: "The identifier of the webhook", + Optional: true, + Computed: true, + }, + "title": schema.StringAttribute{ + MarkdownDescription: "The title of the webhook", + Optional: true, + }, + "icon": schema.StringAttribute{ + MarkdownDescription: "The icon of the webhook", + Optional: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "The description of the webhook", + Optional: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Whether the webhook is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "security": schema.SingleNestedAttribute{ + MarkdownDescription: "The security of the webhook", + Optional: true, + Attributes: map[string]schema.Attribute{ + "secret": schema.StringAttribute{ + MarkdownDescription: "The secret of the webhook", + Optional: true, + }, + "signature_header_name": schema.StringAttribute{ + MarkdownDescription: "The signature header name of the webhook", + Optional: true, + }, + "signature_algorithm": schema.StringAttribute{ + MarkdownDescription: "The signature algorithm of the webhook", + Optional: true, + }, + "signature_prefix": schema.StringAttribute{ + MarkdownDescription: "The signature prefix of the webhook", + Optional: true, + }, + "request_identifier_path": schema.StringAttribute{ + MarkdownDescription: "The request identifier path of the webhook", + Optional: true, + }, + }, + }, + "mappings": schema.ListNestedAttribute{ + MarkdownDescription: "The mappings of the webhook", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "properties": schema.MapNestedAttribute{ + MarkdownDescription: "The properties of the mapping", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "blueprint": schema.StringAttribute{ + MarkdownDescription: "The blueprint of the mapping", + Required: true, + }, + "filter": schema.StringAttribute{ + MarkdownDescription: "The filter of the mapping", + Optional: true, + }, + "items_to_parser": schema.StringAttribute{ + MarkdownDescription: "The items to parser of the mapping", + Optional: true, + }, + "entity": schema.MapNestedAttribute{ + MarkdownDescription: "The entity of the mapping", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "identifier": schema.StringAttribute{ + MarkdownDescription: "The identifier of the entity", + Required: true, + }, + "title": schema.StringAttribute{ + MarkdownDescription: "The title of the entity", + Optional: true, + }, + "icon": schema.StringAttribute{ + MarkdownDescription: "The icon of the entity", + Optional: true, + }, + "team": schema.StringAttribute{ + MarkdownDescription: "The team of the entity", + Optional: true, + }, + "properties": schema.MapAttribute{ + MarkdownDescription: "The properties of the entity", + Optional: true, + ElementType: types.StringType, + }, + "relations": schema.MapAttribute{ + MarkdownDescription: "The relations of the entity", + Optional: true, + ElementType: types.StringType, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "created_at": schema.StringAttribute{ + MarkdownDescription: "The creation date of the webhook", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "created_by": schema.StringAttribute{ + MarkdownDescription: "The creator of the webhook", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "updated_at": schema.StringAttribute{ + MarkdownDescription: "The last update date of the webhook", + Computed: true, + }, + "updated_by": schema.StringAttribute{ + MarkdownDescription: "The last updater of the webhook", + Computed: true, + }, + } +} + +func (r *WebhookResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Entity resource", + Attributes: WebhookSchema(), + } +} diff --git a/port/webhook/webhookResourceToPortBody.go b/port/webhook/webhookResourceToPortBody.go new file mode 100644 index 00000000..91f158b2 --- /dev/null +++ b/port/webhook/webhookResourceToPortBody.go @@ -0,0 +1,61 @@ +package webhook + +import ( + "context" + + "github.com/port-labs/terraform-provider-port-labs/internal/cli" +) + +func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webhook, error) { + w := &cli.Webhook{ + Identifier: state.Identifier.ValueString(), + } + + if !state.Icon.IsNull() { + icon := state.Icon.ValueString() + w.Icon = &icon + } + + if !state.Title.IsNull() { + title := state.Title.ValueString() + w.Title = &title + } + + if !state.Description.IsNull() { + description := state.Description.ValueString() + w.Description = &description + } + + if !state.Enabled.IsNull() { + enabled := state.Enabled.ValueBool() + w.Enabled = &enabled + } + + if state.Security != nil { + w.Security = &cli.Security{} + if !state.Security.Secret.IsNull() { + secret := state.Security.Secret.ValueString() + w.Security.Secret = &secret + } + if !state.Security.SignatureHeaderName.IsNull() { + signatureHeaderName := state.Security.SignatureHeaderName.ValueString() + w.Security.SignatureHeaderName = &signatureHeaderName + } + if !state.Security.SignatureAlgorithm.IsNull() { + signatureAlgorithm := state.Security.SignatureAlgorithm.ValueString() + w.Security.SignatureAlgorithm = &signatureAlgorithm + } + if !state.Security.SignaturePrefix.IsNull() { + signaturePrefix := state.Security.SignaturePrefix.ValueString() + w.Security.SignaturePrefix = &signaturePrefix + } + + if !state.Security.RequestIdentifierPath.IsNull() { + requestIdentifierPath := state.Security.RequestIdentifierPath.ValueString() + w.Security.RequestIdentifierPath = &requestIdentifierPath + } + + } + + return w, nil +} diff --git a/provider/provider.go b/provider/provider.go index 19328322..a007ea4b 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -13,6 +13,7 @@ import ( "github.com/port-labs/terraform-provider-port-labs/port/action" "github.com/port-labs/terraform-provider-port-labs/port/blueprint" "github.com/port-labs/terraform-provider-port-labs/port/entity" + "github.com/port-labs/terraform-provider-port-labs/port/webhook" "github.com/port-labs/terraform-provider-port-labs/version" ) @@ -124,6 +125,7 @@ func (p *PortLabsProvider) Resources(ctx context.Context) []func() resource.Reso blueprint.NewBlueprintResource, entity.NewEntityResource, action.NewActionResource, + webhook.NewWebhookResource, } } From bd5522f955e881b2a19dca907fa094995e6e7f80 Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Thu, 10 Aug 2023 14:48:02 +0300 Subject: [PATCH 2/7] Finish create + read webhook resource --- internal/cli/models.go | 10 +-- port/webhook/model.go | 8 +- port/webhook/refreshWebhookState.go | 67 +++++++++++++++++ port/webhook/resource.go | 41 ++++------ port/webhook/schema.go | 92 ++++++++++------------- port/webhook/webhookResourceToPortBody.go | 63 +++++++++++++++- 6 files changed, 187 insertions(+), 94 deletions(-) diff --git a/internal/cli/models.go b/internal/cli/models.go index 41650963..7da6d33a 100644 --- a/internal/cli/models.go +++ b/internal/cli/models.go @@ -216,7 +216,7 @@ type ( } EntityProperty struct { - Identifier *string `json:"identifier,omitempty"` + Identifier string `json:"identifier,omitempty"` Title *string `json:"title,omitempty"` Icon *string `json:"icon,omitempty"` Team *string `json:"team,omitempty"` @@ -224,16 +224,12 @@ type ( Relations map[string]string `json:"relations,omitempty"` } - WebhookProperty struct { - Blueprint *string `json:"blueprint,omitempty"` + Mappings struct { + Blueprint string `json:"blueprint,omitempty"` Filter *string `json:"filter,omitempty"` ItemsToParse *string `json:"items_to_parse,omitempty"` Entity *EntityProperty `json:"entity,omitempty"` } - - Mappings struct { - Properties *WebhookProperty `json:"properties,omitempty"` - } ) type PortBody struct { diff --git a/port/webhook/model.go b/port/webhook/model.go index e24ab63c..033d432a 100644 --- a/port/webhook/model.go +++ b/port/webhook/model.go @@ -21,17 +21,13 @@ type EntityModel struct { Relations map[string]string `tfsdk:"relations"` } -type PropertiesModel struct { - Identifier types.String `tfsdk:"identifier"` +type MappingsModel struct { + Blueprint types.String `tfsdk:"blueprint"` Filter types.String `tfsdk:"filter"` ItemsToParse types.String `tfsdk:"items_to_parse"` Entity *EntityModel `tfsdk:"entity"` } -type MappingsModel struct { - Properties *PropertiesModel `tfsdk:"properties"` -} - type WebhookModel struct { ID types.String `tfsdk:"id"` Icon types.String `tfsdk:"icon"` diff --git a/port/webhook/refreshWebhookState.go b/port/webhook/refreshWebhookState.go index bfd3c3cf..6e20b204 100644 --- a/port/webhook/refreshWebhookState.go +++ b/port/webhook/refreshWebhookState.go @@ -31,5 +31,72 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo state.Enabled = types.BoolValue(*w.Enabled) } + if w.Security != nil { + state.Security = &SecurityModel{} + if w.Security.Secret != nil { + state.Security.Secret = types.StringValue(*w.Security.Secret) + } + if w.Security.SignatureHeaderName != nil { + state.Security.SignatureHeaderName = types.StringValue(*w.Security.SignatureHeaderName) + } + if w.Security.SignatureAlgorithm != nil { + state.Security.SignatureAlgorithm = types.StringValue(*w.Security.SignatureAlgorithm) + } + if w.Security.SignaturePrefix != nil { + state.Security.SignaturePrefix = types.StringValue(*w.Security.SignaturePrefix) + } + if w.Security.RequestIdentifierPath != nil { + state.Security.RequestIdentifierPath = types.StringValue(*w.Security.RequestIdentifierPath) + } + } + + if w.Mappings != nil { + state.Mappings = []MappingsModel{} + for _, v := range w.Mappings { + mapping := MappingsModel{ + Blueprint: types.StringValue(v.Blueprint), + Entity: &EntityModel{ + Identifier: types.StringValue(v.Entity.Identifier), + }, + } + + if v.Filter != nil { + mapping.Filter = types.StringValue(*v.Filter) + } + + if v.ItemsToParse != nil { + mapping.ItemsToParse = types.StringValue(*v.ItemsToParse) + } + + if v.Entity.Icon != nil { + mapping.Entity.Icon = types.StringValue(*v.Entity.Icon) + } + + if v.Entity.Title != nil { + mapping.Entity.Title = types.StringValue(*v.Entity.Title) + } + + if v.Entity.Team != nil { + mapping.Entity.Team = types.StringValue(*v.Entity.Team) + } + + if v.Entity.Properties != nil { + mapping.Entity.Properties = map[string]string{} + for k, v := range v.Entity.Properties { + mapping.Entity.Properties[k] = v + } + } + + if v.Entity.Relations != nil { + mapping.Entity.Relations = map[string]string{} + for k, v := range v.Entity.Relations { + mapping.Entity.Relations[k] = v + } + } + + state.Mappings = append(state.Mappings, mapping) + } + } + return nil } diff --git a/port/webhook/resource.go b/port/webhook/resource.go index eca8cbd9..5820e52d 100644 --- a/port/webhook/resource.go +++ b/port/webhook/resource.go @@ -81,12 +81,12 @@ func (r *WebhookResource) Create(ctx context.Context, req resource.CreateRequest return } - writeEntityComputedFieldsToState(state, wp) + writeWebhookComputedFieldsToState(state, wp) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } -func writeEntityComputedFieldsToState(state *WebhookModel, wp *cli.Webhook) { +func writeWebhookComputedFieldsToState(state *WebhookModel, wp *cli.Webhook) { state.ID = types.StringValue(wp.Identifier) state.Identifier = types.StringValue(wp.Identifier) state.CreatedAt = types.StringValue(wp.CreatedAt.String()) @@ -103,30 +103,19 @@ func (r *WebhookResource) Update(ctx context.Context, req resource.UpdateRequest return } - // bp, _, err := r.portClient.ReadBlueprint(ctx, state.Blueprint.ValueString()) - // if err != nil { - // resp.Diagnostics.AddError("failed to read blueprint", err.Error()) - // return - // } - - // e, err := entityResourceToBody(ctx, state, bp) - // if err != nil { - // resp.Diagnostics.AddError("failed to convert entity resource to body", err.Error()) - // return - // } - - // runID := "" - // if !state.RunID.IsNull() { - // runID = state.RunID.ValueString() - // } - - // en, err := r.portClient.CreateEntity(ctx, e, runID) - // if err != nil { - // resp.Diagnostics.AddError("failed to create entity", err.Error()) - // return - // } - - // writeEntityComputedFieldsToState(state, en) + w, err := webhookResourceToBody(ctx, state) + if err != nil { + resp.Diagnostics.AddError("failed to convert webhook resource to body", err.Error()) + return + } + + wp, err := r.portClient.UpdateWebhook(ctx, w.Identifier, w) + if err != nil { + resp.Diagnostics.AddError("failed to create webhook", err.Error()) + return + } + + writeWebhookComputedFieldsToState(state, wp) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/port/webhook/schema.go b/port/webhook/schema.go index ff8c8e1c..35c89c8a 100644 --- a/port/webhook/schema.go +++ b/port/webhook/schema.go @@ -70,57 +70,47 @@ func WebhookSchema() map[string]schema.Attribute { Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "properties": schema.MapNestedAttribute{ - MarkdownDescription: "The properties of the mapping", + "blueprint": schema.StringAttribute{ + MarkdownDescription: "The blueprint of the mapping", + Required: true, + }, + "filter": schema.StringAttribute{ + MarkdownDescription: "The filter of the mapping", + Optional: true, + }, + "items_to_parse": schema.StringAttribute{ + MarkdownDescription: "The items to parser of the mapping", Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "blueprint": schema.StringAttribute{ - MarkdownDescription: "The blueprint of the mapping", - Required: true, - }, - "filter": schema.StringAttribute{ - MarkdownDescription: "The filter of the mapping", - Optional: true, - }, - "items_to_parser": schema.StringAttribute{ - MarkdownDescription: "The items to parser of the mapping", - Optional: true, - }, - "entity": schema.MapNestedAttribute{ - MarkdownDescription: "The entity of the mapping", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "identifier": schema.StringAttribute{ - MarkdownDescription: "The identifier of the entity", - Required: true, - }, - "title": schema.StringAttribute{ - MarkdownDescription: "The title of the entity", - Optional: true, - }, - "icon": schema.StringAttribute{ - MarkdownDescription: "The icon of the entity", - Optional: true, - }, - "team": schema.StringAttribute{ - MarkdownDescription: "The team of the entity", - Optional: true, - }, - "properties": schema.MapAttribute{ - MarkdownDescription: "The properties of the entity", - Optional: true, - ElementType: types.StringType, - }, - "relations": schema.MapAttribute{ - MarkdownDescription: "The relations of the entity", - Optional: true, - ElementType: types.StringType, - }, - }, - }, - }, + }, + "entity": schema.SingleNestedAttribute{ + MarkdownDescription: "The entity of the mapping", + Required: true, + Attributes: map[string]schema.Attribute{ + "identifier": schema.StringAttribute{ + MarkdownDescription: "The identifier of the entity", + Required: true, + }, + "title": schema.StringAttribute{ + MarkdownDescription: "The title of the entity", + Optional: true, + }, + "icon": schema.StringAttribute{ + MarkdownDescription: "The icon of the entity", + Optional: true, + }, + "team": schema.StringAttribute{ + MarkdownDescription: "The team of the entity", + Optional: true, + }, + "properties": schema.MapAttribute{ + MarkdownDescription: "The properties of the entity", + Optional: true, + ElementType: types.StringType, + }, + "relations": schema.MapAttribute{ + MarkdownDescription: "The relations of the entity", + Optional: true, + ElementType: types.StringType, }, }, }, @@ -154,7 +144,7 @@ func WebhookSchema() map[string]schema.Attribute { func (r *WebhookResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Entity resource", + MarkdownDescription: "Webhook resource", Attributes: WebhookSchema(), } } diff --git a/port/webhook/webhookResourceToPortBody.go b/port/webhook/webhookResourceToPortBody.go index 91f158b2..2389dd6a 100644 --- a/port/webhook/webhookResourceToPortBody.go +++ b/port/webhook/webhookResourceToPortBody.go @@ -33,10 +33,10 @@ func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webho if state.Security != nil { w.Security = &cli.Security{} - if !state.Security.Secret.IsNull() { - secret := state.Security.Secret.ValueString() - w.Security.Secret = &secret - } + // if !state.Security.Secret.IsNull() { + // secret := state.Security.Secret.ValueString() + // w.Security.Secret = &secret + // } if !state.Security.SignatureHeaderName.IsNull() { signatureHeaderName := state.Security.SignatureHeaderName.ValueString() w.Security.SignatureHeaderName = &signatureHeaderName @@ -57,5 +57,60 @@ func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webho } + if len(state.Mappings) > 0 { + w.Mappings = []cli.Mappings{} + for _, v := range state.Mappings { + mapping := cli.Mappings{ + Blueprint: v.Blueprint.ValueString(), + Entity: &cli.EntityProperty{ + Identifier: v.Entity.Identifier.ValueString(), + }, + } + + if !v.Filter.IsNull() { + filter := v.Filter.ValueString() + mapping.Filter = &filter + } + + if !v.ItemsToParse.IsNull() { + ItemsToParse := v.ItemsToParse.ValueString() + mapping.ItemsToParse = &ItemsToParse + } + + if !v.Entity.Icon.IsNull() { + icon := v.Entity.Icon.ValueString() + mapping.Entity.Icon = &icon + } + + if !v.Entity.Title.IsNull() { + title := v.Entity.Title.ValueString() + mapping.Entity.Title = &title + } + + if !v.Entity.Team.IsNull() { + team := v.Entity.Team.ValueString() + mapping.Entity.Team = &team + } + + if v.Entity.Properties != nil { + properties := make(map[string]string) + for k, v := range v.Entity.Properties { + properties[k] = v + } + mapping.Entity.Properties = properties + } + + if v.Entity.Relations != nil { + relations := make(map[string]string) + for k, v := range v.Entity.Relations { + relations[k] = v + } + mapping.Entity.Relations = relations + } + + w.Mappings = append(w.Mappings, mapping) + } + } + return w, nil } From 31dce480274cb769052d9a259e5b80f6952d7628 Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Thu, 10 Aug 2023 17:55:56 +0300 Subject: [PATCH 3/7] Add tests --- port/webhook/refreshWebhookState.go | 11 +- port/webhook/resource_test.go | 200 ++++++++++++++++++++++ port/webhook/webhookResourceToPortBody.go | 12 +- 3 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 port/webhook/resource_test.go diff --git a/port/webhook/refreshWebhookState.go b/port/webhook/refreshWebhookState.go index 6e20b204..856bc813 100644 --- a/port/webhook/refreshWebhookState.go +++ b/port/webhook/refreshWebhookState.go @@ -32,7 +32,9 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo } if w.Security != nil { - state.Security = &SecurityModel{} + if state.Security == nil { + state.Security = &SecurityModel{} + } if w.Security.Secret != nil { state.Security.Secret = types.StringValue(*w.Security.Secret) } @@ -50,10 +52,10 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo } } - if w.Mappings != nil { + if len(w.Mappings) > 0 { state.Mappings = []MappingsModel{} for _, v := range w.Mappings { - mapping := MappingsModel{ + mapping := &MappingsModel{ Blueprint: types.StringValue(v.Blueprint), Entity: &EntityModel{ Identifier: types.StringValue(v.Entity.Identifier), @@ -93,8 +95,7 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo mapping.Entity.Relations[k] = v } } - - state.Mappings = append(state.Mappings, mapping) + state.Mappings = append(state.Mappings, *mapping) } } diff --git a/port/webhook/resource_test.go b/port/webhook/resource_test.go new file mode 100644 index 00000000..fa72fb92 --- /dev/null +++ b/port/webhook/resource_test.go @@ -0,0 +1,200 @@ +package webhook_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/port-labs/terraform-provider-port-labs/internal/acctest" + "github.com/port-labs/terraform-provider-port-labs/internal/utils" +) + +func testAccCreateBlueprintConfig(identifier string) string { + return fmt.Sprintf(` + resource "port_blueprint" "microservice" { + title = "TF test microservice" + icon = "Terraform" + identifier = "%s" + properties = { + string_props = { + "author" = { + type = "string" + title = "text" + } + "url" = { + type = "string" + title = "text" + } + } + } + } + `, identifier) +} + +func TestAccPortWebhookBasic(t *testing.T) { + identifier := utils.GenID() + webhookIdentifier := utils.GenID() + var testAccActionConfigCreate = testAccCreateBlueprintConfig(identifier) + fmt.Sprintf(` + resource "port_webhook" "create_pr" { + identifier = "%s" + title = "Test" + icon = "Terraform" + enabled = true + }`, webhookIdentifier) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: acctest.ProviderConfig + testAccActionConfigCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("port_webhook.create_pr", "identifier", webhookIdentifier), + resource.TestCheckResourceAttr("port_webhook.create_pr", "title", "Test"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "icon", "Terraform"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "enabled", "true"), + ), + }, + }, + }) +} + +func TestAccPortWebhook(t *testing.T) { + identifier := utils.GenID() + webhookIdentifier := utils.GenID() + var testAccActionConfigCreate = testAccCreateBlueprintConfig(identifier) + fmt.Sprintf(` + resource "port_webhook" "create_pr" { + identifier = "%s" + title = "Test" + icon = "Terraform" + enabled = true + security = { + secret = "test" + signature_header_name = "X-Hub-Signature-256" + signature_algorithm = "sha256" + signature_prefix = "sha256=" + request_identifier_path = "body.repository.full_name" + } + mappings = [ + { + "blueprint" = "pullRequest", + "filter" = ".headers.\"X-GitHub-Event\" == \"pull_request\"", + "entity" = { + "identifier" = ".body.pull_request.id | tostring", + "title" = ".body.pull_request.title", + "icon" = "Terraform", + "team" = "port", + "properties" = { + "author" = ".body.pull_request.user.login", + "url" = ".body.pull_request.html_url" + } + } + } + ] + lifecycle { + ignore_changes = [ + security.secret + ] + } + }`, webhookIdentifier) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: acctest.ProviderConfig + testAccActionConfigCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("port_webhook.create_pr", "identifier", webhookIdentifier), + resource.TestCheckResourceAttr("port_webhook.create_pr", "title", "Test"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "icon", "Terraform"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "enabled", "true"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_header_name", "X-Hub-Signature-256"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_algorithm", "sha256"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_prefix", "sha256="), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.request_identifier_path", "body.repository.full_name"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", "pullRequest"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.filter", ".headers.\"X-GitHub-Event\" == \"pull_request\""), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.identifier", ".body.pull_request.id | tostring"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.title", ".body.pull_request.title"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.icon", "Terraform"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.team", "port"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.properties.author", ".body.pull_request.user.login"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.properties.url", ".body.pull_request.html_url"), + ), + }, + }, + }) +} + +func TestAccPortWebhookImport(t *testing.T) { + identifier := utils.GenID() + webhookIdentifier := utils.GenID() + var testAccActionConfigCreate = testAccCreateBlueprintConfig(identifier) + fmt.Sprintf(` + resource "port_webhook" "create_pr" { + identifier = "%s" + title = "Test" + icon = "Terraform" + enabled = true + security = { + signature_header_name = "X-Hub-Signature-256" + signature_algorithm = "sha256" + signature_prefix = "sha256=" + request_identifier_path = "body.repository.full_name" + } + mappings = [ + { + "blueprint" = "pullRequest", + "filter" = ".headers.\"X-GitHub-Event\" == \"pull_request\"", + "entity" = { + "identifier" = ".body.pull_request.id | tostring", + "title" = ".body.pull_request.title", + "icon" = "Terraform", + "team" = "port", + "properties" = { + "author" = ".body.pull_request.user.login", + "url" = ".body.pull_request.html_url" + } + } + } + ] + lifecycle { + ignore_changes = [ + security.secret + ] + } + }`, webhookIdentifier) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: acctest.ProviderConfig + testAccActionConfigCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("port_webhook.create_pr", "identifier", webhookIdentifier), + resource.TestCheckResourceAttr("port_webhook.create_pr", "title", "Test"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "icon", "Terraform"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "enabled", "true"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_header_name", "X-Hub-Signature-256"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_algorithm", "sha256"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_prefix", "sha256="), + resource.TestCheckResourceAttr("port_webhook.create_pr", "security.request_identifier_path", "body.repository.full_name"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", "pullRequest"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.filter", ".headers.\"X-GitHub-Event\" == \"pull_request\""), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.identifier", ".body.pull_request.id | tostring"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.title", ".body.pull_request.title"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.icon", "Terraform"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.team", "port"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.properties.author", ".body.pull_request.user.login"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.properties.url", ".body.pull_request.html_url"), + ), + }, + { + ResourceName: "port_webhook.create_pr", + ImportState: true, + ImportStateVerify: true, + ImportStateId: webhookIdentifier, + }, + }, + }) +} diff --git a/port/webhook/webhookResourceToPortBody.go b/port/webhook/webhookResourceToPortBody.go index 2389dd6a..e447e25f 100644 --- a/port/webhook/webhookResourceToPortBody.go +++ b/port/webhook/webhookResourceToPortBody.go @@ -9,6 +9,7 @@ import ( func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webhook, error) { w := &cli.Webhook{ Identifier: state.Identifier.ValueString(), + Security: &cli.Security{}, } if !state.Icon.IsNull() { @@ -30,13 +31,11 @@ func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webho enabled := state.Enabled.ValueBool() w.Enabled = &enabled } - if state.Security != nil { - w.Security = &cli.Security{} - // if !state.Security.Secret.IsNull() { - // secret := state.Security.Secret.ValueString() - // w.Security.Secret = &secret - // } + if !state.Security.Secret.IsNull() { + secret := state.Security.Secret.ValueString() + w.Security.Secret = &secret + } if !state.Security.SignatureHeaderName.IsNull() { signatureHeaderName := state.Security.SignatureHeaderName.ValueString() w.Security.SignatureHeaderName = &signatureHeaderName @@ -54,7 +53,6 @@ func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webho requestIdentifierPath := state.Security.RequestIdentifierPath.ValueString() w.Security.RequestIdentifierPath = &requestIdentifierPath } - } if len(state.Mappings) > 0 { From 0fe40ae4477009aae11947693cfd5aa416057a57 Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Fri, 11 Aug 2023 11:59:24 +0300 Subject: [PATCH 4/7] Add docs of webhook --- docs/index.md | 4 +- docs/resources/port_webhook.md | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 docs/resources/port_webhook.md diff --git a/docs/index.md b/docs/index.md index 58359ca4..e94e382f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,5 +21,7 @@ description: |- ### Optional -- `base_url` (String) Base URL for Port-labs (environment: `PORT_BASE_URL`) +- `base_url` (String) +- `client_id` (String) Client ID for Port-labsß +- `secret` (String, Sensitive) Client Secret for Port-labs - `token` (String, Sensitive) Token for Port-labs diff --git a/docs/resources/port_webhook.md b/docs/resources/port_webhook.md new file mode 100644 index 00000000..afbd2d35 --- /dev/null +++ b/docs/resources/port_webhook.md @@ -0,0 +1,77 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "port_webhook Resource - terraform-provider-port-labs" +subcategory: "" +description: |- + Webhook resource +--- + +# port_webhook (Resource) + +Webhook resource + + + + +## Schema + +### Optional + +- `description` (String) The description of the webhook +- `enabled` (Boolean) Whether the webhook is enabled +- `icon` (String) The icon of the webhook +- `identifier` (String) The identifier of the webhook +- `mappings` (Attributes List) The mappings of the webhook (see [below for nested schema](#nestedatt--mappings)) +- `security` (Attributes) The security of the webhook (see [below for nested schema](#nestedatt--security)) +- `title` (String) The title of the webhook + +### Read-Only + +- `created_at` (String) The creation date of the webhook +- `created_by` (String) The creator of the webhook +- `id` (String) The ID of this resource. +- `updated_at` (String) The last update date of the webhook +- `updated_by` (String) The last updater of the webhook + + +### Nested Schema for `mappings` + +Required: + +- `blueprint` (String) The blueprint of the mapping +- `entity` (Attributes) The entity of the mapping (see [below for nested schema](#nestedatt--mappings--entity)) + +Optional: + +- `filter` (String) The filter of the mapping +- `items_to_parse` (String) The items to parser of the mapping + + +### Nested Schema for `mappings.entity` + +Required: + +- `identifier` (String) The identifier of the entity + +Optional: + +- `icon` (String) The icon of the entity +- `properties` (Map of String) The properties of the entity +- `relations` (Map of String) The relations of the entity +- `team` (String) The team of the entity +- `title` (String) The title of the entity + + + + +### Nested Schema for `security` + +Optional: + +- `request_identifier_path` (String) The request identifier path of the webhook +- `secret` (String) The secret of the webhook +- `signature_algorithm` (String) The signature algorithm of the webhook +- `signature_header_name` (String) The signature header name of the webhook +- `signature_prefix` (String) The signature prefix of the webhook + + From dd31c05a9333fd778c2304e6f033cd188a26dda7 Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Sun, 13 Aug 2023 16:44:13 +0300 Subject: [PATCH 5/7] Fix tests --- port/webhook/refreshWebhookState.go | 72 ++++++++--------------------- port/webhook/resource_test.go | 18 +++++--- 2 files changed, 30 insertions(+), 60 deletions(-) diff --git a/port/webhook/refreshWebhookState.go b/port/webhook/refreshWebhookState.go index 856bc813..d7e5b9c6 100644 --- a/port/webhook/refreshWebhookState.go +++ b/port/webhook/refreshWebhookState.go @@ -5,6 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/port-labs/terraform-provider-port-labs/internal/cli" + "github.com/port-labs/terraform-provider-port-labs/internal/flex" ) func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhook) error { @@ -14,41 +15,18 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo state.CreatedBy = types.StringValue(w.CreatedBy) state.UpdatedAt = types.StringValue(w.UpdatedAt.String()) state.UpdatedBy = types.StringValue(w.UpdatedBy) - - if w.Icon != nil { - state.Icon = types.StringValue(*w.Icon) - } - - if w.Title != nil { - state.Title = types.StringValue(*w.Title) - } - - if w.Description != nil { - state.Description = types.StringValue(*w.Description) - } - - if w.Enabled != nil { - state.Enabled = types.BoolValue(*w.Enabled) - } - - if w.Security != nil { - if state.Security == nil { - state.Security = &SecurityModel{} - } - if w.Security.Secret != nil { - state.Security.Secret = types.StringValue(*w.Security.Secret) - } - if w.Security.SignatureHeaderName != nil { - state.Security.SignatureHeaderName = types.StringValue(*w.Security.SignatureHeaderName) - } - if w.Security.SignatureAlgorithm != nil { - state.Security.SignatureAlgorithm = types.StringValue(*w.Security.SignatureAlgorithm) - } - if w.Security.SignaturePrefix != nil { - state.Security.SignaturePrefix = types.StringValue(*w.Security.SignaturePrefix) - } - if w.Security.RequestIdentifierPath != nil { - state.Security.RequestIdentifierPath = types.StringValue(*w.Security.RequestIdentifierPath) + state.Icon = flex.GoStringToFramework(w.Icon) + state.Title = flex.GoStringToFramework(w.Title) + state.Description = flex.GoStringToFramework(w.Description) + state.Enabled = flex.GoBoolToFramework(w.Enabled) + + if w.Security.RequestIdentifierPath != nil || w.Security.Secret != nil || w.Security.SignatureHeaderName != nil || w.Security.SignatureAlgorithm != nil || w.Security.SignaturePrefix != nil { + state.Security = &SecurityModel{ + Secret: flex.GoStringToFramework(w.Security.Secret), + SignatureHeaderName: flex.GoStringToFramework(w.Security.SignatureHeaderName), + SignatureAlgorithm: flex.GoStringToFramework(w.Security.SignatureAlgorithm), + SignaturePrefix: flex.GoStringToFramework(w.Security.SignaturePrefix), + RequestIdentifierPath: flex.GoStringToFramework(w.Security.RequestIdentifierPath), } } @@ -62,25 +40,11 @@ func refreshWebhookState(ctx context.Context, state *WebhookModel, w *cli.Webhoo }, } - if v.Filter != nil { - mapping.Filter = types.StringValue(*v.Filter) - } - - if v.ItemsToParse != nil { - mapping.ItemsToParse = types.StringValue(*v.ItemsToParse) - } - - if v.Entity.Icon != nil { - mapping.Entity.Icon = types.StringValue(*v.Entity.Icon) - } - - if v.Entity.Title != nil { - mapping.Entity.Title = types.StringValue(*v.Entity.Title) - } - - if v.Entity.Team != nil { - mapping.Entity.Team = types.StringValue(*v.Entity.Team) - } + mapping.Filter = flex.GoStringToFramework(v.Filter) + mapping.ItemsToParse = flex.GoStringToFramework(v.ItemsToParse) + mapping.Entity.Icon = flex.GoStringToFramework(v.Entity.Icon) + mapping.Entity.Title = flex.GoStringToFramework(v.Entity.Title) + mapping.Entity.Team = flex.GoStringToFramework(v.Entity.Team) if v.Entity.Properties != nil { mapping.Entity.Properties = map[string]string{} diff --git a/port/webhook/resource_test.go b/port/webhook/resource_test.go index fa72fb92..6447dabe 100644 --- a/port/webhook/resource_test.go +++ b/port/webhook/resource_test.go @@ -76,7 +76,7 @@ func TestAccPortWebhook(t *testing.T) { } mappings = [ { - "blueprint" = "pullRequest", + "blueprint" = "%s", "filter" = ".headers.\"X-GitHub-Event\" == \"pull_request\"", "entity" = { "identifier" = ".body.pull_request.id | tostring", @@ -95,7 +95,10 @@ func TestAccPortWebhook(t *testing.T) { security.secret ] } - }`, webhookIdentifier) + depends_on = [ + port_blueprint.microservice + ] + }`, webhookIdentifier, identifier) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -112,7 +115,7 @@ func TestAccPortWebhook(t *testing.T) { resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_algorithm", "sha256"), resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_prefix", "sha256="), resource.TestCheckResourceAttr("port_webhook.create_pr", "security.request_identifier_path", "body.repository.full_name"), - resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", "pullRequest"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", identifier), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.filter", ".headers.\"X-GitHub-Event\" == \"pull_request\""), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.identifier", ".body.pull_request.id | tostring"), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.title", ".body.pull_request.title"), @@ -143,7 +146,7 @@ func TestAccPortWebhookImport(t *testing.T) { } mappings = [ { - "blueprint" = "pullRequest", + "blueprint" = "%s", "filter" = ".headers.\"X-GitHub-Event\" == \"pull_request\"", "entity" = { "identifier" = ".body.pull_request.id | tostring", @@ -162,7 +165,10 @@ func TestAccPortWebhookImport(t *testing.T) { security.secret ] } - }`, webhookIdentifier) + depends_on = [ + port_blueprint.microservice + ] + }`, webhookIdentifier, identifier) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -179,7 +185,7 @@ func TestAccPortWebhookImport(t *testing.T) { resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_algorithm", "sha256"), resource.TestCheckResourceAttr("port_webhook.create_pr", "security.signature_prefix", "sha256="), resource.TestCheckResourceAttr("port_webhook.create_pr", "security.request_identifier_path", "body.repository.full_name"), - resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", "pullRequest"), + resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.blueprint", identifier), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.filter", ".headers.\"X-GitHub-Event\" == \"pull_request\""), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.identifier", ".body.pull_request.id | tostring"), resource.TestCheckResourceAttr("port_webhook.create_pr", "mappings.0.entity.title", ".body.pull_request.title"), From 75ac34fc305cc4f39bde2dfce6ce486fb6cbc10c Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Sun, 13 Aug 2023 18:23:22 +0300 Subject: [PATCH 6/7] Fix renames + Fix schema order + fix docs --- docs/index.md | 5 +- port/webhook/resource.go | 6 +- port/webhook/schema.go | 145 ++++++++++++---------- port/webhook/webhookResourceToPortBody.go | 2 +- 4 files changed, 82 insertions(+), 76 deletions(-) diff --git a/docs/index.md b/docs/index.md index e94e382f..a1fe66ac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,4 @@ description: |- ### Optional -- `base_url` (String) -- `client_id` (String) Client ID for Port-labsß -- `secret` (String, Sensitive) Client Secret for Port-labs -- `token` (String, Sensitive) Token for Port-labs +- `base_url` (String) Base URL for Port-labs (environment: `PORT_BASE_URL`) diff --git a/port/webhook/resource.go b/port/webhook/resource.go index 5820e52d..e63bac69 100644 --- a/port/webhook/resource.go +++ b/port/webhook/resource.go @@ -69,7 +69,7 @@ func (r *WebhookResource) Create(ctx context.Context, req resource.CreateRequest return } - w, err := webhookResourceToBody(ctx, state) + w, err := webhookResourceToPortBody(ctx, state) if err != nil { resp.Diagnostics.AddError("failed to convert webhook resource to body", err.Error()) return @@ -103,7 +103,7 @@ func (r *WebhookResource) Update(ctx context.Context, req resource.UpdateRequest return } - w, err := webhookResourceToBody(ctx, state) + w, err := webhookResourceToPortBody(ctx, state) if err != nil { resp.Diagnostics.AddError("failed to convert webhook resource to body", err.Error()) return @@ -111,7 +111,7 @@ func (r *WebhookResource) Update(ctx context.Context, req resource.UpdateRequest wp, err := r.portClient.UpdateWebhook(ctx, w.Identifier, w) if err != nil { - resp.Diagnostics.AddError("failed to create webhook", err.Error()) + resp.Diagnostics.AddError("failed to update the webhook", err.Error()) return } diff --git a/port/webhook/schema.go b/port/webhook/schema.go index 35c89c8a..79e4e974 100644 --- a/port/webhook/schema.go +++ b/port/webhook/schema.go @@ -11,6 +11,80 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +func WebhookSecuritySchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "secret": schema.StringAttribute{ + MarkdownDescription: "The secret of the webhook", + Optional: true, + }, + "signature_header_name": schema.StringAttribute{ + MarkdownDescription: "The signature header name of the webhook", + Optional: true, + }, + "signature_algorithm": schema.StringAttribute{ + MarkdownDescription: "The signature algorithm of the webhook", + Optional: true, + }, + "signature_prefix": schema.StringAttribute{ + MarkdownDescription: "The signature prefix of the webhook", + Optional: true, + }, + "request_identifier_path": schema.StringAttribute{ + MarkdownDescription: "The request identifier path of the webhook", + Optional: true, + }, + } +} + +func WebhookMappingSchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "blueprint": schema.StringAttribute{ + MarkdownDescription: "The blueprint of the mapping", + Required: true, + }, + "filter": schema.StringAttribute{ + MarkdownDescription: "The filter of the mapping", + Optional: true, + }, + "items_to_parse": schema.StringAttribute{ + MarkdownDescription: "The items to parser of the mapping", + Optional: true, + }, + "entity": schema.SingleNestedAttribute{ + MarkdownDescription: "The entity of the mapping", + Required: true, + Attributes: map[string]schema.Attribute{ + "identifier": schema.StringAttribute{ + MarkdownDescription: "The identifier of the entity", + Required: true, + }, + "title": schema.StringAttribute{ + MarkdownDescription: "The title of the entity", + Optional: true, + }, + "icon": schema.StringAttribute{ + MarkdownDescription: "The icon of the entity", + Optional: true, + }, + "team": schema.StringAttribute{ + MarkdownDescription: "The team of the entity", + Optional: true, + }, + "properties": schema.MapAttribute{ + MarkdownDescription: "The properties of the entity", + Optional: true, + ElementType: types.StringType, + }, + "relations": schema.MapAttribute{ + MarkdownDescription: "The relations of the entity", + Optional: true, + ElementType: types.StringType, + }, + }, + }, + } +} + func WebhookSchema() map[string]schema.Attribute { return map[string]schema.Attribute{ "id": schema.StringAttribute{ @@ -42,79 +116,14 @@ func WebhookSchema() map[string]schema.Attribute { "security": schema.SingleNestedAttribute{ MarkdownDescription: "The security of the webhook", Optional: true, - Attributes: map[string]schema.Attribute{ - "secret": schema.StringAttribute{ - MarkdownDescription: "The secret of the webhook", - Optional: true, - }, - "signature_header_name": schema.StringAttribute{ - MarkdownDescription: "The signature header name of the webhook", - Optional: true, - }, - "signature_algorithm": schema.StringAttribute{ - MarkdownDescription: "The signature algorithm of the webhook", - Optional: true, - }, - "signature_prefix": schema.StringAttribute{ - MarkdownDescription: "The signature prefix of the webhook", - Optional: true, - }, - "request_identifier_path": schema.StringAttribute{ - MarkdownDescription: "The request identifier path of the webhook", - Optional: true, - }, - }, + Attributes: WebhookSecuritySchema(), }, + "mappings": schema.ListNestedAttribute{ MarkdownDescription: "The mappings of the webhook", Optional: true, NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "blueprint": schema.StringAttribute{ - MarkdownDescription: "The blueprint of the mapping", - Required: true, - }, - "filter": schema.StringAttribute{ - MarkdownDescription: "The filter of the mapping", - Optional: true, - }, - "items_to_parse": schema.StringAttribute{ - MarkdownDescription: "The items to parser of the mapping", - Optional: true, - }, - "entity": schema.SingleNestedAttribute{ - MarkdownDescription: "The entity of the mapping", - Required: true, - Attributes: map[string]schema.Attribute{ - "identifier": schema.StringAttribute{ - MarkdownDescription: "The identifier of the entity", - Required: true, - }, - "title": schema.StringAttribute{ - MarkdownDescription: "The title of the entity", - Optional: true, - }, - "icon": schema.StringAttribute{ - MarkdownDescription: "The icon of the entity", - Optional: true, - }, - "team": schema.StringAttribute{ - MarkdownDescription: "The team of the entity", - Optional: true, - }, - "properties": schema.MapAttribute{ - MarkdownDescription: "The properties of the entity", - Optional: true, - ElementType: types.StringType, - }, - "relations": schema.MapAttribute{ - MarkdownDescription: "The relations of the entity", - Optional: true, - ElementType: types.StringType, - }, - }, - }, - }, + Attributes: WebhookMappingSchema(), }, }, "created_at": schema.StringAttribute{ diff --git a/port/webhook/webhookResourceToPortBody.go b/port/webhook/webhookResourceToPortBody.go index e447e25f..ab0f0e4e 100644 --- a/port/webhook/webhookResourceToPortBody.go +++ b/port/webhook/webhookResourceToPortBody.go @@ -6,7 +6,7 @@ import ( "github.com/port-labs/terraform-provider-port-labs/internal/cli" ) -func webhookResourceToBody(ctx context.Context, state *WebhookModel) (*cli.Webhook, error) { +func webhookResourceToPortBody(ctx context.Context, state *WebhookModel) (*cli.Webhook, error) { w := &cli.Webhook{ Identifier: state.Identifier.ValueString(), Security: &cli.Security{}, From b7bef3bd51cba21518553130324ed9f9823644bd Mon Sep 17 00:00:00 2001 From: dvirsegev Date: Mon, 14 Aug 2023 09:28:14 +0300 Subject: [PATCH 7/7] Update code owners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 077e2529..01bc4a96 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@talsabagport @danielsinai @dvirsegev @matarpeles \ No newline at end of file +@talsabagport @danielsinai @dvirsegev @matarpeles @pazhersh @MPTG94 \ No newline at end of file