feat: Implemented (un)install,start/stop flow and fixed actions flow

This commit is contained in:
2026-08-01 20:39:27 +01:00
parent eb01569f7d
commit d24fe0ffbf
29 changed files with 2331 additions and 166 deletions
+53 -32
View File
@@ -10,6 +10,7 @@ import (
"time"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/logger"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
@@ -17,29 +18,6 @@ import (
"golang.org/x/mod/semver"
)
// RegistryContainer represents a clean API payload specifically for container registry items.
type RegistryContainer struct {
Name string `json:"name"`
Version string `json:"version"`
Image string `json:"image"`
Title string `json:"title,omitempty"`
Vendor string `json:"vendor,omitempty"`
Author string `json:"author,omitempty"`
Description string `json:"description,omitempty"`
Source string `json:"source,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
}
type RawPackage struct {
Name string `json:"name"`
Version string `json:"version"`
CreatedAt string `json:"created_at"`
HTMLURL string `json:"html_url"`
Repository *struct {
HTMLURL string `json:"html_url"`
} `json:"repository"`
}
func GetRegistry(w http.ResponseWriter, r *http.Request) {
// 1. Query Gitea REST API using the exact Org name
apiURL := fmt.Sprintf("https://git.dcntrlized.org/api/v1/packages/%s?type=container", os.Getenv("GITEA_ORG"))
@@ -51,7 +29,7 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
return
}
logger.Debug(fmt.Sprintf("%s", os.Getenv("GITEA_RW_PACKAGE_TOKEN")))
logger.Debug("Querying Gitea package API at %s", apiURL)
req.Header.Set("Authorization", "token "+os.Getenv("GITEA_RW_PACKAGE_TOKEN"))
client := &http.Client{Timeout: 10 * time.Second}
@@ -69,7 +47,7 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
return
}
var rawPackages []RawPackage
var rawPackages []types.RawPackage
if err := json.NewDecoder(resp.Body).Decode(&rawPackages); err != nil {
logger.Error("Failed to decode registry response: %v", err)
http.Error(w, "Failed to decode registry response", http.StatusInternalServerError)
@@ -77,7 +55,7 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
}
// 2. Group by Name and filter out "latest" & "sha256:"
groupedPackages := make(map[string][]RawPackage)
groupedPackages := make(map[string][]types.RawPackage)
for _, pkg := range rawPackages {
if pkg.Version == "latest" || strings.HasPrefix(pkg.Version, "sha256:") {
continue
@@ -94,7 +72,7 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
}
// 3. For each container name, find the package entry with the highest semver
var latestPackages []RawPackage
var latestPackages []types.RawPackage
for _, pkgs := range groupedPackages {
if len(pkgs) == 0 {
continue
@@ -118,23 +96,24 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
ociAuth := remote.WithAuth(&authn.Basic{
Username: os.Getenv("GITEA_USER"),
Password: os.Getenv("GITEA_RO_TOKEN"),
Password: os.Getenv("GITEA_RW_PACKAGE_TOKEN"),
})
ociOrg := strings.ToLower(os.Getenv("GITEA_ORG"))
var registryItems []RegistryContainer
var registryItems []types.RegistryContainer
// 4. Fetch OCI metadata only for the highest version of each package
for _, pkg := range latestPackages {
pkgNameLower := strings.ToLower(pkg.Name)
imageRef := fmt.Sprintf("git.dcntrlized.org/%s/%s:%s", ociOrg, pkgNameLower, pkg.Version)
// Default fallback source from Gitea REST API
source := pkg.HTMLURL
if pkg.Repository != nil && pkg.Repository.HTMLURL != "" {
source = pkg.Repository.HTMLURL
}
item := RegistryContainer{
item := types.RegistryContainer{
Name: pkg.Name,
Version: pkg.Version,
Image: imageRef,
@@ -156,7 +135,12 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
continue
}
// Extract annotations from OCI manifest
// Fetch image digest (sha256:...)
if digest, err := img.Digest(); err == nil {
item.Digest = digest.String()
}
// 1. Extract standard OCI annotations from OCI Manifest
if manifest, err := img.Manifest(); err == nil && manifest != nil {
if title, ok := manifest.Annotations["org.opencontainers.image.title"]; ok {
item.Title = title
@@ -170,11 +154,30 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
if desc, ok := manifest.Annotations["org.opencontainers.image.description"]; ok {
item.Description = desc
}
if src, ok := manifest.Annotations["org.opencontainers.image.source"]; ok && src != "" {
item.Source = src
}
if rev, ok := manifest.Annotations["org.opencontainers.image.revision"]; ok {
item.Revision = rev
}
if lic, ok := manifest.Annotations["org.opencontainers.image.licenses"]; ok {
item.Licenses = lic
}
if url, ok := manifest.Annotations["org.opencontainers.image.url"]; ok {
item.URL = url
}
if doc, ok := manifest.Annotations["org.opencontainers.image.documentation"]; ok {
item.Documentation = doc
}
if created, ok := manifest.Annotations["org.opencontainers.image.created"]; ok && created != "" {
item.CreatedAt = created
}
}
// Fallback to Image Config labels
// 2. Fallback to Image Config Labels (where docker buildx puts them when targeting v2 registries)
if configFile, err := img.ConfigFile(); err == nil && configFile != nil {
labels := configFile.Config.Labels
if item.Title == "" {
item.Title = labels["org.opencontainers.image.title"]
}
@@ -187,6 +190,24 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
if item.Description == "" {
item.Description = labels["org.opencontainers.image.description"]
}
if src, ok := labels["org.opencontainers.image.source"]; ok && src != "" {
item.Source = src
}
if item.Revision == "" {
item.Revision = labels["org.opencontainers.image.revision"]
}
if item.Licenses == "" {
item.Licenses = labels["org.opencontainers.image.licenses"]
}
if item.URL == "" {
item.URL = labels["org.opencontainers.image.url"]
}
if item.Documentation == "" {
item.Documentation = labels["org.opencontainers.image.documentation"]
}
if item.CreatedAt == "" && labels["org.opencontainers.image.created"] != "" {
item.CreatedAt = labels["org.opencontainers.image.created"]
}
}
registryItems = append(registryItems, item)