diff --git a/docs/resources/monitor.md b/docs/resources/monitor.md new file mode 100644 index 000000000..ebc6138e9 --- /dev/null +++ b/docs/resources/monitor.md @@ -0,0 +1,60 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "sentry_monitor Resource - terraform-provider-sentry" +subcategory: "" +description: |- + Return a client monitor bound to a project. +--- + +# sentry_monitor (Resource) + +Return a client monitor bound to a project. + + + + +## Schema + +### Required + +- `config` (Attributes) (see [below for nested schema](#nestedatt--config)) +- `name` (String) +- `organization` (String) The organization of this resource. +- `project` (String) The project of this resource. +- `slug` (String) + +### Optional + +- `is_muted` (Boolean) +- `owner` (String) +- `status` (String) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `config` + +Optional: + +- `alert_rule_id` (Number) +- `checkin_margin` (Number) +- `failure_issue_threshold` (Number) +- `max_runtime` (Number) +- `recovery_threshold` (Number) +- `schedule_crontab` (String) +- `schedule_interval` (Attributes) (see [below for nested schema](#nestedatt--config--schedule_interval)) +- `timezone` (String) + + +### Nested Schema for `config.schedule_interval` + +Optional: + +- `day` (Number) +- `hour` (Number) +- `minute` (Number) +- `month` (Number) +- `week` (Number) +- `year` (Number) diff --git a/go.mod b/go.mod index 9f33fd474..81ce7cda7 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/orange-cloudavenue/terraform-plugin-framework-superschema v1.11.0 github.com/orange-cloudavenue/terraform-plugin-framework-supertypes v1.2.0 github.com/peterhellberg/link v1.2.0 + github.com/stretchr/testify v1.10.0 golang.org/x/sync v0.14.0 ) @@ -38,6 +39,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/fatih/color v1.18.0 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect @@ -81,6 +83,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oklog/run v1.1.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect diff --git a/go.sum b/go.sum index f74d9895a..04cf1df1a 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,8 @@ github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZH github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0/go.mod h1:t339KhmxnaF4SzdpxmqW8HnQBHVGYazwtfxU0qCs4eE= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg= github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0= github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= @@ -241,8 +243,6 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/orange-cloudavenue/terraform-plugin-framework-superschema v1.11.0 h1:uezhiY+wp4khq2i9z2jbr26e816BQYKFolCjsZlLIzU= github.com/orange-cloudavenue/terraform-plugin-framework-superschema v1.11.0/go.mod h1:2R4+FiimXRb6we3IP4Jy4XwkmPG17isxcjUfYEWLJ44= -github.com/orange-cloudavenue/terraform-plugin-framework-supertypes v1.1.1 h1:5r0bXWxmY/p8YtYsS0ZQNE+fJ5BhUVSVGtVbvENad1c= -github.com/orange-cloudavenue/terraform-plugin-framework-supertypes v1.1.1/go.mod h1:iIp5TGuiJnDvcSCYq7mWYIn4HSmjntHv+IJGMK0MsZw= github.com/orange-cloudavenue/terraform-plugin-framework-supertypes v1.2.0 h1:tvDLBfqkKQdlcPyb3dZsZMM52mpzVpys2a9ar/38ulE= github.com/orange-cloudavenue/terraform-plugin-framework-supertypes v1.2.0/go.mod h1:5SJrMDAHcQopy+p9Oq3anoaqclPZgTs27b0WQ3J97E0= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -343,8 +343,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/internal/apiclient/api.yaml b/internal/apiclient/api.yaml index 3ed50a882..dc40620cc 100644 --- a/internal/apiclient/api.yaml +++ b/internal/apiclient/api.yaml @@ -857,7 +857,91 @@ paths: description: Forbidden "404": description: Not Found - + /0/organizations/{organization_id_or_slug}/monitors/: + parameters: + - $ref: "#/components/parameters/organization_id_or_slug" + post: + operationId: createMonitor + description: Create a new monitor. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/MonitorRequest" + required: true + responses: + "201": + content: + application/json: + schema: + $ref: "#/components/schemas/Monitor" + description: "" + "400": + description: Bad Request + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found + /0/organizations/{organization_id_or_slug}/monitors/{monitor_id_or_slug}/: + parameters: + - $ref: "#/components/parameters/organization_id_or_slug" + - $ref: "#/components/parameters/monitor_id_or_slug" + get: + operationId: getOrganizationMonitor + description: Retrieves details for a monitor. + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Monitor" + description: "" + "400": + description: Bad Request + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found + put: + operationId: updateOrganizationMonitor + description: Update a monitor. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/MonitorRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Monitor" + description: "" + "400": + description: Bad Request + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found + delete: + operationId: deleteOrganizationMonitor + description: Delete a monitor. + responses: + "202": + description: Accepted + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found security: - bearerAuth: [] components: @@ -872,6 +956,12 @@ components: required: true schema: type: string + monitor_id_or_slug: + name: monitor_id_or_slug + in: path + required: true + schema: + type: string project_id_or_slug: name: project_id_or_slug in: path @@ -903,6 +993,211 @@ components: schema: type: string schemas: + Monitor: + type: object + properties: + alertRule: + $ref: "#/components/schemas/MonitorAlertRule" + id: + type: string + name: + type: string + slug: + type: string + status: + type: string + isMuted: + type: boolean + config: + $ref: "#/components/schemas/MonitorConfig" + dateCreated: + type: string + format: date-time + project: + type: object + properties: + id: + type: string + slug: + type: string + required: + - id + - slug + # environments: + # type: object + # properties: + # name: + # type: string + # required: + # - name + owner: + type: object + properties: + id: + type: string + type: + enum: + - user + - team + type: string + required: + - id + - type + required: + - config + - dateCreated + # - environments + - id + - isMuted + - name + - owner + - project + - slug + - status + MonitorConfig: + type: object + properties: + schedule_type: + $ref: "#/components/schemas/MonitorConfigScheduleType" + schedule: + anyOf: + - $ref: "#/components/schemas/MonitorConfigScheduleString" + - $ref: "#/components/schemas/MonitorConfigScheduleInterval" + checkin_margin: + type: integer + format: int64 + minimum: 1 + nullable: true + description: >- + How long (in minutes) after the expected checkin + time will we wait until we consider the checkin to + have been missed. + max_runtime: + type: integer + format: int64 + maximum: 40320 + minimum: 1 + nullable: true + description: >- + How long (in minutes) is the checkin allowed to run + for in CheckInStatus.IN_PROGRESS before it is + considered failed. + timezone: + type: string + failure_issue_threshold: + type: integer + format: int64 + maximum: 720 + minimum: 1 + nullable: true + description: >- + How many consecutive missed or failed check-ins in a + row before creating a new issue. + recovery_threshold: + type: integer + format: int64 + maximum: 720 + minimum: 1 + nullable: true + description: >- + How many successful check-ins in a row before + resolving an issue. + alert_rule_id: + type: integer + format: int64 + nullable: true + required: + - alert_rule_id + - checkin_margin + - failure_issue_threshold + - max_runtime + - recovery_threshold + - schedule + - schedule_type + - timezone + MonitorConfigScheduleType: + type: string + enum: + - crontab + - interval + MonitorConfigScheduleString: + type: string + MonitorConfigScheduleInterval: + type: array + minItems: 2 + maxItems: 2 + MonitorAlertRule: + type: object + properties: + targets: + type: array + items: + $ref: "#/components/schemas/MonitorAlertRuleTarget" + environment: + type: string + required: + - environment + - targets + MonitorAlertRuleTarget: + type: object + properties: + targetIdentifier: + type: integer + x-go-type: json.Number + targetType: + type: string + required: + - targetIdentifier + - targetType + MonitorRequest: + type: object + properties: + project: + type: string + description: The project slug to associate the monitor to. + name: + type: string + description: >- + Name of the monitor. Used for notifications. If not set the + slug will be derived from your monitor name. + maxLength: 128 + config: + $ref: "#/components/schemas/MonitorConfig" + slug: + type: string + description: >- + Uniquely identifies your monitor within your organization. + Changing this slug will require updates to any instrumented + check-in calls. + maxLength: 50 + pattern: '^(?![0-9]+$)[a-z0-9_\-]+$' + status: + enum: + - active + - disabled + type: string + default: active + description: >- + Status of the monitor. Disabled monitors will not accept + events and will not count towards the monitor quota. + + + * `active` + + * `disabled` + owner: + type: string + nullable: true + description: >- + The ID of the team or user that owns the monitor. (eg. + user:51 or team:6) + is_muted: + type: boolean + description: Disable creation of monitor incidents + required: + - config + - name + - project Organization: type: object required: diff --git a/internal/apiclient/apiclient.gen.go b/internal/apiclient/apiclient.gen.go index 59d9326ff..19aed1c14 100644 --- a/internal/apiclient/apiclient.gen.go +++ b/internal/apiclient/apiclient.gen.go @@ -22,6 +22,24 @@ const ( BearerAuthScopes = "bearerAuth.Scopes" ) +// Defines values for MonitorOwnerType. +const ( + MonitorOwnerTypeTeam MonitorOwnerType = "team" + MonitorOwnerTypeUser MonitorOwnerType = "user" +) + +// Defines values for MonitorConfigScheduleType. +const ( + Crontab MonitorConfigScheduleType = "crontab" + Interval MonitorConfigScheduleType = "interval" +) + +// Defines values for MonitorRequestStatus. +const ( + MonitorRequestStatusActive MonitorRequestStatus = "active" + MonitorRequestStatusDisabled MonitorRequestStatus = "disabled" +) + // Defines values for OrganizationIntegrationOpsgenieProviderKey. const ( Opsgenie OrganizationIntegrationOpsgenieProviderKey = "opsgenie" @@ -189,10 +207,111 @@ const ( // Defines values for ListProjectClientKeysParamsStatus. const ( - Active ListProjectClientKeysParamsStatus = "active" - Inactive ListProjectClientKeysParamsStatus = "inactive" + ListProjectClientKeysParamsStatusActive ListProjectClientKeysParamsStatus = "active" + ListProjectClientKeysParamsStatusInactive ListProjectClientKeysParamsStatus = "inactive" ) +// Monitor defines model for Monitor. +type Monitor struct { + AlertRule *MonitorAlertRule `json:"alertRule,omitempty"` + Config MonitorConfig `json:"config"` + DateCreated time.Time `json:"dateCreated"` + Id string `json:"id"` + IsMuted bool `json:"isMuted"` + Name string `json:"name"` + Owner struct { + Id string `json:"id"` + Type MonitorOwnerType `json:"type"` + } `json:"owner"` + Project struct { + Id string `json:"id"` + Slug string `json:"slug"` + } `json:"project"` + Slug string `json:"slug"` + Status string `json:"status"` +} + +// MonitorOwnerType defines model for Monitor.Owner.Type. +type MonitorOwnerType string + +// MonitorAlertRule defines model for MonitorAlertRule. +type MonitorAlertRule struct { + Environment string `json:"environment"` + Targets []MonitorAlertRuleTarget `json:"targets"` +} + +// MonitorAlertRuleTarget defines model for MonitorAlertRuleTarget. +type MonitorAlertRuleTarget struct { + TargetIdentifier json.Number `json:"targetIdentifier"` + TargetType string `json:"targetType"` +} + +// MonitorConfig defines model for MonitorConfig. +type MonitorConfig struct { + AlertRuleId *int64 `json:"alert_rule_id"` + + // CheckinMargin How long (in minutes) after the expected checkin time will we wait until we consider the checkin to have been missed. + CheckinMargin *int64 `json:"checkin_margin"` + + // FailureIssueThreshold How many consecutive missed or failed check-ins in a row before creating a new issue. + FailureIssueThreshold *int64 `json:"failure_issue_threshold"` + + // MaxRuntime How long (in minutes) is the checkin allowed to run for in CheckInStatus.IN_PROGRESS before it is considered failed. + MaxRuntime *int64 `json:"max_runtime"` + + // RecoveryThreshold How many successful check-ins in a row before resolving an issue. + RecoveryThreshold *int64 `json:"recovery_threshold"` + Schedule MonitorConfig_Schedule `json:"schedule"` + ScheduleType MonitorConfigScheduleType `json:"schedule_type"` + Timezone string `json:"timezone"` +} + +// MonitorConfig_Schedule defines model for MonitorConfig.Schedule. +type MonitorConfig_Schedule struct { + union json.RawMessage +} + +// MonitorConfigScheduleInterval defines model for MonitorConfigScheduleInterval. +type MonitorConfigScheduleInterval = []interface{} + +// MonitorConfigScheduleString defines model for MonitorConfigScheduleString. +type MonitorConfigScheduleString = string + +// MonitorConfigScheduleType defines model for MonitorConfigScheduleType. +type MonitorConfigScheduleType string + +// MonitorRequest defines model for MonitorRequest. +type MonitorRequest struct { + Config MonitorConfig `json:"config"` + + // IsMuted Disable creation of monitor incidents + IsMuted *bool `json:"is_muted,omitempty"` + + // Name Name of the monitor. Used for notifications. If not set the slug will be derived from your monitor name. + Name string `json:"name"` + + // Owner The ID of the team or user that owns the monitor. (eg. user:51 or team:6) + Owner *string `json:"owner"` + + // Project The project slug to associate the monitor to. + Project string `json:"project"` + + // Slug Uniquely identifies your monitor within your organization. Changing this slug will require updates to any instrumented check-in calls. + Slug *string `json:"slug,omitempty"` + + // Status Status of the monitor. Disabled monitors will not accept events and will not count towards the monitor quota. + // + // * `active` + // * `disabled` + Status *MonitorRequestStatus `json:"status,omitempty"` +} + +// MonitorRequestStatus Status of the monitor. Disabled monitors will not accept events and will not count towards the monitor quota. +// +// * `active` +// * `disabled` +type MonitorRequestStatus string + // Organization defines model for Organization. type Organization struct { Features *[]string `json:"features,omitempty"` @@ -819,6 +938,9 @@ type IntegrationId = string // MemberId defines model for member_id. type MemberId = string +// MonitorIdOrSlug defines model for monitor_id_or_slug. +type MonitorIdOrSlug = string + // OrganizationIdOrSlug defines model for organization_id_or_slug. type OrganizationIdOrSlug = string @@ -983,6 +1105,12 @@ type CreateOrganizationMemberJSONRequestBody CreateOrganizationMemberJSONBody // UpdateOrganizationMemberJSONRequestBody defines body for UpdateOrganizationMember for application/json ContentType. type UpdateOrganizationMemberJSONRequestBody UpdateOrganizationMemberJSONBody +// CreateMonitorJSONRequestBody defines body for CreateMonitor for application/json ContentType. +type CreateMonitorJSONRequestBody = MonitorRequest + +// UpdateOrganizationMonitorJSONRequestBody defines body for UpdateOrganizationMonitor for application/json ContentType. +type UpdateOrganizationMonitorJSONRequestBody = MonitorRequest + // DisableSpikeProtectionJSONRequestBody defines body for DisableSpikeProtection for application/json ContentType. type DisableSpikeProtectionJSONRequestBody DisableSpikeProtectionJSONBody @@ -1010,6 +1138,68 @@ type UpdateProjectRuleJSONRequestBody UpdateProjectRuleJSONBody // CreateOrganizationTeamProjectJSONRequestBody defines body for CreateOrganizationTeamProject for application/json ContentType. type CreateOrganizationTeamProjectJSONRequestBody CreateOrganizationTeamProjectJSONBody +// AsMonitorConfigScheduleString returns the union data inside the MonitorConfig_Schedule as a MonitorConfigScheduleString +func (t MonitorConfig_Schedule) AsMonitorConfigScheduleString() (MonitorConfigScheduleString, error) { + var body MonitorConfigScheduleString + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromMonitorConfigScheduleString overwrites any union data inside the MonitorConfig_Schedule as the provided MonitorConfigScheduleString +func (t *MonitorConfig_Schedule) FromMonitorConfigScheduleString(v MonitorConfigScheduleString) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeMonitorConfigScheduleString performs a merge with any union data inside the MonitorConfig_Schedule, using the provided MonitorConfigScheduleString +func (t *MonitorConfig_Schedule) MergeMonitorConfigScheduleString(v MonitorConfigScheduleString) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsMonitorConfigScheduleInterval returns the union data inside the MonitorConfig_Schedule as a MonitorConfigScheduleInterval +func (t MonitorConfig_Schedule) AsMonitorConfigScheduleInterval() (MonitorConfigScheduleInterval, error) { + var body MonitorConfigScheduleInterval + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromMonitorConfigScheduleInterval overwrites any union data inside the MonitorConfig_Schedule as the provided MonitorConfigScheduleInterval +func (t *MonitorConfig_Schedule) FromMonitorConfigScheduleInterval(v MonitorConfigScheduleInterval) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeMonitorConfigScheduleInterval performs a merge with any union data inside the MonitorConfig_Schedule, using the provided MonitorConfigScheduleInterval +func (t *MonitorConfig_Schedule) MergeMonitorConfigScheduleInterval(v MonitorConfigScheduleInterval) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t MonitorConfig_Schedule) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *MonitorConfig_Schedule) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // AsOrganizationIntegrationOpsgenie returns the union data inside the OrganizationIntegration as a OrganizationIntegrationOpsgenie func (t OrganizationIntegration) AsOrganizationIntegrationOpsgenie() (OrganizationIntegrationOpsgenie, error) { var body OrganizationIntegrationOpsgenie @@ -2499,6 +2689,22 @@ type ClientInterface interface { UpdateOrganizationMember(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, memberId MemberId, body UpdateOrganizationMemberJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateMonitorWithBody request with any body + CreateMonitorWithBody(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, body CreateMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteOrganizationMonitor request + DeleteOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetOrganizationMonitor request + GetOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateOrganizationMonitorWithBody request with any body + UpdateOrganizationMonitorWithBody(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, body UpdateOrganizationMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListOrganizationProjects request ListOrganizationProjects(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, params *ListOrganizationProjectsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -2734,6 +2940,78 @@ func (c *Client) UpdateOrganizationMember(ctx context.Context, organizationIdOrS return c.Client.Do(req) } +func (c *Client) CreateMonitorWithBody(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMonitorRequestWithBody(c.Server, organizationIdOrSlug, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, body CreateMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateMonitorRequest(c.Server, organizationIdOrSlug, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteOrganizationMonitorRequest(c.Server, organizationIdOrSlug, monitorIdOrSlug) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetOrganizationMonitorRequest(c.Server, organizationIdOrSlug, monitorIdOrSlug) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateOrganizationMonitorWithBody(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateOrganizationMonitorRequestWithBody(c.Server, organizationIdOrSlug, monitorIdOrSlug, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateOrganizationMonitor(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, body UpdateOrganizationMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateOrganizationMonitorRequest(c.Server, organizationIdOrSlug, monitorIdOrSlug, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ListOrganizationProjects(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, params *ListOrganizationProjectsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewListOrganizationProjectsRequest(c.Server, organizationIdOrSlug, params) if err != nil { @@ -3549,6 +3827,189 @@ func NewUpdateOrganizationMemberRequestWithBody(server string, organizationIdOrS return req, nil } +// NewCreateMonitorRequest calls the generic CreateMonitor builder with application/json body +func NewCreateMonitorRequest(server string, organizationIdOrSlug OrganizationIdOrSlug, body CreateMonitorJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateMonitorRequestWithBody(server, organizationIdOrSlug, "application/json", bodyReader) +} + +// NewCreateMonitorRequestWithBody generates requests for CreateMonitor with any type of body +func NewCreateMonitorRequestWithBody(server string, organizationIdOrSlug OrganizationIdOrSlug, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organization_id_or_slug", runtime.ParamLocationPath, organizationIdOrSlug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/0/organizations/%s/monitors/", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteOrganizationMonitorRequest generates requests for DeleteOrganizationMonitor +func NewDeleteOrganizationMonitorRequest(server string, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organization_id_or_slug", runtime.ParamLocationPath, organizationIdOrSlug) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "monitor_id_or_slug", runtime.ParamLocationPath, monitorIdOrSlug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/0/organizations/%s/monitors/%s/", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetOrganizationMonitorRequest generates requests for GetOrganizationMonitor +func NewGetOrganizationMonitorRequest(server string, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organization_id_or_slug", runtime.ParamLocationPath, organizationIdOrSlug) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "monitor_id_or_slug", runtime.ParamLocationPath, monitorIdOrSlug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/0/organizations/%s/monitors/%s/", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateOrganizationMonitorRequest calls the generic UpdateOrganizationMonitor builder with application/json body +func NewUpdateOrganizationMonitorRequest(server string, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, body UpdateOrganizationMonitorJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateOrganizationMonitorRequestWithBody(server, organizationIdOrSlug, monitorIdOrSlug, "application/json", bodyReader) +} + +// NewUpdateOrganizationMonitorRequestWithBody generates requests for UpdateOrganizationMonitor with any type of body +func NewUpdateOrganizationMonitorRequestWithBody(server string, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organization_id_or_slug", runtime.ParamLocationPath, organizationIdOrSlug) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "monitor_id_or_slug", runtime.ParamLocationPath, monitorIdOrSlug) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/0/organizations/%s/monitors/%s/", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewListOrganizationProjectsRequest generates requests for ListOrganizationProjects func NewListOrganizationProjectsRequest(server string, organizationIdOrSlug OrganizationIdOrSlug, params *ListOrganizationProjectsParams) (*http.Request, error) { var err error @@ -4676,6 +5137,22 @@ type ClientWithResponsesInterface interface { UpdateOrganizationMemberWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, memberId MemberId, body UpdateOrganizationMemberJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateOrganizationMemberResponse, error) + // CreateMonitorWithBodyWithResponse request with any body + CreateMonitorWithBodyWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMonitorResponse, error) + + CreateMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, body CreateMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMonitorResponse, error) + + // DeleteOrganizationMonitorWithResponse request + DeleteOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*DeleteOrganizationMonitorResponse, error) + + // GetOrganizationMonitorWithResponse request + GetOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*GetOrganizationMonitorResponse, error) + + // UpdateOrganizationMonitorWithBodyWithResponse request with any body + UpdateOrganizationMonitorWithBodyWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateOrganizationMonitorResponse, error) + + UpdateOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, body UpdateOrganizationMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateOrganizationMonitorResponse, error) + // ListOrganizationProjectsWithResponse request ListOrganizationProjectsWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, params *ListOrganizationProjectsParams, reqEditors ...RequestEditorFn) (*ListOrganizationProjectsResponse, error) @@ -4972,6 +5449,93 @@ func (r UpdateOrganizationMemberResponse) StatusCode() int { return 0 } +type CreateMonitorResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Monitor +} + +// Status returns HTTPResponse.Status +func (r CreateMonitorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateMonitorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteOrganizationMonitorResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteOrganizationMonitorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteOrganizationMonitorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetOrganizationMonitorResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Monitor +} + +// Status returns HTTPResponse.Status +func (r GetOrganizationMonitorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetOrganizationMonitorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateOrganizationMonitorResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Monitor +} + +// Status returns HTTPResponse.Status +func (r UpdateOrganizationMonitorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateOrganizationMonitorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ListOrganizationProjectsResponse struct { Body []byte HTTPResponse *http.Response @@ -5524,6 +6088,58 @@ func (c *ClientWithResponses) UpdateOrganizationMemberWithResponse(ctx context.C return ParseUpdateOrganizationMemberResponse(rsp) } +// CreateMonitorWithBodyWithResponse request with arbitrary body returning *CreateMonitorResponse +func (c *ClientWithResponses) CreateMonitorWithBodyWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateMonitorResponse, error) { + rsp, err := c.CreateMonitorWithBody(ctx, organizationIdOrSlug, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateMonitorResponse(rsp) +} + +func (c *ClientWithResponses) CreateMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, body CreateMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMonitorResponse, error) { + rsp, err := c.CreateMonitor(ctx, organizationIdOrSlug, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateMonitorResponse(rsp) +} + +// DeleteOrganizationMonitorWithResponse request returning *DeleteOrganizationMonitorResponse +func (c *ClientWithResponses) DeleteOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*DeleteOrganizationMonitorResponse, error) { + rsp, err := c.DeleteOrganizationMonitor(ctx, organizationIdOrSlug, monitorIdOrSlug, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteOrganizationMonitorResponse(rsp) +} + +// GetOrganizationMonitorWithResponse request returning *GetOrganizationMonitorResponse +func (c *ClientWithResponses) GetOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, reqEditors ...RequestEditorFn) (*GetOrganizationMonitorResponse, error) { + rsp, err := c.GetOrganizationMonitor(ctx, organizationIdOrSlug, monitorIdOrSlug, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetOrganizationMonitorResponse(rsp) +} + +// UpdateOrganizationMonitorWithBodyWithResponse request with arbitrary body returning *UpdateOrganizationMonitorResponse +func (c *ClientWithResponses) UpdateOrganizationMonitorWithBodyWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateOrganizationMonitorResponse, error) { + rsp, err := c.UpdateOrganizationMonitorWithBody(ctx, organizationIdOrSlug, monitorIdOrSlug, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateOrganizationMonitorResponse(rsp) +} + +func (c *ClientWithResponses) UpdateOrganizationMonitorWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, monitorIdOrSlug MonitorIdOrSlug, body UpdateOrganizationMonitorJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateOrganizationMonitorResponse, error) { + rsp, err := c.UpdateOrganizationMonitor(ctx, organizationIdOrSlug, monitorIdOrSlug, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateOrganizationMonitorResponse(rsp) +} + // ListOrganizationProjectsWithResponse request returning *ListOrganizationProjectsResponse func (c *ClientWithResponses) ListOrganizationProjectsWithResponse(ctx context.Context, organizationIdOrSlug OrganizationIdOrSlug, params *ListOrganizationProjectsParams, reqEditors ...RequestEditorFn) (*ListOrganizationProjectsResponse, error) { rsp, err := c.ListOrganizationProjects(ctx, organizationIdOrSlug, params, reqEditors...) @@ -6006,6 +6622,100 @@ func ParseUpdateOrganizationMemberResponse(rsp *http.Response) (*UpdateOrganizat return response, nil } +// ParseCreateMonitorResponse parses an HTTP response from a CreateMonitorWithResponse call +func ParseCreateMonitorResponse(rsp *http.Response) (*CreateMonitorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateMonitorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Monitor + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteOrganizationMonitorResponse parses an HTTP response from a DeleteOrganizationMonitorWithResponse call +func ParseDeleteOrganizationMonitorResponse(rsp *http.Response) (*DeleteOrganizationMonitorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteOrganizationMonitorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetOrganizationMonitorResponse parses an HTTP response from a GetOrganizationMonitorWithResponse call +func ParseGetOrganizationMonitorResponse(rsp *http.Response) (*GetOrganizationMonitorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetOrganizationMonitorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Monitor + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateOrganizationMonitorResponse parses an HTTP response from a UpdateOrganizationMonitorWithResponse call +func ParseUpdateOrganizationMonitorResponse(rsp *http.Response) (*UpdateOrganizationMonitorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateOrganizationMonitorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Monitor + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseListOrganizationProjectsResponse parses an HTTP response from a ListOrganizationProjectsWithResponse call func ParseListOrganizationProjectsResponse(rsp *http.Response) (*ListOrganizationProjectsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/internal/provider/model_monitor.go b/internal/provider/model_monitor.go new file mode 100644 index 000000000..4934f1f64 --- /dev/null +++ b/internal/provider/model_monitor.go @@ -0,0 +1,103 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" + "github.com/jianyuan/terraform-provider-sentry/internal/tfutils" +) + +type MonitorResourceModel struct { + Id types.String `tfsdk:"id"` + Organization types.String `tfsdk:"organization"` + Project types.String `tfsdk:"project"` + Name types.String `tfsdk:"name"` + Slug types.String `tfsdk:"slug"` + Owner types.String `tfsdk:"owner"` + Config types.Object `tfsdk:"config"` + Status types.String `tfsdk:"status"` + IsMuted types.Bool `tfsdk:"is_muted"` +} + +func (m MonitorResourceModel) Attributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": ResourceIdAttribute(), + "organization": ResourceOrganizationAttribute(), + "project": ResourceProjectAttribute(), + "name": schema.StringAttribute{ + Required: true, + }, + "slug": schema.StringAttribute{ + Required: true, + }, + "owner": schema.StringAttribute{ + Optional: true, + }, + "config": MonitorConfigResourceModel{}.SchemaAttribute(true), + "status": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + // unfortunately is_muted is not respected on creation via sentry's public api (but is for subsequent updates) + "is_muted": schema.BoolAttribute{ + Computed: true, + }, + } +} + +func (m MonitorResourceModel) ToMonitorRequest(ctx context.Context) (apiclient.MonitorRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + + var monitorConfigResourceModel MonitorConfigResourceModel + diags.Append(m.Config.As(ctx, &monitorConfigResourceModel, basetypes.ObjectAsOptions{})...) + + monitorConfig, monitorConfigDiags := monitorConfigResourceModel.ToMonitorRequest(ctx, path.Root("config")) + diags.Append(monitorConfigDiags...) + + request := apiclient.MonitorRequest{ + Name: m.Name.ValueString(), + Slug: m.Slug.ValueStringPointer(), + Project: m.Project.ValueString(), + Owner: m.Owner.ValueStringPointer(), + Config: monitorConfig, + Status: (*apiclient.MonitorRequestStatus)(m.Status.ValueStringPointer()), + IsMuted: m.IsMuted.ValueBoolPointer(), + } + + return request, diags +} + +func (m *MonitorResourceModel) Fill(ctx context.Context, organization string, monitor apiclient.Monitor) (diags diag.Diagnostics) { + path := path.Empty() + + var config MonitorConfigResourceModel + diags.Append(config.Fill(ctx, path.AtName("config"), monitor.Config)...) + + m.Organization = types.StringValue(organization) + m.Id = types.StringValue(monitor.Id) + m.Name = types.StringValue(monitor.Name) + m.Slug = types.StringValue(monitor.Slug) + m.Project = types.StringValue(monitor.Project.Slug) + m.Owner = types.StringPointerValue(formatMonitorOwner(monitor.Owner.Type, monitor.Owner.Id)) + m.Config = tfutils.MergeDiagnostics(types.ObjectValueFrom(ctx, config.AttributeTypes(), config))(&diags) + + m.Status = types.StringValue(monitor.Status) + m.IsMuted = types.BoolValue(monitor.IsMuted) + + return +} + +func formatMonitorOwner(ownerType apiclient.MonitorOwnerType, ownerId string) *string { + if ownerType == "" && ownerId == "" { + return nil + } + + owner := string(ownerType) + ":" + ownerId + + return &owner +} diff --git a/internal/provider/model_monitor_config.go b/internal/provider/model_monitor_config.go new file mode 100644 index 000000000..f9549eaba --- /dev/null +++ b/internal/provider/model_monitor_config.go @@ -0,0 +1,152 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" + "github.com/jianyuan/terraform-provider-sentry/internal/tfutils" +) + +type MonitorConfigResourceModel struct { + ScheduleCrontab types.String `tfsdk:"schedule_crontab"` + ScheduleInterval types.Object `tfsdk:"schedule_interval"` + CheckinMargin types.Int64 `tfsdk:"checkin_margin"` + MaxRuntime types.Int64 `tfsdk:"max_runtime"` + Timezone types.String `tfsdk:"timezone"` + FailureIssueThreshold types.Int64 `tfsdk:"failure_issue_threshold"` + RecoveryThreshold types.Int64 `tfsdk:"recovery_threshold"` + AlertRuleId types.Int64 `tfsdk:"alert_rule_id"` +} + +func (m MonitorConfigResourceModel) SchemaAttribute(required bool) schema.Attribute { + return schema.SingleNestedAttribute{ + Required: required, + Attributes: m.SchemaAttributes(), + } +} + +func (m MonitorConfigResourceModel) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "schedule_crontab": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("schedule_interval")), + }, + }, + "schedule_interval": schema.SingleNestedAttribute{ + Optional: true, + Attributes: MonitorConfigScheduleIntervalResourceModel{}.SchemaAttributes(), + Validators: []validator.Object{ + objectvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("schedule_crontab")), + }, + }, + "checkin_margin": schema.Int64Attribute{ + Optional: true, + }, + "max_runtime": schema.Int64Attribute{ + Optional: true, + }, + "timezone": schema.StringAttribute{ + Optional: true, + }, + "failure_issue_threshold": schema.Int64Attribute{ + Optional: true, + }, + "recovery_threshold": schema.Int64Attribute{ + Optional: true, + }, + "alert_rule_id": schema.Int64Attribute{ + Optional: true, + }, + } +} + +func (m *MonitorConfigResourceModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "schedule_crontab": types.StringType, + "schedule_interval": types.ObjectType{AttrTypes: (&MonitorConfigScheduleIntervalResourceModel{}).AttributeTypes()}, + "checkin_margin": types.Int64Type, + "max_runtime": types.Int64Type, + "timezone": types.StringType, + "failure_issue_threshold": types.Int64Type, + "recovery_threshold": types.Int64Type, + "alert_rule_id": types.Int64Type, + } +} + +func (m *MonitorConfigResourceModel) ToMonitorRequest(ctx context.Context, path path.Path) (apiclient.MonitorConfig, diag.Diagnostics) { + diags := diag.Diagnostics{} + + var scheduleType apiclient.MonitorConfigScheduleType + var configSchedule apiclient.MonitorConfig_Schedule + + if !m.ScheduleCrontab.IsUnknown() && !m.ScheduleCrontab.IsNull() { + scheduleType = apiclient.Crontab + + configSchedule.FromMonitorConfigScheduleString(m.ScheduleCrontab.ValueString()) + } else if !m.ScheduleInterval.IsUnknown() && !m.ScheduleInterval.IsNull() { + scheduleType = apiclient.Interval + + var scheduleIntervalModel MonitorConfigScheduleIntervalResourceModel + diags.Append(m.ScheduleInterval.As(ctx, &scheduleIntervalModel, basetypes.ObjectAsOptions{})...) + + scheduleInterval, scheduleIntervalDiags := formatMonitorConfigScheduleInterval(scheduleIntervalModel) + diags.Append(scheduleIntervalDiags...) + + configSchedule.FromMonitorConfigScheduleInterval(scheduleInterval) + } + + return apiclient.MonitorConfig{ + ScheduleType: scheduleType, + Schedule: configSchedule, + Timezone: m.Timezone.ValueString(), + CheckinMargin: m.CheckinMargin.ValueInt64Pointer(), + MaxRuntime: m.MaxRuntime.ValueInt64Pointer(), + FailureIssueThreshold: m.FailureIssueThreshold.ValueInt64Pointer(), + RecoveryThreshold: m.RecoveryThreshold.ValueInt64Pointer(), + AlertRuleId: m.AlertRuleId.ValueInt64Pointer(), + }, diags +} + +func (m *MonitorConfigResourceModel) Fill(ctx context.Context, path path.Path, config apiclient.MonitorConfig) (diags diag.Diagnostics) { + switch config.ScheduleType { + case apiclient.Crontab: + schedule, scheduleErr := config.Schedule.AsMonitorConfigScheduleString() + if scheduleErr != nil { + diags.AddAttributeError(path.AtName("schedule"), "Invalid schedule", scheduleErr.Error()) + break + } + m.ScheduleCrontab = types.StringValue(schedule) + m.ScheduleInterval = types.ObjectNull((&MonitorConfigScheduleIntervalResourceModel{}).AttributeTypes()) + case apiclient.Interval: + schedule, scheduleErr := config.Schedule.AsMonitorConfigScheduleInterval() + if scheduleErr != nil { + diags.AddAttributeError(path.AtName("schedule"), "Invalid schedule", scheduleErr.Error()) + break + } + parsedSchedule := tfutils.MergeDiagnostics(parseMonitorConfigScheduleInterval(schedule))(&diags) + m.ScheduleCrontab = types.StringNull() + m.ScheduleInterval = tfutils.MergeDiagnostics(types.ObjectValueFrom(ctx, (&MonitorConfigScheduleIntervalResourceModel{}).AttributeTypes(), parsedSchedule))(&diags) + default: + diags.AddAttributeError(path.AtName("schedule"), "Invalid schedule type", string(config.ScheduleType)) + } + + m.CheckinMargin = types.Int64PointerValue(config.CheckinMargin) + m.MaxRuntime = types.Int64PointerValue(config.MaxRuntime) + m.Timezone = types.StringValue(config.Timezone) + m.FailureIssueThreshold = types.Int64PointerValue(config.FailureIssueThreshold) + m.RecoveryThreshold = types.Int64PointerValue(config.RecoveryThreshold) + + m.AlertRuleId = types.Int64PointerValue(config.AlertRuleId) + + return +} diff --git a/internal/provider/model_monitor_config_schedule_interval.go b/internal/provider/model_monitor_config_schedule_interval.go new file mode 100644 index 000000000..14697f4e0 --- /dev/null +++ b/internal/provider/model_monitor_config_schedule_interval.go @@ -0,0 +1,135 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" +) + +type MonitorConfigScheduleIntervalResourceModel struct { + Year types.Int64 `tfsdk:"year"` + Month types.Int64 `tfsdk:"month"` + Week types.Int64 `tfsdk:"week"` + Day types.Int64 `tfsdk:"day"` + Hour types.Int64 `tfsdk:"hour"` + Minute types.Int64 `tfsdk:"minute"` +} + +func (m MonitorConfigScheduleIntervalResourceModel) SchemaAttributes() map[string]schema.Attribute { + attributeNames := []string{"year", "month", "week", "day", "hour", "minute"} + + attributes := make(map[string]schema.Attribute, len(attributeNames)) + + for _, name := range attributeNames { + var conflictingPaths []path.Expression + + for _, conflictingName := range attributeNames { + if conflictingName != name { + conflictingPaths = append(conflictingPaths, path.MatchRelative().AtParent().AtName(conflictingName)) + } + } + + attributes[name] = schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + int64validator.ConflictsWith(conflictingPaths...), + }, + } + } + + return attributes +} + +func (m *MonitorConfigScheduleIntervalResourceModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "year": types.Int64Type, + "month": types.Int64Type, + "week": types.Int64Type, + "day": types.Int64Type, + "hour": types.Int64Type, + "minute": types.Int64Type, + } +} + +func parseMonitorConfigScheduleInterval(m apiclient.MonitorConfigScheduleInterval) (MonitorConfigScheduleIntervalResourceModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + rm := MonitorConfigScheduleIntervalResourceModel{} + + if len(m) != 2 { + diags.AddError("Invalid schedule", "Invalid schedule") + return rm, diags + } + + var number int64 + number, ok := m[0].(int64) + if !ok { + diags.AddError("Invalid schedule", "Invalid schedule") + return rm, diags + } + + var unit string + unit, ok = m[1].(string) + if !ok { + diags.AddError("Invalid schedule", "Invalid schedule") + return rm, diags + } + + switch unit { + case "year": + rm.Year = types.Int64Value(number) + case "month": + rm.Month = types.Int64Value(number) + case "week": + rm.Week = types.Int64Value(number) + case "day": + rm.Day = types.Int64Value(number) + case "hour": + rm.Hour = types.Int64Value(number) + case "minute": + rm.Minute = types.Int64Value(number) + default: + diags.AddError("Invalid schedule", "Invalid schedule") + } + + return rm, diags +} + +func formatMonitorConfigScheduleInterval(schedule MonitorConfigScheduleIntervalResourceModel) (apiclient.MonitorConfigScheduleInterval, diag.Diagnostics) { + diags := diag.Diagnostics{} + + scheduleInterval := make(apiclient.MonitorConfigScheduleInterval, 2) + + if schedule.Year.IsNull() && schedule.Month.IsNull() && schedule.Week.IsNull() && + schedule.Day.IsNull() && schedule.Hour.IsNull() && schedule.Minute.IsNull() { + return nil, diags + } + + if !schedule.Year.IsNull() { + scheduleInterval[0] = schedule.Year.ValueInt64() + scheduleInterval[1] = "year" + } else if !schedule.Month.IsNull() { + scheduleInterval[0] = schedule.Month.ValueInt64() + scheduleInterval[1] = "month" + } else if !schedule.Week.IsNull() { + scheduleInterval[0] = schedule.Week.ValueInt64() + scheduleInterval[1] = "week" + } else if !schedule.Day.IsNull() { + scheduleInterval[0] = schedule.Day.ValueInt64() + scheduleInterval[1] = "day" + } else if !schedule.Hour.IsNull() { + scheduleInterval[0] = schedule.Hour.ValueInt64() + scheduleInterval[1] = "hour" + } else if !schedule.Minute.IsNull() { + scheduleInterval[0] = schedule.Minute.ValueInt64() + scheduleInterval[1] = "minute" + } + + return scheduleInterval, diags +} diff --git a/internal/provider/model_monitor_test.go b/internal/provider/model_monitor_test.go new file mode 100644 index 000000000..43def5874 --- /dev/null +++ b/internal/provider/model_monitor_test.go @@ -0,0 +1,130 @@ +package provider + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" + + // "github.com/jianyuan/terraform-provider-sentry/internal/provider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMonitorResourceModelToMonitorRequestScheduleCrontab(t *testing.T) { + config := MonitorConfigResourceModel{ + ScheduleCrontab: types.StringValue("* * * * *"), + ScheduleInterval: types.ObjectNull((*MonitorConfigScheduleIntervalResourceModel)(nil).AttributeTypes()), + Timezone: types.StringValue("UTC"), + CheckinMargin: types.Int64Value(10), + MaxRuntime: types.Int64Value(20), + FailureIssueThreshold: types.Int64Value(5), + RecoveryThreshold: types.Int64Value(10), + AlertRuleId: types.Int64Value(123), + } + + configObject, configObjectDiags := types.ObjectValueFrom(context.Background(), (*MonitorConfigResourceModel)(nil).AttributeTypes(), config) + require.Empty(t, configObjectDiags) + + model := MonitorResourceModel{ + Organization: types.StringValue("sentry-org"), + Id: types.StringValue("monitor_id"), + Project: types.StringValue("sentry-project"), + Name: types.StringValue("monitor name"), + Slug: types.StringValue("monitor-slug"), + Owner: types.StringValue("team:123"), + IsMuted: types.BoolValue(false), + Status: types.StringValue("active"), + Config: configObject, + } + + monitorRequest, monitorRequestDiags := model.ToMonitorRequest(context.Background()) + + require.Empty(t, monitorRequestDiags) + + assert.Equal(t, "monitor name", monitorRequest.Name) + assert.Equal(t, "monitor-slug", *monitorRequest.Slug) + assert.Equal(t, "sentry-project", monitorRequest.Project) + assert.Equal(t, "team:123", *monitorRequest.Owner) + assert.Equal(t, apiclient.MonitorRequestStatusActive, *monitorRequest.Status) + assert.Equal(t, false, *monitorRequest.IsMuted) + + assert.Equal(t, apiclient.Crontab, monitorRequest.Config.ScheduleType) + + monitorRequestConfigScheduleCrontab, monitorRequestConfigScheduleCrontabErr := monitorRequest.Config.Schedule.AsMonitorConfigScheduleString() + assert.NoError(t, monitorRequestConfigScheduleCrontabErr) + assert.Equal(t, "* * * * *", monitorRequestConfigScheduleCrontab) + + _, monitorRequestConfigScheduleIntervalErr := monitorRequest.Config.Schedule.AsMonitorConfigScheduleInterval() + assert.Error(t, monitorRequestConfigScheduleIntervalErr) + + assert.Equal(t, "UTC", monitorRequest.Config.Timezone) + assert.Equal(t, int64(10), *monitorRequest.Config.CheckinMargin) + assert.Equal(t, int64(20), *monitorRequest.Config.MaxRuntime) + assert.Equal(t, int64(5), *monitorRequest.Config.FailureIssueThreshold) + assert.Equal(t, int64(10), *monitorRequest.Config.RecoveryThreshold) + assert.Equal(t, int64(123), *monitorRequest.Config.AlertRuleId) +} + +func TestMonitorResourceModelToMonitorRequestScheduleInterval(t *testing.T) { + scheduleInterval := MonitorConfigScheduleIntervalResourceModel{ + Day: types.Int64Value(1), + } + + scheduleIntervalObject, scheduleIntervalObjectDiags := types.ObjectValueFrom(context.Background(), (*MonitorConfigScheduleIntervalResourceModel)(nil).AttributeTypes(), scheduleInterval) + require.Empty(t, scheduleIntervalObjectDiags) + + config := MonitorConfigResourceModel{ + ScheduleCrontab: types.StringNull(), + ScheduleInterval: scheduleIntervalObject, + Timezone: types.StringValue("UTC"), + CheckinMargin: types.Int64Value(10), + MaxRuntime: types.Int64Value(20), + FailureIssueThreshold: types.Int64Value(5), + RecoveryThreshold: types.Int64Value(10), + AlertRuleId: types.Int64Value(123), + } + + configObject, configObjectDiags := types.ObjectValueFrom(context.Background(), (*MonitorConfigResourceModel)(nil).AttributeTypes(), config) + require.Empty(t, configObjectDiags) + + model := MonitorResourceModel{ + Organization: types.StringValue("sentry-org"), + Id: types.StringValue("monitor_id"), + Project: types.StringValue("sentry-project"), + Name: types.StringValue("monitor name"), + Slug: types.StringValue("monitor-slug"), + Owner: types.StringValue("team:123"), + IsMuted: types.BoolValue(false), + Status: types.StringValue("active"), + Config: configObject, + } + + monitorRequest, monitorRequestDiags := model.ToMonitorRequest(context.Background()) + + require.Empty(t, monitorRequestDiags) + + assert.Equal(t, "monitor name", monitorRequest.Name) + assert.Equal(t, "monitor-slug", *monitorRequest.Slug) + assert.Equal(t, "sentry-project", monitorRequest.Project) + assert.Equal(t, "team:123", *monitorRequest.Owner) + assert.Equal(t, apiclient.MonitorRequestStatusActive, *monitorRequest.Status) + assert.Equal(t, false, *monitorRequest.IsMuted) + + assert.Equal(t, apiclient.Interval, monitorRequest.Config.ScheduleType) + + _, monitorRequestConfigScheduleCrontabErr := monitorRequest.Config.Schedule.AsMonitorConfigScheduleString() + assert.Error(t, monitorRequestConfigScheduleCrontabErr) + + monitorRequestConfigScheduleInterval, monitorRequestConfigScheduleIntervalErr := monitorRequest.Config.Schedule.AsMonitorConfigScheduleInterval() + assert.NoError(t, monitorRequestConfigScheduleIntervalErr) + assert.Equal(t, []any{float64(1), "day"}, monitorRequestConfigScheduleInterval) + + assert.Equal(t, "UTC", monitorRequest.Config.Timezone) + assert.Equal(t, int64(10), *monitorRequest.Config.CheckinMargin) + assert.Equal(t, int64(20), *monitorRequest.Config.MaxRuntime) + assert.Equal(t, int64(5), *monitorRequest.Config.FailureIssueThreshold) + assert.Equal(t, int64(10), *monitorRequest.Config.RecoveryThreshold) + assert.Equal(t, int64(123), *monitorRequest.Config.AlertRuleId) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a96cc8ea1..2f1214dd2 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -142,6 +142,7 @@ func (p *SentryProvider) Resources(ctx context.Context) []func() resource.Resour NewIntegrationOpsgenie, NewIntegrationPagerDuty, NewIssueAlertResource, + NewMonitorResource, NewNotificationActionResource, NewOrganizationRepositoryResource, NewProjectInboundDataFilterResource, diff --git a/internal/provider/resource_monitor.go b/internal/provider/resource_monitor.go new file mode 100644 index 000000000..a2c65a333 --- /dev/null +++ b/internal/provider/resource_monitor.go @@ -0,0 +1,193 @@ +package provider + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/jianyuan/terraform-provider-sentry/internal/tfutils" +) + +var _ resource.Resource = &MonitorResource{} +var _ resource.ResourceWithConfigure = &MonitorResource{} +var _ resource.ResourceWithImportState = &MonitorResource{} + +func NewMonitorResource() resource.Resource { + return &MonitorResource{} +} + +type MonitorResource struct { + baseResource +} + +func (r *MonitorResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_monitor" +} + +func (r *MonitorResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Return a client monitor bound to a project.", + Attributes: MonitorResourceModel{}.Attributes(), + } +} + +func (r *MonitorResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data MonitorResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + monitorRequest, monitorRequestDiags := data.ToMonitorRequest(ctx) + resp.Diagnostics.Append(monitorRequestDiags...) + + if resp.Diagnostics.HasError() { + return + } + + response, err := r.apiClient.CreateMonitorWithResponse(ctx, data.Organization.ValueString(), monitorRequest) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Create error: %s", err.Error())) + return + } + + if response.JSON201 == nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Create error: %s", response.HTTPResponse.Status)) + return + } + + resp.Diagnostics.Append(data.Fill(ctx, data.Organization.ValueString(), *response.JSON201)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *MonitorResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data MonitorResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + response, err := r.apiClient.GetOrganizationMonitorWithResponse( + ctx, + data.Organization.ValueString(), + data.Id.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Read error: %s", err.Error())) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddError("Client Error", "Not found") + // resp.State.RemoveResource(ctx) + return + } + + if response.JSON200 == nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Read error: %s", response.HTTPResponse.Status)) + return + } + + resp.Diagnostics.Append(data.Fill(ctx, data.Organization.ValueString(), *response.JSON200)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *MonitorResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data MonitorResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + monitorRequest, monitorRequestDiags := data.ToMonitorRequest(ctx) + resp.Diagnostics.Append(monitorRequestDiags...) + + if resp.Diagnostics.HasError() { + return + } + + response, err := r.apiClient.UpdateOrganizationMonitorWithResponse( + ctx, + data.Organization.ValueString(), + data.Id.ValueString(), + monitorRequest, + ) + + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Update error: %s", err.Error())) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddError("Client Error", "Not found") + // resp.State.RemoveResource(ctx) + return + } + + if response.JSON200 == nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Read error: %s", response.HTTPResponse.Status)) + return + } + + resp.Diagnostics.Append(data.Fill(ctx, data.Organization.ValueString(), *response.JSON200)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *MonitorResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data MonitorResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + apiResp, err := r.apiClient.DeleteOrganizationMonitorWithResponse( + ctx, + data.Organization.ValueString(), + data.Id.ValueString(), + ) + + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Delete error: %s", err.Error())) + return + } + + if apiResp.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddWarning("Monitor not found", "Monitor may have been deleted already") + return + } +} + +func (r *MonitorResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + organization, id, err := tfutils.SplitTwoPartId(req.ID, "organization", "monitor-id") + if err != nil { + resp.Diagnostics.AddError("Invalid ID", fmt.Sprintf("Error parsing ID: %s", err.Error())) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("organization"), organization, + )...) + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("id"), id, + )...) +}