fix: Resolve repository URL for install/update (registry + install fallbacks)
This commit is contained in:
@@ -111,6 +111,7 @@ export function App() {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
existing.origin = 'both'
|
existing.origin = 'both'
|
||||||
existing.remoteSource = r.source
|
existing.remoteSource = r.source
|
||||||
|
existing.url = r.url || existing.url
|
||||||
existing.latestVersion = r.version
|
existing.latestVersion = r.version
|
||||||
existing.title = r.title || existing.title
|
existing.title = r.title || existing.title
|
||||||
existing.vendor = r.vendor || existing.vendor
|
existing.vendor = r.vendor || existing.vendor
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface RegistryItem {
|
|||||||
version: string
|
version: string
|
||||||
image?: string
|
image?: string
|
||||||
source?: string
|
source?: string
|
||||||
|
url?: string
|
||||||
title?: string
|
title?: string
|
||||||
vendor?: string
|
vendor?: string
|
||||||
author?: string
|
author?: string
|
||||||
@@ -64,6 +65,7 @@ export interface Container {
|
|||||||
|
|
||||||
// Fields populated by remote Git Registry
|
// Fields populated by remote Git Registry
|
||||||
source?: string;
|
source?: string;
|
||||||
|
url?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
|
||||||
// Optional properties for update state
|
// Optional properties for update state
|
||||||
|
|||||||
@@ -137,17 +137,13 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
|||||||
pkgNameLower := strings.ToLower(pkg.Name)
|
pkgNameLower := strings.ToLower(pkg.Name)
|
||||||
imageRef := fmt.Sprintf("git.dcntrlized.org/%s/%s:%s", ociOrg, pkgNameLower, pkg.Version)
|
imageRef := fmt.Sprintf("git.dcntrlized.org/%s/%s:%s", ociOrg, pkgNameLower, pkg.Version)
|
||||||
|
|
||||||
// Default fallback source from Gitea REST API
|
// The Gitea package API does not link org-level container packages to a
|
||||||
source := pkg.HTMLURL
|
// repository, so the repository URL is resolved from the image's OCI
|
||||||
if pkg.Repository != nil && pkg.Repository.HTMLURL != "" {
|
// labels below, or derived from the image reference as a last resort.
|
||||||
source = pkg.Repository.HTMLURL
|
|
||||||
}
|
|
||||||
|
|
||||||
item := types.RegistryContainer{
|
item := types.RegistryContainer{
|
||||||
Name: pkg.Name,
|
Name: pkg.Name,
|
||||||
Version: normalizeSemver(pkg.Version),
|
Version: normalizeSemver(pkg.Version),
|
||||||
Image: imageRef,
|
Image: imageRef,
|
||||||
Source: source,
|
|
||||||
CreatedAt: pkg.CreatedAt,
|
CreatedAt: pkg.CreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +236,17 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the source repository URL: OCI source label if present, else
|
||||||
|
// derived from the image reference (host/org/repo).
|
||||||
|
if item.Source == "" {
|
||||||
|
item.Source = repoURLFromImageRef(imageRef)
|
||||||
|
}
|
||||||
|
// The compose file is fetched from the git repository, so fall back to
|
||||||
|
// the resolved source URL when the OCI url label is absent.
|
||||||
|
if item.URL == "" {
|
||||||
|
item.URL = item.Source
|
||||||
|
}
|
||||||
|
|
||||||
registryItems = append(registryItems, item)
|
registryItems = append(registryItems, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,3 +255,13 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
|||||||
logger.Error("Failed to write response: %v", err)
|
logger.Error("Failed to write response: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// repoURLFromImageRef derives the repository browse URL from a container image
|
||||||
|
// reference of the form host/org/repo:tag (https://host/org/repo).
|
||||||
|
func repoURLFromImageRef(imageRef string) string {
|
||||||
|
ref, err := name.ParseReference(imageRef)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "https://" + ref.Context().RegistryStr() + "/" + ref.Context().RepositoryStr()
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -34,11 +35,14 @@ func InstallContainer(ctx context.Context, item types.RegistryContainer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fetch raw docker-compose.yaml from the Git URL
|
// 2. Fetch raw docker-compose.yaml from the Git URL
|
||||||
rawURL := fmt.Sprintf("%s/raw/branch/master/docker-compose.yaml", item.URL)
|
rawURL, err := composeRawURL(item)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to resolve repository URL: %w", err)
|
||||||
|
}
|
||||||
composePath := filepath.Join(targetDir, "docker-compose.yaml")
|
composePath := filepath.Join(targetDir, "docker-compose.yaml")
|
||||||
|
|
||||||
logger.Info("Fetching compose file from %s...", rawURL)
|
logger.Info("Fetching compose file from %s...", rawURL)
|
||||||
if err := downloadFile(ctx, rawURL, composePath); err != nil {
|
if err := downloadCompose(ctx, rawURL, composePath); err != nil {
|
||||||
return fmt.Errorf("failed to download docker-compose.yaml: %w", err)
|
return fmt.Errorf("failed to download docker-compose.yaml: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +351,37 @@ func getContainerDataDir(serviceName string) (string, error) {
|
|||||||
return filepath.Join(root, serviceName), nil
|
return filepath.Join(root, serviceName), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// composeRawURL resolves the raw docker-compose.yaml URL for a container,
|
||||||
|
// preferring the repository URL in item.URL (falling back to item.Source) and
|
||||||
|
// rejecting URLs without an http/https scheme.
|
||||||
|
func composeRawURL(item types.RegistryContainer) (string, error) {
|
||||||
|
base := strings.TrimRight(item.URL, "/")
|
||||||
|
if base == "" {
|
||||||
|
base = strings.TrimRight(item.Source, "/")
|
||||||
|
}
|
||||||
|
u, err := url.Parse(base)
|
||||||
|
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
||||||
|
return "", fmt.Errorf("invalid repository URL %q", base)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/raw/branch/master/docker-compose.yaml", base), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadCompose fetches the compose file, retrying on the main branch when
|
||||||
|
// the repository's default branch is not master.
|
||||||
|
func downloadCompose(ctx context.Context, url, destPath string) error {
|
||||||
|
err := downloadFile(ctx, url, destPath)
|
||||||
|
if err != nil {
|
||||||
|
mainURL := strings.Replace(url, "/raw/branch/master/", "/raw/branch/main/", 1)
|
||||||
|
if mainURL != url {
|
||||||
|
if retryErr := downloadFile(ctx, mainURL, destPath); retryErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func downloadFile(ctx context.Context, url string, destPath string) error {
|
func downloadFile(ctx context.Context, url string, destPath string) error {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user