Compare commits

..

22 Commits

Author SHA1 Message Date
09f49cdc76 chore: fix tests 2021-10-01 12:57:34 +02:00
22118b88e4 chore: appease formatter 2021-10-01 12:56:04 +02:00
e6db064149 chore: publish next tag 0.1.5-alpha 2021-10-01 12:32:41 +02:00
3wc
3688ea9d69 feat: support local server with --local 2021-10-01 11:59:17 +02:00
3wc
7c4cdc530c fix: don't crash if no abra.sh 2021-10-01 11:40:19 +02:00
3wc
49781c7e3f fix: ignore "env" files which don't end in .env 2021-10-01 11:40:19 +02:00
10b15d65b4 docs: use same style log messages [ci skip] 2021-09-29 22:37:16 +02:00
1c5d6d6357 docs: attempt some cmd docs 2021-09-29 22:36:43 +02:00
75bdd59585 Merge pull request 'feat: add a flag to commit your changes before creating a tag' (#102) from knoflook/abra:recipe-release into main
Reviewed-on: coop-cloud/abra#102
2021-09-29 20:24:55 +00:00
96bb145981 feat: check and sanitize user-specified tag 2021-09-29 16:25:39 +02:00
c4c76f4848 feat: add a flag to commit your changes before creating a tag 2021-09-29 16:08:02 +02:00
2076c566bb Merge pull request 'feat: tag recipes with abra' (#99) from knoflook/abra:recipe-release into main
Reviewed-on: coop-cloud/abra#99
2021-09-29 12:39:35 +00:00
62f6327b66 refactor: use usual naming style [ci skip] 2021-09-28 21:28:46 +02:00
6f9120b59c chore: run mod tidy 2021-09-28 21:27:31 +02:00
8c617a9f12 Merge pull request 'feat: print stack traces for errors when debugging' (#101) from knoflook/abra:main into main
Reviewed-on: coop-cloud/abra#101
2021-09-28 19:26:56 +00:00
857d12d23c feat: print stack traces for errors when debugging 2021-09-27 12:24:02 +02:00
22c4d0d864 style: remove doubled debug message 2021-09-24 11:05:49 +02:00
e700e44363 feat: add main apps version as a semver build metadata when releasing 2021-09-24 10:48:09 +02:00
9faefd2592 feat: push the new tag with --push 2021-09-23 18:52:21 +02:00
cd179175f5 refactor: dont' create the same objects twice 2021-09-23 18:32:58 +02:00
c0f92ca13d feat: support --major/-x --minor/-y --patch/-z for tag calculation 2021-09-23 18:27:19 +02:00
48d28c8dd1 feat: tag recipes with abra 2021-09-22 16:03:56 +02:00
12 changed files with 360 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/cli/catalogue"
"coopcloud.tech/abra/cli/recipe"
"coopcloud.tech/abra/cli/server"
logrusStack "github.com/Gurpartap/logrus-stack"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
@ -76,6 +77,9 @@ func RunApp(version, commit string) {
app.Before = func(c *cli.Context) error {
if Debug {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.SetOutput(os.Stderr)
logrus.AddHook(logrusStack.StandardHook())
}
return nil
}

View File

@ -18,6 +18,7 @@ Cloud community and you can use Abra to read them and create apps for you.
Subcommands: []*cli.Command{
recipeListCommand,
recipeVersionCommand,
recipeReleaseCommand,
recipeNewCommand,
recipeUpgradeCommand,
recipeSyncCommand,

302
cli/recipe/release.go Normal file
View File

@ -0,0 +1,302 @@
package recipe
import (
"fmt"
"path"
"strconv"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var Push bool
var PushFlag = &cli.BoolFlag{
Name: "push",
Value: false,
Destination: &Push,
}
var Dry bool
var DryFlag = &cli.BoolFlag{
Name: "dry-run",
Value: false,
Aliases: []string{"d"},
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 CommitMessageFlag = &cli.StringFlag{
Name: "commit-message",
Usage: "commit message",
Aliases: []string{"cm"},
Destination: &CommitMessage,
}
var Commit bool
var CommitFlag = &cli.BoolFlag{
Name: "commit",
Value: false,
Aliases: []string{"c"},
Destination: &Commit,
}
var recipeReleaseCommand = &cli.Command{
Name: "release",
Usage: "tag a recipe",
Aliases: []string{"rl"},
ArgsUsage: "<recipe> [<tag>]",
Description: `
This command is used to specify a new tag for a recipe. These tags are used to
identify different versions of the recipe and are published on the Co-op Cloud
recipe catalogue.
These tags take the following form:
a.b.c+x.y.z
Where the "a.b.c" part is maintained as a semantic version of the recipe by the
recipe maintainer. And the "x.y.z" part is the image tag of the recipe "app"
service (the main container which contains the software to be used).
We maintain a semantic versioning scheme ("a.b.c") alongside the libre app
versioning scheme in order to maximise the chances that the nature of recipe
updates are properly communicated.
Abra does its best to read the "a.b.c" version scheme and communicate what
action needs to be taken when performing different operations such as an update
or a rollback of an app.
`,
Flags: []cli.Flag{
DryFlag,
PatchFlag,
MinorFlag,
MajorFlag,
PushFlag,
CommitFlag,
CommitMessageFlag,
},
Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c)
directory := path.Join(config.APPS_DIR, recipe.Name)
tagstring := c.Args().Get(1)
imagesTmp := getImageVersions(recipe)
mainApp := getMainApp(recipe)
mainAppVersion := imagesTmp[mainApp]
if mainAppVersion == "" {
logrus.Fatal("main app version is empty?")
}
if tagstring != "" {
_, err := tagcmp.Parse(tagstring)
if err != nil {
logrus.Fatal("invalid tag specified")
}
}
if Commit || (CommitMessage != "") {
commitRepo, err := git.PlainOpen(directory)
if err != nil {
logrus.Fatal(err)
}
commitWorktree, err := commitRepo.Worktree()
if err != nil {
logrus.Fatal(err)
}
if CommitMessage == "" {
prompt := &survey.Input{
Message: "commit message",
}
survey.AskOne(prompt, &CommitMessage)
}
_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{})
if err != nil {
logrus.Fatal(err)
}
logrus.Info("changes commited")
}
repo, err := git.PlainOpen(directory)
if err != nil {
logrus.Fatal(err)
}
head, err := repo.Head()
if err != nil {
logrus.Fatal(err)
}
// bumpType is used to decide what part of the tag should be incremented
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.")
}
}
if tagstring != "" {
if bumpType > 0 {
logrus.Warn("user specified a version number and --major/--minor/--patch at the same time! using version number...")
}
tag, err := tagcmp.Parse(tagstring)
if err != nil {
logrus.Fatal(err)
}
if tag.MissingMinor {
tag.Minor = "0"
tag.MissingMinor = false
}
if tag.MissingPatch {
tag.Patch = "0"
tag.MissingPatch = false
}
tagstring = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion)
if Dry {
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagstring, head.Hash()))
return nil
}
repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{
Message: tag,
})*/
logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash()))
if Push {
if err := repo.Push(&git.PushOptions{}); err != nil {
logrus.Fatal(err)
}
logrus.Info(fmt.Sprintf("pushed tag %s to remote", tagstring))
}
return nil
}
// get the latest tag with its hash, name etc
var lastGitTag *object.Tag
iter, err := repo.Tags()
if err != nil {
logrus.Fatal(err)
}
if err := iter.ForEach(func(ref *plumbing.Reference) error {
obj, err := repo.TagObject(ref.Hash())
if err == nil {
lastGitTag = obj
return nil
}
return err
}); err != nil {
logrus.Fatal(err)
}
newTag, err := tagcmp.Parse(lastGitTag.Name)
if err != nil {
logrus.Fatal(err)
}
var newTagString string
if bumpType > 0 {
if Patch {
now, err := strconv.Atoi(newTag.Patch)
if err != nil {
logrus.Fatal(err)
}
newTag.Patch = strconv.Itoa(now + 1)
} else if Minor {
now, err := strconv.Atoi(newTag.Minor)
if err != nil {
logrus.Fatal(err)
}
newTag.Minor = strconv.Itoa(now + 1)
} else if Major {
now, err := strconv.Atoi(newTag.Major)
if err != nil {
logrus.Fatal(err)
}
newTag.Major = strconv.Itoa(now + 1)
}
newTagString = newTag.String()
} else {
logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch")
}
newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion)
if Dry {
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash()))
return nil
}
repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{
Message: tag,
})*/
logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash()))
if Push {
if err := repo.Push(&git.PushOptions{}); err != nil {
logrus.Fatal(err)
}
logrus.Info(fmt.Sprintf("pushed tag %s to remote", newTagString))
}
return nil
},
}
func getImageVersions(recipe recipe.Recipe) map[string]string {
var services = make(map[string]string)
for _, service := range recipe.Config.Services {
srv := strings.Split(service.Image, ":")
services[srv[0]] = srv[1]
}
return services
}
func getMainApp(recipe recipe.Recipe) string {
for _, service := range recipe.Config.Services {
name := service.Name
if name == "app" {
return strings.Split(service.Image, ":")[0]
}
}
return ""
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}

