Skip to content

Instantly share code, notes, and snippets.

@aslafy-z
Last active July 11, 2025 08:33
Show Gist options
  • Save aslafy-z/458a80fe62c94099b1540548528307e6 to your computer and use it in GitHub Desktop.
Save aslafy-z/458a80fe62c94099b1540548528307e6 to your computer and use it in GitHub Desktop.
List all Scaleway resources in an organization
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"regexp"
"sort"
"strings"
"sync"
"time"
)
var (
cmdPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
outputSerial = make(chan string)
wg sync.WaitGroup
allEverything bool
allProjects bool
allZones bool
currentProjectID string
projectIDs []string
currentZone string
zones []string
projectFlag string
zoneFlag string
outputFormat string
onlyPrefix string
matchFilter string
)
func isCommand(s string) bool {
return cmdPattern.MatchString(s) && !strings.HasPrefix(s, "-")
}
func main() {
flag.BoolVar(&allProjects, "all-projects", false, "Run list commands for all discovered projects. Cannot be used with -project.")
flag.BoolVar(&allZones, "all-zones", false, "Run list commands for all discovered zones. Cannot be used with -zone.")
flag.BoolVar(&allEverything, "A", false, "Shortcut for -all-projects and -all-zones.")
flag.StringVar(&outputFormat, "output", "", "Set output format (e.g., 'json', 'human=Name,PublicIP'). Defaults to the CLI's standard format if not specified.")
flag.StringVar(&outputFormat, "o", "", "Alias for -output.")
flag.StringVar(&projectFlag, "project", "", "Comma-separated project ID(s) to use (e.g., 'proj-abc123,proj-def456'). Overrides default project. Cannot be used with -all-projects or -A.")
flag.StringVar(&zoneFlag, "zone", "", "Comma-separated zone(s) to use (e.g., 'fr-par-1,nl-ams-1'). Overrides default zone. Cannot be used with -all-zones or -A.")
flag.StringVar(&onlyPrefix, "only", "", "Only explore commands under this top-level namespace (e.g., 'instance').")
flag.StringVar(&matchFilter, "match", "", "Only run 'list' commands where the full command path includes this substring.")
flag.Parse()
if allEverything {
allProjects = true
allZones = true
}
if projectFlag != "" && allProjects {
fmt.Fprintln(os.Stderr, "❌ Cannot use --project with --all-projects or -A")
os.Exit(1)
}
if zoneFlag != "" && allZones {
fmt.Fprintln(os.Stderr, "❌ Cannot use --zone with --all-zones or -A")
os.Exit(1)
}
var err error
projectIDs, err = getProjectIDs()
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to list projects: %v\n", err)
os.Exit(1)
}
zones, err = getZonesFromHelp()
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to get zones: %v\n", err)
os.Exit(1)
}
// Sort zones to ensure consistent ordering
sort.Strings(zones)
fmt.Fprintf(os.Stderr, "Found zones: %v\n", zones)
currentProjectID, currentZone = getDefaultsFromInfo()
if projectFlag != "" {
projectIDs = strings.Split(projectFlag, ",")
allProjects = true
}
if zoneFlag != "" {
zones = strings.Split(zoneFlag, ",")
allZones = true
}
outputDone := make(chan struct{})
go func() {
defer close(outputDone)
for msg := range outputSerial {
fmt.Print(msg)
}
}()
visited := make(map[string]bool)
dumpHelpAndRun("scw", []string{}, visited)
wg.Wait()
close(outputSerial)
<-outputDone
}
func getProjectIDs() ([]string, error) {
cmd := exec.Command("scw", "account", "project", "list", "-o", "json")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("scw project list failed: %s", out.String())
}
var raw []map[string]interface{}
if err := json.Unmarshal(out.Bytes(), &raw); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %v", err)
}
var ids []string
for _, obj := range raw {
if id, ok := obj["id"].(string); ok {
ids = append(ids, id)
}
}
if len(ids) == 0 {
return nil, fmt.Errorf("no project IDs found")
}
return ids, nil
}
func getZonesFromHelp() ([]string, error) {
cmd := exec.Command("scw", "config", "set", "--help")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("scw config set --help failed: %s", out.String())
}
re := regexp.MustCompile(`(?i)default-zone.*?\((.*?)\)`)
matches := re.FindStringSubmatch(out.String())
if len(matches) < 2 {
return nil, fmt.Errorf("zone pattern not found")
}
var result []string
for _, z := range strings.Split(matches[1], "|") {
zone := strings.TrimSpace(z)
if zone != "" {
result = append(result, zone)
}
}
return result, nil
}
func getDefaultsFromInfo() (projectID, zone string) {
cmd := exec.Command("scw", "info")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
_ = cmd.Run()
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) >= 2 {
key, value := fields[0], fields[1]
if key == "default_project_id" {
projectID = value
}
if key == "default_zone" {
zone = value
}
}
}
return
}
func containsArg(help, name string) (bool, bool) {
bracketed := fmt.Sprintf("[%s]", name)
bracketedEq := fmt.Sprintf("[%s=", name)
if strings.Contains(help, bracketed) || strings.Contains(help, bracketedEq) {
return true, false
}
lines := strings.Split(help, "\n")
inArgs := false
for _, line := range lines {
trim := strings.TrimSpace(line)
if strings.HasPrefix(trim, "ARGS:") {
inArgs = true
continue
}
if strings.HasPrefix(trim, "FLAGS:") || strings.HasPrefix(trim, "GLOBAL FLAGS:") || trim == "" {
inArgs = false
}
if inArgs {
re := regexp.MustCompile(fmt.Sprintf(`^%s(\s|=|$)`, regexp.QuoteMeta(name)))
if re.MatchString(trim) {
return true, true
}
}
}
return false, false
}
func dumpHelpAndRun(base string, path []string, visited map[string]bool) {
if onlyPrefix != "" {
onlyParts := strings.Fields(onlyPrefix)
if len(path) > len(onlyParts) {
for i, part := range onlyParts {
if path[i] != part {
return
}
}
} else {
for i, part := range path {
if onlyParts[i] != part {
return
}
}
}
}
key := strings.Join(append([]string{base}, path...), " ")
if visited[key] {
return
}
visited[key] = true
helpOutput := getHelpOutput(base, path)
lines := strings.Split(helpOutput, "\n")
var subcommands []string
inCommandBlock := false
for _, line := range lines {
trim := strings.TrimSpace(line)
if strings.HasSuffix(trim, "COMMANDS:") {
inCommandBlock = true
continue
}
if inCommandBlock && strings.HasPrefix(line, " ") {
fields := strings.Fields(line)
if len(fields) > 0 && isCommand(fields[0]) {
subcommands = append(subcommands, fields[0])
}
}
if trim == "" || strings.HasPrefix(trim, "FLAGS:") {
inCommandBlock = false
}
}
for _, sub := range subcommands {
if sub == "list" {
fullPath := append(path, sub)
listHelp := getHelpOutput(base, fullPath)
supportsProjectID, _ := containsArg(listHelp, "project-id")
supportsZone, _ := containsArg(listHelp, "zone")
if matchFilter != "" && !strings.Contains(strings.Join(path, " "), matchFilter) {
continue
}
projectList := []string{""}
if allProjects && supportsProjectID {
projectList = make([]string, len(projectIDs))
copy(projectList, projectIDs)
} else if supportsProjectID {
projectList = []string{currentProjectID}
}
zoneList := []string{""}
if allZones && supportsZone {
zoneList = make([]string, len(zones))
copy(zoneList, zones)
} else if supportsZone {
zoneList = []string{currentZone}
}
for _, project := range projectList {
for _, zone := range zoneList {
pathCopy := make([]string, len(path)+1)
copy(pathCopy, path)
pathCopy[len(path)] = sub
runListCommand(pathCopy, project, zone)
}
}
} else {
dumpHelpAndRun(base, append(path, sub), visited)
}
}
}
func getHelpOutput(base string, path []string) string {
cmd := exec.Command(base, append(path, "--help")...)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
_ = cmd.Run()
return out.String()
}
func runListCommand(path []string, projectID, zone string) {
wg.Add(1)
go func() {
defer wg.Done()
cmdArgs := path
if projectID != "" {
cmdArgs = append(cmdArgs, fmt.Sprintf("project-id=%s", projectID))
}
if zone != "" {
cmdArgs = append(cmdArgs, fmt.Sprintf("zone=%s", zone))
}
if outputFormat != "" {
cmdArgs = append(cmdArgs, "-o", outputFormat)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "scw", cmdArgs...)
var outBuf, errBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
err := cmd.Run()
var b strings.Builder
b.WriteString(fmt.Sprintf("\n=== scw %s ===\n\n", strings.Join(cmdArgs, " ")))
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
b.WriteString("[error] Command timed out after 60 seconds.\n")
} else {
b.WriteString(fmt.Sprintf("[error] Command failed: %v\n", err))
}
}
if errBuf.Len() > 0 {
b.WriteString("[stderr]\n" + errBuf.String())
}
if outBuf.Len() > 0 {
b.WriteString(outBuf.String())
}
outputSerial <- b.String()
}()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment