Skip to content

ADD FUNC TO CHANGE VISIBILTY FOR GH PACKAGESΒ #129

@patrick-hermann-sva

Description

@patrick-hermann-sva
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

// GitHub API base URL
const baseURL = "https://api.github.com"

func ghRequest(url, token string) (map[string]interface{}, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&result); err == io.EOF {
		return nil, nil
	} else if err != nil {
		return nil, err
	}
	return result, nil
}

func ghRequestList(url, token string) ([]map[string]interface{}, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
	}

	var result []map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}
	return result, nil
}

func findInList(list []map[string]interface{}, name string) map[string]interface{} {
	for _, pkg := range list {
		if pkg["name"] == name {
			return pkg
		}
	}
	return nil
}

// πŸ”’ Function to change a package's visibility
func setPackageVisibility(org, pkgName, visibility, token string) error {
	url := fmt.Sprintf("%s/orgs/%s/packages/container/%s/visibility", baseURL, org, pkgName)

	body := map[string]string{"visibility": visibility}
	jsonBody, _ := json.Marshal(body)

	req, err := http.NewRequest("PATCH", url, bytes.NewReader(jsonBody))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	responseBody, _ := io.ReadAll(resp.Body)

	if resp.StatusCode >= 400 {
		return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(responseBody))
	}

	fmt.Printf("βœ… Updated visibility to %s (HTTP %d)\n", visibility, resp.StatusCode)
	return nil
}

func main() {
	org := "stuttgart-things"
	pkgName := "flux-source"
	token := os.Getenv("GITHUB_TOKEN")
	if token == "" {
		log.Fatal("❌ Missing GITHUB_TOKEN in environment")
	}

	listURL := fmt.Sprintf("%s/orgs/%s/packages?package_type=container", baseURL, org)
	fmt.Printf("πŸ”Ž Listing from: %s\n", listURL)
	list, err := ghRequestList(listURL, token)
	if err != nil {
		log.Printf("⚠️ Error listing packages: %v\n", err)
	}

	found := findInList(list, pkgName)
	if found != nil {
		fmt.Printf("βœ… Found %s (visibility: %s)\n", found["name"], found["visibility"])
		return
	}

	directURL := fmt.Sprintf("%s/orgs/%s/packages/container/%s", baseURL, org, pkgName)
	fmt.Printf("πŸ” Not found in list, trying direct lookup: %s\n", directURL)
	direct, err := ghRequest(directURL, token)
	if err != nil {
		log.Fatalf("❌ Package not found: %v", err)
	}

	name := direct["name"]
	visibility := direct["visibility"]
	versionCount := direct["version_count"]

	fmt.Printf("βœ… Found package: %s\n", name)
	fmt.Printf("   β†’ Visibility: %s\n", visibility)
	fmt.Printf("   β†’ Versions: %.0f\n", versionCount)

	// πŸ”„ Example: change visibility to public
	newVisibility := "public" // or "private"
	if visibility != newVisibility {
		fmt.Printf("βš™οΈ Changing visibility from %s β†’ %s...\n", visibility, newVisibility)
		if err := setPackageVisibility(org, pkgName, newVisibility, token); err != nil {
			log.Fatalf("❌ Failed to update visibility: %v", err)
		}
	} else {
		fmt.Println("ℹ️ Package already has desired visibility.")
	}
}

This is not a bug in your Go code. It’s a GitHub REST API limitation for org-owned GHCR packages:

For organization-owned container packages (flux-source under stuttgart-things), the API does not allow changing visibility at all.

That’s why the PATCH endpoint returns 404 Not Found, even though the package exists and your token is valid.

Only user-owned packages can currently have their visibility changed via API.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions