Compare commits

...

45 Commits

Author SHA1 Message Date
4235e06943 chore: new 0.1.8-alpha release 2021-10-12 11:19:26 +02:00
a9af0b3627 fix: let gofmt do its magic 2021-10-12 10:34:10 +02:00
3wc
a0b4886eba WIP: default to compose.yml instead of all of 'em 2021-10-12 10:25:37 +02:00
84489495dc fix: load STACK_NAME if not present 2021-10-12 09:03:48 +02:00
a8683dc38a refactor: better formatting 2021-10-12 08:59:14 +02:00
e2128ea5b6 fix: check key existance correctly 2021-10-12 08:55:42 +02:00
ca3c5fef0f refactor: better wording [ci skip] 2021-10-12 08:49:38 +02:00
4a01e411be refactor: handle STACK_NAME override in one place 2021-10-12 01:14:14 +02:00
777d49ac1d fix: handle STACK_NAME for the ps command 2021-10-12 01:11:34 +02:00
deb7d21158 fix: dont loop over dead tags
Closes coop-cloud/organising#195.
2021-10-12 00:56:52 +02:00
6db1fdcfba refactor!: recipe upgrade: use new tagcmp version 2021-10-11 14:43:06 +00:00
44dc0edf7b refactor: use ; trick for inline checking [ci skip] 2021-10-11 13:48:25 +02:00
36ff50312c fix!: use annotated tags with recipe release 2021-10-11 10:45:00 +02:00
ff4b978876 fix: only list new versions
Closes coop-cloud/organising#192.
2021-10-11 01:17:52 +02:00
b68547b2c2 fix: dont overwrite generated catalogue
Closes coop-cloud/organising#190.
2021-10-11 01:06:51 +02:00
0140f96ca1 fix: make sure to clone recipe
Closes coop-cloud/organising#193.
2021-10-11 00:34:23 +02:00
3wc
1cb45113db fix: default linux binary in installer, add context
Closes coop-cloud/organising#184
2021-10-09 21:45:28 +02:00
c764243f3a fix: manage multiple version showing edge cases 2021-10-08 10:50:48 +02:00
dde8afcd43 feat: support version/upgrade listing
Closes coop-cloud/organising#130.
2021-10-08 09:51:47 +02:00
98ffc210e1 fix: show descending orders on releases [ci skip] 2021-10-06 09:13:07 +02:00
7c0d883135 chore: new release 2021-10-06 08:48:23 +02:00
e78ced41fb fix: use freifunk DNS resolver
Closes coop-cloud/organising#180.
2021-10-06 08:47:01 +02:00
e9113500d8 feat: allow to override STACK_NAME 2021-10-05 20:40:16 +02:00
7368cabc49 fix: format output correctly 2021-10-05 20:24:52 +02:00
f75e264811 fix: ensure dirs are created
Also use debug logging for help.

Closes coop-cloud/organising#183.
Closes coop-cloud/organising#183.
2021-10-05 20:24:41 +02:00
8bfd76fd04 feat: generate versions for catalogue also
Closes coop-cloud/organising#179.
2021-10-05 20:14:00 +02:00
1cb5e3509d fix: add compose.yml before commiting with recipe release; reset parts of tag according to semver when releasing 2021-10-05 16:36:15 +02:00
3cd2399cca fix: ignore WIP stuff and sort [ci skip] 2021-10-05 11:56:26 +02:00
11c4651a3b fix: don't crash when there is a more serious upgrade available 2021-10-05 09:55:25 +00:00
49f90674f2 fix: --major/minor/patch is the most serious upgrade you want to do 2021-10-05 09:55:25 +00:00
74a70edb03 feat: upgrade an app with no user input with --minor/major/patch flag 2021-10-05 09:55:25 +00:00
6fc5c31347 WIP: #172 upgrade --major/minor/patch placeholder 2021-10-05 09:55:25 +00:00
c616907b71 feat: teach recipe sync to understand new versions
Closes coop-cloud/organising#177.
2021-10-05 10:28:09 +02:00
a58cea3e0a docs: dont assume that yet [ci skip] 2021-10-02 23:30:18 +02:00
700f89425a chore: publish new release 2021-10-02 23:01:25 +02:00
8cc0a350e6 fix: pass sample env when loading recipe
Closes coop-cloud/organising#176.
2021-10-02 23:00:09 +02:00
46e67fa420 feat: support darwin builds 2021-10-02 22:53:07 +02:00
cacbb5a0f1 docs: remove extra change log items 2021-10-02 22:51:27 +02:00
e7046a15aa docs: keep it all lowercase 2021-10-02 22:51:12 +02:00
c1fd97c427 fix: handle new local server is listing 2021-10-02 22:40:08 +02:00
2f218bd99f fix: ensure ~/.abra is created
Also make that debug message less cringe.
2021-10-02 22:37:30 +02:00
48290aa316 fix: make server path creation more robust 2021-10-02 22:30:08 +02:00
db5cbfa992 docs: reword this local flag usage 2021-10-02 22:14:01 +02:00
4c11e813e8 test: ensure .env reading tests work 2021-10-02 22:10:00 +02:00
6ae75e013a refactor: move Major, Minor and Patch to recipe.go 2021-10-01 19:49:18 +02:00
24 changed files with 612 additions and 178 deletions

View File

@ -14,12 +14,12 @@ builds:
dir: cmd/abra dir: cmd/abra
goos: goos:
- linux - linux
- darwin
ldflags: ldflags:
- "-X 'main.Commit={{ .Commit }}'" - "-X 'main.Commit={{ .Commit }}'"
- "-X 'main.Version={{ .Version }}'" - "-X 'main.Version={{ .Version }}'"
archives: archives:
- replacements: - replacements:
linux: Linux
386: i386 386: i386
amd64: x86_64 amd64: x86_64
format: binary format: binary
@ -28,8 +28,13 @@ checksum:
snapshot: snapshot:
name_template: "{{ incpatch .Version }}-next" name_template: "{{ incpatch .Version }}-next"
changelog: changelog:
sort: asc sort: desc
filters: filters:
exclude: exclude:
- "^WIP:"
- "^chore:"
- "^docs:" - "^docs:"
- "^refactor:"
- "^style:"
- "^test:" - "^test:"
- "^tests:"

