diff --git a/terraform/provider/internal/resources/function_resource.go b/terraform/provider/internal/resources/function_resource.go index dea1dae..00348d5 100644 --- a/terraform/provider/internal/resources/function_resource.go +++ b/terraform/provider/internal/resources/function_resource.go @@ -32,8 +32,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// buildTimeout — максимальное время ожидания kaniko-сборки образа. -const buildTimeout = 5 * time.Minute +// defaultBuildTimeoutSec — дефолтный таймаут ожидания kaniko-сборки (300 сек = 5 мин). +// Пользователь может переопределить через build_timeout_sec. +const defaultBuildTimeoutSec = 300 var _ resource.Resource = &FunctionResource{} @@ -58,8 +59,10 @@ type FunctionModel struct { // code_hash — sha256/md5 zip-файла, пользователь задаёт через filemd5(). // Изменение hash → провайдер перезагружает код и запускает пересборку. CodeHash types.String `tfsdk:"code_hash"` - Phase types.String `tfsdk:"phase"` - ImageRef types.String `tfsdk:"image_ref"` + // build_timeout_sec — максимальное ожидание kaniko-сборки. Дефолт 300 сек. + BuildTimeoutSec types.Int64 `tfsdk:"build_timeout_sec"` + Phase types.String `tfsdk:"phase"` + ImageRef types.String `tfsdk:"image_ref"` } func (r *FunctionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -114,6 +117,13 @@ func (r *FunctionResource) Schema(_ context.Context, _ resource.SchemaRequest, r "code_hash": schema.StringAttribute{ Optional: true, }, + // build_timeout_sec — таймаут ожидания kaniko-сборки. Дефолт 300 сек (5 мин). + // Увеличь если функция с тяжёлыми зависимостями (например torch). + "build_timeout_sec": schema.Int64Attribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Таймаут ожидания сборки образа в секундах. По умолчанию 300 (5 мин).", + }, // phase, image_ref — только для чтения, вычисляются оператором "phase": schema.StringAttribute{ Computed: true, @@ -178,7 +188,11 @@ func (r *FunctionResource) Create(ctx context.Context, req resource.CreateReques resp.Diagnostics.AddError("upload code", err.Error()) return } - fn, err = r.client.WaitReady(ctx, ns, fn.Name, buildTimeout) + buildSec := plan.BuildTimeoutSec.ValueInt64() + if buildSec <= 0 { + buildSec = defaultBuildTimeoutSec + } + fn, err = r.client.WaitReady(ctx, ns, fn.Name, time.Duration(buildSec)*time.Second) if err != nil { resp.Diagnostics.AddError("waiting for function ready", err.Error()) return @@ -246,7 +260,11 @@ func (r *FunctionResource) Update(ctx context.Context, req resource.UpdateReques resp.Diagnostics.AddError("upload code", err.Error()) return } - fn, err = r.client.WaitReady(ctx, ns, name, buildTimeout) + buildSec := plan.BuildTimeoutSec.ValueInt64() + if buildSec <= 0 { + buildSec = defaultBuildTimeoutSec + } + fn, err = r.client.WaitReady(ctx, ns, name, time.Duration(buildSec)*time.Second) if err != nil { resp.Diagnostics.AddError("waiting for function ready", err.Error()) return @@ -269,20 +287,26 @@ func (r *FunctionResource) Delete(ctx context.Context, req resource.DeleteReques } // fnToModel конвертирует API-ответ + plan (для локальных полей) → state модель. -// plan используется для code_path и code_hash — API их не хранит. +// plan используется для code_path, code_hash, build_timeout_sec — API их не хранит. func fnToModel(plan FunctionModel, fn *client.FunctionResponse) FunctionModel { + buildTimeoutSec := plan.BuildTimeoutSec + // Если не задан пользователем — записываем дефолт чтобы не было null в state + if buildTimeoutSec.IsNull() || buildTimeoutSec.IsUnknown() || buildTimeoutSec.ValueInt64() <= 0 { + buildTimeoutSec = types.Int64Value(defaultBuildTimeoutSec) + } return FunctionModel{ - Namespace: types.StringValue(fn.Namespace), - Name: types.StringValue(fn.Name), - Runtime: types.StringValue(fn.Runtime), - Entrypoint: types.StringValue(fn.Entrypoint), - MemoryMB: types.Int64Value(int64(fn.MemoryMB)), - TimeoutSec: types.Int64Value(int64(fn.TimeoutSec)), - EnvVars: plan.EnvVars, // API возвращает null для пустого map — берём из plan - CodePath: plan.CodePath, - CodeHash: plan.CodeHash, - Phase: types.StringValue(fn.Phase), - ImageRef: types.StringValue(fn.ImageRef), + Namespace: types.StringValue(fn.Namespace), + Name: types.StringValue(fn.Name), + Runtime: types.StringValue(fn.Runtime), + Entrypoint: types.StringValue(fn.Entrypoint), + MemoryMB: types.Int64Value(int64(fn.MemoryMB)), + TimeoutSec: types.Int64Value(int64(fn.TimeoutSec)), + EnvVars: plan.EnvVars, // API возвращает null для пустого map — берём из plan + CodePath: plan.CodePath, + CodeHash: plan.CodeHash, + BuildTimeoutSec: buildTimeoutSec, + Phase: types.StringValue(fn.Phase), + ImageRef: types.StringValue(fn.ImageRef), } } diff --git a/terraform/provider/internal/resources/job_resource.go b/terraform/provider/internal/resources/job_resource.go index fee5df9..3df77d8 100644 --- a/terraform/provider/internal/resources/job_resource.go +++ b/terraform/provider/internal/resources/job_resource.go @@ -28,8 +28,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// jobTimeout — максимальное время ожидания завершения джоба. -const jobTimeout = 10 * time.Minute +// defaultWaitTimeoutSec — дефолтный таймаут ожидания завершения джоба (600 сек = 10 мин). +// Пользователь может переопределить через wait_timeout_sec. +const defaultWaitTimeoutSec = 600 var _ resource.Resource = &JobResource{} @@ -43,10 +44,12 @@ func NewJobResource() resource.Resource { // JobModel — модель состояния terraform для sless_job. type JobModel struct { - Namespace types.String `tfsdk:"namespace"` - Name types.String `tfsdk:"name"` - Function types.String `tfsdk:"function"` - EventJSON types.String `tfsdk:"event_json"` + Namespace types.String `tfsdk:"namespace"` + Name types.String `tfsdk:"name"` + Function types.String `tfsdk:"function"` + EventJSON types.String `tfsdk:"event_json"` + // wait_timeout_sec — максимальное ожидание завершения джоба. Дефолт 600 сек. + WaitTimeoutSec types.Int64 `tfsdk:"wait_timeout_sec"` Phase types.String `tfsdk:"phase"` StartTime types.String `tfsdk:"start_time"` CompletionTime types.String `tfsdk:"completion_time"` @@ -88,6 +91,12 @@ func (r *JobResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * stringplanmodifier.RequiresReplace(), }, }, + // wait_timeout_sec — сколько ждать завершения джоба. Увеличь если код долго работает (например миграция БД). + "wait_timeout_sec": schema.Int64Attribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Таймаут ожидания завершения джоба в секундах. По умолчанию 600 (10 мин).", + }, // Computed — заполняются после завершения джоба "phase": schema.StringAttribute{ Computed: true, @@ -148,7 +157,11 @@ func (r *JobResource) Create(ctx context.Context, req resource.CreateRequest, re } // Блокируем apply до завершения джоба (Succeeded или Failed) - j, err := r.client.WaitJobDone(ctx, ns, plan.Name.ValueString(), jobTimeout) + waitSec := plan.WaitTimeoutSec.ValueInt64() + if waitSec <= 0 { + waitSec = defaultWaitTimeoutSec + } + j, err := r.client.WaitJobDone(ctx, ns, plan.Name.ValueString(), time.Duration(waitSec)*time.Second) if err != nil { resp.Diagnostics.AddError("waiting for job to complete", err.Error()) return @@ -198,11 +211,16 @@ func (r *JobResource) Delete(ctx context.Context, req resource.DeleteRequest, re // jobToModel конвертирует API-ответ → state модель. func jobToModel(plan JobModel, j *client.JobResponse) JobModel { + waitTimeoutSec := plan.WaitTimeoutSec + if waitTimeoutSec.IsNull() || waitTimeoutSec.IsUnknown() || waitTimeoutSec.ValueInt64() <= 0 { + waitTimeoutSec = types.Int64Value(defaultWaitTimeoutSec) + } return JobModel{ Namespace: types.StringValue(j.Namespace), Name: types.StringValue(j.Name), Function: types.StringValue(j.FunctionRef), - EventJSON: plan.EventJSON, // API отдаёт event_json но берём из plan для консистентности + EventJSON: plan.EventJSON, + WaitTimeoutSec: waitTimeoutSec, Phase: types.StringValue(j.Phase), StartTime: types.StringValue(j.StartTime), CompletionTime: types.StringValue(j.CompletionTime),