coder / coder/registry

Rearchitecture Modules

Open
#30 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
HCL
Stars
79
Forks
161
Avg merge
1d 10h
Merged PRs (30d)
38

Description

We need a consistent approach to manage shell scripts within Terraform modules. Key challenges include:

1. Common Code Inclusion: Best way to include `lib.sh` across all modules.
2. `shellcheck` Validation: Ensure all scripts pass `shellcheck` for code quality.
3. TF Variable Access: Reliable access to Terraform variables in scripts.
4. Variable Name Conflicts: Avoid `${VAR}` misusage between shell and Terraform variables.

Essentially, this is what I've come up with:

```tf
resource "coder_script" "vscode-web" {
script = templatefile("${path.module}/script.tftpl", {
LOADER: "/usr/bin/env bash",
LIB: file("${path.module}/lib.sh"),
RUN: file("${path.module}/run.sh"),
VARS: templatefile("${path.module}/vars.tftpl", {
PORT : var.port,
LOG_PATH : var.log_path,
INSTALL_PREFIX : var.install_prefix,
EXTENSIONS : join(",", var.extensions),
TELEMETRY_LEVEL : var.telemetry_level,
// This is necessary otherwise the quotes are stripped!
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
OFFLINE : var.offline,
USE_CACHED : var.use_cached,
EXTENSIONS_DIR : var.extensions_dir,
FOLDER : var.folder,
AUTO_INSTALL_EXTENSIONS : var.auto_install_extensions,
}),
})
}
```

Where:

- `script.tftpl` is generated/based on a template
- `lib.sh` is copied to all modules (from e.g. `.scripts/lib.sh`)
- `run.sh` is a _pure_ editable shell scripts, no TF variables, shellcheck and shfmt works
- `vars.tftpl` is autogenerated from `main.tf`

`script.tftpl`:
```tftpl
#!${LOADER}

# lib.sh
touch "$CODER_SCRIPT_DATA_DIR/lib.sh"
${LIB}

# vars.tftpl
touch "$CODER_SCRIPT_DATA_DIR/vars.tftpl"
${VARS}

# run.sh
${RUN}
```
(The `touch`es are present here to avoid errors when executing `run.sh` content and sourcing the files. Alternatively the source lines could be stripped out but puts logic in `main.tf`.)

`lib.sh`:
```sh
#!/bin/sh

log() { echo "$@" }
```

`run.sh`:
```sh
#!/usr/bin/env bash

# shellcheck source=vscode-web/lib.sh
. "${CODER_SCRIPT_DATA_DIR}/lib.sh"
# shellcheck source=vscode-web/vars.tftpl
. "${CODER_SCRIPT_DATA_DIR}/vars.tftpl"

run_vscode_web() {
log running... # log from lib.sh
# ...
```

`vars.tftpl`:
```sh
#!/usr/bin/env sh
# Code generated by [insert name]. DO NOT EDIT.

# shellcheck disable=SC2269
PORT="${PORT}" # type: number, default: 13338, description: The port to run VS Code Web on.
LOG_PATH="${LOG_PATH}" # ...
INSTALL_PREFIX="${INSTALL_PREFIX}"
EXTENSIONS="${EXTENSIONS}"
TELEMETRY_LEVEL="${TELEMETRY_LEVEL}"
SETTINGS="${SETTINGS}"
OFFLINE="${OFFLINE}"
USE_CACHED="${USE_CACHED}"
EXTENSIONS_DIR="${EXTENSIONS_DIR}"
FOLDER="${FOLDER}"
AUTO_INSTALL_EXTENSIONS="${AUTO_INSTALL_EXTENSIONS}"
```

End result (coder script):

```sh
#!/usr/bin/env bash

# lib.sh
touch "$CODER_SCRIPT_DATA_DIR/lib.sh"
#!/bin/sh

log() { echo "$@" }

# vars.tftpl
touch "$CODER_SCRIPT_DATA_DIR/vars.tftpl"
#!/usr/bin/env sh
# Code generated by [insert name]. DO NOT EDIT.

# shellcheck disable=SC2269
PORT="${PORT}" # type: number, default: 13338, description: The port to run VS Code Web on.
LOG_PATH="${LOG_PATH}" # ...
INSTALL_PREFIX="${INSTALL_PREFIX}"
EXTENSIONS="${EXTENSIONS}"
TELEMETRY_LEVEL="${TELEMETRY_LEVEL}"
SETTINGS="${SETTINGS}"
OFFLINE="${OFFLINE}"
USE_CACHED="${USE_CACHED}"
EXTENSIONS_DIR="${EXTENSIONS_DIR}"
FOLDER="${FOLDER}"
AUTO_INSTALL_EXTENSIONS="${AUTO_INSTALL_EXTENSIONS}"

# run.sh
#!/usr/bin/env bash

# shellcheck source=vscode-web/lib.sh
. "${CODER_SCRIPT_DATA_DIR}/lib.sh"
# shellcheck source=vscode-web/vars.tftpl
. "${CODER_SCRIPT_DATA_DIR}/vars.tftpl"

run_vscode_web() {
log running... # log from lib.sh
# ...
```

---

Not sure if this will make it hard to understand the project structure and how/where to contribute. I have some other ideas too, but that requires introducing a pre-filter for `shellcheck` and supporting `shfmt` would be hard. Also you'd still have to be careful with `$${VAR}` vs `${VAR}` but we could introduce a new linter for that and enforce that all tf is at the top of the file, for instance.

Also, it'd be possible to combine `script.tftpl` and `vars.tftpl` if that makes things easier, but at the same time it'd be a bit of inception (run.sh imports script.tftpl and script.tftpl embeds run.sh)

_Originally posted by @mafredri in [#331](https://github.com/coder/registry/issues/46)_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.