View File

@ -31,7 +31,6 @@ var appDeployCommand = &cli.Command{
for k, v := range abraShEnv { for k, v := range abraShEnv {
app.Env[k] = v app.Env[k] = v
} }
app.Env["STACK_NAME"] = app.StackName()
composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
if err != nil { if err != nil {

View File

@ -2,9 +2,12 @@ package app
import ( import (
"sort" "sort"
"strings"
abraFormatter "coopcloud.tech/abra/cli/formatter" abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/tagcmp"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -65,10 +68,10 @@ can take some time.
} }
sort.Sort(config.ByServerAndType(apps)) sort.Sort(config.ByServerAndType(apps))
statuses := map[string]string{} statuses := make(map[string]map[string]string)
tableCol := []string{"Server", "Type", "Domain"} tableCol := []string{"Server", "Type", "Domain"}
if status { if status {
tableCol = append(tableCol, "Status") tableCol = append(tableCol, "Status", "Version", "Updates")
statuses, err = config.GetAppStatuses(appFiles) statuses, err = config.GetAppStatuses(appFiles)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -84,10 +87,57 @@ can take some time.
// If type flag is set, check for it, if not, Type == "" // If type flag is set, check for it, if not, Type == ""
tableRow = []string{app.Server, app.Type, app.Domain} tableRow = []string{app.Server, app.Type, app.Domain}
if status { if status {
if status, ok := statuses[app.StackName()]; ok { stackName := app.StackName()
tableRow = append(tableRow, status) status := "unknown"
version := "unknown"
if statusMeta, ok := statuses[stackName]; ok {
if currentVersion, exists := statusMeta["version"]; exists {
version = currentVersion
}
if statusMeta["status"] != "" {
status = statusMeta["status"]
}
tableRow = append(tableRow, status, version)
} else { } else {
tableRow = append(tableRow, "unknown") tableRow = append(tableRow, status, version)
}
var newUpdates []string
if version != "unknown" {
updates, err := catalogue.GetRecipeCatalogueVersions(app.Type)
if err != nil {
logrus.Fatal(err)
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
logrus.Fatal(err)
}
for _, update := range updates {
parsedUpdate, err := tagcmp.Parse(update)
if err != nil {
logrus.Fatal(err)
}
if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
newUpdates = append(newUpdates, update)
}
}
}
if len(newUpdates) == 0 {
if version == "unknown" {
tableRow = append(tableRow, "unknown")
} else {
tableRow = append(tableRow, "on latest")
}
} else {
// FIXME: jeezus golang why do you not have a list reverse function
for i, j := 0, len(newUpdates)-1; i < j; i, j = i+1, j-1 {
newUpdates[i], newUpdates[j] = newUpdates[j], newUpdates[i]
}
tableRow = append(tableRow, strings.Join(newUpdates, "\n"))
} }
} }
} }

View File

@ -63,7 +63,7 @@ var appRemoveCommand = &cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if statuses[app.Name] == "deployed" { if statuses[app.Name]["status"] == "deployed" {
logrus.Fatalf("'%s' is still deployed. Run \"abra app %s undeploy\" or pass --force", app.Name, app.Name) logrus.Fatalf("'%s' is still deployed. Run \"abra app %s undeploy\" or pass --force", app.Name, app.Name)
} }
} }

View File

@ -2,7 +2,9 @@ package catalogue
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os"
"path" "path"
"coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/formatter"
@ -45,11 +47,22 @@ var CatalogueSkipList = map[string]bool{
} }
var catalogueGenerateCommand = &cli.Command{ var catalogueGenerateCommand = &cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate a new copy of the catalogue", Usage: "Generate a new copy of the catalogue",
ArgsUsage: "[<recipe>]", ArgsUsage: "[<recipe>]",
BashComplete: func(c *cli.Context) {}, BashComplete: func(c *cli.Context) {
catl, err := catalogue.ReadRecipeCatalogue()
if err != nil {
logrus.Warn(err)
}
if c.NArg() > 0 {
return
}
for name := range catl {
fmt.Println(name)
}
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipeName := c.Args().First() recipeName := c.Args().First()
@ -60,18 +73,18 @@ var catalogueGenerateCommand = &cli.Command{
logrus.Debugf("ensuring '%v' recipe(s) are locally present and up-to-date", len(repos)) logrus.Debugf("ensuring '%v' recipe(s) are locally present and up-to-date", len(repos))
bar := formatter.CreateProgressbar(len(repos), "retrieving recipes...") retrieveBar := formatter.CreateProgressbar(len(repos), "retrieving recipes...")
ch := make(chan string, len(repos)) ch := make(chan string, len(repos))
for _, repoMeta := range repos { for _, repoMeta := range repos {
go func(rm catalogue.RepoMeta) { go func(rm catalogue.RepoMeta) {
if recipeName != "" && recipeName != rm.Name { if recipeName != "" && recipeName != rm.Name {
ch <- rm.Name ch <- rm.Name
bar.Add(1) retrieveBar.Add(1)
return return
} }
if _, exists := CatalogueSkipList[rm.Name]; exists { if _, exists := CatalogueSkipList[rm.Name]; exists {
ch <- rm.Name ch <- rm.Name
bar.Add(1) retrieveBar.Add(1)
return return
} }
@ -86,7 +99,7 @@ var catalogueGenerateCommand = &cli.Command{
} }
ch <- rm.Name ch <- rm.Name
bar.Add(1) retrieveBar.Add(1)
}(repoMeta) }(repoMeta)
} }
@ -95,14 +108,23 @@ var catalogueGenerateCommand = &cli.Command{
} }
catl := make(catalogue.RecipeCatalogue) catl := make(catalogue.RecipeCatalogue)
catlBar := formatter.CreateProgressbar(len(repos), "generating catalogue...")
for _, recipeMeta := range repos { for _, recipeMeta := range repos {
if recipeName != "" && recipeName != recipeMeta.Name { if recipeName != "" && recipeName != recipeMeta.Name {
catlBar.Add(1)
continue continue
} }
if _, exists := CatalogueSkipList[recipeMeta.Name]; exists { if _, exists := CatalogueSkipList[recipeMeta.Name]; exists {
catlBar.Add(1)
continue continue
} }
versions, err := catalogue.GetRecipeVersions(recipeMeta.Name)
if err != nil {
logrus.Fatal(err)
}
catl[recipeMeta.Name] = catalogue.RecipeMeta{ catl[recipeMeta.Name] = catalogue.RecipeMeta{
Name: recipeMeta.Name, Name: recipeMeta.Name,
Repository: recipeMeta.CloneURL, Repository: recipeMeta.CloneURL,
@ -110,10 +132,11 @@ var catalogueGenerateCommand = &cli.Command{
DefaultBranch: recipeMeta.DefaultBranch, DefaultBranch: recipeMeta.DefaultBranch,
Description: recipeMeta.Description, Description: recipeMeta.Description,
Website: recipeMeta.Website, Website: recipeMeta.Website,
// Versions: ..., // FIXME: once the new versions work goes down Versions: versions,
// Category: ..., // FIXME: once we sort out the machine-readable catalogue interface // Category: ..., // FIXME: once we sort out the machine-readable catalogue interface
// Features: ..., // FIXME: once we figure out the machine-readable catalogue interface // Features: ..., // FIXME: once we figure out the machine-readable catalogue interface
} }
catlBar.Add(1)
} }
recipesJSON, err := json.MarshalIndent(catl, "", " ") recipesJSON, err := json.MarshalIndent(catl, "", " ")
@ -121,11 +144,29 @@ var catalogueGenerateCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil { if _, err := os.Stat(config.APPS_JSON); err != nil && os.IsNotExist(err) {
logrus.Fatal(err) if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil {
logrus.Fatal(err)
}
} else {
if recipeName != "" {
catlFS, err := catalogue.ReadRecipeCatalogue()
if err != nil {
logrus.Fatal(err)
}
catlFS[recipeName] = catl[recipeName]
updatedRecipesJSON, err := json.MarshalIndent(catlFS, "", " ")
if err != nil {
logrus.Fatal(err)
}
if err := ioutil.WriteFile(config.APPS_JSON, updatedRecipesJSON, 0644); err != nil {
logrus.Fatal(err)
}
}
} }
logrus.Debugf("generated new recipe catalogue in '%s'", config.APPS_JSON) logrus.Infof("generated new recipe catalogue in '%s'", config.APPS_JSON)
return nil return nil
}, },

