mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-09 08:27:48 +09:00
Adds terraform/opentofu state registry with locking. Implements: https://github.com/go-gitea/gitea/issues/33644. I also checked [encrypted state](https://opentofu.org/docs/language/state/encryption), it works out of the box. Docs PR: https://gitea.com/gitea/docs/pulls/357 --------- Co-authored-by: Andras Elso <elso.andras@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
303 lines
16 KiB
Go
303 lines
16 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/models/packages"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPackageTerraform(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
|
|
packageName := "te-st_pac.kage"
|
|
// generate the state json
|
|
genState := func(serial int) string {
|
|
return fmt.Sprintf(`{
|
|
"version": 4,
|
|
"terraform_version": "1.10.4",
|
|
"serial": %d,
|
|
"lineage": "bca3c5f6-01dc-cdad-5310-d1b12e02e430",
|
|
"outputs": {},
|
|
"resources": [{
|
|
"mode": "managed",
|
|
"type": "hello",
|
|
"name": "null_resource",
|
|
"provider": "provider[\"registry.terraform.io/hashicorp/null\"]",
|
|
"instances": [{
|
|
"schema_version": 0,
|
|
"attributes": {
|
|
"id": "3832416504545530133",
|
|
"triggers": null
|
|
},
|
|
"sensitive_attributes": []
|
|
}]
|
|
}],
|
|
"check_results": null
|
|
}`, serial)
|
|
}
|
|
genEncryptedState := func(serial int) string {
|
|
// json taken from wireshark inspection
|
|
return fmt.Sprintf(`{
|
|
"serial": %d,
|
|
"lineage": "4dad5e35-cbd5-ca2f-3c20-97c5e13b7033",
|
|
"meta": {
|
|
"key_provider.pbkdf2.foo": "eyJzYWx0IjoiWE55NnRDZTlSQnFNWGRqWm5xc202TU1DREZNbW5FbXRHczc2UXI0NEpXST0iLCJpdGVyYXRpb25zIjo2MDAwMDAsImhhc2hfZnVuY3Rpb24iOiJzaGE1MTIiLCJrZXlfbGVuZ3RoIjozMn0="
|
|
},
|
|
"encrypted_data": "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",
|
|
"encryption_version": "v0"
|
|
}`, serial)
|
|
}
|
|
genLock := func(uuid string) string {
|
|
return fmt.Sprintf(`{
|
|
"ID": "%s",
|
|
"Operation": "OperationTypePlan",
|
|
"Info": "",
|
|
"Who": "test-user@localhost",
|
|
"Version": "1.0",
|
|
"Created": "2023-01-01T00:00:00Z",
|
|
"Path": "test.tfstate"
|
|
}`, uuid)
|
|
}
|
|
|
|
url := fmt.Sprintf("/api/packages/%s/terraform/state/%s", user.Name, packageName)
|
|
lockURL := fmt.Sprintf("/api/packages/%s/terraform/state/%s/lock", user.Name, packageName)
|
|
|
|
// Covers non-existing package retrieval and deletion
|
|
t.Run("GetOrDeleteNonExisting", func(t *testing.T) {
|
|
// Package does not exist yet
|
|
req := NewRequest(t, "GET", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
// So deleting it also should not work
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
t.Run("RegularOperations", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
statefunc func(int) string
|
|
}{
|
|
{
|
|
name: "Plain",
|
|
statefunc: genState,
|
|
},
|
|
{
|
|
name: "Encrypted",
|
|
statefunc: genEncryptedState,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// 1. Lock the state
|
|
lockID := uuid.New().String()
|
|
lockInfo := genLock(lockID)
|
|
req := NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Verify lock property in DB
|
|
p, err := packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
|
|
require.NoError(t, err)
|
|
props, err := packages.GetPropertiesByName(t.Context(), packages.PropertyTypePackage, p.ID, "terraform.lock")
|
|
require.NoError(t, err)
|
|
require.Len(t, props, 1)
|
|
assert.Contains(t, props[0].Value, lockID)
|
|
|
|
// Upload state with correct Lock ID
|
|
state1 := tc.statefunc(1)
|
|
req = NewRequestWithBody(t, "POST", url+"?ID="+lockID, strings.NewReader(state1)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// Verify version created
|
|
pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeTerraformState, packageName, "1")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, pv)
|
|
|
|
// 3. Unlock the state
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Verify lock property is cleared
|
|
props, err = packages.GetPropertiesByName(t.Context(), packages.PropertyTypePackage, p.ID, "terraform.lock")
|
|
require.NoError(t, err)
|
|
require.Len(t, props, 1)
|
|
assert.Empty(t, props[0].Value)
|
|
|
|
// Get latest state
|
|
req = NewRequest(t, "GET", url).AddBasicAuth(user.Name)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, state1, resp.Body.String())
|
|
|
|
// Upload new version without lock
|
|
state2 := genState(2)
|
|
req = NewRequestWithBody(t, "POST", url, strings.NewReader(state2)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// 6. Delete the entire package
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Verify package is deleted from DB
|
|
_, err = packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
|
|
assert.ErrorIs(t, err, packages.ErrPackageNotExist)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("StateHistory", func(t *testing.T) {
|
|
// Upload 3 versions
|
|
for i := range 3 {
|
|
state := genState(i + 1) // 1-based
|
|
req := NewRequestWithBody(t, "POST", url, strings.NewReader(state)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
}
|
|
|
|
// Verify latest is 3
|
|
req := NewRequest(t, "GET", url).AddBasicAuth(user.Name)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, genState(3), resp.Body.String())
|
|
|
|
// Verify version 2 is accessible
|
|
req = NewRequest(t, "GET", url+"/versions/2").AddBasicAuth(user.Name)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, genState(2), resp.Body.String())
|
|
|
|
// Delete version 2
|
|
req = NewRequest(t, "DELETE", url+"/versions/2").AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
// Verify version 2 is gone from DB
|
|
_, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeTerraformState, packageName, "2")
|
|
assert.ErrorIs(t, err, packages.ErrPackageNotExist)
|
|
|
|
// Verify version 2 is gone from API
|
|
req = NewRequest(t, "GET", url+"/versions/2").AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
// Deleting latest version (3) should be forbidden
|
|
req = NewRequest(t, "DELETE", url+"/versions/3").AddBasicAuth(user.Name)
|
|
resp = MakeRequest(t, req, http.StatusForbidden)
|
|
assert.Contains(t, resp.Body.String(), "cannot delete the latest version")
|
|
|
|
// Cleanup
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
})
|
|
|
|
t.Run("BadOperations", func(t *testing.T) {
|
|
t.Run("LockingIssues", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lockID1 := uuid.New().String()
|
|
lockID2 := uuid.New().String()
|
|
lockInfo1 := genLock(lockID1)
|
|
lockInfo2 := genLock(lockID2)
|
|
|
|
// Pre-create package - it's required for unlock on the non-locked package to work
|
|
req := NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// Unlock non-locked state (should return 200)
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Lock the state
|
|
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Another lock attempt should fail
|
|
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo2)).AddBasicAuth(user.Name)
|
|
resp := MakeRequest(t, req, http.StatusLocked)
|
|
assert.JSONEq(t, lockInfo1, resp.Body.String())
|
|
|
|
// Unlock with wrong ID should fail
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo2)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusLocked)
|
|
|
|
// Same user locking again should fail (already locked)
|
|
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusLocked)
|
|
|
|
// Unlock with correct ID
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Clean up
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
})
|
|
|
|
t.Run("UploadWithoutValidLock", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lockID := uuid.New().String()
|
|
lockInfo := genLock(lockID)
|
|
|
|
// Lock the state
|
|
req := NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Upload without ID should fail
|
|
req = NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusLocked)
|
|
|
|
// Upload with wrong ID should fail
|
|
req = NewRequestWithBody(t, "POST", url+"?ID="+uuid.New().String(), strings.NewReader(genState(1))).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusLocked)
|
|
|
|
// Cleanup lock
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
})
|
|
|
|
t.Run("DeleteWithLock", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
// Create package and lock it
|
|
req := NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
lockID := uuid.New().String()
|
|
lockInfo := genLock(lockID)
|
|
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Delete package should fail
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusLocked)
|
|
|
|
// Verify package exists
|
|
p, err := packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, p)
|
|
|
|
// Cleanup
|
|
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
})
|
|
t.Run("PutEmpty", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
// safeguard against null payload
|
|
req := NewRequestWithBody(t, "POST", url, strings.NewReader("null")).AddBasicAuth(user.Name)
|
|
MakeRequest(t, req, http.StatusBadRequest)
|
|
})
|
|
})
|
|
}
|