Compare commits
1 commit
main
...
migrate-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1de96168 |
5 changed files with 48 additions and 26 deletions
22
DETAILS.md
22
DETAILS.md
|
|
@ -53,7 +53,7 @@ obm/
|
|||
│ │ │ └── hetzner_test.go
|
||||
│ │ └── provider_test.go
|
||||
│ ├── terraform/
|
||||
│ │ └── terraform.go # Runner: Init, Plan, Apply, Destroy wrappers
|
||||
│ │ └── terraform.go # Runner: Init, Plan, Apply, Destroy (OpenTofu/Terraform)
|
||||
│ └── validation/
|
||||
│ ├── validation.go # Check interface, Runner, CheckResult, Status enum
|
||||
│ └── validation_test.go
|
||||
|
|
@ -82,7 +82,7 @@ obm/
|
|||
| `obm deploy` | `--config <path>` | Interactive walkthrough (default) or non-interactive from YAML |
|
||||
| `obm validate` | `--env-file <path>` | Load `.env`, check required vars, validate API keys |
|
||||
| `obm status` | — | Show deployment state (not yet implemented) |
|
||||
| `obm destroy` | — | Confirmation prompt → `terraform destroy` → state cleanup |
|
||||
| `obm destroy` | — | Confirmation prompt → `tofu destroy` → state cleanup |
|
||||
| `obm version` | — | Print version with commit hash and build time |
|
||||
| `obm help` | — | Print usage |
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ The `obm deploy` interactive flow runs 8 steps in sequence:
|
|||
7. **Tailscale** — Optional VPN setup. Auth key and tailnet domain.
|
||||
8. **Discord** — Optional bot integration. Bot token, server ID, user IDs.
|
||||
|
||||
Final step: summary display with cost estimate → confirm → write `.env` → optionally run `terraform init && terraform apply`.
|
||||
Final step: summary display with cost estimate → confirm → write `.env` → optionally run `tofu init && tofu apply` (or `terraform` if installed).
|
||||
|
||||
### Framework-specific defaults
|
||||
|
||||
|
|
@ -355,15 +355,15 @@ DigitalOcean prices:
|
|||
|
||||
---
|
||||
|
||||
## Terraform Integration
|
||||
## Terraform / OpenTofu Integration
|
||||
|
||||
`obm` generates the `.env` file that Terraform expects. The actual Terraform configs live in the separate [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo.
|
||||
`obm` generates the `.env` file that OpenTofu (or Terraform) expects. The actual infrastructure configs live in the separate [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo.
|
||||
|
||||
The `internal/terraform/terraform.go` wrapper provides:
|
||||
- `Runner.Init()` — `terraform init -input=false`
|
||||
- `Runner.Plan(destroy bool)` — `terraform plan` (with optional `-destroy` flag)
|
||||
- `Runner.Apply()` — `terraform apply -auto-approve`
|
||||
- `Runner.Destroy()` — `terraform destroy -auto-approve`
|
||||
The `internal/terraform/terraform.go` wrapper provides (auto-detects OpenTofu first, Terraform fallback):
|
||||
- `Runner.Init()` — `tofu init -input=false`
|
||||
- `Runner.Plan(destroy bool)` — `tofu plan` (with optional `-destroy` flag)
|
||||
- `Runner.Apply()` — `tofu apply -auto-approve`
|
||||
- `Runner.Destroy()` — `tofu destroy -auto-approve`
|
||||
|
||||
All commands run in the `WorkDir` and capture combined output.
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ User sets `TF_VAR_*` env vars → sourced from `.env` → Terraform reads them
|
|||
|
||||
## Destroy Flow
|
||||
|
||||
`obm destroy` reads `terraform.tfstate` to list resources, shows them, asks for confirmation, runs `terraform destroy`, then cleans up state files unless `--keep-state` is set.
|
||||
`obm destroy` reads `terraform.tfstate` to list resources, shows them, asks for confirmation, runs `tofu destroy`, then cleans up state files unless `--keep-state` is set.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
4
LLMs.md
4
LLMs.md
|
|
@ -85,7 +85,7 @@ When your human runs `obm deploy`, they'll go through these in order:
|
|||
7. **Tailscale** — Optional VPN (auth key + tailnet)
|
||||
8. **Discord** — Optional bot integration (token, server ID, user IDs)
|
||||
|
||||
After all 8 steps: summary + cost estimate → confirm → `.env` written → optional `terraform init && apply`.
|
||||
After all 8 steps: summary + cost estimate → confirm → `.env` written → optional `tofu init && tofu apply` (or `terraform` if installed).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ This loads `.env`, checks required variables, and validates inference API keys a
|
|||
obm destroy
|
||||
```
|
||||
|
||||
It asks for confirmation. It reads `terraform.tfstate` to show what will be destroyed.
|
||||
It asks for confirmation. It reads the state file (`terraform.tfstate`) to show what will be destroyed.
|
||||
|
||||
### "Set up CI/CD deployment"
|
||||
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ The AI model API costs are separate, and depend on your usage. Generally that's
|
|||
|
||||
## What happens under the hood?
|
||||
|
||||
`obm` generates a `.env` file that [Terraform](https://terraform.io) reads to provision your server, install the agent software, and configure everything. You don't need to know Terraform — `obm` handles it.
|
||||
`obm` generates a `.env` file that [OpenTofu](https://opentofu.org) (or [Terraform](https://terraform.io) as a fallback) reads to provision your server, install the agent software, and configure everything. You don't need to know either — `obm` handles it.
|
||||
|
||||
The Terraform configs live in the [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo. `obm` is the friendly CLI wrapper around them.
|
||||
The infrastructure configs live in the [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo. `obm` is the friendly CLI wrapper around them.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@ import (
|
|||
"github.com/openboatmobile/obm/internal/prompt"
|
||||
)
|
||||
|
||||
// tfBinary returns the path to the IaC binary: tofu preferred, terraform fallback.
|
||||
func tfBinary() string {
|
||||
for _, name := range []string{"tofu", "terraform"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "tofu"
|
||||
}
|
||||
|
||||
// Options configures the destroy operation.
|
||||
type Options struct {
|
||||
WorkDir string // Working directory (default: current)
|
||||
|
|
@ -198,7 +208,7 @@ func displayDestroyPlan(resources []Resource) {
|
|||
fmt.Println()
|
||||
}
|
||||
|
||||
// runTerraformDestroy executes terraform destroy.
|
||||
// runTerraformDestroy executes tofu/terraform destroy.
|
||||
func runTerraformDestroy(workDir string, opts *Options) error {
|
||||
args := []string{"destroy", "-auto-approve"}
|
||||
|
||||
|
|
@ -207,7 +217,7 @@ func runTerraformDestroy(workDir string, opts *Options) error {
|
|||
args = append(args, "-var-file", vf)
|
||||
}
|
||||
|
||||
cmd := exec.Command("terraform", args...)
|
||||
cmd := exec.Command(tfBinary(), args...)
|
||||
cmd.Dir = workDir
|
||||
|
||||
// Stream output to stdout/stderr
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Package terraform wraps Terraform operations (init, plan, apply, destroy).
|
||||
// Package terraform wraps IaC operations (init, plan, apply, destroy).
|
||||
// Supports OpenTofu (preferred) and Terraform (fallback).
|
||||
package terraform
|
||||
|
||||
import (
|
||||
|
|
@ -6,22 +7,32 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
// Runner executes Terraform commands.
|
||||
// binary returns the path to the IaC binary: tofu preferred, terraform fallback.
|
||||
func binary() string {
|
||||
for _, name := range []string{"tofu", "terraform"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "tofu" // fallback — will produce a useful error at runtime
|
||||
}
|
||||
|
||||
// Runner executes infrastructure-as-code commands.
|
||||
type Runner struct {
|
||||
WorkDir string
|
||||
}
|
||||
|
||||
// NewRunner creates a Terraform runner for the given working directory.
|
||||
// NewRunner creates a runner for the given working directory.
|
||||
func NewRunner(workDir string) *Runner {
|
||||
return &Runner{WorkDir: workDir}
|
||||
}
|
||||
|
||||
// Init runs terraform init.
|
||||
// Init runs init.
|
||||
func (r *Runner) Init() error {
|
||||
return r.run("init", "-input=false")
|
||||
}
|
||||
|
||||
// Plan runs terraform plan.
|
||||
// Plan runs plan.
|
||||
func (r *Runner) Plan(destroy bool) error {
|
||||
args := []string{"plan"}
|
||||
if destroy {
|
||||
|
|
@ -30,22 +41,23 @@ func (r *Runner) Plan(destroy bool) error {
|
|||
return r.run(args...)
|
||||
}
|
||||
|
||||
// Apply runs terraform apply.
|
||||
// Apply runs apply.
|
||||
func (r *Runner) Apply() error {
|
||||
return r.run("apply", "-auto-approve")
|
||||
}
|
||||
|
||||
// Destroy runs terraform destroy.
|
||||
// Destroy runs destroy.
|
||||
func (r *Runner) Destroy() error {
|
||||
return r.run("destroy", "-auto-approve")
|
||||
}
|
||||
|
||||
func (r *Runner) run(args ...string) error {
|
||||
cmd := exec.Command("terraform", args...)
|
||||
bin := binary()
|
||||
cmd := exec.Command(bin, args...)
|
||||
cmd.Dir = r.WorkDir
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("terraform %v: %w\n%s", args, err, output)
|
||||
return fmt.Errorf("%s %v: %w\n%s", bin, args, err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue