Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ad/GOAD-variant-1/data/inventory
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
; GLOBAL CONFIG
[all:vars]
; domain_name : folder inside ad/
domain_name=GOAD
domain_name=GOAD-variant-1

; administrator user
admin_user=administrator
Expand Down
2 changes: 1 addition & 1 deletion ad/GOAD-variant-1/data/inventory_disable_vagrant
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03 ansible_user=a

[all:vars]
; domain_name : folder inside ad/
domain_name=GOAD
domain_name=GOAD-variant-1

; winrm connection (windows)
ansible_winrm_transport=ntlm
Expand Down
1 change: 1 addition & 0 deletions ad/GOAD-variant-1/providers/aws/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03 ansible_user=ans
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03 ansible_user=ansible ansible_password=978i2pF43UJ-

[all:vars]
domain_name=GOAD-variant-1
admin_user=goadmin
1 change: 1 addition & 0 deletions ad/GOAD-variant-1/providers/azure/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03 ansible_user=ans
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03 ansible_user=ansible ansible_password=978i2pF43UJ-

[all:vars]
domain_name=GOAD-variant-1
admin_user=goadmin
1 change: 1 addition & 0 deletions ad/GOAD-variant-1/providers/ludus/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03

[all:vars]
domain_name=GOAD-variant-1
force_dns_server=no
dns_server={{ip_range}}.254

Expand Down
1 change: 1 addition & 0 deletions ad/GOAD-variant-1/providers/proxmox/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03

[all:vars]
domain_name=GOAD-variant-1
force_dns_server=yes
dns_server={{ip_range}}.1
3 changes: 3 additions & 0 deletions ad/GOAD-variant-1/providers/virtualbox/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ srv02 ansible_host={{ip_range}}.22 dns_domain=dc02 dict_key=srv02
; ------------------------------------------------
dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03

[all:vars]
domain_name=GOAD-variant-1
3 changes: 3 additions & 0 deletions ad/GOAD-variant-1/providers/vmware/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ srv02 ansible_host={{ip_range}}.22 dns_domain=dc02 dict_key=srv02
; ------------------------------------------------
dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03

[all:vars]
domain_name=GOAD-variant-1
3 changes: 3 additions & 0 deletions ad/GOAD-variant-1/providers/vmware_esxi/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ srv02 ansible_host={{ip_range}}.22 dns_domain=dc02 dict_key=srv02
; ------------------------------------------------
dc03 ansible_host={{ip_range}}.12 dns_domain=dc03 dict_key=dc03
srv03 ansible_host={{ip_range}}.23 dns_domain=dc03 dict_key=srv03

[all:vars]
domain_name=GOAD-variant-1
42 changes: 42 additions & 0 deletions cli/cmd/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,45 @@ func TestBootstrapInventory(t *testing.T) {
}
})
}

func TestBootstrapFromProviderTemplateUsesDefaultVariantTarget(t *testing.T) {
dir := t.TempDir()
templatePath := filepath.Join(
dir,
"ad",
"GOAD-variant-1",
"providers",
"proxmox",
"inventory",
)
if err := os.MkdirAll(filepath.Dir(templatePath), 0o755); err != nil {
t.Fatal(err)
}
template := "[default]\ndc01 ansible_host={{ip_range}}.10\n\n[all:vars]\ndomain_name=GOAD-variant-1\n"
if err := os.WriteFile(templatePath, []byte(template), 0o644); err != nil {
t.Fatal(err)
}

cfg := &config.Config{
Env: "dev",
Provider: "proxmox",
ProjectRoot: dir,
Environments: map[string]config.EnvironmentConfig{
"dev": {Variant: true},
},
Proxmox: config.ProxmoxConfig{IPRange: "10.20.30"},
}
invPath := filepath.Join(dir, "dev-inventory")
if err := bootstrapFromProviderTemplate(invPath, cfg); err != nil {
t.Fatalf("bootstrapFromProviderTemplate() error: %v", err)
}

got, err := os.ReadFile(invPath)
if err != nil {
t.Fatal(err)
}
want := "[default]\ndc01 ansible_host=10.20.30.10\n\n[all:vars]\ndomain_name=GOAD-variant-1\n"
if string(got) != want {
t.Fatalf("bootstrapped inventory = %q, want %q", got, want)
}
}
26 changes: 19 additions & 7 deletions cli/cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,26 @@ func bootstrapInventory(invPath string) error {
}

