diff --git a/pkg/devops/sources/github.go b/pkg/devops/sources/github.go new file mode 100644 index 0000000..98a86b6 --- /dev/null +++ b/pkg/devops/sources/github.go @@ -0,0 +1,70 @@ +package sources + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" +) + +// GitHubSource downloads images from GitHub Releases. +type GitHubSource struct { + config SourceConfig +} + +// Compile-time interface check. +var _ ImageSource = (*GitHubSource)(nil) + +// NewGitHubSource creates a new GitHub source. +func NewGitHubSource(cfg SourceConfig) *GitHubSource { + return &GitHubSource{config: cfg} +} + +// Name returns "github". +func (s *GitHubSource) Name() string { + return "github" +} + +// Available checks if gh CLI is installed and authenticated. +func (s *GitHubSource) Available() bool { + _, err := exec.LookPath("gh") + if err != nil { + return false + } + // Check if authenticated + cmd := exec.Command("gh", "auth", "status") + return cmd.Run() == nil +} + +// LatestVersion returns the latest release tag. +func (s *GitHubSource) LatestVersion(ctx context.Context) (string, error) { + cmd := exec.CommandContext(ctx, "gh", "release", "view", + "-R", s.config.GitHubRepo, + "--json", "tagName", + "-q", ".tagName", + ) + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("github.LatestVersion: %w", err) + } + return strings.TrimSpace(string(out)), nil +} + +// Download downloads the image from the latest release. +func (s *GitHubSource) Download(ctx context.Context, dest string, progress func(downloaded, total int64)) error { + // Get release assets to find our image + cmd := exec.CommandContext(ctx, "gh", "release", "download", + "-R", s.config.GitHubRepo, + "-p", s.config.ImageName, + "-D", dest, + "--clobber", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("github.Download: %w", err) + } + return nil +} diff --git a/pkg/devops/sources/github_test.go b/pkg/devops/sources/github_test.go new file mode 100644 index 0000000..9d7a2a3 --- /dev/null +++ b/pkg/devops/sources/github_test.go @@ -0,0 +1,19 @@ +package sources + +import ( + "testing" +) + +func TestGitHubSource_Good_Available(t *testing.T) { + src := NewGitHubSource(SourceConfig{ + GitHubRepo: "host-uk/core-images", + ImageName: "core-devops-darwin-arm64.qcow2", + }) + + if src.Name() != "github" { + t.Errorf("expected name 'github', got %q", src.Name()) + } + + // Available depends on gh CLI being installed + _ = src.Available() +}