View File

@ -4,11 +4,13 @@ package cli
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"coopcloud.tech/abra/cli/app" "coopcloud.tech/abra/cli/app"
"coopcloud.tech/abra/cli/catalogue" "coopcloud.tech/abra/cli/catalogue"
"coopcloud.tech/abra/cli/recipe" "coopcloud.tech/abra/cli/recipe"
"coopcloud.tech/abra/cli/server" "coopcloud.tech/abra/cli/server"
"coopcloud.tech/abra/pkg/config"
logrusStack "github.com/Gurpartap/logrus-stack" logrusStack "github.com/Gurpartap/logrus-stack"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -81,11 +83,30 @@ func RunApp(version, commit string) {
logrus.SetOutput(os.Stderr) logrus.SetOutput(os.Stderr)
logrus.AddHook(logrusStack.StandardHook()) logrus.AddHook(logrusStack.StandardHook())
} }
paths := []string{
config.ABRA_DIR,
path.Join(config.ABRA_DIR, "servers"),
path.Join(config.ABRA_DIR, "apps"),
path.Join(config.ABRA_DIR, "vendor"),
}
for _, path := range paths {
if err := os.Mkdir(path, 0755); err != nil {
if !os.IsExist(err) {
logrus.Fatal(err)
}
logrus.Debugf("'%s' already created, moving on...", path)
continue
}
logrus.Debugf("'%s' is missing, creating...", path)
}
logrus.Debugf("abra version '%s', commit '%s'", version, commit)
return nil return nil
} }
logrus.Debugf("Flying abra version '%s', commit '%s', enjoy the ride", version, commit)
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -41,6 +41,10 @@ func ValidateApp(c *cli.Context) config.App {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := recipe.EnsureExists(app.Type); err != nil {
logrus.Fatal(err)
}
logrus.Debugf("validated '%s' as app argument", appName) logrus.Debugf("validated '%s' as app argument", appName)
return app return app

View File

@ -4,6 +4,30 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var Major bool
var MajorFlag = &cli.BoolFlag{
Name: "major",
Value: false,
Aliases: []string{"ma", "x"},
Destination: &Major,
}
var Minor bool
var MinorFlag = &cli.BoolFlag{
Name: "minor",
Value: false,
Aliases: []string{"mi", "y"},
Destination: &Minor,
}
var Patch bool
var PatchFlag = &cli.BoolFlag{
Name: "patch",
Value: false,
Aliases: []string{"p", "z"},
Destination: &Patch,
}
// RecipeCommand defines all recipe related sub-commands. // RecipeCommand defines all recipe related sub-commands.
var RecipeCommand = &cli.Command{ var RecipeCommand = &cli.Command{
Name: "recipe", Name: "recipe",

View File

@ -9,11 +9,11 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -33,34 +33,10 @@ var DryFlag = &cli.BoolFlag{
Destination: &Dry, Destination: &Dry,
} }
var Major bool
var MajorFlag = &cli.BoolFlag{
Name: "major",
Value: false,
Aliases: []string{"ma", "x"},
Destination: &Major,
}
var Minor bool
var MinorFlag = &cli.BoolFlag{
Name: "minor",
Value: false,
Aliases: []string{"mi", "y"},
Destination: &Minor,
}
var Patch bool
var PatchFlag = &cli.BoolFlag{
Name: "patch",
Value: false,
Aliases: []string{"p", "z"},
Destination: &Patch,
}
var CommitMessage string var CommitMessage string
var CommitMessageFlag = &cli.StringFlag{ var CommitMessageFlag = &cli.StringFlag{
Name: "commit-message", Name: "commit-message",
Usage: "commit message", Usage: "commit message. Implies --commit",
Aliases: []string{"cm"}, Aliases: []string{"cm"},
Destination: &CommitMessage, Destination: &CommitMessage,
} }
@ -68,11 +44,20 @@ var CommitMessageFlag = &cli.StringFlag{
var Commit bool var Commit bool
var CommitFlag = &cli.BoolFlag{ var CommitFlag = &cli.BoolFlag{
Name: "commit", Name: "commit",
Usage: "add compose.yml to staging area and commit changes",
Value: false, Value: false,
Aliases: []string{"c"}, Aliases: []string{"c"},
Destination: &Commit, Destination: &Commit,
} }
var TagMessage string
var TagMessageFlag = &cli.StringFlag{
Name: "tag-comment",
Usage: "tag comment. If not given, user will be asked for it",
Aliases: []string{"t", "tm"},
Destination: &TagMessage,
}
var recipeReleaseCommand = &cli.Command{ var recipeReleaseCommand = &cli.Command{
Name: "release", Name: "release",
Usage: "tag a recipe", Usage: "tag a recipe",
@ -107,6 +92,7 @@ or a rollback of an app.
PushFlag, PushFlag,
CommitFlag, CommitFlag,
CommitMessageFlag, CommitMessageFlag,
TagMessageFlag,
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)
@ -115,17 +101,31 @@ or a rollback of an app.
imagesTmp := getImageVersions(recipe) imagesTmp := getImageVersions(recipe)
mainApp := getMainApp(recipe) mainApp := getMainApp(recipe)
mainAppVersion := imagesTmp[mainApp] mainAppVersion := imagesTmp[mainApp]
if err := recipePkg.EnsureExists(recipe.Name); err != nil {
logrus.Fatal(err)
}
if mainAppVersion == "" { if mainAppVersion == "" {
logrus.Fatal("main app version is empty?") logrus.Fatal("main app version is empty?")
} }
if tagstring != "" { if tagstring != "" {
_, err := tagcmp.Parse(tagstring) if _, err := tagcmp.Parse(tagstring); err != nil {
if err != nil {
logrus.Fatal("invalid tag specified") logrus.Fatal("invalid tag specified")
} }
} }
if TagMessage == "" {
prompt := &survey.Input{
Message: "tag message",
}
survey.AskOne(prompt, &TagMessage)
}
var createTagOptions git.CreateTagOptions
createTagOptions.Message = TagMessage
if Commit || (CommitMessage != "") { if Commit || (CommitMessage != "") {
commitRepo, err := git.PlainOpen(directory) commitRepo, err := git.PlainOpen(directory)
if err != nil { if err != nil {
@ -142,6 +142,12 @@ or a rollback of an app.
} }
survey.AskOne(prompt, &CommitMessage) survey.AskOne(prompt, &CommitMessage)
} }
err = commitWorktree.AddGlob("compose.**yml")
if err != nil {
logrus.Fatal(err)
}
logrus.Debug("staged compose.**yml for commit")
_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{}) _, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -189,9 +195,7 @@ or a rollback of an app.
return nil return nil
} }
repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{ repo.CreateTag(tagstring, head.Hash(), &createTagOptions)
Message: tag,
})*/
logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash())) logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash()))
if Push { if Push {
if err := repo.Push(&git.PushOptions{}); err != nil { if err := repo.Push(&git.PushOptions{}); err != nil {
@ -204,27 +208,33 @@ or a rollback of an app.
} }
// get the latest tag with its hash, name etc // get the latest tag with its hash, name etc
var lastGitTag *object.Tag var lastGitTag tagcmp.Tag
iter, err := repo.Tags() iter, err := repo.Tags()
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := iter.ForEach(func(ref *plumbing.Reference) error { if err := iter.ForEach(func(ref *plumbing.Reference) error {
obj, err := repo.TagObject(ref.Hash()) obj, err := repo.TagObject(ref.Hash())
if err == nil { if err != nil {
lastGitTag = obj return err
return nil
} }
return err tagcmpTag, err := tagcmp.Parse(obj.Name)
if err != nil {
return err
}
if (lastGitTag == tagcmp.Tag{}) {
lastGitTag = tagcmpTag
} else if tagcmpTag.IsGreaterThan(lastGitTag) {
lastGitTag = tagcmpTag
}
return nil
}); err != nil { }); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
newTag, err := tagcmp.Parse(lastGitTag.Name)
if err != nil {
logrus.Fatal(err)
}
fmt.Println(lastGitTag)
newTag := lastGitTag
var newTagString string var newTagString string
if bumpType > 0 { if bumpType > 0 {
if Patch { if Patch {
@ -238,28 +248,28 @@ or a rollback of an app.
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
newTag.Patch = "0"
newTag.Minor = strconv.Itoa(now + 1) newTag.Minor = strconv.Itoa(now + 1)
} else if Major { } else if Major {
now, err := strconv.Atoi(newTag.Major) now, err := strconv.Atoi(newTag.Major)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
newTag.Patch = "0"
newTag.Minor = "0"
newTag.Major = strconv.Itoa(now + 1) newTag.Major = strconv.Itoa(now + 1)
} }
newTagString = newTag.String()
} else { } else {
logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch") logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch")
} }
newTag.Metadata = mainAppVersion
newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion) newTagString = newTag.String()
if Dry { if Dry {
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash())) logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash()))
return nil return nil
} }
repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{ repo.CreateTag(newTagString, head.Hash(), &createTagOptions)
Message: tag,
})*/
logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash())) logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash()))
if Push { if Push {
if err := repo.Push(&git.PushOptions{}); err != nil { if err := repo.Push(&git.PushOptions{}); err != nil {

View File

@ -1,65 +1,88 @@
package recipe package recipe
import ( import (
"errors"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/catalogue"
"github.com/docker/distribution/reference" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var recipeSyncCommand = &cli.Command{ var recipeSyncCommand = &cli.Command{
Name: "sync", Name: "sync",
Usage: "Generate new recipe labels", Usage: "Ensure recipe version labels are up-to-date",
Aliases: []string{"s"}, Aliases: []string{"s"},
ArgsUsage: "<recipe> <version>",
Description: ` Description: `
This command will generate labels for each service which correspond to the This command will generate labels for the main recipe service (i.e. by
convention, typically the service named "app") which corresponds to the
following format: following format:
coop-cloud.${STACK_NAME}.${SERVICE_NAME}.version=${IMAGE_TAG}-${IMAGE_DIGEST} coop-cloud.${STACK_NAME}.version=<version>
The <recipe> configuration will be updated on the local file system. These The <version> is determined by the recipe maintainer and is specified on the
labels are consumed by abra in other command invocations and used to determine command-line. The <recipe> configuration will be updated on the local file
the versioning metadata of up-and-running containers are. system.
`, `,
ArgsUsage: "<recipe>", BashComplete: func(c *cli.Context) {
catl, err := catalogue.ReadRecipeCatalogue()
if err != nil {
logrus.Warn(err)
}
if c.NArg() > 0 {
return
}
for name := range catl {
fmt.Println(name)
}
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if c.Args().Len() != 2 {
internal.ShowSubcommandHelpAndError(c, errors.New("missing <recipe>/<version> arguments?"))
}
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)
// TODO: validate with tagcmp when new commits come in
// See https://git.coopcloud.tech/coop-cloud/abra/pulls/109
nextTag := c.Args().Get(1)
mainService := "app"
var services []string
hasAppService := false hasAppService := false
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
services = append(services, service.Name)
if service.Name == "app" { if service.Name == "app" {
hasAppService = true hasAppService = true
logrus.Debugf("detected app service in '%s'", recipe.Name)
} }
} }
if !hasAppService { if !hasAppService {
logrus.Fatal(fmt.Sprintf("no 'app' service defined in '%s'", recipe.Name)) logrus.Warnf("no 'app' service defined in '%s'", recipe.Name)
var chosenService string
prompt := &survey.Select{
Message: fmt.Sprintf("what is the main service name for '%s'?", recipe.Name),
Options: services,
}
if err := survey.AskOne(prompt, &chosenService); err != nil {
logrus.Fatal(err)
}
mainService = chosenService
} }
for _, service := range recipe.Config.Services { logrus.Debugf("selecting '%s' as the service to sync version labels", mainService)
img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
logrus.Fatal(err)
}
logrus.Debugf("detected image '%s' for service '%s'", img, service.Name)
digest, err := client.GetTagDigest(img) label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
if err != nil { if err := recipe.UpdateLabel(mainService, label); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
}
logrus.Debugf("retrieved digest '%s' for '%s'", digest, img)
tag := img.(reference.NamedTagged).Tag()
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest)
if err := recipe.UpdateLabel(service.Name, label); err != nil {
logrus.Fatal(err)
}
logrus.Debugf("added label '%s' to service '%s'", label, service.Name)
} }
logrus.Infof("synced label '%s' to service '%s'", label, mainService)
return nil return nil
}, },
} }

