diff --git a/docs/resources/integration_zendesk.md b/docs/resources/integration_zendesk.md
new file mode 100644
index 0000000..e667ef4
--- /dev/null
+++ b/docs/resources/integration_zendesk.md
@@ -0,0 +1,87 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "mondoo_integration_zendesk Resource - terraform-provider-mondoo"
+subcategory: ""
+description: |-
+ Zendesk integration to keep track of security tasks and add Zendesk tickets directly from within the Mondoo Console.
+---
+
+# mondoo_integration_zendesk (Resource)
+
+Zendesk integration to keep track of security tasks and add Zendesk tickets directly from within the Mondoo Console.
+
+## Example Usage
+
+```terraform
+variable "zendesk_token" {
+ description = "The GitHub Token"
+ type = string
+ sensitive = true
+}
+
+provider "mondoo" {
+ space = "hungry-poet-123456"
+}
+
+# Setup the zendesk integration
+resource "mondoo_integration_zendesk" "zendesk_integration" {
+ name = "My Zendesk Integration"
+ subdomain = "your-subdomain"
+ email = "zendeskowner@email.com"
+
+ custom_fields = [
+ {
+ id = "123456"
+ value = "custom_value_1"
+ },
+ {
+ id = "123457"
+ value = "custom_value_2"
+ }
+ ]
+
+ auto_create = true
+ auto_close = true
+
+ credentials = {
+ token = var.zendesk_token
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials))
+- `email` (String) Zendesk email.
+- `name` (String) Name of the integration.
+- `subdomain` (String) Zendesk subdomain.
+
+### Optional
+
+- `auto_close` (Boolean) Automatically close tickets.
+- `auto_create` (Boolean) Automatically create tickets.
+- `custom_fields` (Attributes List) Custom fields to be added to the Zendesk ticket. (see [below for nested schema](#nestedatt--custom_fields))
+- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used.
+
+### Read-Only
+
+- `mrn` (String) Integration identifier
+
+
+### Nested Schema for `credentials`
+
+Required:
+
+- `token` (String, Sensitive) Token for GitHub integration.
+
+
+
+### Nested Schema for `custom_fields`
+
+Required:
+
+- `id` (Number) Custom field ID.
+- `value` (String) Custom field value.
diff --git a/examples/resources/mondoo_integration_zendesk/main.tf b/examples/resources/mondoo_integration_zendesk/main.tf
new file mode 100644
index 0000000..24d24a1
--- /dev/null
+++ b/examples/resources/mondoo_integration_zendesk/main.tf
@@ -0,0 +1,8 @@
+terraform {
+ required_providers {
+ mondoo = {
+ source = "mondoohq/mondoo"
+ version = ">= 0.19"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/resources/mondoo_integration_zendesk/resource.tf b/examples/resources/mondoo_integration_zendesk/resource.tf
new file mode 100644
index 0000000..fa25048
--- /dev/null
+++ b/examples/resources/mondoo_integration_zendesk/resource.tf
@@ -0,0 +1,34 @@
+variable "zendesk_token" {
+ description = "The GitHub Token"
+ type = string
+ sensitive = true
+}
+
+provider "mondoo" {
+ space = "hungry-poet-123456"
+}
+
+# Setup the zendesk integration
+resource "mondoo_integration_zendesk" "zendesk_integration" {
+ name = "My Zendesk Integration"
+ subdomain = "your-subdomain"
+ email = "zendeskowner@email.com"
+
+ custom_fields = [
+ {
+ id = "123456"
+ value = "custom_value_1"
+ },
+ {
+ id = "123457"
+ value = "custom_value_2"
+ }
+ ]
+
+ auto_create = true
+ auto_close = true
+
+ credentials = {
+ token = var.zendesk_token
+ }
+}
\ No newline at end of file
diff --git a/internal/provider/gql.go b/internal/provider/gql.go
index 4454e72..6ea0c55 100644
--- a/internal/provider/gql.go
+++ b/internal/provider/gql.go
@@ -617,6 +617,19 @@ type ShodanConfigurationOptions struct {
Targets []string
}
+type ZendeskConfigurationOptions struct {
+ Subdomain string
+ Email string
+ AutoCloseTickets bool
+ AutoCreateTickets bool
+ CustomFields []ZendeskCustomField
+}
+
+type ZendeskCustomField struct {
+ ID int64
+ Value string
+}
+
type JiraConfigurationOptions struct {
Host string
Email string
@@ -647,6 +660,7 @@ type ClientIntegrationConfigurationOptions struct {
GithubConfigurationOptions GithubConfigurationOptions `graphql:"... on GithubConfigurationOptions"`
HostedAwsConfigurationOptions HostedAwsConfigurationOptions `graphql:"... on HostedAwsConfigurationOptions"`
ShodanConfigurationOptions ShodanConfigurationOptions `graphql:"... on ShodanConfigurationOptions"`
+ ZendeskConfigurationOptions ZendeskConfigurationOptions `graphql:"... on ZendeskConfigurationOptions"`
JiraConfigurationOptions JiraConfigurationOptions `graphql:"... on JiraConfigurationOptions"`
EmailConfigurationOptions EmailConfigurationOptions `graphql:"... on EmailConfigurationOptions"`
GitlabConfigurationOptions GitlabConfigurationOptions `graphql:"... on GitlabConfigurationOptions"`
diff --git a/internal/provider/integration_zendesk_resource.go b/internal/provider/integration_zendesk_resource.go
new file mode 100644
index 0000000..e31823b
--- /dev/null
+++ b/internal/provider/integration_zendesk_resource.go
@@ -0,0 +1,329 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "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/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ mondoov1 "go.mondoo.com/mondoo-go"
+)
+
+var _ resource.Resource = (*integrationZendeskResource)(nil)
+
+func NewIntegrationZendeskResource() resource.Resource {
+ return &integrationZendeskResource{}
+}
+
+type integrationZendeskResource struct {
+ client *ExtendedGqlClient
+}
+
+type integrationZendeskResourceModel struct {
+ // scope
+ SpaceID types.String `tfsdk:"space_id"`
+
+ // integration details
+ Mrn types.String `tfsdk:"mrn"`
+ Name types.String `tfsdk:"name"`
+ Subdomain types.String `tfsdk:"subdomain"`
+ Email types.String `tfsdk:"email"`
+
+ // (Optional.)
+ AutoClose types.Bool `tfsdk:"auto_close"`
+ AutoCreate types.Bool `tfsdk:"auto_create"`
+ CustomFields *[]integrationZendeskCustomFieldModel `tfsdk:"custom_fields"`
+
+ // credentials
+ Credential *integrationZendeskCredentialModel `tfsdk:"credentials"`
+}
+
+type integrationZendeskCustomFieldModel struct {
+ ID types.Int64 `tfsdk:"id"`
+ Value types.String `tfsdk:"value"`
+}
+
+type integrationZendeskCredentialModel struct {
+ Token types.String `tfsdk:"token"`
+}
+
+func (m integrationZendeskResourceModel) GetConfigurationOptions() *mondoov1.ZendeskConfigurationOptionsInput {
+ opts := &mondoov1.ZendeskConfigurationOptionsInput{
+ Subdomain: mondoov1.String(m.Subdomain.ValueString()),
+ Email: mondoov1.String(m.Email.ValueString()),
+ AutoCloseTickets: mondoov1.Boolean(m.AutoClose.ValueBool()),
+ AutoCreateTickets: mondoov1.Boolean(m.AutoCreate.ValueBool()),
+ CustomFields: convertCustomFields(m.CustomFields),
+ APIToken: mondoov1.String(m.Credential.Token.ValueString()),
+ }
+
+ return opts
+}
+
+func (r *integrationZendeskResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_integration_zendesk"
+}
+
+func (r *integrationZendeskResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: `Zendesk integration to keep track of security tasks and add Zendesk tickets directly from within the Mondoo Console.`,
+ Attributes: map[string]schema.Attribute{
+ "space_id": schema.StringAttribute{
+ MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "mrn": schema.StringAttribute{
+ Computed: true,
+ MarkdownDescription: "Integration identifier",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Name of the integration.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtMost(250),
+ },
+ },
+ "subdomain": schema.StringAttribute{
+ MarkdownDescription: "Zendesk subdomain.",
+ Required: true,
+ },
+ "email": schema.StringAttribute{
+ MarkdownDescription: "Zendesk email.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`),
+ "must be a valid email",
+ ),
+ },
+ },
+ "auto_close": schema.BoolAttribute{
+ MarkdownDescription: "Automatically close tickets.",
+ Optional: true,
+ },
+ "auto_create": schema.BoolAttribute{
+ MarkdownDescription: "Automatically create tickets.",
+ Optional: true,
+ },
+ "custom_fields": schema.ListNestedAttribute{
+ MarkdownDescription: "Custom fields to be added to the Zendesk ticket.",
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.Int64Attribute{
+ MarkdownDescription: "Custom field ID.",
+ Required: true,
+ },
+ "value": schema.StringAttribute{
+ MarkdownDescription: "Custom field value.",
+ Required: true,
+ },
+ },
+ },
+ },
+ "credentials": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "token": schema.StringAttribute{
+ MarkdownDescription: "Token for GitHub integration.",
+ Required: true,
+ Sensitive: true,
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *integrationZendeskResource) 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.(*ExtendedGqlClient)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = client
+}
+
+func convertCustomFields(recipients *[]integrationZendeskCustomFieldModel) *[]mondoov1.ZendeskCustomFieldInput {
+ if recipients == nil {
+ return nil
+ }
+ var result []mondoov1.ZendeskCustomFieldInput
+ for _, r := range *recipients {
+ result = append(result, mondoov1.ZendeskCustomFieldInput{
+ ID: mondoov1.Int(r.ID.ValueInt64()),
+ Value: mondoov1.String(r.Value.ValueString()),
+ })
+ }
+ return &result
+}
+
+func (r *integrationZendeskResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data integrationZendeskResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Compute and validate the space
+ space, err := r.client.ComputeSpace(data.SpaceID)
+ if err != nil {
+ resp.Diagnostics.AddError("Invalid Configuration", err.Error())
+ return
+ }
+ ctx = tflog.SetField(ctx, "space_mrn", space.MRN())
+
+ // Do GraphQL request to API to create the resource.
+ tflog.Debug(ctx, "Creating integration")
+ integration, err := r.client.CreateIntegration(ctx,
+ space.MRN(),
+ data.Name.ValueString(),
+ mondoov1.ClientIntegrationTypeTicketSystemZendesk,
+ mondoov1.ClientIntegrationConfigurationInput{
+ ZendeskConfigurationOptions: data.GetConfigurationOptions(),
+ })
+ if err != nil {
+ resp.Diagnostics.
+ AddError("Client Error",
+ fmt.Sprintf("Unable to create Zendesk integration, got error: %s", err),
+ )
+ return
+ }
+
+ // Save space mrn into the Terraform state.
+ data.Mrn = types.StringValue(string(integration.Mrn))
+ data.Name = types.StringValue(string(integration.Name))
+ data.SpaceID = types.StringValue(space.ID())
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *integrationZendeskResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data integrationZendeskResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Read API call logic
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *integrationZendeskResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data integrationZendeskResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Do GraphQL request to API to update the resource.
+ opts := mondoov1.ClientIntegrationConfigurationInput{
+ ZendeskConfigurationOptions: data.GetConfigurationOptions(),
+ }
+
+ _, err := r.client.UpdateIntegration(ctx,
+ data.Mrn.ValueString(),
+ data.Name.ValueString(),
+ mondoov1.ClientIntegrationTypeTicketSystemZendesk,
+ opts,
+ )
+ if err != nil {
+ resp.Diagnostics.
+ AddError("Client Error",
+ fmt.Sprintf("Unable to update Zendesk integration, got error: %s", err),
+ )
+ return
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *integrationZendeskResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var data integrationZendeskResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Do GraphQL request to API to update the resource.
+ _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString())
+ if err != nil {
+ resp.Diagnostics.
+ AddError("Client Error",
+ fmt.Sprintf("Unable to delete Zendesk integration, got error: %s", err),
+ )
+ return
+ }
+}
+
+func (r *integrationZendeskResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ integration, ok := r.client.ImportIntegration(ctx, req, resp)
+ if !ok {
+ return
+ }
+
+ var customFields []integrationZendeskCustomFieldModel
+ for _, field := range integration.ConfigurationOptions.ZendeskConfigurationOptions.CustomFields {
+ customFields = append(customFields, integrationZendeskCustomFieldModel{
+ ID: types.Int64Value(field.ID),
+ Value: types.StringValue(field.Value),
+ })
+ }
+
+ model := integrationZendeskResourceModel{
+ Mrn: types.StringValue(integration.Mrn),
+ Name: types.StringValue(integration.Name),
+ SpaceID: types.StringValue(integration.SpaceID()),
+ Subdomain: types.StringValue(integration.ConfigurationOptions.ZendeskConfigurationOptions.Subdomain),
+ Email: types.StringValue(integration.ConfigurationOptions.ZendeskConfigurationOptions.Email),
+ AutoClose: types.BoolValue(integration.ConfigurationOptions.ZendeskConfigurationOptions.AutoCloseTickets),
+ AutoCreate: types.BoolValue(integration.ConfigurationOptions.ZendeskConfigurationOptions.AutoCreateTickets),
+ CustomFields: &customFields,
+ Credential: &integrationZendeskCredentialModel{
+ Token: types.StringPointerValue(nil),
+ },
+ }
+
+ resp.State.Set(ctx, &model)
+}
diff --git a/internal/provider/integration_zendesk_resource_test.go b/internal/provider/integration_zendesk_resource_test.go
new file mode 100644
index 0000000..f0658db
--- /dev/null
+++ b/internal/provider/integration_zendesk_resource_test.go
@@ -0,0 +1,119 @@
+// Copyright (c) Mondoo, Inc.
+// SPDX-License-Identifier: BUSL-1.1
+
+package provider
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccZendeskResource(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: testAccZendeskResourceConfig(accSpace.ID(), "one", "your-subdomain", "zendeskowner@email.com", `[
+ {id: "123456", value: "custom_value_1"},
+ {id: "123457", value: "custom_value_2"},
+ ]`),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "name", "one"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "space_id", accSpace.ID()),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "subdomain", "your-subdomain"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "email", "zendeskowner@email.com"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "custom_fields.0.value", "custom_value_1"),
+ ),
+ },
+ {
+ Config: testAccZendeskResourceWithSpaceInProviderConfig(accSpace.ID(), "two", "abctoken12345", true, true),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "name", "two"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "space_id", accSpace.ID()),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "auto_create", "true"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "auto_close", "true"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "credentials.token", "abctoken12345"),
+ ),
+ },
+ // Update and Read testing
+ {
+ Config: testAccZendeskResourceConfig(accSpace.ID(), "three", "updated-subdomain", "updated@email.com", `[
+ {id: "123456", value: "new_custom_value_1"},
+ ]`),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "name", "three"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "space_id", accSpace.ID()),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "subdomain", "updated-subdomain"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "email", "updated@email.com"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "custom_fields.0.value", "new_custom_value_1"),
+ ),
+ },
+ {
+ Config: testAccZendeskResourceWithSpaceInProviderConfig(accSpace.ID(), "four", "0987xyzabc7654", false, false),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "name", "four"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "space_id", accSpace.ID()),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "auto_create", "false"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "auto_close", "false"),
+ resource.TestCheckResourceAttr("mondoo_integration_zendesk.test", "credentials.token", "0987xyzabc7654"),
+ ),
+ },
+ // Delete testing automatically occurs in TestCase
+ },
+ })
+}
+
+func testAccZendeskResourceConfig(spaceID, name, subdomain, email, customFields string) string {
+ return fmt.Sprintf(`
+resource "mondoo_integration_zendesk" "test" {
+ space_id = %[1]q
+ name = %[2]q
+ subdomain = %[3]q
+ email = %[4]q
+
+ custom_fields = %[5]s
+
+ auto_create = true
+ auto_close = true
+
+ credentials = {
+ token = "abcd1234567890"
+ }
+}
+`, spaceID, name, subdomain, email, customFields)
+}
+
+func testAccZendeskResourceWithSpaceInProviderConfig(spaceID, intName, token string, autoCreate, autoClose bool) string {
+ return fmt.Sprintf(`
+provider "mondoo" {
+ space = %[1]q
+}
+resource "mondoo_integration_zendesk" "test" {
+ name = %[2]q
+ subdomain = "your-subdomain"
+ email = "zendeskowner@email.com"
+
+ custom_fields = [
+ {
+ id = "123456"
+ value = "custom_value_1"
+ },
+ {
+ id = "123457"
+ value = "custom_value_2"
+ }
+ ]
+
+ auto_create = %[3]t
+ auto_close = %[4]t
+
+ credentials = {
+ token = %[5]q
+ }
+}
+`, spaceID, intName, autoCreate, autoClose, token)
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index bbb21dc..fec7263 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -204,6 +204,7 @@ func (p *MondooProvider) Resources(ctx context.Context) []func() resource.Resour
NewIntegrationShodanResource,
NewFrameworkAssignmentResource,
NewCustomFrameworkResource,
+ NewIntegrationZendeskResource,
NewIntegrationJiraResource,
NewIntegrationEmailResource,
NewIntegrationGitlabResource,