add a TF http backend to store state, see #515

Signed-off-by: Kiara Grouwstra <kiara@procolix.eu>
This commit is contained in:
Kiara Grouwstra 2025-10-06 13:07:49 +02:00
parent 1efd15e654
commit d75eacc996
Signed by: kiara
SSH key fingerprint: SHA256:COspvLoLJ5WC5rFb9ZDe5urVCkK4LJZOsjfF4duRJFU
13 changed files with 187 additions and 17 deletions

View file

@ -0,0 +1,14 @@
diff --git a/internal/command/init.go b/internal/command/init.go
index 4491590ea9..56241d93e9 100644
--- a/internal/command/init.go
+++ b/internal/command/init.go
@@ -263,8 +263,7 @@ func (c *InitCommand) Run(args []string) int {
}
if err := sMgr.RefreshState(); err != nil {
- c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
- return 1
+ c.Ui.Warn(fmt.Sprintf("Issue refreshing state: %s", err))
}
state = sMgr.State()

View file

@ -1,10 +1,40 @@
{
runNixOSTest,
inputs,
sources,
system,
}:
runNixOSTest {
let
overlay = _: prev: {
terraform-backend = prev.callPackage ../../modules/terraform-backend/package.nix { };
# FIXME centralize overlays
# XXX using recent revision for https://github.com/NixOS/nixpkgs/pull/447849
opentofu =
(pkgs.callPackage "${sources.nixpkgs-unstable}/pkgs/by-name/op/opentofu/package.nix" { })
.overrideAttrs
(old: rec {
patches = (old.patches or [ ]) ++ [
# TF with back-end poses a problem for nix: initialization involves both
# mutation (nix: only inside build) and a network call (nix: not inside build)
../../check/data-model-tf/02-opentofu-sandboxed-init.patch
];
# versions > 1.9.0 need go 1.24+
version = "1.9.0";
src = pkgs.fetchFromGitHub {
owner = "opentofu";
repo = "opentofu";
tag = "v${version}";
hash = "sha256-e0ZzbQdex0DD7Bj9WpcVI5roh0cMbJuNr5nsSVaOSu4=";
};
vendorHash = "sha256-fMTbLSeW+pw6GK8/JLZzG2ER90ss2g1FSDX5+f292do=";
});
};
pkgs = import sources.nixpkgs {
inherit system;
overlays = [ overlay ];
};
in
pkgs.testers.runNixOSTest {
imports = [
../../data-model.nix
../../function.nix

View file

@ -1,14 +1,15 @@
{
lib,
pkgs,
sources,
...
}:
let
inherit (pkgs) system;
inherit (import ./constants.nix) pathToRoot;
nodeName = "target";
deployment-config = {
inherit nodeName;
inherit (import ./constants.nix) pathToRoot;
inherit nodeName pathToRoot;
targetSystem = system;
sshOpts = [ ];
};
@ -33,8 +34,13 @@ in
nodes.deployer =
{ ... }:
{
imports = [
../../modules/terraform-backend
];
environment.systemPackages = [
deploy
(pkgs.callPackage ../../run/tf-single-host/tf.nix { inherit sources; })
];
# needed only when building from deployer
@ -45,6 +51,13 @@ in
hello
];
};
services.terraform-backend = {
enable = true;
settings = {
LISTEN_ADDR = ":8080";
KMS_KEY = "l99yC7MhbuuraACQ8bjaU1rMrT6L4PXEYupX6BzhJvY=";
};
};
};
extraTestScript = ''
@ -52,6 +65,8 @@ in
target.fail("hello 1>&2")
with subtest("Run the deployment"):
deployer.wait_for_unit("multi-user.target")
deployer.succeed("curl -u basic:fake-secret -X GET http://localhost:8080/state/project1/example")
output = deployer.fail("""
${lib.getExe deploy}
""")

View file

@ -3,7 +3,6 @@
config,
inputs,
pkgs,
sources ? import ../npins,
...
}:
let
@ -266,7 +265,7 @@ let
pkgs.writers.writeBashBin "deploy-tf.sh"
(withPackages [
pkgs.jq
(pkgs.callPackage ./run/tf-single-host/tf.nix { inherit sources; })
(pkgs.callPackage ./run/tf-single-host/tf.nix { })
])
''
env ${toString (lib.mapAttrsToList (k: v: "TF_VAR_${k}=\"${toBash v}\"") environment)} \

View file

@ -38,8 +38,7 @@
};
deployment-model-tf = import ./check/data-model-tf {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
inherit inputs sources system;
};
};
};

View file

@ -0,0 +1,39 @@
{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.terraform-backend;
in
{
options.services.terraform-backend = {
enable = lib.mkEnableOption "Nimbolus Terraform HTTP back-end";
package = lib.mkPackageOption pkgs "terraform-backend" { };
settings = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = ''
[Environment variables](https://github.com/nimbolus/terraform-backend#default-settings)
for the Terraform HTTP back-end.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.terraform-backend = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "exec";
DynamicUser = true;
ExecStart = lib.getExe cfg.package;
Environment = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.settings;
# FIXME remove after switching away from file storage?
StateDirectory = "terraform-backend";
WorkingDirectory = "/var/lib/terraform-backend";
StateDirectoryMode = "0700";
};
};
};
}