View File

@ -27,14 +27,24 @@ update the relevant compose file tags on the local file system.
Some image tags cannot be parsed because they do not follow some sort of Some image tags cannot be parsed because they do not follow some sort of
semver-like convention. In this case, all possible tags will be listed and it semver-like convention. In this case, all possible tags will be listed and it
is up to the end-user to decide. is up to the end-user to decide.
This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
<recipe>".
`, `,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{
PatchFlag,
MinorFlag,
MajorFlag,
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)
bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch)
if bumpType != 0 {
// a bitwise check if the number is a power of 2
if (bumpType & (bumpType - 1)) != 0 {
logrus.Fatal("you can only use one of: --major, --minor, --patch.")
}
}
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name) catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name)
if err != nil { if err != nil {
@ -107,31 +117,48 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
} }
logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name) logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name)
msg := fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
tag := img.(reference.NamedTagged).Tag()
logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag))
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
compatibleStrings = []string{}
for _, regVersion := range regVersions {
compatibleStrings = append(compatibleStrings, regVersion.Name)
}
}
var upgradeTag string var upgradeTag string
prompt := &survey.Select{ if bumpType != 0 {
Message: msg, for _, upTag := range compatible {
Options: compatibleStrings, upElement, err := tag.UpgradeDelta(upTag)
} if err != nil {
if err := survey.AskOne(prompt, &upgradeTag); err != nil { return err
logrus.Fatal(err) }
delta := upElement.UpgradeType()
if delta <= bumpType {
upgradeTag = upTag.String()
break
}
}
if upgradeTag == "" {
logrus.Warnf("not upgrading from '%s' to '%s' for '%s', because the upgrade type is more serious than what user wants.", tag.String(), compatible[0].String(), image)
continue
}
} else {
msg := fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
tag := img.(reference.NamedTagged).Tag()
logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag))
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
compatibleStrings = []string{}
for _, regVersion := range regVersions {
compatibleStrings = append(compatibleStrings, regVersion.Name)
}
}
prompt := &survey.Select{
Message: msg,
Options: compatibleStrings,
}
if err := survey.AskOne(prompt, &upgradeTag); err != nil {
logrus.Fatal(err)
}
} }
if err := recipe.UpdateTag(image, upgradeTag); err != nil { if err := recipe.UpdateTag(image, upgradeTag); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("tag updated from '%s' to '%s' for '%s'", image, upgradeTag, recipe.Name) logrus.Infof("tag upgraded from '%s' to '%s' for '%s'", tag.String(), upgradeTag, image)
} }
return nil return nil

