abra/catalogue/catalogue.go
decentral1se 932803453e
WIP: still hacking on the app new command
Finally had to fork godotenv because it strips comments and we need
those to parse length values (e.g. "FOO=v1  # length=10") (or in other
words, motivation to move to the YAML format).

There is a new secret module now, with functionality for dealing with
generation and parsing of secrets.

The final output needs some work and there is also the final step of
implementing the sending of secrets to the docker daemon. Coming Soon
™️.
2021-07-31 12:49:22 +02:00

215 lines
4.7 KiB
Go

package catalogue
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"time"
"coopcloud.tech/abra/config"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
type Image struct {
Image string `json:"image"`
Rating string `json:"rating"`
Source string `json:"source"`
URL string `json:"url"`
}
type Feature struct {
Backups string `json:"backups"`
Email string `json:"email"`
Healthcheck string `json:"healthcheck"`
Image Image `json:"image"`
Status int `json:"status"`
Tests string `json:"tests"`
}
type Tag = string
type Service = string
type ServiceMeta struct {
Digest string `json:"digest"`
Image string `json:"image"`
Tag string `json:"tag"`
}
type App struct {
Category string `json:"category"`
DefaultBranch string `json:"default_branch"`
Description string `json:"description"`
Features Feature `json:"features"`
Icon string `json:"icon"`
Name string `json:"name"`
Repository string `json:"repository"`
Versions map[Tag]map[Service]ServiceMeta `json:"versions"`
Website string `json:"website"`
}
func (a App) EnsureExists() error {
appDir := path.Join(config.ABRA_DIR, "apps", strings.ToLower(a.Name))
if _, err := os.Stat(appDir); os.IsNotExist(err) {
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, a.Name)
_, err := git.PlainClone(appDir, false, &git.CloneOptions{URL: url, Tags: git.AllTags})
if err != nil {
return err
}
}
return nil
}
func (a App) EnsureVersion(version string) error {
appDir := path.Join(config.ABRA_DIR, "apps", strings.ToLower(a.Name))
repo, err := git.PlainOpen(appDir)
if err != nil {
return err
}
tags, err := repo.Tags()
if err != nil {
return nil
}
var tagRef plumbing.ReferenceName
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
if ref.Name().Short() == version {
tagRef = ref.Name()
}
return nil
}); err != nil {
return err
}
if tagRef.String() == "" {
return fmt.Errorf("%s is not available?", version)
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
opts := &git.CheckoutOptions{Branch: tagRef, Keep: true}
if err := worktree.Checkout(opts); err != nil {
return err
}
return nil
}
func (a App) LatestVersion() string {
var latestVersion string
for tag := range a.Versions {
// apps.json versions are sorted so the last key is latest
latestVersion = tag
}
return latestVersion
}
type Name = string
type AppsCatalogue map[Name]App
func (a AppsCatalogue) Flatten() []App {
apps := make([]App, 0, len(a))
for name := range a {
apps = append(apps, a[name])
}
return apps
}
type ByAppName []App
func (a ByAppName) Len() int { return len(a) }
func (a ByAppName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAppName) Less(i, j int) bool {
return strings.ToLower(a[i].Name) < strings.ToLower(a[j].Name)
}
var AppsCatalogueURL = "https://apps.coopcloud.tech"
func AppsCatalogueFSIsLatest() (bool, error) {
httpClient := &http.Client{Timeout: 5 * time.Second}
res, err := httpClient.Head(AppsCatalogueURL)
if err != nil {
return false, err
}
lastModified := res.Header["Last-Modified"][0]
parsed, err := time.Parse(time.RFC1123, lastModified)
if err != nil {
return false, err
}
info, err := os.Stat(config.APPS_JSON)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
localModifiedTime := info.ModTime().Unix()
remoteModifiedTime := parsed.Unix()
if localModifiedTime < remoteModifiedTime {
return false, nil
}
return true, nil
}
func ReadAppsCatalogue() (AppsCatalogue, error) {
apps := make(AppsCatalogue)
appsFSIsLatest, err := AppsCatalogueFSIsLatest()
if err != nil {
return nil, err
}
if !appsFSIsLatest {
if err := ReadAppsCatalogueWeb(&apps); err != nil {
return nil, err
}
return apps, nil
}
if err := ReadAppsCatalogueFS(&apps); err != nil {
return nil, err
}
return apps, nil
}
func ReadAppsCatalogueFS(target interface{}) error {
appsJsonFS, err := ioutil.ReadFile(config.APPS_JSON)
if err != nil {
return err
}
if err := json.Unmarshal(appsJsonFS, &target); err != nil {
return err
}
return nil
}
func ReadAppsCatalogueWeb(target interface{}) error {
if err := readJson(AppsCatalogueURL, &target); err != nil {
return err
}
appsJson, err := json.MarshalIndent(target, "", " ")
if err != nil {
return err
}
if err := ioutil.WriteFile(config.APPS_JSON, appsJson, 0644); err != nil {
return err
}
return nil
}