// Package external реализует расширяемость модулей без пересборки // бинарника goherence: сторонний модуль — любой исполняемый файл, // который читает JSON (module.Input) из stdin и пишет JSON (module.Result) // в stdout. Контракт совпадает с тем, как устроены сами модули Ansible — // осознанное решение, чтобы язык реализации модуля был не важен. package external import ( "encoding/json" "fmt" "os" "path/filepath" ) // Manifest — метаданные одного внешнего модуля, лежат в modules.d//manifest.json. type Manifest struct { Name string `json:"name"` Version string `json:"version"` SchemaVersion int `json:"schema_version"` Architectures []string `json:"architectures"` // "linux/amd64", "linux/arm64" Checksums map[string]string `json:"checksums"` // arch → sha256 (без префикса) } // LoadManifest читает и валидирует manifest.json. func LoadManifest(path string) (*Manifest, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("читаю manifest.json: %w", err) } var m Manifest if err := json.Unmarshal(data, &m); err != nil { return nil, fmt.Errorf("парсинг manifest.json: %w", err) } if m.Name == "" || m.Version == "" { return nil, fmt.Errorf("manifest.json: name и version обязательны") } if len(m.Architectures) == 0 { return nil, fmt.Errorf("manifest.json: architectures не может быть пустым") } return &m, nil } // binaryPath — путь к локальному (control node) бинарнику модуля для // конкретной архитектуры. Соглашение об именовании: /--, // например modules.d/hello/hello-linux-amd64 для "linux/amd64". func binaryPath(moduleDir, name, arch string) string { goos, goarch := splitArch(arch) return filepath.Join(moduleDir, fmt.Sprintf("%s-%s-%s", name, goos, goarch)) } func splitArch(arch string) (goos, goarch string) { for i := 0; i < len(arch); i++ { if arch[i] == '/' { return arch[:i], arch[i+1:] } } return "linux", arch }