0
0
forked from toolshed/abra

Compare commits

...

10 Commits

14 changed files with 305 additions and 68 deletions

View File

@ -183,7 +183,7 @@ does not).`,
if err := internal.RunCmdRemote(
cl,
app,
requestTTY,
disableTTY,
app.Recipe.AbraShPath,
targetServiceName, cmdName, parsedCmdArgs, remoteUser); err != nil {
log.Fatal(err)
@ -238,7 +238,7 @@ func parseCmdArgs(args []string, isLocal bool) (bool, string) {
var (
local bool
remoteUser string
requestTTY bool
disableTTY bool
)
func init() {
@ -259,11 +259,11 @@ func init() {
)
AppCmdCommand.Flags().BoolVarP(
&requestTTY,
&disableTTY,
"tty",
"T",
false,
"request remote TTY",
"disable remote TTY",
)
AppCmdCommand.Flags().BoolVarP(

View File

@ -142,7 +142,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
appStats.AutoUpdate = autoUpdate
var newUpdates []string
if version != "unknown" {
if version != "unknown" && chaosVersion == "unknown" {
if err := app.Recipe.EnsureExists(); err != nil {
log.Fatalf("unable to clone %s: %s", app.Name, err)
}

View File

@ -3,6 +3,7 @@ package app
import (
"context"
"fmt"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
@ -213,9 +214,7 @@ beforehand. See "abra app backup" for more.`,
return
}
if upgradeReleaseNotes != "" && chosenUpgrade != "" {
fmt.Print(upgradeReleaseNotes)
} else {
if upgradeReleaseNotes == "" {
upgradeWarnMessages = append(
upgradeWarnMessages,
fmt.Sprintf("no release notes available for %s", chosenUpgrade),
@ -337,6 +336,11 @@ func getReleaseNotes(
}
if note != "" {
// NOTE(d1): trim any final newline on the end of the note itself before
// we manually handle newlines (for multiple release notes and
// ensuring space between the warning messages)
note = strings.TrimSuffix(note, "\n")
*upgradeReleaseNotes += fmt.Sprintf("%s\n", note)
}
}

View File

@ -24,7 +24,7 @@ import (
func RunCmdRemote(
cl *dockerClient.Client,
app appPkg.App,
requestTTY bool,
disableTTY bool,
abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error {
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))
@ -84,8 +84,10 @@ func RunCmdRemote(
}
execCreateOpts.Cmd = cmd
execCreateOpts.Tty = requestTTY
if !requestTTY {
execCreateOpts.Tty = true
if disableTTY {
execCreateOpts.Tty = false
log.Debugf("not requesting a remote TTY")
}

View File

@ -46,7 +46,7 @@ func DeployOverview(
app appPkg.App,
deployedVersion string,
toDeployVersion string,
info string,
releaseNotes string,
warnMessages []string,
) error {
deployConfig := "compose.yml"
@ -85,8 +85,8 @@ func DeployOverview(
fmt.Println(overview)
if info != "" {
fmt.Println(info)
if releaseNotes != "" {
fmt.Print(releaseNotes)
}
for _, msg := range warnMessages {

View File

@ -32,12 +32,12 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
localRecipes, err := recipe.GetRecipesLocal()
if err != nil {
log.Fatal(err)
}
for _, recipeLocal := range localRecipes {
if _, ok := knownRecipes[recipeLocal]; !ok {
knownRecipes[recipeLocal] = true
log.Infof("can't read local recipes: %s", err)
} else {
for _, recipeLocal := range localRecipes {
if _, ok := knownRecipes[recipeLocal]; !ok {
knownRecipes[recipeLocal] = true
}
}
}

View File

@ -1,11 +1,15 @@
package recipe
import (
"os"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5"
gitCfg "github.com/go-git/go-git/v5/config"
"github.com/spf13/cobra"
)
@ -13,7 +17,16 @@ var RecipeFetchCommand = &cobra.Command{
Use: "fetch [recipe | --all] [flags]",
Aliases: []string{"f"},
Short: "Clone recipe(s) locally",
Long: `Using "--force/-f" Git syncs an existing recipe. It does not erase unstaged changes.`,
Args: cobra.RangeArgs(0, 1),
Example: ` # fetch from recipe catalogue
abra recipe fetch gitea
# fetch from remote recipe
abra recipe fetch git.foo.org/recipes/myrecipe
# fetch with ssh remote for hacking
abra recipe fetch gitea --ssh`,
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
@ -36,10 +49,39 @@ var RecipeFetchCommand = &cobra.Command{
ensureCtx := internal.GetEnsureContext()
if recipeName != "" {
r := internal.ValidateRecipe(args, cmd.Name())
if err := r.Ensure(ensureCtx); err != nil {
log.Fatal(err)
r := recipe.Get(recipeName)
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
if !force {
log.Warnf("%s is already fetched", r.Name)
return
}
}
r = internal.ValidateRecipe(args, cmd.Name())
if sshRemote {
if r.SSHURL == "" {
log.Warnf("unable to discover SSH remote for %s", r.Name)
return
}
repo, err := git.PlainOpen(r.Dir)
if err != nil {
log.Fatalf("unable to open %s: %s", r.Dir, err)
}
if err = repo.DeleteRemote("origin"); err != nil {
log.Fatalf("unable to remove default remote in %s: %s", r.Dir, err)
}
if _, err := repo.CreateRemote(&gitCfg.RemoteConfig{
Name: "origin",
URLs: []string{r.SSHURL},
}); err != nil {
log.Fatalf("unable to set SSH remote in %s: %s", r.Dir, err)
}
}
return
}
@ -61,6 +103,8 @@ var RecipeFetchCommand = &cobra.Command{
var (
fetchAllRecipes bool
sshRemote bool
force bool
)
func init() {
@ -71,4 +115,20 @@ func init() {
false,
"fetch all recipes",
)
RecipeFetchCommand.Flags().BoolVarP(
&sshRemote,
"ssh",
"s",
false,
"automatically set ssh remote",
)
RecipeFetchCommand.Flags().BoolVarP(
&force,
"force",
"f",
false,
"force re-fetch",
)
}

View File

@ -1,7 +1,10 @@
package git
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"coopcloud.tech/abra/pkg/log"
@ -22,46 +25,81 @@ func gitCloneIgnoreErr(err error) bool {
return false
}
// Clone runs a git clone which accounts for different default branches.
// Clone runs a git clone which accounts for different default branches. This
// function respects Ctrl+C (SIGINT) calls from the user, cancelling the
// context and deleting the (typically) half-baked clone of the repository.
// This avoids broken state for future clone / recipe ops.
func Clone(dir, url string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
log.Debugf("git clone: %s", url)
ctx := context.Background()
ctx, cancelCtx := context.WithCancel(ctx)
_, err := git.PlainClone(dir, false, &git.CloneOptions{
URL: url,
Tags: git.AllTags,
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
SingleBranch: true,
})
sigIntCh := make(chan os.Signal, 1)
signal.Notify(sigIntCh, os.Interrupt)
defer func() {
signal.Stop(sigIntCh)
cancelCtx()
}()
if err != nil && gitCloneIgnoreErr(err) {
log.Debugf("git clone: %s cloned successfully", dir)
return nil
}
errCh := make(chan error)
if err != nil {
log.Debug("git clone: main branch failed, attempting master branch")
go func() {
if _, err := os.Stat(dir); os.IsNotExist(err) {
log.Debugf("git clone: %s", url)
_, err := git.PlainClone(dir, false, &git.CloneOptions{
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
URL: url,
Tags: git.AllTags,
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
SingleBranch: true,
})
if err != nil && gitCloneIgnoreErr(err) {
log.Debugf("git clone: %s cloned successfully", dir)
return nil
errCh <- nil
}
if err := ctx.Err(); err != nil {
errCh <- fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
}
if err != nil {
return err
log.Debug("git clone: main branch failed, attempting master branch")
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
URL: url,
Tags: git.AllTags,
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
SingleBranch: true,
})
if err != nil && gitCloneIgnoreErr(err) {
log.Debugf("git clone: %s cloned successfully", dir)
errCh <- nil
}
if err != nil {
errCh <- err
}
}
log.Debugf("git clone: %s cloned successfully", dir)
} else {
log.Debugf("git clone: %s already exists", dir)
}
log.Debugf("git clone: %s cloned successfully", dir)
} else {
log.Debugf("git clone: %s already exists", dir)
errCh <- nil
}()
select {
case <-sigIntCh:
cancelCtx()
fmt.Println() // NOTE(d1): newline after ^C
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("unable to clean up git clone of %s: %s", dir, err)
}
return fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
case err := <-errCh:
return err
}
return nil

48
pkg/git/clone_test.go Normal file
View File

@ -0,0 +1,48 @@
package git
import (
"fmt"
"os"
"path"
"syscall"
"testing"
"coopcloud.tech/abra/pkg/config"
)
func TestClone(t *testing.T) {
dir := path.Join(config.RECIPES_DIR, "gitea")
os.RemoveAll(dir)
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea")
if err := Clone(dir, gitURL); err != nil {
t.Fatalf("unable to git clone gitea: %s", err)
}
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
t.Fatal("gitea repo was not cloned successfully")
}
}
func TestCancelGitClone(t *testing.T) {
dir := path.Join(config.RECIPES_DIR, "gitea")
os.RemoveAll(dir)
go func() {
p, err := os.FindProcess(os.Getpid())
if err != nil {
t.Fatalf("unable to find current process: %s", err)
}
p.Signal(syscall.SIGINT)
}()
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea")
if err := Clone(dir, gitURL); err == nil {
t.Fatal("cloning should have been interrupted")
}
if _, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
t.Fatal("recipe repo was not deleted")
}
}

View File

@ -401,8 +401,6 @@ teardown(){
# bats test_tags=slow
@test "ignore env version on new deploy" {
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input --no-converge-checks
assert_success

View File

@ -19,6 +19,7 @@ setup(){
}
teardown(){
_reset_recipe
_undeploy_app
}
@ -117,3 +118,22 @@ teardown(){
run $ABRA app ls --status
assert_success
}
# bats test_tags=slow
@test "list with chaos version" {
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks --chaos
assert_success
run $ABRA app ls --status
assert_success
assert_output --partial "+U"
run rm -rf "$ABRA_DIR/servers/foo.com"
assert_success
assert_not_exists "$ABRA_DIR/servers/foo.com"
}

View File

@ -25,13 +25,3 @@ teardown(){
run "$HOME/.local/bin/abra" -v
assert_output --partial 'beta'
}
# bats test_tags=slow
@test "install release candidate from script" {
run bash -c 'curl https://install.abra.coopcloud.tech | bash -s -- --rc'
assert_success
assert_exists "$HOME/.local/bin/abra"
run "$HOME/.local/bin/abra" -v
assert_output --partial '-rc'
}

View File

@ -5,6 +5,16 @@ setup() {
_common_setup
}
teardown(){
run rm -rf "$ABRA_DIR/recipes/matrix-synapse"
assert_success
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
run rm -rf "$ABRA_DIR/recipes/git_coopcloud_tech_coop-cloud_matrix-synapse"
assert_success
assert_not_exists "$ABRA_DIR/recipes/git_coopcloud_tech_coop-cloud_matrix-synapse"
}
# bats test_tags=slow
@test "recipe fetch all" {
run rm -rf "$ABRA_DIR/recipes/matrix-synapse"
@ -35,3 +45,81 @@ setup() {
run $ABRA recipe fetch matrix-synapse --all
assert_failure
}
@test "do not refetch without --force" {
run $ABRA recipe fetch matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run $ABRA recipe fetch matrix-synapse
assert_output --partial "already fetched"
}
@test "refetch with --force" {
run $ABRA recipe fetch matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run $ABRA recipe fetch matrix-synapse --force
assert_success
refute_output --partial "already fetched"
}
@test "refetch with --force does not erase unstaged changes" {
run $ABRA recipe fetch matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run bash -c "echo foo >> $ABRA_DIR/recipes/matrix-synapse/foo"
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse/foo"
run $ABRA recipe fetch matrix-synapse --force
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
assert_exists "$ABRA_DIR/recipes/matrix-synapse/foo"
}
@test "fetch with --ssh" {
run $ABRA recipe fetch matrix-synapse --ssh
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run git -C "$ABRA_DIR/recipes/matrix-synapse" remote -v
assert_success
assert_output --partial "ssh://"
}
@test "re-fetch with --ssh/--force" {
run $ABRA recipe fetch matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run git -C "$ABRA_DIR/recipes/matrix-synapse" remote -v
assert_success
assert_output --partial "https://"
run $ABRA recipe fetch matrix-synapse --ssh --force
assert_success
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
run git -C "$ABRA_DIR/recipes/matrix-synapse" remote -v
assert_success
assert_output --partial "ssh://"
}
@test "fetch remote recipe" {
run $ABRA recipe fetch git.coopcloud.tech/coop-cloud/matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/git_coopcloud_tech_coop-cloud_matrix-synapse"
}
@test "remote recipe do not refetch without --force" {
run $ABRA recipe fetch git.coopcloud.tech/coop-cloud/matrix-synapse
assert_success
assert_exists "$ABRA_DIR/recipes/git_coopcloud_tech_coop-cloud_matrix-synapse"
run $ABRA recipe fetch git.coopcloud.tech/coop-cloud/matrix-synapse
assert_success
assert_output --partial "already fetched"
}

View File

@ -26,14 +26,3 @@ teardown(){
run "$HOME/.local/bin/abra" -v
assert_output --partial 'beta'
}
# bats test_tags=slow
@test "abra upgrade release candidate" {
run $ABRA upgrade --rc
assert_success
assert_output --partial 'Public interest infrastructure'
assert_exists "$HOME/.local/bin/abra"
run "$HOME/.local/bin/abra" -v
assert_output --partial '-rc'
}