View File

@ -3,14 +3,12 @@ package server
import ( import (
"context" "context"
"errors" "errors"
"os"
"os/user" "os/user"
"path"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/server"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -30,10 +28,14 @@ var serverAddCommand = &cli.Command{
Description: ` Description: `
This command adds a new server that abra will communicate with, to deploy apps. This command adds a new server that abra will communicate with, to deploy apps.
The <domain> argument must be a publicy accessible domain name which points to If "--local" is passed, then Abra assumes that the current local server is
your server. You should have SSH access to this server, Abra will assume port intended as the target server.
22 and will use your current system username to make an initial connection. You
can use the <user> and <port> arguments to adjust this. Otherwise, you may specify a remote server. The <domain> argument must be a
publicy accessible domain name which points to your server. You should have SSH
access to this server, Abra will assume port 22 and will use your current
system username to make an initial connection. You can use the <user> and
<port> arguments to adjust this.
For example: For example:
@ -44,9 +46,6 @@ Abra will construct the following SSH connection string then:
ssh://globemodem@varia.zone:12345 ssh://globemodem@varia.zone:12345
All communication between Abra and the server will use this SSH connection. All communication between Abra and the server will use this SSH connection.
NOTE: If you specify --local, none of the above applies 🙃
`, `,
Aliases: []string{"a"}, Aliases: []string{"a"},
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -67,7 +66,10 @@ NOTE: If you specify --local, none of the above applies 🙃
domainName := "default" domainName := "default"
if local { if local {
os.Mkdir(path.Join(config.ABRA_DIR, "servers", domainName), 0755) if err := server.CreateServerDir(domainName); err != nil {
logrus.Fatal(err)
}
logrus.Info("local server has been added")
return nil return nil
} }
@ -124,9 +126,12 @@ NOTE: If you specify --local, none of the above applies 🙃
} }
logrus.Debugf("remote connection to '%s' is definitely up", domainName) logrus.Debugf("remote connection to '%s' is definitely up", domainName)
logrus.Infof("server at '%s' has been added", domainName)
os.Mkdir(path.Join(config.ABRA_DIR, "servers", domainName), 0755) if err := server.CreateServerDir(domainName); err != nil {
logrus.Fatal(err)
}
logrus.Infof("server at '%s' has been added", domainName)
return nil return nil
}, },

