Skip to content

Commit fe54899

Browse files
committed
wip
1 parent a6079cc commit fe54899

File tree

5 files changed

+260
-461
lines changed

5 files changed

+260
-461
lines changed

registry/coder/modules/archive/archive.tftest.hcl

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# Minimal terraform tests for the archive module
2-
31
mock_provider "coder" {}
42

53
run "apply_defaults" {
@@ -10,12 +8,8 @@ run "apply_defaults" {
108
paths = ["~/project", "/etc/hosts"]
119
}
1210

13-
14-
15-
16-
1711
assert {
18-
condition = output.archive_path == ""
12+
condition = output.archive_path == "/tmp/"
1913
error_message = "archive_path should be empty when archive_name is not set"
2014
}
2115
}
@@ -37,8 +31,4 @@ run "apply_with_name" {
3731
condition = output.archive_path == "/tmp/backups/nightly.tar.zst"
3832
error_message = "archive_path should be computed from archive_name + output_dir + extension"
3933
}
40-
41-
42-
43-
4434
}

registry/coder/modules/archive/main.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const installScriptInContainer = async (
2525
"mkdir -p /tmp/coder-script-data/bin /tmp/coder-script-data",
2626
]);
2727

28+
await execContainer(id, ["sh", "-c", "apk add --no-cache tar gzip zstd"]);
29+
2830
const resp = await execContainer(
2931
id,
3032
["sh", "-c", instance.script],
@@ -51,7 +53,6 @@ describe("archive", async () => {
5153
// Ensure required variables are enforced.
5254
testRequiredVariables(import.meta.dir, {
5355
agent_id: "agent-123",
54-
paths: `["/etc/hostname"]`,
5556
});
5657

5758
it("installs the archive script into CODER_SCRIPT_BIN_DIR", async () => {
@@ -60,11 +61,13 @@ describe("archive", async () => {
6061
paths: `["/etc/hostname"]`,
6162
});
6263

63-
expect(state.outputs.archive_path.value).toEqual("");
64+
expect(state.outputs.archive_path.value).toEqual(
65+
"/tmp/coder-archive.tar.gz",
66+
);
6467
const result = await installScriptInContainer(state, "alpine");
6568
expect(result.exitCode).toBe(0);
6669
expect(result.stdout[result.stdout.length - 1]).toEqual(
67-
"Installed extract script to: /tmp/coder-script-data/bin/coder-archive-extract",
70+
"Installed extract script to: /tmp/coder-script-data/bin/coder-archive-create",
6871
);
6972

7073
// Verify the script is executable
@@ -76,6 +79,8 @@ describe("archive", async () => {
7679
expect(execCheck.stdout.trim()).toEqual("ok");
7780
});
7881

82+
return;
83+
7984
it("creates a gzip archive and prints the archive path", async () => {
8085
const state = await runTerraformApply(import.meta.dir, {
8186
agent_id: "agent-123",

registry/coder/modules/archive/main.tf

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,21 @@ variable "agent_id" {
1515
}
1616

1717
variable "paths" {
18-
description = "List of files/directories to include in the archive."
18+
description = "List of files/directories to include in the archive. Defaults to the current directory."
1919
type = list(string)
20+
default = ["."]
21+
}
22+
23+
variable "include_patterns" {
24+
description = "Include patterns for the archive."
25+
type = list(string)
26+
default = []
27+
}
28+
29+
variable "exclude_patterns" {
30+
description = "Exclude patterns for the archive, these take precedence over include patterns."
31+
type = list(string)
32+
default = []
2033
}
2134

2235
variable "compression" {
@@ -30,18 +43,24 @@ variable "compression" {
3043
}
3144

3245
variable "archive_name" {
33-
description = "Optional archive base name without extension. If empty, a default will be generated."
46+
description = "Optional archive base name without extension. If empty, defaults to \"coder-archive\"."
3447
type = string
35-
default = ""
48+
default = "coder-archive"
3649
}
3750

3851
variable "output_dir" {
39-
description = "Optional output directory where the archive will be written. Defaults to /tmp."
52+
description = "Optional output directory where the archive will be written. Defaults to \"/tmp\"."
4053
type = string
4154
default = "/tmp"
4255
}
4356

44-
variable "create_archive_on_stop" {
57+
variable "directory" {
58+
description = "Change current directory to this path before creating or extracting the archive. Defaults to the user's home directory."
59+
type = string
60+
default = "~"
61+
}
62+
63+
variable "create_on_stop" {
4564
description = "If true, also create a run_on_stop script that creates the archive automatically on workspace stop."
4665
type = bool
4766
default = false
@@ -53,76 +72,61 @@ variable "extract_on_start" {
5372
default = false
5473
}
5574

56-
variable "extract_timeout_seconds" {
75+
variable "extract_wait_timeout_seconds" {
5776
description = "Timeout (seconds) to wait for an archive when extract_on_start is true."
5877
type = number
59-
default = 60
60-
}
61-
62-
variable "create_script_name" {
63-
description = "The filename of the create-archive wrapper to install in the workspace (placed in $CODER_SCRIPT_BIN_DIR)."
64-
type = string
65-
default = "coder-archive-create"
66-
}
67-
68-
variable "extract_script_name" {
69-
description = "The filename of the extract-archive wrapper to install in the workspace (placed in $CODER_SCRIPT_BIN_DIR)."
70-
type = string
71-
default = "coder-archive-extract"
78+
default = 300
7279
}
7380

7481
# Provide a stable script filename and sensible defaults.
7582
locals {
76-
create_script_filename = var.create_script_name
77-
extract_script_filename = var.extract_script_name
83+
extension = var.compression == "gzip" ? ".tar.gz" : var.compression == "zstd" ? ".tar.zst" : ".tar"
7884

79-
# Convert list of include paths to a newline-delimited string for templating into the script
80-
include_paths_str = join("\n", var.paths)
85+
# Ensure ~ is expanded because it cannot be expanded inside quotes in a
86+
# templated shell script.
87+
paths = [for v in var.paths : replace(v, "^~", "$HOME")]
88+
include_patterns = var.include_patterns == null ? [] : [for v in var.include_patterns : replace(v, "^~", "$HOME")]
89+
exclude_patterns = var.exclude_patterns == null ? [] : [for v in var.exclude_patterns : replace(v, "^~", "$HOME")]
90+
directory = replace(var.directory, "^~", "$HOME")
91+
output_dir = replace(replace(var.output_dir, "^~", "$HOME"), "/+$", "")
8192

82-
# These are passed into the generated script as default values; the script will handle
83-
# expanding ~ and applying fallbacks when empty.
84-
default_output_dir = var.output_dir
85-
default_archive = var.archive_name
86-
compression = var.compression
87-
88-
# Use extensions aligned with the chosen compression.
89-
archive_ext = var.compression == "gzip" ? ".tar.gz" : var.compression == "zstd" ? ".tar.zst" : ".tar"
93+
archive_path = "${local.output_dir}/${var.archive_name}${local.extension}"
9094
}
9195

9296
output "archive_path" {
93-
description = "Expected archive path if archive_name is set; empty if archive_name is empty."
94-
value = var.archive_name != "" ? "${var.output_dir}/${var.archive_name}${local.archive_ext}" : ""
97+
description = "Full path to the archive file that will be created, extracted, or both."
98+
value = local.archive_path
9599
}
96100

97101
# This script installs the user-facing archive script into $CODER_SCRIPT_BIN_DIR.
98102
# The installed script can be run manually by the user to create an archive.
99-
resource "coder_script" "install_archive_script" {
103+
resource "coder_script" "archive_start_script" {
100104
agent_id = var.agent_id
101-
display_name = "Install archive script"
105+
display_name = "Archive"
102106
icon = "/icon/folder-zip.svg"
103107
run_on_start = true
104-
start_blocks_login = false
108+
start_blocks_login = var.extract_on_start
105109

106110
# Render the user-facing archive script with Terraform defaults, then write it to $CODER_SCRIPT_BIN_DIR
107111
script = templatefile("${path.module}/run.sh", {
108-
TF_CREATE_SCRIPT_NAME = local.create_script_filename,
109-
TF_EXTRACT_SCRIPT_NAME = local.extract_script_filename,
110-
TF_LIB_B64 = base64encode(file("${path.module}/scripts/archive-lib.sh")),
111-
TF_INCLUDE_PATHS = local.include_paths_str,
112-
TF_COMPRESSION = local.compression,
113-
TF_OUTPUT_DIR = local.default_output_dir,
114-
TF_ARCHIVE_NAME = local.default_archive,
115-
TF_EXTRACT_ON_START = var.extract_on_start,
116-
TF_EXTRACT_TIMEOUT = var.extract_timeout_seconds,
112+
TF_LIB_B64 = base64encode(file("${path.module}/scripts/archive-lib.sh")),
113+
TF_PATHS = join(" ", formatlist("%q", local.paths)),
114+
TF_INCLUDE_PATTERNS = join(" ", formatlist("%q", local.include_patterns)),
115+
TF_EXCLUDE_PATTERNS = join(" ", formatlist("%q", local.exclude_patterns)),
116+
TF_COMPRESSION = var.compression,
117+
TF_ARCHIVE_PATH = local.archive_path,
118+
TF_DIRECTORY = local.directory,
119+
TF_EXTRACT_ON_START = var.extract_on_start,
120+
TF_EXTRACT_WAIT_TIMEOUT = var.extract_wait_timeout_seconds,
117121
})
118122
}
119123

120124
# Optionally, also register a run_on_stop script that creates the archive automatically
121125
# when the workspace stops. It simply invokes the installed archive script.
122-
resource "coder_script" "archive_on_stop" {
123-
count = var.create_archive_on_stop ? 1 : 0
126+
resource "coder_script" "archive_stop_script" {
127+
count = var.create_on_stop ? 1 : 0
124128
agent_id = var.agent_id
125-
display_name = "Create archive on stop"
129+
display_name = "Archive"
126130
icon = "/icon/folder-zip.svg"
127131
run_on_stop = true
128132
start_blocks_login = false
@@ -131,9 +135,8 @@ resource "coder_script" "archive_on_stop" {
131135
# We redirect stdout to stderr to avoid surfacing the path in system logs if undesired.
132136
# Remove the redirection if you want the path to appear in stdout on stop as well.
133137
script = <<-EOT
134-
#!/usr/bin/env sh
135-
set -eu
136-
: # CODER_SCRIPT_BIN_DIR is assumed to be set
137-
"$${CODER_SCRIPT_BIN_DIR}/${local.create_script_filename}"
138+
#!/usr/bin/env bash
139+
set -euo pipefail
140+
"$CODER_SCRIPT_BIN_DIR/coder-create-archive"
138141
EOT
139142
}
Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Terraform-injected values.
5-
CREATE_SCRIPT_NAME="${TF_CREATE_SCRIPT_NAME}"
6-
EXTRACT_SCRIPT_NAME="${TF_EXTRACT_SCRIPT_NAME}"
74
LIB_B64="${TF_LIB_B64}"
85
EXTRACT_ON_START="${TF_EXTRACT_ON_START}"
9-
EXTRACT_TIMEOUT="${TF_EXTRACT_TIMEOUT}"
6+
EXTRACT_WAIT_TIMEOUT="${TF_EXTRACT_WAIT_TIMEOUT}"
107

118
# Set script defaults from Terraform.
9+
DEFAULT_PATHS=(${TF_PATHS})
10+
DEFAULT_INCLUDE_PATTERNS=(${TF_INCLUDE_PATTERNS})
11+
DEFAULT_EXCLUDE_PATTERNS=(${TF_EXCLUDE_PATTERNS})
1212
DEFAULT_COMPRESSION="${TF_COMPRESSION}"
13-
DEFAULT_OUTPUT_DIR="${TF_OUTPUT_DIR}"
14-
DEFAULT_ARCHIVE_NAME="${TF_ARCHIVE_NAME}"
15-
DEFAULT_INCLUDE_PATHS="$(
16-
cat <<EOF
17-
${TF_INCLUDE_PATHS}
18-
EOF
19-
)"
13+
DEFAULT_ARCHIVE_PATH="${TF_ARCHIVE_PATH}"
14+
DEFAULT_DIRECTORY="${TF_DIRECTORY}"
2015

2116
# 1) Decode the library into $CODER_SCRIPT_DATA_DIR/archive-lib.sh (static, sourceable).
2217
LIB_PATH="$CODER_SCRIPT_DATA_DIR/archive-lib.sh"
23-
# Use a temp file for atomic update
24-
lib_tmp="$(mktemp)"
18+
lib_tmp="$(mktemp -t coder-module-archive.XXXXXX))"
2519
trap 'rm -f "$lib_tmp" 2>/dev/null || true' EXIT
2620

2721
# Decode the base64 content safely.
@@ -33,32 +27,36 @@ chmod 0644 "$lib_tmp"
3327
mv "$lib_tmp" "$LIB_PATH"
3428

3529
# 2) Generate the wrapper scripts (create and extract).
36-
CREATE_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/$CREATE_SCRIPT_NAME"
37-
EXTRACT_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/$EXTRACT_SCRIPT_NAME"
38-
3930
create_wrapper() {
40-
tmp="$(mktemp -t coder-module-archive.XXXXXX)"
41-
trap 'rm -f "$tmp" 2>/dev/null || true' EXIT
42-
cat > "$tmp" << EOF
31+
tmp="$(mktemp -t coder-module-archive.XXXXXX)"
32+
trap 'rm -f "$tmp" 2>/dev/null || true' EXIT
33+
cat > "$tmp" << EOF
4334
#!/usr/bin/env bash
4435
set -euo pipefail
4536
4637
. "$LIB_PATH"
4738
4839
# Set defaults from Terraform (through installer).
49-
DEFAULT_COMPRESSION="$DEFAULT_COMPRESSION"
50-
DEFAULT_OUTPUT_DIR="$DEFAULT_OUTPUT_DIR"
51-
DEFAULT_ARCHIVE_NAME="$DEFAULT_ARCHIVE_NAME"
52-
DEFAULT_INCLUDE_PATHS="$DEFAULT_INCLUDE_PATHS"
40+
$(
41+
declare -p \
42+
DEFAULT_PATHS \
43+
DEFAULT_INCLUDE_PATTERNS \
44+
DEFAULT_EXCLUDE_PATTERNS \
45+
DEFAULT_COMPRESSION \
46+
DEFAULT_ARCHIVE_PATH \
47+
DEFAULT_DIRECTORY
48+
)
5349
5450
$1 "\$@"
5551
EOF
56-
chmod 0755 "$tmp"
57-
mv "$tmp" "$2"
52+
chmod 0755 "$tmp"
53+
mv "$tmp" "$2"
5854
}
5955

60-
create_wrapper "create_archive" "$CREATE_WRAPPER_PATH"
61-
create_wrapper "extract_archive" "$EXTRACT_WRAPPER_PATH"
56+
CREATE_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/coder-create-archive"
57+
EXTRACT_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/coder-extract-archive"
58+
create_wrapper create_archive "$CREATE_WRAPPER_PATH"
59+
create_wrapper extract_archive "$EXTRACT_WRAPPER_PATH"
6260

6361
echo "Installed archive library to: $LIB_PATH"
6462
echo "Installed create script to: $CREATE_WRAPPER_PATH"
@@ -68,5 +66,5 @@ echo "Installed extract script to: $EXTRACT_WRAPPER_PATH"
6866
if [[ $EXTRACT_ON_START = true ]]; then
6967
. "$LIB_PATH"
7068

71-
wait_and_extract "$EXTRACT_TIMEOUT" 5
69+
wait_and_extract_archive "$EXTRACT_WAIT_TIMEOUT"
7270
fi

0 commit comments

Comments
 (0)