From b00c93e56848139af50349166c79bc544e0accc6 Mon Sep 17 00:00:00 2001 From: Performance and Reliability Team Date: Wed, 17 Jul 2024 12:43:39 -0700 Subject: [PATCH] Export aerolab as an Ansible dynamic inventory This adds support for exporting the clusters deployed via aerolab as an Ansible dynamic inventory. Adding a symlink called `aerolab-ansible` to `aerolab` allows you to pass it to ansible. ``` ln -s aerolab aerolab-ansible ansible-playbook -i ./aerolab-ansible playbook.yaml ``` --- .gitignore | 3 +- src/cmd.go | 2 +- src/cmdInventory.go | 1 + src/cmdInventoryExport.go | 141 ++++++++++++++++++++++++++++++++++++++ src/main.go | 7 +- 5 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/cmdInventoryExport.go diff --git a/.gitignore b/.gitignore index 37dca35f..e7a72417 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ !bin/aerolabrpm .DS_Store .vscode -VERSION.md \ No newline at end of file +.idea +VERSION.md diff --git a/src/cmd.go b/src/cmd.go index 32c2bd8e..e84cae14 100644 --- a/src/cmd.go +++ b/src/cmd.go @@ -57,7 +57,7 @@ func (c *showcommandsCmd) Execute(args []string) error { return fmt.Errorf("failed to get absolute path os self: %s", err) } log.Printf("Discovered absolute path: %s", cur) - for _, dest := range []string{"showconf", "showsysinfo", "showinterrupts"} { + for _, dest := range []string{"showconf", "showsysinfo", "showinterrupts", "aerolab-ansible"} { d := filepath.Join(c.DestDir, dest) log.Printf("> ln -s %s %s", cur, d) if _, err := os.Stat(d); err == nil { diff --git a/src/cmdInventory.go b/src/cmdInventory.go index 90134c5d..26d05100 100644 --- a/src/cmdInventory.go +++ b/src/cmdInventory.go @@ -6,6 +6,7 @@ import ( type inventoryCmd struct { List inventoryListCmd `command:"list" subcommands-optional:"true" description:"List clusters, clients and templates" webicon:"fas fa-list"` + Ansible inventoryAnsibleCmd `command:"ansible" subcommands-optional:"true" description:"Export inventory as ansible inventory" webicon:"fas fa-list"` InstanceTypes inventoryInstanceTypesCmd `command:"instance-types" subcommands-optional:"true" description:"Lookup GCP|AWS available instance types" webicon:"fas fa-table-list"` Help helpCmd `command:"help" subcommands-optional:"true" description:"Print help"` } diff --git a/src/cmdInventoryExport.go b/src/cmdInventoryExport.go new file mode 100644 index 00000000..a1dfc3ab --- /dev/null +++ b/src/cmdInventoryExport.go @@ -0,0 +1,141 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +type HostVars map[string]interface{} +type Hosts map[string]HostVars + +type Group struct { + Hosts []string `json:"hosts,omitempty"` + Vars map[string]interface{} `json:"vars,omitempty"` +} + +type Groups map[string]Group + +type Meta struct { + Hostvars Hosts `json:"hostvars"` +} + +type AnsibleInventory struct { + Groups Groups `json:"groups"` + Meta Meta `json:"_meta"` +} + +func (a AnsibleInventory) MarshalJSON() ([]byte, error) { + aux := make(map[string]interface{}) + + for key, value := range a.Groups { + aux[key] = value + } + + aux["_meta"] = a.Meta + + return json.Marshal(aux) +} + +type inventoryAnsibleCmd struct{} + +func (c *inventoryAnsibleCmd) Execute(args []string) error { + if earlyProcess(args) { + return nil + } + return c.run() +} + +func (c *inventoryAnsibleCmd) run() error { + projectName := os.Getenv("PROJECT_NAME") + + inventoryItems := []int{} + inventoryItems = append(inventoryItems, InventoryItemClusters) + inventoryItems = append(inventoryItems, InventoryItemClients) + + inventory, err := b.Inventory("", inventoryItems) + if err != nil { + return fmt.Errorf("b.Inventory: %s", err) + } + + inv := AnsibleInventory{ + Groups: Groups{}, + Meta: Meta{ + Hostvars: Hosts{}, + }, + } + + processCluster := func(clusterName, groupName, project, nodeNo, privateIP, instanceId, sshKeyPath string) { + inv.Meta.Hostvars[privateIP] = HostVars{ + "ansible_host": privateIP, + "instance_id": instanceId, + "node_name": fmt.Sprintf("%s-%s", clusterName, nodeNo), + "ansible_user": "root", + "aerolab_cluster": clusterName, + } + + if project != "" { + inv.Meta.Hostvars[privateIP]["project"] = project + } + + if sshKeyPath != "" { + inv.Meta.Hostvars[privateIP]["ansible_ssh_private_key_file"] = sshKeyPath + } + + if _, exists := inv.Groups[groupName]; !exists { + inv.Groups[groupName] = Group{Hosts: []string{}, Vars: map[string]interface{}{}} + } + group := inv.Groups[groupName] + group.Hosts = append(group.Hosts, privateIP) + inv.Groups[groupName] = group + } + + for _, cluster := range inventory.Clusters { + project := searchField("project", cluster.AwsTags, cluster.GcpLabels, cluster.DockerLabels) + + if projectName != "" { + if project != projectName { + continue + } + } + + groupName := "aerospike" + if cluster.Features&ClusterFeatureAGI > 0 { + groupName = "agi" + } + + processCluster(cluster.ClusterName, groupName, project, cluster.NodeNo, cluster.PrivateIp, cluster.InstanceId, cluster.SSHKeyPath) + } + + for _, cluster := range inventory.Clients { + project := searchField("project", cluster.AwsTags, cluster.GcpLabels, cluster.DockerLabels) + + if projectName != "" { + if project != projectName { + continue + } + } + + processCluster(cluster.ClientName, cluster.ClientType, project, cluster.NodeNo, cluster.PrivateIp, cluster.InstanceId, cluster.SSHKeyPath) + } + + inventoryJson, err := json.MarshalIndent(inv, "", " ") + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + fmt.Println(string(inventoryJson)) + + return nil +} + +func searchField(field string, maps ...map[string]string) string { + for _, m := range maps { + if value, ok := m[field]; ok { + return value + } + } + + return "" +} diff --git a/src/main.go b/src/main.go index acdd9dab..0e52d86a 100644 --- a/src/main.go +++ b/src/main.go @@ -22,11 +22,12 @@ import ( "sync" "time" - "github.com/aerospike/aerolab/eksexpiry" "github.com/aws/aws-sdk-go/aws" "github.com/bestmethod/inslice" "github.com/google/uuid" flags "github.com/rglonek/jeddevdk-goflags" + + "github.com/aerospike/aerolab/eksexpiry" ) type helpCmd struct{} @@ -138,6 +139,10 @@ func main() { showcommands() case "eksexpiry": eksexpiry.Expiry() + case "aerolab-ansible": + os.Args = []string{os.Args[0], "inventory", "ansible"} + + fallthrough default: if beepenv := os.Getenv("AEROLAB_BEEP"); beepenv != "" { bp, err := strconv.Atoi(beepenv)