View File

@ -35,6 +35,9 @@ later for more advanced use cases.
return err return err
} }
// https://www.privacy-handbuch.de/handbuch_93d.htm
freifunkDNS := "5.1.66.255:53"
resolver := &net.Resolver{ resolver := &net.Resolver{
PreferGo: false, PreferGo: false,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
@ -42,10 +45,10 @@ later for more advanced use cases.
Timeout: time.Millisecond * time.Duration(10000), Timeout: time.Millisecond * time.Duration(10000),
} }
// comrade librehosters DNS resolver https://snopyta.org/service/dns/ // comrade librehosters DNS resolver https://snopyta.org/service/dns/
return d.DialContext(ctx, "udp", "95.216.24.230:53") return d.DialContext(ctx, "udp", freifunkDNS)
}, },
} }
logrus.Debugf("created DNS resolver via 95.216.24.230") logrus.Debugf("created DNS resolver via '%s'", freifunkDNS)
ctx := context.Background() ctx := context.Background()
ips, err := resolver.LookupIPAddr(ctx, domainName) ips, err := resolver.LookupIPAddr(ctx, domainName)

View File

@ -45,7 +45,11 @@ var serverListCommand = &cli.Command{
} }
} }
if len(row) == 0 { if len(row) == 0 {
row = []string{serverName, "UNKNOWN"} if serverName == "default" {
row = []string{serverName, "local"}
} else {
row = []string{serverName, "unknown"}
}
} }
table.Append(row) table.Append(row)
} }

2
go.mod
View File

@ -3,7 +3,7 @@ module coopcloud.tech/abra
go 1.17 go 1.17
require ( require (
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb
github.com/AlecAivazis/survey/v2 v2.3.1 github.com/AlecAivazis/survey/v2 v2.3.1
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4 github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4

6
go.sum
View File

@ -21,8 +21,10 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d h1:5jeUIiToqQ7vTlLeycdGp4Ezurd6/RTNl5K38usHtoo= coopcloud.tech/tagcmp v0.0.0-20211007122926-aa7b4ff851f6 h1:EV1aomKx6n0jLZ847ejeqroE4K9E+yX5NvEyQufkZVk=
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ= coopcloud.tech/tagcmp v0.0.0-20211007122926-aa7b4ff851f6/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb h1:Jf+Dnna2kXcNQvcA5JMp6d2Uyvg2pIVJfip9+X5FrH0=
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.3.1 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA= github.com/AlecAivazis/survey/v2 v2.3.1 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA=
github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=

View File

@ -9,12 +9,17 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path"
"strings" "strings"
"time" "time"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/web" "coopcloud.tech/abra/pkg/web"
"github.com/docker/distribution/reference"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -56,17 +61,20 @@ type serviceMeta struct {
Tag string `json:"tag"` Tag string `json:"tag"`
} }
// RecipeVersions are the versions associated with a recipe.
type RecipeVersions []map[tag]map[service]serviceMeta
// RecipeMeta represents metadata for a recipe in the abra catalogue. // RecipeMeta represents metadata for a recipe in the abra catalogue.
type RecipeMeta struct { type RecipeMeta struct {
Category string `json:"category"` Category string `json:"category"`
DefaultBranch string `json:"default_branch"` DefaultBranch string `json:"default_branch"`
Description string `json:"description"` Description string `json:"description"`
Features features `json:"features"` Features features `json:"features"`
Icon string `json:"icon"` Icon string `json:"icon"`
Name string `json:"name"` Name string `json:"name"`
Repository string `json:"repository"` Repository string `json:"repository"`
Versions []map[tag]map[service]serviceMeta `json:"versions"` Versions RecipeVersions `json:"versions"`
Website string `json:"website"` Website string `json:"website"`
} }
// LatestVersion returns the latest version of a recipe. // LatestVersion returns the latest version of a recipe.
@ -365,3 +373,130 @@ func ReadReposMetadata() (RepoCatalogue, error) {
return reposMeta, nil return reposMeta, nil
} }
// GetRecipeVersions retrieves all recipe versions.
func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
versions := RecipeVersions{}
recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName)
logrus.Debugf("attempting to open git repository in '%s'", recipeDir)
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return versions, err
}
worktree, err := repo.Worktree()
if err != nil {
logrus.Fatal(err)
}
gitTags, err := repo.Tags()
if err != nil {
return versions, err
}
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/")
logrus.Debugf("processing '%s' for '%s'", tag, recipeName)
checkOutOpts := &git.CheckoutOptions{
Create: false,
Force: true,
Keep: false,
Branch: plumbing.ReferenceName(ref.Name()),
}
if err := worktree.Checkout(checkOutOpts); err != nil {
logrus.Debugf("failed to check out '%s' in '%s'", tag, recipeDir)
return err
}
logrus.Debugf("successfully checked out '%s' in '%s'", ref.Name(), recipeDir)
recipe, err := recipe.Get(recipeName)
if err != nil {
return err
}
versionMeta := make(map[string]serviceMeta)
for _, service := range recipe.Config.Services {
img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return err
}
path := reference.Path(img)
if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1]
}
digest, err := client.GetTagDigest(img)
if err != nil {
return err
}
versionMeta[service.Name] = serviceMeta{
Digest: digest,
Image: path,
Tag: img.(reference.NamedTagged).Tag(),
}
logrus.Debugf("collecting digest: '%s', image: '%s', tag: '%s'", digest, path, tag)
}
versions = append(versions, map[string]map[string]serviceMeta{tag: versionMeta})
return nil
}); err != nil {
return versions, err
}
branch := "master"
if _, err := repo.Branch("master"); err != nil {
if _, err := repo.Branch("main"); err != nil {
logrus.Debugf("failed to select branch in '%s'", recipeDir)
logrus.Fatal(err)
}
branch = "main"
}
refName := fmt.Sprintf("refs/heads/%s", branch)
checkOutOpts := &git.CheckoutOptions{
Create: false,
Force: true,
Keep: false,
Branch: plumbing.ReferenceName(refName),
}
if err := worktree.Checkout(checkOutOpts); err != nil {
logrus.Debugf("failed to check out '%s' in '%s'", branch, recipeDir)
logrus.Fatal(err)
}
logrus.Debugf("switched back to '%s' in '%s'", branch, recipeDir)
logrus.Debugf("collected '%s' for '%s'", versions, recipeName)
return versions, nil
}
// GetRecipeCatalogueVersions list the recipe versions listed in the recipe catalogue.
func GetRecipeCatalogueVersions(recipeName string) ([]string, error) {
var versions []string
catl, err := ReadRecipeCatalogue()
if err != nil {
return versions, err
}
if recipeMeta, exists := catl[recipeName]; exists {
for _, versionMeta := range recipeMeta.Versions {
for tag := range versionMeta {
versions = append(versions, tag)
}
}
}
return versions, nil
}