View File

@ -40,7 +40,6 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
if err != nil {
logrus.Fatal(err)
}
logrus.Debugf("read '%s' from the recipe catalogue for '%s'", catlVersions, service.Name)
img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {

View File

@ -2,15 +2,28 @@ package server
import (
"context"
"errors"
"os"
"os/user"
"path"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var local bool
var localFlag = &cli.BoolFlag{
Name: "local",
Aliases: []string{"L"},
Value: false,
Usage: "Set up the local server",
Destination: &local,
}
var serverAddCommand = &cli.Command{
Name: "add",
Usage: "Add a new server",
@ -32,11 +45,33 @@ Abra will construct the following SSH connection string then:
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{
localFlag,
},
ArgsUsage: "<domain> [<user>] [<port>]",
Action: func(c *cli.Context) error {
domainName := internal.ValidateDomain(c)
if c.Args().Len() == 1 && !local {
err := errors.New("missing arguments <domain> or '--local'")
internal.ShowSubcommandHelpAndError(c, err)
}
if c.Args().Get(1) != "" && local {
err := errors.New("cannot use '<domain>' and '--local' together")
internal.ShowSubcommandHelpAndError(c, err)
}
domainName := "default"
if local {
os.Mkdir(path.Join(config.ABRA_DIR, "servers", domainName), 0755)
return nil
}
domainName = internal.ValidateDomain(c)
var username string
var port string
@ -91,6 +126,8 @@ All communication between Abra and the server will use this SSH connection.
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)
return nil
},
}

2
go.mod
View File

@ -6,6 +6,7 @@ require (
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d
github.com/AlecAivazis/survey/v2 v2.3.1
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
github.com/docker/cli v20.10.8+incompatible
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v20.10.8+incompatible
@ -39,6 +40,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fvbommel/sortorder v1.0.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect

4
go.sum
View File

@ -44,6 +44,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBhJJEVNFMBNhRSFDRCB6O16T28VhvqRgqFyn8=
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@ -295,6 +297,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=

View File

@ -73,6 +73,11 @@ func getAllFilesInDirectory(directory string) ([]fs.FileInfo, error) {
for _, file := range files {
// Follow any symlinks
filePath := path.Join(directory, file.Name())
if filepath.Ext(strings.TrimSpace(filePath)) != ".env" {
continue
}
realPath, err := filepath.EvalSymlinks(filePath)
if err != nil {
logrus.Warningf("broken symlink in your abra config folders: '%s'", filePath)
@ -137,7 +142,7 @@ func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
file, err := os.Open(abraSh)
if err != nil {
if os.IsNotExist(err) {
return envVars, fmt.Errorf("'%s' does not exist?", abraSh)
return envVars, nil
}
return envVars, err
}

View File

@ -13,7 +13,7 @@ var validAbraConf = os.ExpandEnv("$PWD/../../tests/resources/valid_abra_config")
// make sure these are in alphabetical order
var tFolders = []string{"folder1", "folder2"}
var tFiles = []string{"bar", "foo"}
var tFiles = []string{"bar.env", "foo.env"}
var appName = "ecloud"
var serverName = "evil.corp"

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
ABRA_VERSION="0.1.4-alpha"
ABRA_VERSION="0.1.5-alpha"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
function show_banner {