-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(blocks): add update resource operation #198
Conversation
Implements Update method for BlockResource type. Closes #182
Catches two scenarios where we need to 'return' when err != nil.
There's a fair amount of repetition in |
blockTypeClient, err := r.client.BlockTypes(plan.AccountID.ValueUUID(), plan.WorkspaceID.ValueUUID()) | ||
if err != nil { | ||
resp.Diagnostics.Append(helpers.CreateClientErrorDiagnostic("Block Types", err)) | ||
|
||
return | ||
} | ||
|
||
blockSchemaClient, err := r.client.BlockSchemas(plan.AccountID.ValueUUID(), plan.WorkspaceID.ValueUUID()) | ||
if err != nil { | ||
resp.Diagnostics.Append(helpers.CreateClientErrorDiagnostic("Block Schema", err)) | ||
|
||
return | ||
} | ||
|
||
blockDocumentClient, err := r.client.BlockDocuments(plan.AccountID.ValueUUID(), plan.WorkspaceID.ValueUUID()) | ||
if err != nil { | ||
resp.Diagnostics.Append(helpers.CreateClientErrorDiagnostic("Block Document", err)) | ||
|
||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wonder if we just make a BlockManager
aggregator class that initializes the sub-clients and exposes a single set of methods that calls these underneath the hood
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I started abstracting a helper method to get the clients but then the PR started getting bigger since I was modifying other methods. Let me make an issue for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return | ||
} | ||
|
||
blockType, err := blockTypeClient.GetBySlug(ctx, plan.TypeSlug.ValueString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok here's an interesting thing that came up - so the PATCH endpoint accepts a block_schema_id (meaning it's a modifiable attribute). block_schemas are a "version" of a type
but what if the type_slug is modified in the TF resource? then we'll get a failure like so -
Terraform will perform the following actions:
# prefect_block.lambda will be updated in-place
~ resource "prefect_block" "lambda" {
id = "adf48aa0-318d-40d1-aca0-c897f05cc22b"
name = "lambda"
~ type_slug = "lambda-function" -> "secret"
~ updated = "2024-06-06T23:40:59Z" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
prefect_block.lambda: Modifying... [id=adf48aa0-318d-40d1-aca0-c897f05cc22b]
╷
│ Error: Error during update Block Document
│
│ with prefect_block.lambda,
│ on main.tf line 19, in resource "prefect_block" "lambda":
│ 19: resource "prefect_block" "lambda" {
│
│ Could not update Block Document, unexpected error: status code 400 Bad Request, error={"detail":"Must migrate block document to a block schema of the same
│ block type."}
we can find out, but im guessing the intent of the endpoint was to allow changing schema versions of the same block type, but we shouldnt' allow changing the type itself
so there's two things here:
- should we prevent users from changing the
type_slug
? i think we should - but since the Schema object is shared, we may need to enforce this in this method (by comparing theplan.TypeSlug == state.TypeSlug
=> exit early if they do) - should we allow users modifying the schema of the same type? im not sure we need to (eg. the UI does not expose this). also since we take the "[0]" schema from
blockSchemaClient.List
, we're effectively applying the latest schema on every update call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we prevent users from changing the type_slug? i think we should - but since the Schema object is shared, we may need to enforce this in this method (by comparing the plan.TypeSlug == state.TypeSlug => exit early if they do)
@parkedwards good call-out. I started looking into this but haven't yet found a way to work with state
and plan
in the same place 🤔 Am I missing something?
I found a reference to DiffSuppressFunc, which isn't what we want, but does allow you to work with old
and new
values to compare them.
I'll keep looking but if you know of a way off the top of your head just lemme know 👍🏼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe RequiresReplaceIf could help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
take a peek at the service_account.go
resource's Update() method - i had to do some similar kinds of plan vs. state
checks. the high level is that you can just invoke req.Plan.Get()
or req.State.Get()
, depending on what you want to draw from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That change results in:
$ terraform apply
...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# prefect_block.secret must be replaced
-/+ resource "prefect_block" "secret" {
~ created = "2024-06-07T16:32:08Z" -> (known after apply)
~ id = "b015fc44-4a05-4f41-90b6-2d2523bb92bb" -> (known after apply)
name = "foo"
~ type_slug = "secret" -> "lambda" # forces replacement
~ updated = "2024-06-07T16:32:08Z" -> (known after apply)
# (1 unchanged attribute hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
What do you think of that flow @parkedwards? Or would you rather reject the change entirely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh i completely missed your comment about using the planmodifier. yeah i like it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the high level is that you can just invoke req.Plan.Get() or req.State.Get()
🤦🏼 I tried that and my editor didn't want to autocomplete .State
so I assumed it wasn't available. Must have had an error elsewhere in my file. Thanks!
The planmodifier approach feels more "correct" here based on some googling but I'm also open to just grabbing State
and Plan
in the same place. Do you have a preference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with planmodifier.RequiresReplace
in 5995859 for now as as it was the least amount of code, and I saw we use it in 2 other places in the codebase already. If we ever want to change this functionality to instead reject the change and throw an error, we know where to look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we allow users modifying the schema of the same type? im not sure we need to (eg. the UI does not expose this). also since we take the "[0]" schema from blockSchemaClient.List, we're effectively applying the latest schema on every update call
Fair question, probably not for now if the UI doesn't expose that. Although maybe it's worth a debug log of the block schema's metadata 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall look awesome, i tested this a couple different ways in OSS and Cloud and everythign seems to check out 🚀
i would just look into that validation that prevents users from changing type_slug
on an update
If the 'type_slug' attribute changes, we'll need to replace the entire resource because it's of a different type.
@@ -106,6 +106,9 @@ func (r *BlockResource) Schema(_ context.Context, _ resource.SchemaRequest, resp | |||
"type_slug": schema.StringAttribute{ | |||
Required: true, | |||
Description: "Block Type slug, which determines the schema of the `data` JSON attribute. Use `prefect block types ls` to view all available Block type slugs.", | |||
PlanModifiers: []planmodifier.String{ | |||
stringplanmodifier.RequiresReplace(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
Summary
Closes #182.
Testing (click to expand)
Testing - OSS
Setup
First, run
docker-compose up -d
to start a local Prefect instance.Next, create a block resource to use with the local instance, and enable the provider:
Apply it with Terraform:
Now that it's created, try to read the value. We can use a quick Python script in
test.py
:Now let's change it so we can test
Update
. Change the value inmain.tf
tobaz
.Next, run
terraform plan
:Apply the change with
terraform apply
and then check the value again:The value was successfully updated.
Testing - Cloud
First, update the
main.tf
file to point to Staging and reset the block value tobar
:Apply it with Terraform:
Let's test it with Python:
And let's change it to
baz
inmain.tf
and apply it to confirmUpdate
:And get the new value with Python: