mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-23 07:45:43 +00:00
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:
@@ -6,6 +6,7 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -20,3 +21,5 @@ func MarshalKeepOptionalEmpty(v any) ([]byte, error) {
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return DefaultJSONHandler.NewDecoder(reader)
|
||||
}
|
||||
|
||||
type Value = json.RawMessage
|
||||
|
||||
@@ -8,6 +8,7 @@ package json
|
||||
import (
|
||||
"bytes"
|
||||
jsonv1 "encoding/json" //nolint:depguard // this package wraps it
|
||||
"encoding/json/jsontext" //nolint:depguard // this package wraps it
|
||||
jsonv2 "encoding/json/v2" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
@@ -90,3 +91,5 @@ func (d *jsonV2Decoder) Decode(v any) error {
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return &jsonV2Decoder{reader: reader, opts: jsonV2.unmarshalCaseInsensitiveOptions}
|
||||
}
|
||||
|
||||
type Value = jsontext.Value
|
||||
|
||||
100
modules/packages/terraform/lock.go
Normal file
100
modules/packages/terraform/lock.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
const LockFile = "terraform.lock"
|
||||
|
||||
// LockInfo is the metadata for a terraform lock.
|
||||
type LockInfo struct {
|
||||
ID string `json:"ID"`
|
||||
Operation string `json:"Operation"`
|
||||
Info string `json:"Info"`
|
||||
Who string `json:"Who"`
|
||||
Version string `json:"Version"`
|
||||
Created time.Time `json:"Created"`
|
||||
Path string `json:"Path"`
|
||||
}
|
||||
|
||||
func (l *LockInfo) IsLocked() bool {
|
||||
return l.ID != ""
|
||||
}
|
||||
|
||||
func ParseLockInfo(r io.Reader) (*LockInfo, error) {
|
||||
var lock LockInfo
|
||||
err := json.NewDecoder(r).Decode(&lock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ID is required. Rest is less important.
|
||||
if lock.ID == "" {
|
||||
return nil, util.NewInvalidArgumentErrorf("terraform lock is missing an ID")
|
||||
}
|
||||
return &lock, nil
|
||||
}
|
||||
|
||||
// GetLock returns the terraform lock for the given package.
|
||||
// Lock is empty if no lock exists.
|
||||
func GetLock(ctx context.Context, packageID int64) (LockInfo, error) {
|
||||
var lock LockInfo
|
||||
locks, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypePackage, packageID, LockFile)
|
||||
if err != nil {
|
||||
return lock, err
|
||||
}
|
||||
if len(locks) == 0 || locks[0].Value == "" {
|
||||
return lock, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(locks[0].Value), &lock)
|
||||
return lock, err
|
||||
}
|
||||
|
||||
// SetLock sets the terraform lock for the given package.
|
||||
func SetLock(ctx context.Context, packageID int64, lock *LockInfo) error {
|
||||
jsonBytes, err := json.Marshal(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updateLock(ctx, packageID, string(jsonBytes), builder.Eq{"value": ""})
|
||||
}
|
||||
|
||||
// RemoveLock removes the terraform lock for the given package.
|
||||
func RemoveLock(ctx context.Context, packageID int64) error {
|
||||
return updateLock(ctx, packageID, "", builder.Neq{"value": ""})
|
||||
}
|
||||
|
||||
func updateLock(ctx context.Context, refID int64, value string, cond builder.Cond) error {
|
||||
pp := packages_model.PackageProperty{RefType: packages_model.PropertyTypePackage, RefID: refID, Name: LockFile}
|
||||
ok, err := db.GetEngine(ctx).Get(&pp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
n, err := db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", packages_model.PropertyTypePackage, refID, LockFile).And(cond).Cols("value").Update(&packages_model.PackageProperty{Value: value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.New("failed to update lock state")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
_, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, refID, LockFile, value)
|
||||
return err
|
||||
}
|
||||
38
modules/packages/terraform/state.go
Normal file
38
modules/packages/terraform/state.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// Note: this is a subset of the Terraform state file format as the full one has two forms.
|
||||
// If needed, it can be expanded in the future.
|
||||
|
||||
type State struct {
|
||||
Serial uint64 `json:"serial"`
|
||||
Lineage string `json:"lineage"`
|
||||
}
|
||||
|
||||
// ParseState parses the required parts of Terraform state file
|
||||
func ParseState(r io.Reader) (*State, error) {
|
||||
var state State
|
||||
err := json.NewDecoder(r).Decode(&state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Serial starts at 1; 0 means it wasn't set in the state file
|
||||
if state.Serial == 0 {
|
||||
return nil, util.NewInvalidArgumentErrorf("state serial is missing")
|
||||
}
|
||||
// Lineage should always be set
|
||||
if state.Lineage == "" {
|
||||
return nil, util.NewInvalidArgumentErrorf("state lineage is missing")
|
||||
}
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
@@ -16,30 +16,31 @@ var (
|
||||
Storage *Storage
|
||||
Enabled bool
|
||||
|
||||
LimitTotalOwnerCount int64
|
||||
LimitTotalOwnerSize int64
|
||||
LimitSizeAlpine int64
|
||||
LimitSizeArch int64
|
||||
LimitSizeCargo int64
|
||||
LimitSizeChef int64
|
||||
LimitSizeComposer int64
|
||||
LimitSizeConan int64
|
||||
LimitSizeConda int64
|
||||
LimitSizeContainer int64
|
||||
LimitSizeCran int64
|
||||
LimitSizeDebian int64
|
||||
LimitSizeGeneric int64
|
||||
LimitSizeGo int64
|
||||
LimitSizeHelm int64
|
||||
LimitSizeMaven int64
|
||||
LimitSizeNpm int64
|
||||
LimitSizeNuGet int64
|
||||
LimitSizePub int64
|
||||
LimitSizePyPI int64
|
||||
LimitSizeRpm int64
|
||||
LimitSizeRubyGems int64
|
||||
LimitSizeSwift int64
|
||||
LimitSizeVagrant int64
|
||||
LimitTotalOwnerCount int64
|
||||
LimitTotalOwnerSize int64
|
||||
LimitSizeAlpine int64
|
||||
LimitSizeArch int64
|
||||
LimitSizeCargo int64
|
||||
LimitSizeChef int64
|
||||
LimitSizeComposer int64
|
||||
LimitSizeConan int64
|
||||
LimitSizeConda int64
|
||||
LimitSizeContainer int64
|
||||
LimitSizeCran int64
|
||||
LimitSizeDebian int64
|
||||
LimitSizeGeneric int64
|
||||
LimitSizeGo int64
|
||||
LimitSizeHelm int64
|
||||
LimitSizeMaven int64
|
||||
LimitSizeNpm int64
|
||||
LimitSizeNuGet int64
|
||||
LimitSizePub int64
|
||||
LimitSizePyPI int64
|
||||
LimitSizeRpm int64
|
||||
LimitSizeRubyGems int64
|
||||
LimitSizeSwift int64
|
||||
LimitSizeTerraformState int64
|
||||
LimitSizeVagrant int64
|
||||
|
||||
DefaultRPMSignEnabled bool
|
||||
}{
|
||||
@@ -86,6 +87,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
|
||||
Packages.LimitSizeRpm = mustBytes(sec, "LIMIT_SIZE_RPM")
|
||||
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
|
||||
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
|
||||
Packages.LimitSizeTerraformState = mustBytes(sec, "LIMIT_SIZE_TERRAFORM_STATE")
|
||||
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
|
||||
Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false)
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user