View File

@ -3,18 +3,20 @@ package compose
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"coopcloud.tech/abra/pkg/client/stack" "coopcloud.tech/abra/pkg/client/stack"
loader "coopcloud.tech/abra/pkg/client/stack" loader "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// UpdateTag updates an image tag in-place on file system local compose files. // UpdateTag updates an image tag in-place on file system local compose files.
func UpdateTag(pattern, image, tag string) error { func UpdateTag(pattern, image, tag, recipeName string) error {
composeFiles, err := filepath.Glob(pattern) composeFiles, err := filepath.Glob(pattern)
if err != nil { if err != nil {
return err return err
@ -24,8 +26,14 @@ func UpdateTag(pattern, image, tag string) error {
for _, composeFile := range composeFiles { for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}} opts := stack.Deploy{Composefiles: []string{composeFile}}
emptyEnv := make(map[string]string)
compose, err := loader.LoadComposefile(opts, emptyEnv) envSamplePath := path.Join(config.ABRA_DIR, "apps", recipeName, ".env.sample")
sampleEnv, err := config.ReadEnv(envSamplePath)
if err != nil {
return err
}
compose, err := loader.LoadComposefile(opts, sampleEnv)
if err != nil { if err != nil {
return err return err
} }
@ -74,7 +82,7 @@ func UpdateTag(pattern, image, tag string) error {
} }
// UpdateLabel updates a label in-place on file system local compose files. // UpdateLabel updates a label in-place on file system local compose files.
func UpdateLabel(pattern, serviceName, label string) error { func UpdateLabel(pattern, serviceName, label, recipeName string) error {
composeFiles, err := filepath.Glob(pattern) composeFiles, err := filepath.Glob(pattern)
if err != nil { if err != nil {
return err return err
@ -84,8 +92,14 @@ func UpdateLabel(pattern, serviceName, label string) error {
for _, composeFile := range composeFiles { for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}} opts := stack.Deploy{Composefiles: []string{composeFile}}
emptyEnv := make(map[string]string)
compose, err := loader.LoadComposefile(opts, emptyEnv) envSamplePath := path.Join(config.ABRA_DIR, "apps", recipeName, ".env.sample")
sampleEnv, err := config.ReadEnv(envSamplePath)
if err != nil {
return err
}
compose, err := loader.LoadComposefile(opts, sampleEnv)
if err != nil { if err != nil {
return err return err
} }
@ -110,7 +124,7 @@ func UpdateLabel(pattern, serviceName, label string) error {
return err return err
} }
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, value) old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value)
replacedBytes := strings.Replace(string(bytes), old, label, -1) replacedBytes := strings.Replace(string(bytes), old, label, -1)
logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename) logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename)

View File

@ -6,7 +6,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/formatter"
@ -46,7 +45,12 @@ type App struct {
// StackName gets what the docker safe stack name is for the app // StackName gets what the docker safe stack name is for the app
func (a App) StackName() string { func (a App) StackName() string {
return SanitiseAppName(a.Name) if _, exists := a.Env["STACK_NAME"]; exists {
return a.Env["STACK_NAME"]
}
stackName := SanitiseAppName(a.Name)
a.Env["STACK_NAME"] = stackName
return stackName
} }
// SORTING TYPES // SORTING TYPES
@ -139,7 +143,7 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
} }
} }
logrus.Debugf("collecting metadata from '%v' servers: '%s'", len(servers), servers) logrus.Debugf("collecting metadata from '%v' servers: '%s'", len(servers), strings.Join(servers, ", "))
for _, server := range servers { for _, server := range servers {
serverDir := path.Join(ABRA_SERVER_FOLDER, server) serverDir := path.Join(ABRA_SERVER_FOLDER, server)
@ -276,8 +280,8 @@ func SanitiseAppName(name string) string {
} }
// GetAppStatuses queries servers to check the deployment status of given apps // GetAppStatuses queries servers to check the deployment status of given apps
func GetAppStatuses(appFiles AppFiles) (map[string]string, error) { func GetAppStatuses(appFiles AppFiles) (map[string]map[string]string, error) {
statuses := map[string]string{} statuses := make(map[string]map[string]string)
var unique []string var unique []string
servers := make(map[string]struct{}) servers := make(map[string]struct{})
@ -300,10 +304,24 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
for range servers { for range servers {
status := <-ch status := <-ch
for _, service := range status.Services { for _, service := range status.Services {
result := make(map[string]string)
name := service.Spec.Labels[convert.LabelNamespace] name := service.Spec.Labels[convert.LabelNamespace]
if _, ok := statuses[name]; !ok { if _, ok := statuses[name]; !ok {
statuses[name] = "deployed" result["status"] = "deployed"
} }
labelKey := fmt.Sprintf("coop-cloud.%s.version", name)
if version, ok := service.Spec.Labels[labelKey]; ok {
result["version"] = version
} else {
//FIXME: we only need to check containers with the version label not
// every single container and then skip when we see no label perf gains
// to be had here
continue
}
statuses[name] = result
} }
} }
@ -315,20 +333,18 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
// GetAppComposeFiles gets the list of compose files for an app which should be // GetAppComposeFiles gets the list of compose files for an app which should be
// merged into a composetypes.Config while respecting the COMPOSE_FILE env var. // merged into a composetypes.Config while respecting the COMPOSE_FILE env var.
func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) { func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) {
var composeFiles []string
if _, ok := appEnv["COMPOSE_FILE"]; !ok { if _, ok := appEnv["COMPOSE_FILE"]; !ok {
logrus.Debug("no COMPOSE_FILE detected, loading all compose files") logrus.Debug("no COMPOSE_FILE detected, loading compose.yml")
pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe) path := fmt.Sprintf("%s/%s/compose.yml", APPS_DIR, recipe)
composeFiles, err := filepath.Glob(pattern) composeFiles = append(composeFiles, path)
if err != nil {
return composeFiles, err
}
return composeFiles, nil return composeFiles, nil
} }
var composeFiles []string
composeFileEnvVar := appEnv["COMPOSE_FILE"] composeFileEnvVar := appEnv["COMPOSE_FILE"]
envVars := strings.Split(composeFileEnvVar, ":") envVars := strings.Split(composeFileEnvVar, ":")
logrus.Debugf("COMPOSE_FILE detected ('%s'), loading '%s'", composeFileEnvVar, envVars) logrus.Debugf("COMPOSE_FILE detected ('%s'), loading '%s'", composeFileEnvVar, strings.Join(envVars, ", "))
for _, file := range strings.Split(composeFileEnvVar, ":") { for _, file := range strings.Split(composeFileEnvVar, ":") {
path := fmt.Sprintf("%s/%s/%s", APPS_DIR, recipe, file) path := fmt.Sprintf("%s/%s/%s", APPS_DIR, recipe, file)
composeFiles = append(composeFiles, path) composeFiles = append(composeFiles, path)

View File

@ -26,7 +26,7 @@ type Recipe struct {
// UpdateLabel updates a recipe label // UpdateLabel updates a recipe label
func (r Recipe) UpdateLabel(serviceName, label string) error { func (r Recipe) UpdateLabel(serviceName, label string) error {
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name) pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name)
if err := compose.UpdateLabel(pattern, serviceName, label); err != nil { if err := compose.UpdateLabel(pattern, serviceName, label, r.Name); err != nil {
return err return err
} }
return nil return nil
@ -35,12 +35,39 @@ func (r Recipe) UpdateLabel(serviceName, label string) error {
// UpdateTag updates a recipe tag // UpdateTag updates a recipe tag
func (r Recipe) UpdateTag(image, tag string) error { func (r Recipe) UpdateTag(image, tag string) error {
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name) pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name)
if err := compose.UpdateTag(pattern, image, tag); err != nil { if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil {
return err return err
} }
return nil return nil
} }
// Tags list the recipe tags
func (r Recipe) Tags() ([]string, error) {
var tags []string
recipeDir := path.Join(config.ABRA_DIR, "apps", r.Name)
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return tags, err
}
gitTags, err := repo.Tags()
if err != nil {
return tags, err
}
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
tags = append(tags, strings.TrimPrefix(string(ref.Name()), "refs/tags/"))
return nil
}); err != nil {
return tags, err
}
logrus.Debugf("detected '%s' as tags for recipe '%s'", strings.Join(tags, ", "), r.Name)
return tags, nil
}
// Get retrieves a recipe. // Get retrieves a recipe.
func Get(recipeName string) (Recipe, error) { func Get(recipeName string) (Recipe, error) {
if err := EnsureExists(recipeName); err != nil { if err := EnsureExists(recipeName); err != nil {

24
pkg/server/server.go Normal file
View File

@ -0,0 +1,24 @@
package server
import (
"os"
"path"
"coopcloud.tech/abra/pkg/config"
"github.com/sirupsen/logrus"
)
// CreateServerDir creates a server directory under ~/.abra.
func CreateServerDir(serverName string) error {
serverPath := path.Join(config.ABRA_DIR, "servers", serverName)
if err := os.Mkdir(serverPath, 0755); err != nil {
if !os.IsExist(err) {
return err
}
logrus.Infof("'%s' already exists, moving on...", serverPath)
}
return nil
}

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
ABRA_VERSION="0.1.5-alpha" ABRA_VERSION="0.1.8-alpha"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
function show_banner { function show_banner {
@ -38,7 +38,7 @@ function install_abra_release {
release_url=$(curl -s "$ABRA_RELEASE_URL" | release_url=$(curl -s "$ABRA_RELEASE_URL" |
python3 -c "import sys, json; \ python3 -c "import sys, json; \
payload = json.load(sys.stdin); \ payload = json.load(sys.stdin); \
url = [a['browser_download_url'] for a in payload['assets'] if 'x86_64' in a['name']][0]; \ url = [a['browser_download_url'] for a in payload['assets'] if 'linux_x86_64' in a['name']][0]; \
print(url)") print(url)")
echo "downloading $ABRA_VERSION x86_64 binary release for abra..." echo "downloading $ABRA_VERSION x86_64 binary release for abra..."

View File

@ -3,5 +3,5 @@ STACK := abra_installer_script
default: deploy default: deploy
deploy: deploy:
@docker stack rm $(STACK) && \ @DOCKER_CONTEXT=swarm.autonomic.zone docker stack rm $(STACK) && \
docker stack deploy -c compose.yml $(STACK) DOCKER_CONTEXT=swarm.autonomic.zone docker stack deploy -c compose.yml $(STACK)

View File