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
16 changes: 16 additions & 0 deletions cmd/agents/versions.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,8 +33,17 @@ var (
fetchReleases = fetchReleasesFromGitHub
fetchTags = fetchTagsFromGitHub
fetchOCITags = fetchOCITagsFromRegistry
repoArchived = agentRepoArchived
)

// agentRepoArchived reports whether the agent's GitHub repo is archived. An
// archived repo is frozen — it can never publish another version — so drift
// tooling treats it as up to date instead of forever "behind".
func agentRepoArchived(ctx context.Context, agent *resources.Agent) bool {
owner, repo := githubSource(agent)
return gh.Archived(ctx, owner, repo)
}

// releaseInfo is one published GitHub release: the version it tags and the
// os_arch suffixes it ships a downloadable asset for.
type releaseInfo struct {
Expand DownExpand Up@@ -200,6 +209,13 @@ func init() {
// resolvability inventory. GitHub lookups that fail (missing repo, rate limit)
// degrade to a warning so the local-cache and pinned columns still render.
func collectInventory(ctx context.Context, agent *resources.Agent, pinned []string) inventory {
// An archived repo is frozen: it will never publish another tag or release,
// so its pin can't advance. Reporting it as "N versions behind" is pure
// noise (agent list / versions / `codefly ci`). Skip the remote sources and
// report only what's pinned and locally cached, so drift computes 0 behind.
if repoArchived(ctx, agent) {
return buildInventory(agent, nil, nil, localCacheVersions(ctx, agent), pinned, nil, false)
}
releases, err := fetchReleases(ctx, agent)
if err != nil {
cli.Warning("cannot list GitHub releases for %s/%s: %v", agent.Publisher, agent.Name, err)
Expand Down
56 changes: 50 additions & 6 deletions cmd/agents/versions_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,8 +176,12 @@ func TestVersionsBehindIgnoresUnresolvableNewer(t *testing.T) {
}

func TestSummarizeWorkspaceAgentsReportsDrift(t *testing.T) {
restoreReleases, restoreTags, restoreOCI := fetchReleases, fetchTags, fetchOCITags
defer func() { fetchReleases, fetchTags, fetchOCITags = restoreReleases, restoreTags, restoreOCI }()
restoreReleases, restoreTags, restoreOCI, restoreArchived := fetchReleases, fetchTags, fetchOCITags, repoArchived
defer func() {
fetchReleases, fetchTags, fetchOCITags, repoArchived = restoreReleases, restoreTags, restoreOCI, restoreArchived
}()
// Keep the archived check off the network: none of these fixtures are archived.
repoArchived = func(context.Context, *resources.Agent) bool { return false }

fetchReleases = func(_ context.Context, _ *resources.Agent) ([]releaseInfo, error) {
return []releaseInfo{
Expand DownExpand Up@@ -209,8 +213,12 @@ func TestSummarizeWorkspaceAgentsReportsDrift(t *testing.T) {
}

func TestLatestResolvableDriftMatchesInventory(t *testing.T) {
restoreReleases, restoreTags, restoreOCI := fetchReleases, fetchTags, fetchOCITags
defer func() { fetchReleases, fetchTags, fetchOCITags = restoreReleases, restoreTags, restoreOCI }()
restoreReleases, restoreTags, restoreOCI, restoreArchived := fetchReleases, fetchTags, fetchOCITags, repoArchived
defer func() {
fetchReleases, fetchTags, fetchOCITags, repoArchived = restoreReleases, restoreTags, restoreOCI, restoreArchived
}()
// Keep the archived check off the network: none of these fixtures are archived.
repoArchived = func(context.Context, *resources.Agent) bool { return false }

fetchReleases = func(_ context.Context, _ *resources.Agent) ([]releaseInfo, error) {
return []releaseInfo{{version: "0.0.22", platforms: []string{ciPlatform}}}, nil
Expand All@@ -232,6 +240,38 @@ func TestLatestResolvableDriftMatchesInventory(t *testing.T) {
}
}

func TestArchivedRepoReportsNoDrift(t *testing.T) {
restoreReleases, restoreTags, restoreOCI, restoreArchived := fetchReleases, fetchTags, fetchOCITags, repoArchived
defer func() {
fetchReleases, fetchTags, fetchOCITags, repoArchived = restoreReleases, restoreTags, restoreOCI, restoreArchived
}()
// The remote clearly has newer resolvable releases...
fetchReleases = func(_ context.Context, _ *resources.Agent) ([]releaseInfo, error) {
return []releaseInfo{{version: "0.0.99", platforms: []string{ciPlatform}}}, nil
}
fetchTags = func(_ context.Context, _ *resources.Agent) ([]string, error) {
return []string{"0.0.15", "0.0.99"}, nil
}
fetchOCITags = func(_ context.Context, _ *resources.Agent) (bool, []string, error) {
return false, nil, nil
}
// ...but the repo is archived, so drift tooling must ignore it entirely and
// must never even reach for the remote source list.
repoArchived = func(context.Context, *resources.Agent) bool { return true }
fetchReleases = func(_ context.Context, _ *resources.Agent) ([]releaseInfo, error) {
t.Fatal("fetchReleases called for an archived repo")
return nil, nil
}

latest, behind := LatestResolvableDrift(context.Background(), redisAgent(), "0.0.15")
if behind != 0 {
t.Fatalf("behind = %d, want 0 for an archived repo", behind)
}
if latest != "" {
t.Fatalf("latest resolvable = %q, want empty for an archived repo", latest)
}
}

func TestBehindCell(t *testing.T) {
if got := behindCell(0); got != "-" {
t.Fatalf("behindCell(0) = %q, want -", got)
Expand DownExpand Up@@ -304,8 +344,12 @@ func TestLocalCacheVersionsScansAgentDir(t *testing.T) {
}

func TestSummarizeWorkspaceAgentsCachesAndFlagsResolvability(t *testing.T) {
restoreReleases, restoreTags, restoreOCI := fetchReleases, fetchTags, fetchOCITags
defer func() { fetchReleases, fetchTags, fetchOCITags = restoreReleases, restoreTags, restoreOCI }()
restoreReleases, restoreTags, restoreOCI, restoreArchived := fetchReleases, fetchTags, fetchOCITags, repoArchived
defer func() {
fetchReleases, fetchTags, fetchOCITags, repoArchived = restoreReleases, restoreTags, restoreOCI, restoreArchived
}()
// Keep the archived check off the network: none of these fixtures are archived.
repoArchived = func(context.Context, *resources.Agent) bool { return false }

var releaseCalls int
fetchReleases = func(_ context.Context, agent *resources.Agent) ([]releaseInfo, error) {
Expand Down
24 changes: 24 additions & 0 deletions cmd/status/release.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,8 +105,21 @@ func runRelease(cmd *cobra.Command, args []string) error {
if createIssues {
fmt.Printf("\n==> Creating GitHub issues for agents with issues...\n")
issuesCreated := 0
ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}
for _, s := range statuses {
if s.Delta > 50 || len(s.Issues) > 0 {
// Never file a chore issue against an archived repo: it's frozen
// and can't be bumped or released, so the issue would be noise
// nobody can act on. Resolve the repo from the clone's origin so
// this works regardless of org; an unresolvable remote falls
// through to the normal (non-archived) path.
if repoIsArchived(ctx, filepath.Join(baseDir, s.Name)) {
fmt.Printf("⏭ Skipping archived repo %s\n", s.Name)
continue
}
if err := createAgentIssue(baseDir, s); err != nil {
fmt.Printf("⚠ Failed to create issue for %s: %v\n", s.Name, err)
} else {
Expand DownExpand Up@@ -251,6 +264,17 @@ func checkAgentHealth(agentPath string) []string {
return issues
}

// repoIsArchived reports whether the clone at path points at an archived GitHub
// repo. A remote we can't resolve (not a git repo, no origin, non-GitHub) is
// treated as non-archived so the caller proceeds exactly as before.
func repoIsArchived(ctx context.Context, path string) bool {
owner, repo, err := agentRepository(path)
if err != nil {
return false
}
return gh.Archived(ctx, owner, repo)
}

func createAgentIssue(baseDir string, status AgentStatus) error {
agentPath := filepath.Join(baseDir, status.Name)

Expand Down
23 changes: 23 additions & 0 deletions pkg/gh/gh.go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
package gh

import "context"

// Archived reports whether owner/repo is an archived GitHub repository.
// A lookup failure (missing repo, rate limit, no auth, client construction)
// returns false so callers only skip a repo on a *confirmed* archived flag and
// otherwise degrade to their normal behavior rather than silently hiding live
// repos.
func Archived(ctx context.Context, owner, repo string) bool {
if owner == "" || repo == "" {
return false
}
client, err := NewClient()
if err != nil || client == nil {
return false
}
r, _, err := client.Repositories.Get(ctx, owner, repo)
if err != nil {
return false
}
return r.GetArchived()
}
Loading