func bootstrapFromProviderTemplate(invPath string, cfg *config.Config) error {
labName := "GOAD"
if ec := cfg.ActiveEnvironment(); ec.Variant && ec.VariantTarget != "" {
labName = filepath.Base(ec.VariantTarget)
} else if cfg.ResolvedProvider() == "proxmox" {
labName = cfg.ProxmoxLab()
}
providerName := cfg.ResolvedProvider()
templatePath := filepath.Join(cfg.ProjectRoot, "ad", labName, "providers", providerName, "inventory")

// Resolve the lab tree that holds the provider inventory template. For a
// variant environment, read from the variant target tree so the
// bootstrapped inventory (which carries domain_name and the asset layout)
// points at the variant's ad/<target>/ assets rather than the stock
// ad/GOAD/ tree. Falls back to the stock/proxmox path for non-variants.
var templatePath string
if ec := cfg.ActiveEnvironment(); ec.Variant {
if _, target := cfg.ResolvedVariantPaths(); target != "" {
templatePath = filepath.Join(target, "providers", providerName, "inventory")
}
}
if templatePath == "" {
labName := "GOAD"
if providerName == "proxmox" {
labName = cfg.ProxmoxLab()
}
templatePath = filepath.Join(cfg.ProjectRoot, "ad", labName, "providers", providerName, "inventory")
}

data, err := os.ReadFile(templatePath)
if err != nil {
Expand Down
63 changes: 61 additions & 2 deletions cli/internal/variant/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,13 +1045,71 @@ func (g *Generator) transformFile(srcPath, relPath string) (transformed bool, er
newContent = g.fixSecureStrings(newContent)
}
newContent = g.transformConfigJSON(base, newContent)
if strings.HasPrefix(base, "inventory") {
newContent = g.repointDomainName(newContent)
}
Comment thread
mkultraWasHere marked this conversation as resolved.

if err := os.WriteFile(targetFile, []byte(newContent), 0o644); err != nil {
return false, fmt.Errorf("write %s: %w", targetFile, err)
}
return true, nil
}

// repointDomainName rewrites the Ansible `domain_name` inventory variable to
// the variant target folder basename. Playbooks resolve vuln scripts/files as
// ad/{{ domain_name }}/..., so for a variant this must point at the variant's
// own ad/<target>/ tree rather than the stock ad/GOAD/ tree. Existing values
// are replaced in place; missing values are inserted under [all:vars], with
// the section appended when the provider template does not define it.
func (g *Generator) repointDomainName(content string) string {
target := filepath.Base(g.TargetPath)
re := regexp.MustCompile(`(?m)^(\s*domain_name\s*=\s*).*$`)
if re.MatchString(content) {
return re.ReplaceAllStringFunc(content, func(line string) string {
if idx := strings.Index(line, "="); idx >= 0 {
valueStart := idx + 1
for valueStart < len(line) && (line[valueStart] == ' ' || line[valueStart] == '\t') {
valueStart++
}
return line[:valueStart] + target
}
return line
})
}

header := regexp.MustCompile(`(?m)^\[all:vars\][ \t]*`)
loc := header.FindStringIndex(content)
lineEnding := "\n"
if strings.Contains(content, "\r\n") {
lineEnding = "\r\n"
}
if loc == nil {
separator := ""
if content != "" {
separator = lineEnding + lineEnding
if strings.HasSuffix(content, lineEnding) {
separator = lineEnding
}
if strings.HasSuffix(content, lineEnding+lineEnding) {
separator = ""
}
}
return content + separator + "[all:vars]" + lineEnding + "domain_name=" + target + lineEnding
}

insertAt := loc[1]
switch {
case strings.HasPrefix(content[insertAt:], "\r\n"):
insertAt += 2
case strings.HasPrefix(content[insertAt:], "\n"):
insertAt++
default:
return content[:insertAt] + lineEnding + "domain_name=" + target + content[insertAt:]
}

return content[:insertAt] + "domain_name=" + target + lineEnding + content[insertAt:]
}

// copyAndTransform copies the source directory, transforming text files.
// transformConfigJSON applies structural transformations to GOAD config JSON
// files (firstname/surname fixup, password remapping, ACL/share key rebuilds).
Expand Down Expand Up @@ -1092,12 +1150,13 @@ func (g *Generator) copyAndTransform() error {
return nil
}

// Skip .git files and completion markers inherited from a variant source.
// Skip git metadata and inherited completion markers, but preserve
// .gitkeep placeholders required to retain otherwise-empty directories.
rel, err := filepath.Rel(g.SourcePath, path)
if err != nil {
return fmt.Errorf("resolve relative path for %s: %w", path, err)
}
if strings.Contains(rel, ".git") {
if strings.Contains(rel, ".git") && d.Name() != ".gitkeep" {
return nil
}
if d.Name() == CompletionMarkerName || d.Name() == CompletionMarkerName+".tmp" {
Expand Down
73 changes: 73 additions & 0 deletions cli/internal/variant/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,79 @@ func setupTestSource(t *testing.T) (sourceDir, targetDir string) {
return sourceDir, targetDir
}

func TestTransformFileRepointsInventoryDomainName(t *testing.T) {
tests := []struct {
name string
content string
want string
}{
{
name: "replaces existing value",
content: "[all:vars]\n domain_name = GOAD\nadmin_user=administrator\n",
want: "[all:vars]\n domain_name = review-variant\nadmin_user=administrator\n",
},
{
name: "inserts missing value",
content: "[all:vars]\nadmin_user=administrator\n",
want: "[all:vars]\ndomain_name=review-variant\nadmin_user=administrator\n",
},
{
name: "adds missing all vars section",
content: "[default]\ndc01 ansible_host=10.0.0.10\n",
want: "[default]\ndc01 ansible_host=10.0.0.10\n\n[all:vars]\ndomain_name=review-variant\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sourceDir := t.TempDir()
targetDir := filepath.Join(t.TempDir(), "review-variant")
sourcePath := filepath.Join(sourceDir, "inventory_disable_vagrant")
if err := os.WriteFile(sourcePath, []byte(tt.content), 0o644); err != nil {
t.Fatal(err)
}

gen := NewGenerator(sourceDir, targetDir, "test")
transformed, err := gen.transformFile(sourcePath, filepath.Join("data", "inventory_disable_vagrant"))
if err != nil {
t.Fatalf("transformFile() error: %v", err)
}
if !transformed {
t.Fatal("transformFile() reported inventory was copied without transformation")
}

got, err := os.ReadFile(filepath.Join(targetDir, "data", "inventory_disable_vagrant"))
if err != nil {
t.Fatal(err)
}
if string(got) != tt.want {
t.Fatalf("transformed inventory = %q, want %q", got, tt.want)
}
})
}
}

func TestCopyAndTransformPreservesGitkeep(t *testing.T) {
sourceDir := t.TempDir()
targetDir := filepath.Join(t.TempDir(), "target")
relPath := filepath.Join("files", "srv02", "wwwroot", "upload", ".gitkeep")
sourcePath := filepath.Join(sourceDir, relPath)
if err := os.MkdirAll(filepath.Dir(sourcePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(sourcePath, nil, 0o644); err != nil {
t.Fatal(err)
}

gen := NewGenerator(sourceDir, targetDir, "test")
if err := gen.copyAndTransform(); err != nil {
t.Fatalf("copyAndTransform() error: %v", err)
}
if _, err := os.Stat(filepath.Join(targetDir, relPath)); err != nil {
t.Fatalf(".gitkeep was not preserved: %v", err)
}
}

func testConfig() *LabConfig {
config := &LabConfig{}
config.Lab.Hosts = map[string]*HostConfig{
Expand Down
Loading