diff --git a/examples/hello-go/main.tf b/examples/hello-go/main.tf index 137d8b2..fa5a039 100644 --- a/examples/hello-go/main.tf +++ b/examples/hello-go/main.tf @@ -5,7 +5,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.14" + version = "~> 0.1.16" } } } diff --git a/examples/hello-node/main.tf b/examples/hello-node/main.tf index 5f4a402..18107d6 100644 --- a/examples/hello-node/main.tf +++ b/examples/hello-node/main.tf @@ -11,7 +11,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.13" + version = "~> 0.1.16" } } } diff --git a/examples/notes-python/main.tf b/examples/notes-python/main.tf index d4c1772..f6ef5b6 100644 --- a/examples/notes-python/main.tf +++ b/examples/notes-python/main.tf @@ -14,7 +14,7 @@ terraform { # Провайдер для управления serverless функциями через sless API sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.13" + version = "~> 0.1.16" } } } diff --git a/examples/pg-list-python/main.tf b/examples/pg-list-python/main.tf index 7d3d427..242502c 100644 --- a/examples/pg-list-python/main.tf +++ b/examples/pg-list-python/main.tf @@ -5,7 +5,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.14" + version = "~> 0.1.16" } } } diff --git a/examples/simple-node/main.tf b/examples/simple-node/main.tf index 2e1927a..d27735d 100644 --- a/examples/simple-node/main.tf +++ b/examples/simple-node/main.tf @@ -19,7 +19,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.13" + version = "~> 0.1.16" } } } diff --git a/examples/simple-python/main.tf b/examples/simple-python/main.tf index d0d3d86..331023f 100644 --- a/examples/simple-python/main.tf +++ b/examples/simple-python/main.tf @@ -18,7 +18,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.13" + version = "~> 0.1.16" } } } diff --git a/terraform/provider/internal/resources/function_resource.go b/terraform/provider/internal/resources/function_resource.go index 792b894..5ffbb1c 100644 --- a/terraform/provider/internal/resources/function_resource.go +++ b/terraform/provider/internal/resources/function_resource.go @@ -3,10 +3,11 @@ // // Lifecycle: // -// Create: POST /functions → если code_path задан: upload zip → WaitReady (5 мин) -// Read: GET /functions/{name} → sync state -// Update: PUT /functions/{name} → если code_hash изменился: upload zip → WaitReady -// Delete: DELETE /functions/{name} +// Create: POST /functions → если code_path задан: upload zip → WaitReady (5 мин) +// Read: GET /functions/{name} → sync state +// Update: PUT /functions/{name} → если code_hash изменился: upload zip → WaitReady +// Delete: DELETE /functions/{name} +// ModifyPlan: вычисляет hash source_dir на фазе plan → terraform видит diff при изменении кода // // code_hash — пользователь задаёт сам (например filemd5("./handler.zip")). // Изменение hash → провайдер перезагружает zip и ждёт новой сборки. @@ -47,6 +48,7 @@ import ( const defaultBuildTimeoutSec = 300 var _ resource.Resource = &FunctionResource{} +var _ resource.ResourceWithModifyPlan = &FunctionResource{} type FunctionResource struct { client *client.Client @@ -344,6 +346,34 @@ func (r *FunctionResource) Delete(ctx context.Context, req resource.DeleteReques } } +// ModifyPlan вычисляет hash директории source_dir на фазе terraform plan. +// Без этого terraform не видит изменения файлов кода между apply — потому что +// code_hash Computed и при plan берётся из state. Здесь мы явно пересчитываем +// и записываем актуальный hash в plan → если отличается от state, terraform +// показывает diff и вызывает Update. +func (r *FunctionResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // При destroy plan пустой — ничего не делаем + if req.Plan.Raw.IsNull() { + return + } + var plan FunctionModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + // source_dir не задан — нечего считать + if plan.SourceDir.IsNull() || plan.SourceDir.ValueString() == "" { + return + } + _, hash, err := zipDir(plan.SourceDir.ValueString()) + if err != nil { + // Директория может не существовать при первом init — не фейлим plan + return + } + plan.CodeHash = types.StringValue(hash) + resp.Diagnostics.Append(resp.Plan.Set(ctx, plan)...) +} + // fnToModel конвертирует API-ответ + plan (для локальных полей) → state модель. // plan используется для code_path, code_hash, build_timeout_sec — API их не хранит. func fnToModel(plan FunctionModel, fn *client.FunctionResponse) FunctionModel {