View file

@ -0,0 +1,32 @@
{
lib,
buildGoModule,
fetchFromGitHub,
}:
# FIXME upstream: https://github.com/NixOS/nixpkgs/pull/447753
buildGoModule rec {
pname = "terraform-backend";
version = "0.1.3";
src = fetchFromGitHub {
owner = "nimbolus";
repo = "terraform-backend";
tag = "v${version}";
hash = "sha256-S3ih7dLSQs3xJMHyQyWy43OG1maizBPVT8IsrWcSRUM=";
};
vendorHash = "sha256-5L8MNhjEPI3OOmtHdkB9ZQp02d7nzPp5h0/gVHTiCws=";
ldflags = [
"-s"
"-w"
];
meta = {
description = "State backend server which implements the Terraform HTTP backend API with pluggable modules for authentication, storage, locking and state encryption";
homepage = "https://github.com/nimbolus/terraform-backend";
license = lib.licenses.bsd3;
mainProgram = "cmd";
};
}

View file

@ -4,7 +4,7 @@
sources,
}:
pkgs.writeScriptBin "setup" ''
set -xe
set -e
# calculated pins
echo '${lib.strings.toJSON sources}' > ./.npins.json
# generate TF lock for nix's TF providers

View file

@ -1,3 +1,14 @@
terraform {
# TODO un-hardcode
backend "http" {
username = "basic"
password = "fake-secret"
address = "http://localhost:8080/state/project1/example"
lock_address = "http://localhost:8080/state/project1/example"
unlock_address = "http://localhost:8080/state/project1/example"
}
}
# hash of our code directory, used to trigger re-deploy
# FIXME calculate separately to reduce false positives
data "external" "hash" {

View file

@ -6,4 +6,4 @@ export TF_LOG=info
cd "${tf_env}/deployment/run/tf-single-host"
# parallelism=1: limit OOM risk
tofu apply --auto-approve -lock=false -parallelism=1
tofu apply --auto-approve -parallelism=1

View file

@ -13,7 +13,7 @@ pkgs.stdenv.mkDerivation {
fileset = intersection (gitTracked ../../../.) ../../../.;
};
buildInputs = [
(pkgs.callPackage ./tf.nix { })
(pkgs.callPackage ./tf.nix { inherit sources; })
(pkgs.callPackage ../tf-setup.nix { inherit sources; })
];
buildPhase = ''

View file

@ -1,11 +1,29 @@
# FIXME: use overlays so this gets imported just once?
{
pkgs,
sources ? import ../../../npins,
...
}:
let
tf = pkgs.opentofu;
in
tf.withPlugins (p: [
p.external
])
# FIXME centralize overlays
# XXX using recent revision for https://github.com/NixOS/nixpkgs/pull/447849
(
(pkgs.callPackage "${sources.nixpkgs-unstable}/pkgs/by-name/op/opentofu/package.nix" { })
.overrideAttrs
(old: rec {
patches = (old.patches or [ ]) ++ [
# TF with back-end poses a problem for nix: initialization involves both
# mutation (nix: only inside build) and a network call (nix: not inside build)
../../check/data-model-tf/02-opentofu-sandboxed-init.patch
];
# versions > 1.9.0 need go 1.24+
version = "1.9.0";
src = pkgs.fetchFromGitHub {
owner = "opentofu";
repo = "opentofu";
tag = "v${version}";
hash = "sha256-e0ZzbQdex0DD7Bj9WpcVI5roh0cMbJuNr5nsSVaOSu4=";
};
vendorHash = "sha256-fMTbLSeW+pw6GK8/JLZzG2ER90ss2g1FSDX5+f292do=";
})
).withPlugins
(p: [ p.external ])

View file

@ -180,6 +180,19 @@
"url": "https://github.com/nixos/nixpkgs/archive/a1ae8ef72f64a845ecce5c6dcf65d546bf7deeb4.tar.gz",
"hash": "0d7lp30wyy5647gpm8rnihvdcpmgmfr9c5yg4fhl31lsg8mlbg16"
},
"nixpkgs-unstable": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "nixos",
"repo": "nixpkgs"
},
"branch": "nixpkgs-unstable",
"submodules": false,
"revision": "d7f52a7a640bc54c7bb414cca603835bf8dd4b10",
"url": "https://github.com/nixos/nixpkgs/archive/d7f52a7a640bc54c7bb414cca603835bf8dd4b10.tar.gz",
"hash": "0c9kjncpmbdx6gwww9fn81hyr3bngi4hg51g4n2q4808c321kf4j"
},
"proxmox-nixos": {
"type": "Git",
"repository": {