Skip to content

Commit

Permalink
restful_{resource|operation}: precheck(_xxx) supports api and `…
Browse files Browse the repository at this point in the history
…mutex` (#55)
  • Loading branch information
magodo committed Feb 17, 2023
1 parent b3b43c1 commit 101101a
Show file tree
Hide file tree
Showing 10 changed files with 591 additions and 187 deletions.
16 changes: 12 additions & 4 deletions docs/resources/operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ resource "restful_operation" "register_rp" {
- `body` (String) The payload of the API call.
- `header` (Map of String) The header parameters that are applied to each request. This overrides the `header` set in the provider block.
- `poll` (Attributes) The polling option for the "API" operation (see [below for nested schema](#nestedatt--poll))
- `precheck` (Attributes List) An array of prechecks that need to pass prior to the "API" operation. (see [below for nested schema](#nestedatt--precheck))
- `precheck` (Attributes List) An array of prechecks that need to pass prior to the "API" operation. Exactly one of `mutex` or `api` should be specified. (see [below for nested schema](#nestedatt--precheck))
- `query` (Map of List of String) The query parameters that are applied to each request. This overrides the `query` set in the provider block.

### Read-Only
Expand Down Expand Up @@ -82,9 +82,17 @@ Optional:
<a id="nestedatt--precheck"></a>
### Nested Schema for `precheck`

Optional:

- `api` (Attributes) Keeps waiting until the specified API meets the success status (see [below for nested schema](#nestedatt--precheck--api))
- `mutex` (String) The name of the mutex, which implies the resource will keep waiting until this mutex is held

<a id="nestedatt--precheck--api"></a>
### Nested Schema for `precheck.api`

Required:

- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck--status))
- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck--api--status))
- `status_locator` (String) Specifies how to discover the status property. The format is either `code` or `scope.path`, where `scope` can be either `header` or `body`, and the `path` is using the [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).

Optional:
Expand All @@ -94,8 +102,8 @@ Optional:
- `path` (String) The path used to query readiness, relative to the `base_url` of the provider. By default, the `path` of this resource is used.
- `query` (Map of List of String) The query parameters. This overrides the `query` set in the resource block.

<a id="nestedatt--precheck--status"></a>
### Nested Schema for `precheck.status`
<a id="nestedatt--precheck--api--status"></a>
### Nested Schema for `precheck.api.status`

Required:

Expand Down
50 changes: 38 additions & 12 deletions docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ resource "restful_resource" "rg" {
- `poll_create` (Attributes) The polling option for the "Create" operation (see [below for nested schema](#nestedatt--poll_create))
- `poll_delete` (Attributes) The polling option for the "Delete" operation (see [below for nested schema](#nestedatt--poll_delete))
- `poll_update` (Attributes) The polling option for the "Update" operation (see [below for nested schema](#nestedatt--poll_update))
- `precheck_create` (Attributes List) An array of prechecks that need to pass prior to the "Create" operation. (see [below for nested schema](#nestedatt--precheck_create))
- `precheck_delete` (Attributes List) An array of prechecks that need to pass prior to the "Delete" operation. (see [below for nested schema](#nestedatt--precheck_delete))
- `precheck_update` (Attributes List) An array of prechecks that need to pass prior to the "Update" operation. (see [below for nested schema](#nestedatt--precheck_update))
- `precheck_create` (Attributes List) An array of prechecks that need to pass prior to the "Create" operation. Exactly one of `mutex` or `api` should be specified. (see [below for nested schema](#nestedatt--precheck_create))
- `precheck_delete` (Attributes List) An array of prechecks that need to pass prior to the "Delete" operation. Exactly one of `mutex` or `api` should be specified. (see [below for nested schema](#nestedatt--precheck_delete))
- `precheck_update` (Attributes List) An array of prechecks that need to pass prior to the "Update" operation. Exactly one of `mutex` or `api` should be specified. (see [below for nested schema](#nestedatt--precheck_update))
- `query` (Map of List of String) The query parameters that are applied to each request. This overrides the `query` set in the provider block.
- `read_path` (String) The API path used to read the resource, which is used as the `id`. The `path` is used as the `id` instead if `read_path` is absent. The path can be string literal, or combined by followings: `$(path)` expanded to `path`, `$(body.x.y.z)` expands to the `x.y.z` property (urlencoded) in API body, `#(body.id)` expands to the `id` property, with `base_url` prefix trimmed.
- `read_selector` (String) A selector in [gjson query syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md#queries) query syntax, that is used when read returns a collection of resources, to select exactly one member resource of from it. By default, the whole response body is used as the body.
Expand Down Expand Up @@ -159,10 +159,18 @@ Optional:
<a id="nestedatt--precheck_create"></a>
### Nested Schema for `precheck_create`

Optional:

- `api` (Attributes) Keeps waiting until the specified API meets the success status (see [below for nested schema](#nestedatt--precheck_create--api))
- `mutex` (String) The name of the mutex, which implies the resource will keep waiting until this mutex is held

<a id="nestedatt--precheck_create--api"></a>
### Nested Schema for `precheck_create.api`

Required:

- `path` (String) The path used to query readiness, relative to the `base_url` of the provider.
- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_create--status))
- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_create--api--status))
- `status_locator` (String) Specifies how to discover the status property. The format is either `code` or `scope.path`, where `scope` can be either `header` or `body`, and the `path` is using the [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).

Optional:
Expand All @@ -171,8 +179,8 @@ Optional:
- `header` (Map of String) The header parameters. This overrides the `header` set in the resource block.
- `query` (Map of List of String) The query parameters. This overrides the `query` set in the resource block.

<a id="nestedatt--precheck_create--status"></a>
### Nested Schema for `precheck_create.status`
<a id="nestedatt--precheck_create--api--status"></a>
### Nested Schema for `precheck_create.api.status`

Required:

Expand All @@ -184,12 +192,21 @@ Optional:




<a id="nestedatt--precheck_delete"></a>
### Nested Schema for `precheck_delete`

Optional:

- `api` (Attributes) Keeps waiting until the specified API meets the success status (see [below for nested schema](#nestedatt--precheck_delete--api))
- `mutex` (String) The name of the mutex, which implies the resource will keep waiting until this mutex is held

<a id="nestedatt--precheck_delete--api"></a>
### Nested Schema for `precheck_delete.api`

Required:

- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_delete--status))
- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_delete--api--status))
- `status_locator` (String) Specifies how to discover the status property. The format is either `code` or `scope.path`, where `scope` can be either `header` or `body`, and the `path` is using the [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).

Optional:
Expand All @@ -199,8 +216,8 @@ Optional:
- `path` (String) The path used to query readiness, relative to the `base_url` of the provider. By default, the `id` of this resource is used.
- `query` (Map of List of String) The query parameters. This overrides the `query` set in the resource block.

<a id="nestedatt--precheck_delete--status"></a>
### Nested Schema for `precheck_delete.status`
<a id="nestedatt--precheck_delete--api--status"></a>
### Nested Schema for `precheck_delete.api.status`

Required:

Expand All @@ -212,12 +229,21 @@ Optional:




<a id="nestedatt--precheck_update"></a>
### Nested Schema for `precheck_update`

Optional:

- `api` (Attributes) Keeps waiting until the specified API meets the success status (see [below for nested schema](#nestedatt--precheck_update--api))
- `mutex` (String) The name of the mutex, which implies the resource will keep waiting until this mutex is held

<a id="nestedatt--precheck_update--api"></a>
### Nested Schema for `precheck_update.api`

Required:

- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_update--status))
- `status` (Attributes) The expected status sentinels for each polling state. (see [below for nested schema](#nestedatt--precheck_update--api--status))
- `status_locator` (String) Specifies how to discover the status property. The format is either `code` or `scope.path`, where `scope` can be either `header` or `body`, and the `path` is using the [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).

Optional:
Expand All @@ -227,8 +253,8 @@ Optional:
- `path` (String) The path used to query readiness, relative to the `base_url` of the provider. By default, the `id` of this resource is used.
- `query` (Map of List of String) The query parameters. This overrides the `query` set in the resource block.

<a id="nestedatt--precheck_update--status"></a>
### Nested Schema for `precheck_update.status`
<a id="nestedatt--precheck_update--api--status"></a>
### Nested Schema for `precheck_update.api.status`

Required:

Expand Down
132 changes: 132 additions & 0 deletions examples/usecases/azure/route_table/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
terraform {
required_providers {
restful = {
source = "magodo/restful"
}
}
}

variable "client_id" {
type = string
}

variable "client_secret" {
type = string
}

variable "tenant_id" {
type = string
}

variable "subscription_id" {
type = string
}

provider "restful" {
base_url = "https://management.azure.com"
security = {
oauth2 = {
client_credentials = {
client_id = var.client_id
client_secret = var.client_secret
token_url = format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", var.tenant_id)
scopes = ["https://management.azure.com/.default"]
}
}
}
create_method = "PUT"
}

resource "restful_resource" "rg" {
path = format("/subscriptions/%s/resourceGroups/%s", var.subscription_id, "example")
query = {
api-version = ["2020-06-01"]
}
poll_delete = {
status_locator = "code"
status = {
success = "404"
pending = ["202", "200"]
}
}
body = jsonencode({
location = "westus"
tags = {
foo = "bar"
}
})
}

locals {
poll = {
status_locator = "body.status"
status = {
success = "Succeeded"
failure = "Failed"
pending = ["Pending"]
}
url_locator = "header.azure-asyncoperation"
}
route_precheck = [
{
mutex = restful_resource.table.id
}
]
}

resource "restful_resource" "table" {
path = format("%s/providers/Microsoft.Network/routeTables/%s", restful_resource.rg.id, "example")
update_method = "PATCH" # This avoids update to clean up the routes in this table
query = {
api-version = ["2022-07-01"]
}
body = jsonencode({
location = "westus"
})
poll_create = local.poll
poll_delete = local.poll
}

resource "restful_resource" "route1" {
path = format("%s/routes/%s", restful_resource.table.id, "route1")
query = {
api-version = ["2022-07-01"]
}

precheck_create = local.route_precheck
precheck_update = local.route_precheck
precheck_delete = local.route_precheck

poll_create = local.poll
poll_update = local.poll
poll_delete = local.poll

body = jsonencode({
properties = {
nextHopType = "VnetLocal"
addressPrefix = "10.1.0.0/16"
}
})
}

resource "restful_resource" "route2" {
path = format("%s/routes/%s", restful_resource.table.id, "route2")
query = {
api-version = ["2022-07-01"]
}

precheck_create = local.route_precheck
precheck_update = local.route_precheck
precheck_delete = local.route_precheck

poll_create = local.poll
poll_update = local.poll
poll_delete = local.poll

body = jsonencode({
properties = {
nextHopType = "VnetLocal"
addressPrefix = "10.2.0.0/16"
}
})
}
File renamed without changes.
58 changes: 58 additions & 0 deletions internal/locks/lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package locks

import (
"context"
"log"
"sync"
)

type mutexKV struct {
lock sync.Mutex
store map[string]*sync.Mutex
}

func (m *mutexKV) Lock(ctx context.Context, key string) error {
log.Printf("[DEBUG] Locking %q", key)
l := m.get(key)
for locked := false; !locked; {
if err := ctx.Err(); err != nil {
return err
}
locked = l.TryLock()
}
log.Printf("[DEBUG] Locked %q", key)
return nil
}

func (m *mutexKV) Unlock(key string) {
log.Printf("[DEBUG] Unlocking %q", key)
m.get(key).Unlock()
log.Printf("[DEBUG] Unlocked %q", key)
}

func (m *mutexKV) get(key string) *sync.Mutex {
m.lock.Lock()
defer m.lock.Unlock()
mutex, ok := m.store[key]
if !ok {
mutex = &sync.Mutex{}
m.store[key] = mutex
}
return mutex
}

func NewMutexKV() *mutexKV {
return &mutexKV{
store: make(map[string]*sync.Mutex),
}
}

var monoMutexKV = NewMutexKV()

func Lock(ctx context.Context, key string) error {
return monoMutexKV.Lock(ctx, key)
}

func Unlock(key string) {
monoMutexKV.Unlock(key)
}
2 changes: 1 addition & 1 deletion internal/provider/api_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (opt apiOption) ForPoll(ctx context.Context, defaultHeader client.Header, d
}, nil
}

func (opt apiOption) ForPrecheck(ctx context.Context, defaultPath string, defaultHeader client.Header, defaultQuery client.Query, d precheckData) (*client.PollOption, diag.Diagnostics) {
func (opt apiOption) ForPrecheck(ctx context.Context, defaultPath string, defaultHeader client.Header, defaultQuery client.Query, d precheckDataApi) (*client.PollOption, diag.Diagnostics) {
var diags diag.Diagnostics

var status pollStatusGo
Expand Down
33 changes: 5 additions & 28 deletions internal/provider/operation_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,35 +140,12 @@ func (r *OperationResource) createOrUpdate(ctx context.Context, tfplan tfsdk.Pla
}

// Precheck
if !plan.Precheck.IsNull() {
var checks []precheckData
if diags := plan.Precheck.ElementsAs(ctx, &checks, false); diags.HasError() {
diagnostics.Append(diags...)
return
}
for i, d := range checks {
opt, diags := r.p.apiOpt.ForPrecheck(ctx, plan.Path.ValueString(), opt.Header, opt.Query, d)
if diags.HasError() {
diagnostics.Append(diags...)
return
}
p, err := client.NewPollable(*opt)
if err != nil {
diagnostics.AddError(
fmt.Sprintf("Operation: Failed to build poller for %d-th precheck", i),
err.Error(),
)
return
}
if err := p.PollUntilDone(ctx, c); err != nil {
diagnostics.AddError(
fmt.Sprintf("Operation: Pre-checking %d-th check failure", i),
err.Error(),
)
return
}
}
unlockFunc, diags := precheck(ctx, c, r.p.apiOpt, plan.Path.ValueString(), opt.Header, opt.Query, plan.Precheck)
diagnostics.Append(diags...)
if diags.HasError() {
return
}
defer unlockFunc()

response, err := c.Operation(ctx, plan.Path.ValueString(), plan.Body.ValueString(), *opt)
if err != nil {
Expand Down
Loading

0 comments on commit 101101a

Please sign in to comment.