Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b8bff8364 | ||
|
|
e2b6a4472e | ||
|
|
ac2d2a1288 |
@@ -266,3 +266,22 @@
|
||||
- UI: автоматическое обновление дашборда после create/delete
|
||||
- Рассмотреть добавление логов функций (kubectl logs)
|
||||
- Рассмотреть добавление time triggers в UI
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-15 (дополнение) — Fix по результатам баг-репорта
|
||||
|
||||
### Что исправлено в Terraform provider
|
||||
- `fission_package`: добавлен `ModifyPlan`, который автоматически пересчитывает `code_hash` по локальному коду (`source_dir`/`code_path`).
|
||||
- `fission_package`: добавлена валидация существования `environment` до создания/обновления package.
|
||||
- `fission_function`: добавлена валидация существования `environment` и `package` до create/update.
|
||||
- `fission_function`: добавлена pre-flight валидация `entrypoint` для Python-исходника (`main.func` должен существовать как `def func(`).
|
||||
- Добавлены unit-тесты на новый функционал (`code_hash`, entrypoint validation).
|
||||
|
||||
### Проверка на живом кластере
|
||||
- После изменения `examples/hello-python/code/main.py` `terraform plan` теперь показывает `fission_package.hello will be updated in-place` с изменением `code_hash`.
|
||||
- Конфигурация с несуществующим `environment` теперь падает на этапе apply с ошибкой валидации.
|
||||
- Конфигурация с неверным `entrypoint` теперь падает на этапе apply с ошибкой валидации.
|
||||
|
||||
### Ограничения
|
||||
- Runtime-проблемы Fission (зависания на cold start/таймауты выполнения) в этом изменении не трогались: исправлен только provider-слой валидации и обнаружения изменений.
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
# Bug Report: Fission + Terraform Integration Testing (2026-04-15)
|
||||
|
||||
## Executive Summary
|
||||
- **34 functions deployed**, **17 working correctly**
|
||||
- **6 CRITICAL/HIGH bugs identified**
|
||||
- **3 limitations/quirks documented**
|
||||
|
||||
---
|
||||
|
||||
## 🔴 CRITICAL BUGS
|
||||
|
||||
### BUG #1: Hanging on cold start with syntax errors
|
||||
**Severity:** CRITICAL
|
||||
**Scope:** Fission runtime
|
||||
**Symptoms:**
|
||||
- Function with syntax error in `main.py` → curl times out 60+ sec without response
|
||||
- Function without `main()` entrypoint → same behavior
|
||||
- Function with broken import → same behavior
|
||||
- Router never returns 500/400, just silently hangs
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ curl /neg/syntax → timeout (exit 28, HTTP 000)
|
||||
$ curl /neg/nomain → timeout (exit 28, HTTP 000)
|
||||
$ curl /neg/badimport → hangs indefinitely
|
||||
```
|
||||
|
||||
**Root Cause:** Pool Manager has no timeout on code loading/importing; python-env container hangs when trying to import broken module.
|
||||
|
||||
**Impact:** Broken functions make router unavailable for other functions (all requests on same pod hang or queue up).
|
||||
|
||||
---
|
||||
|
||||
### BUG #2: Terraform provider ignores code changes
|
||||
**Severity:** HIGH
|
||||
**Scope:** Terraform provider
|
||||
**Symptoms:**
|
||||
- Modified `code/main.py` on disk
|
||||
- Ran `terraform plan` → `No changes needed`
|
||||
- Ran `terraform apply` → nothing recreated
|
||||
- `curl` still returns OLD code
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ sed 's/v2/v3/' code/ok/main.py
|
||||
$ terraform apply
|
||||
→ "no changes needed"
|
||||
$ curl /auto/ok
|
||||
→ "ok-auto-func-UPDATED-v2" (old version!)
|
||||
```
|
||||
|
||||
**Root Cause:** Provider does not recalculate `code_hash` when source files change. Likely uses mtime check incorrectly or doesn't hash at all.
|
||||
|
||||
**Impact:** Developers cannot update function code without manually tweaking other parameters or destroying/recreating resource.
|
||||
|
||||
**Workaround:** Manually trigger by changing environment version or add explicit `code_hash` parameter.
|
||||
|
||||
---
|
||||
|
||||
### BUG #3: Race condition during concurrent package update + invoke
|
||||
**Severity:** HIGH
|
||||
**Scope:** Kubernetes + Fission runtime
|
||||
**Symptoms:**
|
||||
- Started 30 parallel invokes
|
||||
- Simultaneously modified code and ran `terraform apply`
|
||||
- Result: **11 out of 30 invokes lost** (no response returned)
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ for i in {1..30}; do curl /auto/echo & done &
|
||||
$ terraform apply # simultaneously
|
||||
→ HTTP codes: 19 success, 11 lost/timeout
|
||||
```
|
||||
|
||||
**Root Cause:** No coordination between Terraform provider package CRD updates and live pods using old code versions.
|
||||
|
||||
**Impact:** Request loss (503/timeout), potential data loss.
|
||||
|
||||
---
|
||||
|
||||
### BUG #4: No timeout on function execution
|
||||
**Severity:** HIGH
|
||||
**Scope:** Fission runtime
|
||||
**Symptoms:**
|
||||
- Function with very long operation (fib(100)) → curl times out after 30 sec
|
||||
- No HTTP 504 or 408 sent by router
|
||||
- Pod continues computation until client disconnects
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ curl --max-time 30 /deep-recursion
|
||||
→ timeout (exit 28, HTTP 000)
|
||||
```
|
||||
|
||||
**Root Cause:** Fission router has no timeout on downstream pod request; Python environment has no built-in execution timeout.
|
||||
|
||||
**Impact:** Blocking requests on slow functions can exhaust pod pool and block other functions.
|
||||
|
||||
---
|
||||
|
||||
### BUG #5: No foreign key validation on deploy
|
||||
**Severity:** MEDIUM
|
||||
**Scope:** Terraform provider + Fission CRD validation
|
||||
**Symptoms:**
|
||||
- Created package/function referencing non-existent environment
|
||||
- Terraform applied successfully
|
||||
- Function only fails at invoke time (too late)
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ tf apply (package references "nonexistent-env")
|
||||
→ Apply complete! Resources added successfully
|
||||
$ curl /missing-ref
|
||||
→ 404 or timeout (errors caught too late)
|
||||
```
|
||||
|
||||
**Root Cause:** Provider does not validate environment/package references before creating CRDs. K8s CRD accepts any string value.
|
||||
|
||||
**Impact:** Bad manifests deploy silently, errors only surface during invocation.
|
||||
|
||||
---
|
||||
|
||||
### BUG #6: Invalid entrypoint not validated until invoke
|
||||
**Severity:** MEDIUM
|
||||
**Scope:** Fission runtime
|
||||
**Symptoms:**
|
||||
- Entrypoint references nonexistent function in code
|
||||
- Terraform/Fission accept it
|
||||
- First invoke hangs/times out (same as syntax error)
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ entrypoint = "main.nonexistent_function"
|
||||
$ curl /bad-entrypoint
|
||||
→ timeout (HTTP 000)
|
||||
```
|
||||
|
||||
**Root Cause:** No pre-flight validation of entrypoint. Only caught during cold start import.
|
||||
|
||||
**Impact:** Same as БАГ #1 — hangs entire pod until timeout.
|
||||
|
||||
---
|
||||
|
||||
### Limitation #1: Upload payload size limit
|
||||
**Severity:** MEDIUM
|
||||
**Symptoms:** Uploading ~1MB+ payload to function endpoint hangs connection
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
$ dd if=/dev/zero bs=1M count=1 | curl --data-binary @- /auto/ok
|
||||
→ timeout
|
||||
```
|
||||
|
||||
**Root Cause:** Likely nginx ingress `client_max_body_size` limit (default ~1MB).
|
||||
|
||||
**Impact:** Cannot send large payloads to functions via HTTP.
|
||||
|
||||
---
|
||||
|
||||
### Limitation #2: Cold start depends on image pull time
|
||||
**Severity:** LOW
|
||||
**Symptoms:** First invoke can be slow, especially for new image versions
|
||||
|
||||
**Evidence:** Examples with new python-env versions took 5-10 sec on first invoke.
|
||||
|
||||
---
|
||||
|
||||
### Limitation #3: No function versioning (v1, v2, canary)
|
||||
**Severity:** LOW
|
||||
**Symptoms:** No way to specify version in Terraform/API
|
||||
|
||||
**Impact:** Cannot safely update functions with gradual rollout strategy.
|
||||
|
||||
---
|
||||
|
||||
## ✅ WHAT WORKS WELL
|
||||
|
||||
- Parallel invokes (50+) → all pass
|
||||
- State consistency between Terraform and K8s
|
||||
- Orphaning recovery (manual CRD delete → Terraform recreates)
|
||||
- Console API (CRUD, invoke, delete)
|
||||
- Auth validation (401 on missing JWT)
|
||||
- HTTP method validation (405 on POST to GET-only function)
|
||||
- 404 on nonexistent endpoints
|
||||
- Package + trigger + function CRUD integration
|
||||
|
||||
---
|
||||
|
||||
## 📋 RECOMMENDATIONS
|
||||
|
||||
1. **CRITICAL:** Add execution timeout in router (~60 sec default, configurable)
|
||||
2. **CRITICAL:** Add timeout + graceful shutdown in Pool Manager during code loading
|
||||
3. **HIGH:** Fix Terraform provider to recalculate code_hash on source changes
|
||||
4. **HIGH:** Add coordination between package updates and live pods (graceful drain/reload)
|
||||
5. **HIGH:** Add foreign key validation (environment/package references must exist)
|
||||
6. **HIGH:** Add entrypoint validation during deploy (check function exists in code)
|
||||
7. **MEDIUM:** Document payload size limits and how to adjust
|
||||
8. **MEDIUM:** Add pre-flight code validation (syntax check) on deploy
|
||||
9. **LOW:** Implement function versioning/canary deployment support
|
||||
|
||||
---
|
||||
|
||||
## 📊 TESTING STATISTICS
|
||||
|
||||
- Functions deployed: **34**
|
||||
- Working correctly (5 sec response): **17**
|
||||
- Hanging indefinitely: **4** (syntax-error, no-main, badimport, deep-recursion)
|
||||
- Timing out: **1** (deep-recursion)
|
||||
- Failing correctly (500): **2** (error, runtime-error)
|
||||
- Not deployed: **1** (badimport partially)
|
||||
|
||||
---
|
||||
|
||||
## PARALLEL STRESS RESULTS
|
||||
|
||||
- 50 concurrent invokes to single function → **100% success**
|
||||
- 30 concurrent invokes during terraform apply → **63% success rate** (race condition)
|
||||
|
||||
---
|
||||
|
||||
## TESTING TIMELINE
|
||||
- Start: 2026-04-15 07:00 UTC
|
||||
- End: 2026-04-15 09:00 UTC
|
||||
- Duration: **2 hours** continuous integration testing
|
||||
- Functions tested: ~30 different scenarios
|
||||
- Test cases executed: ~150+
|
||||
- Terraform scenarios: 15+ (create, update, delete, orphaning, race, validation, bad manifests)
|
||||
- Edge cases covered: syntax errors, missing deps, race conditions, payload limits, cold start hangs, entrypoint validation
|
||||
|
||||
---
|
||||
|
||||
## NOTES FOR FOLLOW-UP
|
||||
|
||||
- Syntax error functions should ideally reject at deploy time (validate code before accepting)
|
||||
- Code changes need lifecycle management (versioning, rollback, canary deployment)
|
||||
- Router needs observability: span traces, request duration metrics, timeout tracking
|
||||
- Consider adding health checks per pod to detect hung function execution
|
||||
- Implement stricter validation during CRD creation (foreign keys, entrypoint existence)
|
||||
@@ -2,7 +2,9 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
@@ -107,6 +109,22 @@ func (r *FunctionResource) Create(ctx context.Context, req resource.CreateReques
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
|
||||
if err := ensureEnvironmentExists(ctx, r.client, namespace, plan.Environment.ValueString()); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Environment для Function", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err := ensurePackageExists(ctx, r.client, namespace, plan.PackageName.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Package для Function", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateEntrypointAgainstPackageSource(plan.Entrypoint.ValueString(), pkg); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации entrypoint", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
functionObject := functionToUnstructured(plan, namespace)
|
||||
|
||||
createdFunction, err := r.client.CreateFunction(ctx, functionObject)
|
||||
@@ -152,6 +170,22 @@ func (r *FunctionResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
|
||||
if err := ensureEnvironmentExists(ctx, r.client, namespace, plan.Environment.ValueString()); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Environment для Function", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err := ensurePackageExists(ctx, r.client, namespace, plan.PackageName.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Package для Function", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateEntrypointAgainstPackageSource(plan.Entrypoint.ValueString(), pkg); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации entrypoint", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
existingFunction, err := r.client.GetFunction(ctx, namespace, plan.Name.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка получения Function перед обновлением", err.Error())
|
||||
@@ -254,3 +288,47 @@ func unstructuredToFunctionModel(functionObject *unstructured.Unstructured, base
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func validateEntrypointAgainstPackageSource(entrypoint string, pkg *unstructured.Unstructured) error {
|
||||
parts := strings.Split(entrypoint, ".")
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return fmt.Errorf("entrypoint %q должен иметь формат module.function", entrypoint)
|
||||
}
|
||||
|
||||
if parts[0] != "main" {
|
||||
return nil
|
||||
}
|
||||
|
||||
literalSource, found, err := unstructured.NestedString(pkg.Object, "spec", "deployment", "literal")
|
||||
if err != nil || !found || literalSource == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
literalBytes, err := base64.StdEncoding.DecodeString(literalSource)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := string(literalBytes)
|
||||
if looksLikePythonSource(source) {
|
||||
signature := fmt.Sprintf("def %s(", parts[1])
|
||||
if !strings.Contains(source, signature) {
|
||||
return fmt.Errorf("entrypoint %q не найден в Python исходнике пакета (ожидался %q)", entrypoint, signature)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func looksLikePythonSource(source string) bool {
|
||||
trimmed := strings.TrimSpace(source)
|
||||
if strings.HasPrefix(trimmed, "def ") || strings.Contains(source, "\ndef ") {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(source, "import ") && !strings.Contains(source, "func ") && !strings.Contains(source, "module.exports") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
@@ -36,3 +37,40 @@ func TestFunctionToUnstructuredAndBack(t *testing.T) {
|
||||
t.Fatalf("InvokeStrategy not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEntrypointAgainstPackageSourcePythonOK(t *testing.T) {
|
||||
source := "def main():\n return 'ok'\n"
|
||||
pkg := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"deployment": map[string]interface{}{
|
||||
"literal": base64.StdEncoding.EncodeToString([]byte(source)),
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
if err := validateEntrypointAgainstPackageSource("main.main", pkg); err != nil {
|
||||
t.Fatalf("expected valid entrypoint, got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEntrypointAgainstPackageSourcePythonMissing(t *testing.T) {
|
||||
source := "def another():\n return 'ok'\n"
|
||||
pkg := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"deployment": map[string]interface{}{
|
||||
"literal": base64.StdEncoding.EncodeToString([]byte(source)),
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
if err := validateEntrypointAgainstPackageSource("main.main", pkg); err == nil {
|
||||
t.Fatalf("expected validation error for missing python function")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEntrypointAgainstPackageSourceBadFormat(t *testing.T) {
|
||||
pkg := &unstructured.Unstructured{}
|
||||
if err := validateEntrypointAgainstPackageSource("main", pkg); err == nil {
|
||||
t.Fatalf("expected validation error for bad entrypoint format")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
|
||||
var _ resource.Resource = &PackageResource{}
|
||||
var _ resource.ResourceWithImportState = &PackageResource{}
|
||||
var _ resource.ResourceWithModifyPlan = &PackageResource{}
|
||||
|
||||
// Изменено: 2026-04-14 19:45 UTC.
|
||||
// Resource для управления Fission Package через Kubernetes CRD API.
|
||||
@@ -77,6 +79,7 @@ func (r *PackageResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
},
|
||||
"code_hash": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Произвольный хеш кода для контроля изменений.",
|
||||
},
|
||||
"build_command": schema.StringAttribute{
|
||||
@@ -104,6 +107,46 @@ func (r *PackageResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyPlan пересчитывает code_hash по локальному коду, чтобы terraform видел изменения source_dir/code_path.
|
||||
func (r *PackageResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
|
||||
if req.Plan.Raw.IsNull() {
|
||||
return
|
||||
}
|
||||
|
||||
var plan packageResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var config packageResourceModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if hasManualCodeHash(config.CodeHash) {
|
||||
return
|
||||
}
|
||||
|
||||
if plan.SourceDir.IsUnknown() || plan.CodePath.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if !validatePackageSource(plan.SourceDir, plan.CodePath, &resp.Diagnostics) {
|
||||
return
|
||||
}
|
||||
|
||||
literalBytes, err := loadPackageLiteral(plan.SourceDir.ValueString(), plan.CodePath.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка чтения исходного кода пакета", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.CodeHash = types.StringValue(calculateCodeHash(literalBytes))
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
// Configure получает клиент из provider.Configure().
|
||||
func (r *PackageResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
@@ -135,12 +178,21 @@ func (r *PackageResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
return
|
||||
}
|
||||
|
||||
if err := ensureEnvironmentExists(ctx, r.client, namespace, plan.Environment.ValueString()); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Environment для Package", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
literalBytes, err := loadPackageLiteral(plan.SourceDir.ValueString(), plan.CodePath.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка чтения исходного кода пакета", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !hasManualCodeHash(plan.CodeHash) {
|
||||
plan.CodeHash = types.StringValue(calculateCodeHash(literalBytes))
|
||||
}
|
||||
|
||||
packageObject := packageToUnstructured(plan, namespace, literalBytes)
|
||||
createdPackage, err := r.client.CreatePackage(ctx, packageObject)
|
||||
if err != nil {
|
||||
@@ -189,12 +241,21 @@ func (r *PackageResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
return
|
||||
}
|
||||
|
||||
if err := ensureEnvironmentExists(ctx, r.client, namespace, plan.Environment.ValueString()); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка валидации Environment для Package", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
literalBytes, err := loadPackageLiteral(plan.SourceDir.ValueString(), plan.CodePath.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка чтения исходного кода пакета", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !hasManualCodeHash(plan.CodeHash) {
|
||||
plan.CodeHash = types.StringValue(calculateCodeHash(literalBytes))
|
||||
}
|
||||
|
||||
existingPackage, err := r.client.GetPackage(ctx, namespace, plan.Name.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка получения Package перед обновлением", err.Error())
|
||||
@@ -343,6 +404,7 @@ func unstructuredToPackageModel(packageObject *unstructured.Unstructured, base p
|
||||
buildCommand, _, _ := unstructured.NestedString(packageObject.Object, "spec", "buildcmd")
|
||||
buildStatus, _, _ := unstructured.NestedString(packageObject.Object, "status", "buildstatus")
|
||||
buildLog, _, _ := unstructured.NestedString(packageObject.Object, "status", "buildlog")
|
||||
deploymentLiteral, _, _ := unstructured.NestedString(packageObject.Object, "spec", "deployment", "literal")
|
||||
|
||||
state := packageResourceModel{
|
||||
ID: types.StringValue(fmt.Sprintf("%s/%s", packageObject.GetNamespace(), packageObject.GetName())),
|
||||
@@ -371,5 +433,20 @@ func unstructuredToPackageModel(packageObject *unstructured.Unstructured, base p
|
||||
state.BuildLog = types.StringValue(buildLog)
|
||||
}
|
||||
|
||||
if deploymentLiteral != "" {
|
||||
if literalBytes, err := base64.StdEncoding.DecodeString(deploymentLiteral); err == nil {
|
||||
state.CodeHash = types.StringValue(calculateCodeHash(literalBytes))
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func hasManualCodeHash(codeHash types.String) bool {
|
||||
return !codeHash.IsNull() && !codeHash.IsUnknown() && codeHash.ValueString() != ""
|
||||
}
|
||||
|
||||
func calculateCodeHash(literalBytes []byte) string {
|
||||
sum := sha256.Sum256(literalBytes)
|
||||
return fmt.Sprintf("%x", sum)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -113,6 +115,16 @@ func TestResolveNamespace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCodeHash(t *testing.T) {
|
||||
input := []byte("def main():\n return 'ok'\n")
|
||||
got := calculateCodeHash(input)
|
||||
|
||||
expected := fmt.Sprintf("%x", sha256.Sum256(input))
|
||||
if got != expected {
|
||||
t.Fatalf("unexpected code hash: got %q want %q", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstructuredToPackageModelSetsNullComputed(t *testing.T) {
|
||||
obj := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "fission.io/v1",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"terraform-provider-fission/internal/client"
|
||||
)
|
||||
|
||||
func ensureEnvironmentExists(ctx context.Context, fissionClient *client.Client, namespace, name string) error {
|
||||
_, err := fissionClient.GetEnvironment(ctx, namespace, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment %q не найден в namespace %q: %w", name, namespace, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensurePackageExists(ctx context.Context, fissionClient *client.Client, namespace, name string) (*unstructured.Unstructured, error) {
|
||||
pkg, err := fissionClient.GetPackage(ctx, namespace, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("package %q не найден в namespace %q: %w", name, namespace, err)
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
Reference in New Issue
Block a user