metis/pkg/service/harbor.go

132 lines
4.3 KiB
Go

package service
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
func (a *App) artifactRepo(node string) string {
return fmt.Sprintf("%s/%s/%s", strings.TrimRight(a.settings.HarborRegistry, "/"), strings.Trim(a.settings.HarborProject, "/"), node)
}
func (a *App) ensureHarborProject() error {
if strings.TrimSpace(a.settings.HarborAPIBase) == "" || strings.TrimSpace(a.settings.HarborPassword) == "" {
return fmt.Errorf("harbor admin credentials are not configured")
}
client := &http.Client{Timeout: 30 * time.Second}
project := strings.TrimSpace(a.settings.HarborProject)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/projects?name=%s", strings.TrimRight(a.settings.HarborAPIBase, "/"), url.QueryEscape(project)), nil)
if err != nil {
return err
}
req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return fmt.Errorf("harbor project lookup failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
}
var projects []struct {
Name string `json:"name"`
}
if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&projects); err != nil {
return err
}
for _, item := range projects {
if strings.EqualFold(strings.TrimSpace(item.Name), project) {
return nil
}
}
payload := map[string]any{
"project_name": project,
"metadata": map[string]string{"public": "false"},
}
data, err := json.Marshal(payload)
if err != nil {
return err
}
req, err = http.NewRequest(http.MethodPost, fmt.Sprintf("%s/projects", strings.TrimRight(a.settings.HarborAPIBase, "/")), bytes.NewReader(data))
if err != nil {
return err
}
req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
req.Header.Set("Content-Type", "application/json")
resp, err = client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
return nil
}
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return fmt.Errorf("harbor project create failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
}
func (a *App) pruneHarborArtifacts(node string, keep int) error {
client := &http.Client{Timeout: 30 * time.Second}
repo := url.PathEscape(node)
apiBase := strings.TrimRight(a.settings.HarborAPIBase, "/")
project := url.PathEscape(strings.TrimSpace(a.settings.HarborProject))
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/projects/%s/repositories/%s/artifacts?page_size=100&with_tag=true", apiBase, project, repo), nil)
if err != nil {
return err
}
req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil
}
if resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return fmt.Errorf("harbor artifact list failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
}
var artifacts []struct {
Digest string `json:"digest"`
PushTime string `json:"push_time"`
Tags []struct {
Name string `json:"name"`
} `json:"tags"`
}
if err := json.NewDecoder(io.LimitReader(resp.Body, 2<<20)).Decode(&artifacts); err != nil {
return err
}
sort.Slice(artifacts, func(i, j int) bool {
return artifacts[i].PushTime > artifacts[j].PushTime
})
for idx, artifact := range artifacts {
if idx < keep {
continue
}
ref := url.PathEscape(artifact.Digest)
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/projects/%s/repositories/%s/artifacts/%s", apiBase, project, repo, ref), nil)
if err != nil {
return err
}
req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
resp, err := client.Do(req)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("harbor artifact delete failed for %s: %s", artifact.Digest, resp.Status)
}
}
return nil
}