Add terraform state registry (#36710)

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>
This commit is contained in:
TheFox0x7
2026-04-06 22:41:17 +02:00
committed by GitHub
parent dc197a0058
commit ff777cd2ad
30 changed files with 1379 additions and 58 deletions
+78 -10
View File
@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
"net/url"
"time"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
@@ -18,13 +19,13 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
arch_module "code.gitea.io/gitea/modules/packages/arch"
container_module "code.gitea.io/gitea/modules/packages/container"
debian_module "code.gitea.io/gitea/modules/packages/debian"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
terraform_module "code.gitea.io/gitea/modules/packages/terraform"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
@@ -35,6 +36,8 @@ import (
"code.gitea.io/gitea/services/forms"
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
"github.com/google/uuid"
)
const (
@@ -315,6 +318,11 @@ func ViewPackageVersion(ctx *context.Context) {
}
ctx.Data["LatestVersions"] = pvs
ctx.Data["TotalVersionCount"] = pvsTotal
ctx.Data["PackageVersionViewData"], err = packages_service.GetSpecManager().Get(pd.Package.Type).GetViewPackageVersionData(ctx, pd)
if err != nil {
ctx.ServerError("GetViewPackageVersionData", err)
return
}
ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin()
@@ -498,14 +506,18 @@ func packageSettingsPostActionDelete(ctx *context.Context) {
ctx.Redirect(pd.PackageSettingsLink())
return
}
if err := packages_service.RemovePackage(ctx, ctx.Doer, pd.Package); err != nil {
log.Error("Error deleting package: %v", err)
ctx.Flash.Error(ctx.Tr("packages.settings.delete.error"))
} else {
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
errTr := util.ErrorAsTranslatable(err)
if errTr == nil {
ctx.ServerError("RemovePackage", err)
return
}
ctx.Flash.Error(errTr.Translate(ctx.Locale))
ctx.Redirect(pd.PackageSettingsLink())
return
}
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages")
}
@@ -518,18 +530,21 @@ func PackageVersionDelete(ctx *context.Context) {
}
if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pd.Version); err != nil {
log.Error("Error deleting package version: %v", err)
ctx.Flash.Error(ctx.Tr("packages.settings.delete.error"))
errTr := util.ErrorAsTranslatable(err)
if errTr == nil {
ctx.ServerError("RemovePackageVersion", err)
return
}
ctx.Flash.Error(errTr.Translate(ctx.Locale))
} else {
ctx.Flash.Success(ctx.Tr("packages.settings.delete.version.success"))
}
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
// redirect to the package if there are still versions available
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: pd.Package.ID, IsInternal: optional.Some(false)}); has {
redirectURL = pd.PackageWebLink()
}
ctx.Redirect(redirectURL)
}
@@ -553,3 +568,56 @@ func DownloadPackageFile(ctx *context.Context) {
packages_helper.ServePackageFile(ctx, s, u, pf)
}
// ActionPackageTerraformLock locks a terraform state
func ActionPackageTerraformLock(ctx *context.Context) {
pd := ctx.Package.Descriptor
if pd.Package.Type != packages_model.TypeTerraformState {
ctx.NotFound(nil)
return
}
existingLock, err := terraform_module.GetLock(ctx, pd.Package.ID)
if err != nil {
ctx.ServerError("GetLock", err)
return
}
if existingLock.IsLocked() {
ctx.Flash.Error(ctx.Tr("packages.terraform.lock.error.already_locked"))
ctx.Redirect(pd.VersionWebLink())
return
}
lockID := uuid.New().String()
lockInfo := &terraform_module.LockInfo{
ID: lockID,
Operation: "Manual UI Lock",
Who: ctx.Doer.Name,
Created: time.Now(),
}
if err := terraform_module.SetLock(ctx, pd.Package.ID, lockInfo); err != nil {
ctx.ServerError("SetLock", err)
return
}
ctx.Flash.Success(ctx.Tr("packages.terraform.lock.success"))
ctx.Redirect(pd.VersionWebLink())
}
// ActionPackageTerraformUnlock unlocks a terraform state
func ActionPackageTerraformUnlock(ctx *context.Context) {
pd := ctx.Package.Descriptor
if pd.Package.Type != packages_model.TypeTerraformState {
ctx.NotFound(nil)
return
}
if err := terraform_module.RemoveLock(ctx, pd.Package.ID); err != nil {
ctx.ServerError("RemoveLock", err)
return
}
ctx.Flash.Success(ctx.Tr("packages.terraform.unlock.success"))
ctx.Redirect(pd.VersionWebLink())
}
+4
View File
@@ -1073,6 +1073,10 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("", user.ViewPackageVersion)
m.Post("", reqPackageAccess(perm.AccessModeWrite), user.PackageVersionDelete)
m.Get("/{version_sub}", user.ViewPackageVersion)
m.Group("/terraform", func() {
m.Post("/lock", user.ActionPackageTerraformLock)
m.Post("/unlock", user.ActionPackageTerraformUnlock)
}, reqPackageAccess(perm.AccessModeWrite))
m.Get("/files/{fileid}", user.DownloadPackageFile)
})
})