feat: debug logging

Closes coop-cloud/organising#164.
This commit is contained in:
decentral1se 2021-09-11 00:54:02 +02:00
parent 27d665c3be
commit 9fcdc45851
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
38 changed files with 305 additions and 95 deletions

View File

@ -66,13 +66,10 @@ var appBackupCommand = &cli.Command{
sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd) sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd)
cmd := exec.Command("bash", "-c", sourceAndExec) cmd := exec.Command("bash", "-c", sourceAndExec)
output, err := cmd.Output() if err := internal.RunCmd(cmd); err != nil {
if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
fmt.Print(string(output))
return nil return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {

View File

@ -45,7 +45,7 @@ var appCheckCommand = &cli.Command{
logrus.Fatalf("%s is missing %s", app.Path, missingEnvVars) logrus.Fatalf("%s is missing %s", app.Path, missingEnvVars)
} }
logrus.Info("All necessary environment variables defined") logrus.Infof("all necessary environment variables defined for '%s'", app.Name)
return nil return nil
}, },

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"strings" "strings"
"coopcloud.tech/abra/cli/formatter"
"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/config"
@ -55,11 +56,13 @@ var appCpCommand = &cli.Command{
service = parsedSrc[0] service = parsedSrc[0]
srcPath = parsedSrc[1] srcPath = parsedSrc[1]
dstPath = dst dstPath = dst
logrus.Debugf("assuming transfer is coming FROM the container")
} else if len(parsedDst) == 2 { } else if len(parsedDst) == 2 {
service = parsedDst[0] service = parsedDst[0]
dstPath = parsedDst[1] dstPath = parsedDst[1]
srcPath = src srcPath = src
isToContainer = true // <src> <container:dst> isToContainer = true // <src> <container:dst>
logrus.Debugf("assuming transfer is going TO the container")
} }
appFiles, err := config.LoadAppFiles("") appFiles, err := config.LoadAppFiles("")
@ -90,6 +93,8 @@ var appCpCommand = &cli.Command{
} }
container := containers[0] container := containers[0]
logrus.Debugf("retrieved '%s' as target container on '%s'", formatter.ShortenID(container.ID), app.Server)
if isToContainer { if isToContainer {
if _, err := os.Stat(srcPath); err != nil { if _, err := os.Stat(srcPath); err != nil {
logrus.Fatalf("'%s' does not exist?", srcPath) logrus.Fatalf("'%s' does not exist?", srcPath)

View File

@ -73,8 +73,10 @@ var appLogsCommand = &cli.Command{
serviceName := c.Args().Get(1) serviceName := c.Args().Get(1)
if serviceName == "" { if serviceName == "" {
logrus.Debug("tailing logs for all app services")
stackLogs(app.StackName(), cl) stackLogs(app.StackName(), cl)
} }
logrus.Debugf("tailing logs for '%s'", serviceName)
service := fmt.Sprintf("%s_%s", app.StackName(), serviceName) service := fmt.Sprintf("%s_%s", app.StackName(), serviceName)
filters := filters.NewArgs() filters := filters.NewArgs()

View File

@ -197,6 +197,7 @@ func action(c *cli.Context) error {
if len(sanitisedAppName) > 45 { if len(sanitisedAppName) > 45 {
logrus.Fatalf("'%s' cannot be longer than 45 characters", sanitisedAppName) logrus.Fatalf("'%s' cannot be longer than 45 characters", sanitisedAppName)
} }
logrus.Debugf("'%s' sanitised as '%s' for new app", newAppName, sanitisedAppName)
if err := config.CopyAppEnvSample(recipe.Name, newAppName, newAppServer); err != nil { if err := config.CopyAppEnvSample(recipe.Name, newAppName, newAppServer); err != nil {
logrus.Fatal(err) logrus.Fatal(err)

View File

@ -37,7 +37,7 @@ var appPsCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
tableCol := []string{"ID", "Image", "Command", "Created", "Status", "Ports", "Names"} tableCol := []string{"id", "image", "command", "created", "status", "ports", "names"}
table := abraFormatter.CreateTable(tableCol) table := abraFormatter.CreateTable(tableCol)
for _, container := range containers { for _, container := range containers {
@ -48,7 +48,7 @@ var appPsCommand = &cli.Command{
abraFormatter.HumanDuration(container.Created), abraFormatter.HumanDuration(container.Created),
container.Status, container.Status,
formatter.DisplayablePorts(container.Ports), formatter.DisplayablePorts(container.Ports),
strings.Join(container.Names, ","), strings.Join(container.Names, ", "),
} }
table.Append(tableRow) table.Append(tableRow)
} }

View File

@ -39,13 +39,13 @@ var appRemoveCommand = &cli.Command{
if !internal.Force { if !internal.Force {
response := false response := false
prompt := &survey.Confirm{ prompt := &survey.Confirm{
Message: fmt.Sprintf("About to delete %s, are you sure", app.Name), Message: fmt.Sprintf("about to delete %s, are you sure?", app.Name),
} }
if err := survey.AskOne(prompt, &response); err != nil { if err := survey.AskOne(prompt, &response); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if !response { if !response {
logrus.Fatal("User aborted app removal") logrus.Fatal("user aborted app removal")
} }
} }
@ -89,7 +89,7 @@ var appRemoveCommand = &cli.Command{
var secretNamesToRemove []string var secretNamesToRemove []string
if !internal.Force { if !internal.Force {
secretsPrompt := &survey.MultiSelect{ secretsPrompt := &survey.MultiSelect{
Message: "Which secrets do you want to remove?", Message: "which secrets do you want to remove?",
Options: secretNames, Options: secretNames,
Default: secretNames, Default: secretNames,
} }
@ -103,10 +103,10 @@ var appRemoveCommand = &cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Info(fmt.Sprintf("Secret: %s removed", name)) logrus.Info(fmt.Sprintf("secret: %s removed", name))
} }
} else { } else {
logrus.Info("No secrets to remove") logrus.Info("no secrets to remove")
} }
volumeListOKBody, err := cl.VolumeList(ctx, fs) volumeListOKBody, err := cl.VolumeList(ctx, fs)
@ -125,7 +125,7 @@ var appRemoveCommand = &cli.Command{
var removeVols []string var removeVols []string
if !internal.Force { if !internal.Force {
volumesPrompt := &survey.MultiSelect{ volumesPrompt := &survey.MultiSelect{
Message: "Which volumes do you want to remove?", Message: "which volumes do you want to remove?",
Options: vols, Options: vols,
Default: vols, Default: vols,
} }
@ -138,20 +138,20 @@ var appRemoveCommand = &cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Info(fmt.Sprintf("Volume %s removed", vol)) logrus.Info(fmt.Sprintf("volume %s removed", vol))
} }
} else { } else {
logrus.Info("No volumes were removed") logrus.Info("no volumes were removed")
} }
} else { } else {
logrus.Info("No volumes to remove") logrus.Info("no volumes to remove")
} }
err = os.Remove(app.Path) err = os.Remove(app.Path)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Info(fmt.Sprintf("File: %s removed", app.Path)) logrus.Info(fmt.Sprintf("file: %s removed", app.Path))
return nil return nil
}, },

View File

@ -70,13 +70,10 @@ var appRestoreCommand = &cli.Command{
sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd) sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd)
cmd := exec.Command("bash", "-c", sourceAndExec) cmd := exec.Command("bash", "-c", sourceAndExec)
output, err := cmd.Output() if err := internal.RunCmd(cmd); err != nil {
if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
fmt.Print(string(output))
return nil return nil
}, },
} }

View File

@ -75,6 +75,8 @@ var appRollbackCommand = &cli.Command{
// display table of existing state and expected state and prompt // display table of existing state and expected state and prompt
// run the deployment with this target version! // run the deployment with this target version!
logrus.Fatal("command not implemented yet, coming soon TM")
return nil return nil
}, },
} }

View File

@ -83,13 +83,13 @@ var appSecretGenerateCommand = &cli.Command{
os.Exit(1) os.Exit(1)
} }
tableCol := []string{"Name", "Value"} tableCol := []string{"name", "value"}
table := abraFormatter.CreateTable(tableCol) table := abraFormatter.CreateTable(tableCol)
for name, val := range secretVals { for name, val := range secretVals {
table.Append([]string{name, val}) table.Append([]string{name, val})
} }
table.Render() table.Render()
logrus.Warn("these secrets are not shown again, please take note of them *now*") logrus.Warn("generated secrets are not shown again, please take note of them *now*")
return nil return nil
}, },

View File

@ -25,6 +25,7 @@ func getImagePath(image string) (string, error) {
if strings.Contains(path, "library") { if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1] path = strings.Split(path, "/")[1]
} }
logrus.Debugf("parsed '%s' from '%s'", path, image)
return path, nil return path, nil
} }
@ -53,7 +54,7 @@ var appVersionCommand = &cli.Command{
}(app.Server, label) }(app.Server, label)
} }
tableCol := []string{"Name", "Image", "Version", "Digest"} tableCol := []string{"name", "image", "version", "digest"}
table := abraFormatter.CreateTable(tableCol) table := abraFormatter.CreateTable(tableCol)
statuses := make(map[string]stack.StackStatus) statuses := make(map[string]stack.StackStatus)

View File

@ -15,7 +15,7 @@ import (
var appVolumeListCommand = &cli.Command{ var appVolumeListCommand = &cli.Command{
Name: "list", Name: "list",
Usage: "list volumes associated with an app", Usage: "List volumes associated with an app",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -26,7 +26,7 @@ var appVolumeListCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
table := abraFormatter.CreateTable([]string{"DRIVER", "VOLUME NAME"}) table := abraFormatter.CreateTable([]string{"driver", "volume name"})
var volTable [][]string var volTable [][]string
for _, volume := range volumeList { for _, volume := range volumeList {
volRow := []string{ volRow := []string{
@ -45,7 +45,7 @@ var appVolumeListCommand = &cli.Command{
var appVolumeRemoveCommand = &cli.Command{ var appVolumeRemoveCommand = &cli.Command{
Name: "remove", Name: "remove",
Usage: "remove volume(s) associated with an app", Usage: "Remove volume(s) associated with an app",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.ForceFlag, internal.ForceFlag,
@ -63,7 +63,7 @@ var appVolumeRemoveCommand = &cli.Command{
var volumesToRemove []string var volumesToRemove []string
if !internal.Force { if !internal.Force {
volumesPrompt := &survey.MultiSelect{ volumesPrompt := &survey.MultiSelect{
Message: "Which volumes do you want to remove?", Message: "which volumes do you want to remove?",
Options: volumeNames, Options: volumeNames,
Default: volumeNames, Default: volumeNames,
} }
@ -79,7 +79,7 @@ var appVolumeRemoveCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Info("Volumes removed successfully.") logrus.Info("volumes removed successfully")
return nil return nil
}, },

View File

@ -68,8 +68,18 @@ func RunApp(version, commit string) {
}, },
}, },
} }
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Before = func(c *cli.Context) error {
if Debug {
logrus.SetLevel(logrus.DebugLevel)
}
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

@ -31,6 +31,7 @@ func HumanDuration(timestamp int64) string {
return units.HumanDuration(now.Sub(date)) + " ago" return units.HumanDuration(now.Sub(date)) + " ago"
} }
// CreateTable prepares a table layout for output.
func CreateTable(columns []string) *tablewriter.Table { func CreateTable(columns []string) *tablewriter.Table {
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetHeader(columns) table.SetHeader(columns)

View File

@ -6,6 +6,7 @@ import (
"os/exec" "os/exec"
) )
// RunCmd runs a shell command and streams stdout/stderr in real-time.
func RunCmd(cmd *exec.Cmd) error { func RunCmd(cmd *exec.Cmd) error {
r, err := cmd.StdoutPipe() r, err := cmd.StdoutPipe()
if err != nil { if err != nil {

View File

@ -23,6 +23,8 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("validated '%s' as recipe argument", recipeName)
return recipe return recipe
} }
@ -39,6 +41,8 @@ func ValidateApp(c *cli.Context) config.App {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("validated '%s' as app argument", appName)
return app return app
} }
@ -50,5 +54,7 @@ func ValidateDomain(c *cli.Context) string {
ShowSubcommandHelpAndError(c, errors.New("no domain provided")) ShowSubcommandHelpAndError(c, errors.New("no domain provided"))
} }
logrus.Debugf("validated '%s' as domain argument", domainName)
return domainName return domainName
} }

View File

@ -77,16 +77,16 @@ var recipeLintCommand = &cli.Command{
} }
} }
tableCol := []string{"Rule", "Satisfied"} tableCol := []string{"rule", "satisfied"}
table := formatter.CreateTable(tableCol) table := formatter.CreateTable(tableCol)
table.Append([]string{"Compose files have the expected version", strconv.FormatBool(expectedVersion)}) table.Append([]string{"compose files have the expected version", strconv.FormatBool(expectedVersion)})
table.Append([]string{"Environment configuration is provided", strconv.FormatBool(envSampleProvided)}) table.Append([]string{"environment configuration is provided", strconv.FormatBool(envSampleProvided)})
table.Append([]string{"Recipe contains a service named 'app'", strconv.FormatBool(serviceNamedApp)}) table.Append([]string{"recipe contains a service named 'app'", strconv.FormatBool(serviceNamedApp)})
table.Append([]string{"Traefik routing enabled on at least one service", strconv.FormatBool(traefikEnabled)}) table.Append([]string{"traefik routing enabled on at least one service", strconv.FormatBool(traefikEnabled)})
table.Append([]string{"All services have a healthcheck enabled", strconv.FormatBool(healthChecksForAllServices)}) table.Append([]string{"all services have a healthcheck enabled", strconv.FormatBool(healthChecksForAllServices)})
table.Append([]string{"All images are using a tag", strconv.FormatBool(allImagesTagged)}) table.Append([]string{"all images are using a tag", strconv.FormatBool(allImagesTagged)})
table.Append([]string{"No usage of unstable 'latest' tags", strconv.FormatBool(noUnstableTags)}) table.Append([]string{"no usage of unstable 'latest' tags", strconv.FormatBool(noUnstableTags)})
table.Append([]string{"All tags are using a semver-like format", strconv.FormatBool(semverLikeTags)}) table.Append([]string{"all tags are using a semver-like format", strconv.FormatBool(semverLikeTags)})
table.Render() table.Render()
return nil return nil

View File

@ -23,7 +23,7 @@ var recipeListCommand = &cli.Command{
recipes := catl.Flatten() recipes := catl.Flatten()
sort.Sort(catalogue.ByRecipeName(recipes)) sort.Sort(catalogue.ByRecipeName(recipes))
tableCol := []string{"Name", "Category", "Status"} tableCol := []string{"name", "category", "status"}
table := formatter.CreateTable(tableCol) table := formatter.CreateTable(tableCol)
for _, recipe := range recipes { for _, recipe := range recipes {

View File

@ -8,7 +8,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"github.com/go-git/go-git/v5" "coopcloud.tech/abra/pkg/git"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -28,10 +28,8 @@ var recipeNewCommand = &cli.Command{
} }
url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL) url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL)
_, err := git.PlainClone(directory, false, &git.CloneOptions{URL: url, Tags: git.AllTags}) if err := git.Clone(directory, url); err != nil {
if err != nil { return err
logrus.Fatal(err)
return nil
} }
gitRepo := path.Join(config.APPS_DIR, recipe.Name, ".git") gitRepo := path.Join(config.APPS_DIR, recipe.Name, ".git")
@ -39,6 +37,7 @@ var recipeNewCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
return nil return nil
} }
logrus.Debugf("removed git repo in '%s'", gitRepo)
toParse := []string{ toParse := []string{
path.Join(config.APPS_DIR, recipe.Name, "README.md"), path.Join(config.APPS_DIR, recipe.Name, "README.md"),
@ -71,7 +70,7 @@ var recipeNewCommand = &cli.Command{
} }
logrus.Infof( logrus.Infof(
"New recipe '%s' created in %s, happy hacking!\n", "new recipe '%s' created in %s, happy hacking!\n",
recipe.Name, path.Join(config.APPS_DIR, recipe.Name), recipe.Name, path.Join(config.APPS_DIR, recipe.Name),
) )

View File

@ -36,7 +36,7 @@ the versioning metadata of up-and-running containers are.
} }
if !hasAppService { if !hasAppService {
logrus.Fatal(fmt.Sprintf("No 'app' service defined in '%s', cannot proceed", recipe.Name)) logrus.Fatal(fmt.Sprintf("no 'app' service defined in '%s'", recipe.Name))
} }
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
@ -44,17 +44,20 @@ the versioning metadata of up-and-running containers are.
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("detected image '%s' for service '%s'", img, service.Name)
digest, err := client.GetTagDigest(img) digest, err := client.GetTagDigest(img)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("retrieved digest '%s' for '%s'", digest, img)
tag := img.(reference.NamedTagged).Tag() tag := img.(reference.NamedTagged).Tag()
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest) label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest)
if err := recipe.UpdateLabel(service.Name, label); err != nil { if err := recipe.UpdateLabel(service.Name, label); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("added label '%s' to service '%s'", label, service.Name)
} }
return nil return nil

View File

@ -40,6 +40,7 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("read '%s' from the recipe catalogue for '%s'", catlVersions, service.Name)
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
@ -51,6 +52,7 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("retrieved '%s' from remote registry for '%s'", regVersions, image)
if strings.Contains(image, "library") { if strings.Contains(image, "library") {
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>, // ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
@ -61,6 +63,7 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
semverLikeTag := true semverLikeTag := true
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
logrus.Debugf("'%s' not considered semver-like", img.(reference.NamedTagged).Tag())
semverLikeTag = false semverLikeTag = false
} }
@ -68,6 +71,7 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
if err != nil && semverLikeTag { if err != nil && semverLikeTag {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("parsed '%s' for '%s'", tag, service.Name)
var compatible []tagcmp.Tag var compatible []tagcmp.Tag
for _, regVersion := range regVersions { for _, regVersion := range regVersions {
@ -81,10 +85,12 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
} }
} }
logrus.Debugf("detected potential upgradable tags '%s' for '%s'", compatible, service.Name)
sort.Sort(tagcmp.ByTagDesc(compatible)) sort.Sort(tagcmp.ByTagDesc(compatible))
if len(compatible) == 0 && semverLikeTag { if len(compatible) == 0 && semverLikeTag {
logrus.Info(fmt.Sprintf("No new versions available for '%s', '%s' is the latest", image, tag)) logrus.Info(fmt.Sprintf("no new versions available for '%s', '%s' is the latest", image, tag))
continue // skip on to the next tag and don't update any compose files continue // skip on to the next tag and don't update any compose files
} }
@ -101,11 +107,13 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
} }
} }
msg := fmt.Sprintf("Which tag would you like to upgrade to? (service: %s, tag: %s)", service.Name, tag) 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()) { if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
tag := img.(reference.NamedTagged).Tag() tag := img.(reference.NamedTagged).Tag()
logrus.Warning(fmt.Sprintf("Unable to determine versioning semantics of '%s', listing all tags...", tag)) logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag))
msg = fmt.Sprintf("Which tag would you like to upgrade to? (service: %s, tag: %s)", service.Name, tag) msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
compatibleStrings = []string{} compatibleStrings = []string{}
for _, regVersion := range regVersions { for _, regVersion := range regVersions {
compatibleStrings = append(compatibleStrings, regVersion.Name) compatibleStrings = append(compatibleStrings, regVersion.Name)
@ -124,6 +132,7 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
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)
} }
return nil return nil

View File

@ -62,10 +62,12 @@ All communication between Abra and the server will use this SSH connection.
for _, context := range contexts { for _, context := range contexts {
if context.Name == domainName { if context.Name == domainName {
logrus.Fatalf("Server at '%s' already exists?", domainName) logrus.Fatalf("server at '%s' already exists?", domainName)
} }
} }
logrus.Debugf("creating context with domain '%s', username '%s' and port '%s'", domainName, username, port)
if err := client.CreateContext(domainName, username, port); err != nil { if err := client.CreateContext(domainName, username, port); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -77,11 +79,12 @@ All communication between Abra and the server will use this SSH connection.
} }
if _, err := cl.Info(ctx); err != nil { if _, err := cl.Info(ctx); err != nil {
logrus.Fatalf("Unable to make a connection to '%s'?", domainName) logrus.Fatalf("unable to make a connection to '%s'?", domainName)
logrus.Debug(err) logrus.Debug(err)
} }
logrus.Infof("Server at '%s' has been added", domainName) logrus.Debugf("remote connection to '%s' is definitely up", domainName)
logrus.Infof("server at '%s' has been added", domainName)
return nil return nil
}, },

View File

@ -21,7 +21,7 @@ var serverInitCommand = &cli.Command{
HideHelp: true, HideHelp: true,
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Description: ` Description: `
Initialise swarm mode on the target <server>. Initialise swarm mode on the target <domain>.
This initialisation explicitly chooses the "single host swarm" mode which uses This initialisation explicitly chooses the "single host swarm" mode which uses
the default IPv4 address as the advertising address. This can be re-configured the default IPv4 address as the advertising address. This can be re-configured
@ -45,6 +45,7 @@ later for more advanced use cases.
return d.DialContext(ctx, "udp", "95.216.24.230:53") return d.DialContext(ctx, "udp", "95.216.24.230:53")
}, },
} }
logrus.Debugf("created DNS resolver via 95.216.24.230")
ctx := context.Background() ctx := context.Background()
ips, err := resolver.LookupIPAddr(ctx, domainName) ips, err := resolver.LookupIPAddr(ctx, domainName)
@ -64,11 +65,13 @@ later for more advanced use cases.
if _, err := cl.SwarmInit(ctx, initReq); err != nil { if _, err := cl.SwarmInit(ctx, initReq); err != nil {
return err return err
} }
logrus.Debugf("initialised swarm on '%s'", domainName)
netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"}
if _, err := cl.NetworkCreate(ctx, "proxy", netOpts); err != nil { if _, err := cl.NetworkCreate(ctx, "proxy", netOpts); err != nil {
return err return err
} }
logrus.Debug("swarm overlay network 'proxy' created")
return nil return nil
}, },

View File

@ -22,6 +22,7 @@ var serverListCommand = &cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
tableColumns := []string{"Name", "Connection"} tableColumns := []string{"Name", "Connection"}
table := formatter.CreateTable(tableColumns) table := formatter.CreateTable(tableColumns)
defer table.Render() defer table.Render()
@ -30,8 +31,8 @@ var serverListCommand = &cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
for _, serverName := range serverNames {
for _, serverName := range serverNames {
var row []string var row []string
for _, ctx := range contexts { for _, ctx := range contexts {
endpoint, err := client.GetContextEndpoint(ctx) endpoint, err := client.GetContextEndpoint(ctx)
@ -47,9 +48,8 @@ var serverListCommand = &cli.Command{
row = []string{serverName, "UNKNOWN"} row = []string{serverName, "UNKNOWN"}
} }
table.Append(row) table.Append(row)
} }
return nil
return nil
}, },
} }

View File

@ -79,12 +79,14 @@ environment variable or otherwise passing the "--env/-e" flag.
} }
if hetznerCloudAPIToken == "" { if hetznerCloudAPIToken == "" {
logrus.Fatal("Hetzner Cloud API token is missing, cannot continue") logrus.Fatal("Hetzner Cloud API token is missing")
} }
ctx := context.Background() ctx := context.Background()
client := hcloud.NewClient(hcloud.WithToken(hetznerCloudAPIToken)) client := hcloud.NewClient(hcloud.WithToken(hetznerCloudAPIToken))
logrus.Debugf("successfully created hetzner cloud API client")
var sshKeys []*hcloud.SSHKey var sshKeys []*hcloud.SSHKey
for _, sshKey := range c.StringSlice("ssh-keys") { for _, sshKey := range c.StringSlice("ssh-keys") {
sshKey, _, err := client.SSHKey.GetByName(ctx, sshKey) sshKey, _, err := client.SSHKey.GetByName(ctx, sshKey)
@ -106,13 +108,17 @@ environment variable or otherwise passing the "--env/-e" flag.
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("new server '%s' created", name)
tableColumns := []string{"Name", "IPv4", "Root Password"} tableColumns := []string{"Name", "IPv4", "Root Password"}
table := formatter.CreateTable(tableColumns) table := formatter.CreateTable(tableColumns)
if len(sshKeys) > 0 { if len(sshKeys) > 0 {
table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), "N/A (using SSH keys)"}) table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), "N/A (using SSH keys)"})
} else { } else {
table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), res.RootPassword}) table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), res.RootPassword})
} }
table.Render() table.Render()
return nil return nil
@ -182,13 +188,14 @@ environment variable or otherwise passing the "--env/-e" flag.
} }
if capsulAPIToken == "" { if capsulAPIToken == "" {
logrus.Fatal("Capsul API token is missing, cannot continue") logrus.Fatal("Capsul API token is missing")
} }
// yep, the response time is quite slow, something to fix Capsul side // yep, the response time is quite slow, something to fix on the Capsul side
client := &http.Client{Timeout: 20 * time.Second} client := &http.Client{Timeout: 20 * time.Second}
capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", capsulInstance) capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", capsulInstance)
logrus.Debugf("using '%s' as capsul create url", capsulCreateURL)
values := map[string]string{ values := map[string]string{
"name": name, "name": name,
"size": capsulType, "size": capsulType,
@ -230,6 +237,7 @@ environment variable or otherwise passing the "--env/-e" flag.
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil { if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("capsul created with ID: '%s'", resp.ID)
tableColumns := []string{"Name", "ID"} tableColumns := []string{"Name", "ID"}
table := formatter.CreateTable(tableColumns) table := formatter.CreateTable(tableColumns)

View File

@ -23,7 +23,7 @@ internal bookkeeping so that it is not managed any more.
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("Server at '%s' has been forgotten", domainName) logrus.Infof("server at '%s' has been forgotten", domainName)
return nil return nil
}, },

View File

@ -14,6 +14,7 @@ var UpgradeCommand = &cli.Command{
Usage: "Upgrade abra", Usage: "Upgrade abra",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
cmd := exec.Command("bash", "-c", "curl -s https://install.abra.coopcloud.tech | bash") cmd := exec.Command("bash", "-c", "curl -s https://install.abra.coopcloud.tech | bash")
logrus.Debugf("attempting to run '%s'", cmd)
if err := internal.RunCmd(cmd); err != nil { if err := internal.RunCmd(cmd); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/pkg/client/stack" "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
apiclient "github.com/docker/docker/client" apiclient "github.com/docker/docker/client"
"github.com/sirupsen/logrus"
) )
// Get retrieves an app // Get retrieves an app
@ -22,6 +23,8 @@ func Get(appName string) (config.App, error) {
return config.App{}, err return config.App{}, err
} }
logrus.Debugf("retrieved '%s' for '%s'", app, appName)
return app, nil return app, nil
} }
@ -51,6 +54,14 @@ func DeployedVersions(ctx context.Context, cl *apiclient.Client, app config.App)
} }
} }
deployed := len(services) > 0
if deployed {
logrus.Debugf("detected '%s' as deployed versions of '%s'", appSpec, app.Name)
} else {
logrus.Debugf("detected '%s' as not deployed", app.Name)
}
return appSpec, len(services) > 0, nil return appSpec, len(services) > 0, nil
} }
@ -58,11 +69,17 @@ func DeployedVersions(ctx context.Context, cl *apiclient.Client, app config.App)
func ParseVersionLabel(label string) (string, string) { func ParseVersionLabel(label string) (string, string) {
// versions may look like v4.2-abcd or v4.2-alpine-abcd // versions may look like v4.2-abcd or v4.2-alpine-abcd
idx := strings.LastIndex(label, "-") idx := strings.LastIndex(label, "-")
return label[:idx], label[idx+1:] version := label[:idx]
digest := label[idx+1:]
logrus.Debugf("parsed '%s' as version from '%s'", version, label)
logrus.Debugf("parsed '%s' as digest from '%s'", digest, label)
return version, digest
} }
// ParseVersionName parses a $STACK_NAME_$SERVICE_NAME service label. // ParseVersionName parses a $STACK_NAME_$SERVICE_NAME service label.
func ParseServiceName(label string) string { func ParseServiceName(label string) string {
idx := strings.LastIndex(label, "_") idx := strings.LastIndex(label, "_")
return label[idx+1:] serviceName := label[idx+1:]
logrus.Debugf("parsed '%s' as service name from '%s'", serviceName, label)
return serviceName
} }

View File

@ -15,6 +15,7 @@ import (
"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/sirupsen/logrus"
) )
// RecipeCatalogueURL is the only current recipe catalogue available. // RecipeCatalogueURL is the only current recipe catalogue available.
@ -76,6 +77,8 @@ func (r RecipeMeta) LatestVersion() string {
version = tag version = tag
} }
logrus.Debugf("choosing '%s' as latest version of '%s'", version, r.Name)
return version return version
} }
@ -88,9 +91,11 @@ type RecipeCatalogue map[Name]RecipeMeta
// Flatten converts AppCatalogue to slice // Flatten converts AppCatalogue to slice
func (r RecipeCatalogue) Flatten() []RecipeMeta { func (r RecipeCatalogue) Flatten() []RecipeMeta {
recipes := make([]RecipeMeta, 0, len(r)) recipes := make([]RecipeMeta, 0, len(r))
for name := range r { for name := range r {
recipes = append(recipes, r[name]) recipes = append(recipes, r[name])
} }
return recipes return recipes
} }
@ -117,9 +122,11 @@ func recipeCatalogueFSIsLatest() (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
info, err := os.Stat(config.APPS_JSON) info, err := os.Stat(config.APPS_JSON)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
logrus.Debugf("no recipe catalogue found in file system cache")
return false, nil return false, nil
} }
return false, err return false, err
@ -129,9 +136,12 @@ func recipeCatalogueFSIsLatest() (bool, error) {
remoteModifiedTime := parsed.Unix() remoteModifiedTime := parsed.Unix()
if localModifiedTime < remoteModifiedTime { if localModifiedTime < remoteModifiedTime {
logrus.Debug("file system cached recipe catalogue is out-of-date")
return false, nil return false, nil
} }
logrus.Debug("file system cached recipe catalogue is up-to-date")
return true, nil return true, nil
} }
@ -145,12 +155,14 @@ func ReadRecipeCatalogue() (RecipeCatalogue, error) {
} }
if !recipeFSIsLatest { if !recipeFSIsLatest {
logrus.Debugf("reading recipe catalogue from web to get latest")
if err := readRecipeCatalogueWeb(&recipes); err != nil { if err := readRecipeCatalogueWeb(&recipes); err != nil {
return nil, err return nil, err
} }
return recipes, nil return recipes, nil
} }
logrus.Debugf("reading recipe catalogue from file system cache to get latest")
if err := readRecipeCatalogueFS(&recipes); err != nil { if err := readRecipeCatalogueFS(&recipes); err != nil {
return nil, err return nil, err
} }
@ -164,9 +176,13 @@ func readRecipeCatalogueFS(target interface{}) error {
if err != nil { if err != nil {
return err return err
} }
if err := json.Unmarshal(recipesJSONFS, &target); err != nil { if err := json.Unmarshal(recipesJSONFS, &target); err != nil {
return err return err
} }
logrus.Debugf("read recipe catalogue from file system cache in '%s'", config.APPS_JSON)
return nil return nil
} }
@ -185,6 +201,8 @@ func readRecipeCatalogueWeb(target interface{}) error {
return err return err
} }
logrus.Debugf("read recipe catalogue from web at '%s'", RecipeCatalogueURL)
return nil return nil
} }
@ -211,6 +229,8 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
} }
} }
logrus.Debugf("detected versions '%s' for '%s'", strings.Join(versions, ", "), recipe)
return versions, nil return versions, nil
} }
@ -226,9 +246,12 @@ func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
err := fmt.Errorf("recipe '%s' does not exist?", recipeName) err := fmt.Errorf("recipe '%s' does not exist?", recipeName)
return RecipeMeta{}, err return RecipeMeta{}, err
} }
if err := recipe.EnsureExists(recipeName); err != nil { if err := recipe.EnsureExists(recipeName); err != nil {
return RecipeMeta{}, err return RecipeMeta{}, err
} }
logrus.Debugf("recipe metadata retrieved for '%s'", recipeName)
return recipeMeta, nil return recipeMeta, nil
} }

View File

@ -48,5 +48,7 @@ func New(contextName string) (*client.Client, error) {
logrus.Fatalf("unable to create Docker client: %s", err) logrus.Fatalf("unable to create Docker client: %s", err)
} }
logrus.Debugf("created client for '%s'", contextName)
return cl, nil return cl, nil
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/docker"
contextStore "github.com/docker/cli/cli/context/store" contextStore "github.com/docker/cli/cli/context/store"
"github.com/moby/term" "github.com/moby/term"
"github.com/sirupsen/logrus"
) )
type Context = contextStore.Metadata type Context = contextStore.Metadata
@ -26,6 +27,7 @@ func CreateContext(contextName string, user string, port string) error {
if err := createContext(contextName, host); err != nil { if err := createContext(contextName, host); err != nil {
return err return err
} }
logrus.Debugf("created the '%s' context", contextName)
return nil return nil
} }
@ -36,13 +38,16 @@ func createContext(name string, host string) error {
Endpoints: make(map[string]interface{}), Endpoints: make(map[string]interface{}),
Name: name, Name: name,
} }
contextTLSData := contextStore.ContextTLSData{ contextTLSData := contextStore.ContextTLSData{
Endpoints: make(map[string]contextStore.EndpointTLSData), Endpoints: make(map[string]contextStore.EndpointTLSData),
} }
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(host) dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(host)
if err != nil { if err != nil {
return err return err
} }
contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP
if dockerTLS != nil { if dockerTLS != nil {
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS
@ -51,9 +56,11 @@ func createContext(name string, host string) error {
if err := s.CreateOrUpdate(contextMetadata); err != nil { if err := s.CreateOrUpdate(contextMetadata); err != nil {
return err return err
} }
if err := s.ResetTLSMaterial(name, &contextTLSData); err != nil { if err := s.ResetTLSMaterial(name, &contextTLSData); err != nil {
return err return err
} }
return nil return nil
} }
@ -61,6 +68,7 @@ func DeleteContext(name string) error {
if name == "default" { if name == "default" {
return errors.New("context 'default' cannot be removed") return errors.New("context 'default' cannot be removed")
} }
if _, err := GetContext(name); err != nil { if _, err := GetContext(name); err != nil {
return err return err
} }
@ -81,6 +89,7 @@ func GetContext(contextName string) (contextStore.Metadata, error) {
if err != nil { if err != nil {
return contextStore.Metadata{}, err return contextStore.Metadata{}, err
} }
return ctx, nil return ctx, nil
} }

View File

@ -20,6 +20,8 @@ func UpdateTag(pattern, image, tag string) error {
return err return err
} }
logrus.Debugf("considering '%s' config(s) for tag update", strings.Join(composeFiles, ", "))
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) emptyEnv := make(map[string]string)
@ -35,7 +37,7 @@ func UpdateTag(pattern, image, tag string) error {
img, _ := reference.ParseNormalizedNamed(service.Image) img, _ := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
logrus.Fatal(err) return err
} }
composeImage := reference.Path(img) composeImage := reference.Path(img)
@ -47,16 +49,20 @@ func UpdateTag(pattern, image, tag string) error {
} }
composeTag := img.(reference.NamedTagged).Tag() composeTag := img.(reference.NamedTagged).Tag()
logrus.Debugf("parsed '%s' from '%s'", composeTag, service.Image)
if image == composeImage { if image == composeImage {
bytes, err := ioutil.ReadFile(composeFile) bytes, err := ioutil.ReadFile(composeFile)
if err != nil { if err != nil {
logrus.Fatal(err) return err
} }
old := fmt.Sprintf("%s:%s", composeImage, composeTag) old := fmt.Sprintf("%s:%s", composeImage, composeTag)
new := fmt.Sprintf("%s:%s", composeImage, tag) new := fmt.Sprintf("%s:%s", composeImage, tag)
replacedBytes := strings.Replace(string(bytes), old, new, -1) replacedBytes := strings.Replace(string(bytes), old, new, -1)
logrus.Debugf("updating '%s' to '%s' in '%s'", old, new, compose.Filename)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err return err
} }
@ -74,6 +80,8 @@ func UpdateLabel(pattern, serviceName, label string) error {
return err return err
} }
logrus.Debugf("considering '%s' config(s) for label update", strings.Join(composeFiles, ", "))
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) emptyEnv := make(map[string]string)
@ -104,6 +112,9 @@ func UpdateLabel(pattern, serviceName, label string) error {
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, value) old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, 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)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err return err
} }

View File

@ -13,6 +13,7 @@ import (
loader "coopcloud.tech/abra/pkg/client/stack" loader "coopcloud.tech/abra/pkg/client/stack"
stack "coopcloud.tech/abra/pkg/client/stack" stack "coopcloud.tech/abra/pkg/client/stack"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"github.com/sirupsen/logrus"
) )
// Type aliases to make code hints easier to understand // Type aliases to make code hints easier to understand
@ -93,10 +94,14 @@ func readAppEnvFile(appFile AppFile, name AppName) (App, error) {
if err != nil { if err != nil {
return App{}, fmt.Errorf("env file for '%s' couldn't be read: %s", name, err.Error()) return App{}, fmt.Errorf("env file for '%s' couldn't be read: %s", name, err.Error())
} }
logrus.Debugf("read env '%s' from '%s'", env, appFile.Path)
app, err := newApp(env, name, appFile) app, err := newApp(env, name, appFile)
if err != nil { if err != nil {
return App{}, fmt.Errorf("env file for '%s' has issues: %s", name, err.Error()) return App{}, fmt.Errorf("env file for '%s' has issues: %s", name, err.Error())
} }
return app, nil return app, nil
} }
@ -108,6 +113,7 @@ func newApp(env AppEnv, name string, appFile AppFile) (App, error) {
if !ok { if !ok {
return App{}, errors.New("missing TYPE variable") return App{}, errors.New("missing TYPE variable")
} }
return App{ return App{
Name: name, Name: name,
Domain: domain, Domain: domain,
@ -131,6 +137,9 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
} }
} }
} }
logrus.Debugf("collecting metadata from '%v' servers: '%s'", len(servers), servers)
for _, server := range servers { for _, server := range servers {
serverDir := path.Join(ABRA_SERVER_FOLDER, server) serverDir := path.Join(ABRA_SERVER_FOLDER, server)
files, err := getAllFilesInDirectory(serverDir) files, err := getAllFilesInDirectory(serverDir)
@ -157,16 +166,19 @@ func GetApp(apps AppFiles, name AppName) (App, error) {
if !exists { if !exists {
return App{}, fmt.Errorf("cannot find app with name '%s'", name) return App{}, fmt.Errorf("cannot find app with name '%s'", name)
} }
app, err := readAppEnvFile(appFile, name) app, err := readAppEnvFile(appFile, name)
if err != nil { if err != nil {
return App{}, err return App{}, err
} }
return app, nil return app, nil
} }
// GetApps returns a slice of Apps with their env files read from a given slice of AppFiles // GetApps returns a slice of Apps with their env files read from a given slice of AppFiles
func GetApps(appFiles AppFiles) ([]App, error) { func GetApps(appFiles AppFiles) ([]App, error) {
var apps []App var apps []App
for name := range appFiles { for name := range appFiles {
app, err := GetApp(appFiles, name) app, err := GetApp(appFiles, name)
if err != nil { if err != nil {
@ -174,14 +186,17 @@ func GetApps(appFiles AppFiles) ([]App, error) {
} }
apps = append(apps, app) apps = append(apps, app)
} }
return apps, nil return apps, nil
} }
// GetAppNames retrieves a list of app names.
func GetAppNames() ([]string, error) { func GetAppNames() ([]string, error) {
appFiles, err := LoadAppFiles("") appFiles, err := LoadAppFiles("")
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
apps, err := GetApps(appFiles) apps, err := GetApps(appFiles)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
@ -213,6 +228,8 @@ func CopyAppEnvSample(appType, appName, server string) error {
return err return err
} }
logrus.Debugf("copied '%s' to '%s'", envSamplePath, appEnvPath)
return nil return nil
} }
@ -240,6 +257,8 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
} }
} }
logrus.Debugf("retrieved app statuses: '%s'", statuses)
return statuses, nil return statuses, nil
} }
@ -261,6 +280,9 @@ func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) {
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)
} }
logrus.Debugf("retrieved '%s' configs for '%s'", strings.Join(composeFiles, ", "), recipe)
return composeFiles, nil return composeFiles, nil
} }
@ -272,5 +294,8 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv AppEnv) (*comp
if err != nil { if err != nil {
return &composetypes.Config{}, err return &composetypes.Config{}, err
} }
logrus.Debugf("retrieved '%s' for '%s'", compose, recipe)
return compose, nil return compose, nil
} }

View File

@ -20,8 +20,10 @@ var APPS_JSON = path.Join(ABRA_DIR, "apps.json")
var APPS_DIR = path.Join(ABRA_DIR, "apps") var APPS_DIR = path.Join(ABRA_DIR, "apps")
var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud" var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
// GetServers retrieves all servers.
func (a AppFiles) GetServers() []string { func (a AppFiles) GetServers() []string {
var unique []string var unique []string
servers := make(map[string]struct{}) servers := make(map[string]struct{})
for _, appFile := range a { for _, appFile := range a {
if _, ok := servers[appFile.Server]; !ok { if _, ok := servers[appFile.Server]; !ok {
@ -29,33 +31,48 @@ func (a AppFiles) GetServers() []string {
unique = append(unique, appFile.Server) unique = append(unique, appFile.Server)
} }
} }
logrus.Debugf("retrieved servers: '%s'", unique)
return unique return unique
} }
// ReadEnv loads an app envivornment into a map.
func ReadEnv(filePath string) (AppEnv, error) { func ReadEnv(filePath string) (AppEnv, error) {
var envFile AppEnv var envFile AppEnv
envFile, err := godotenv.Read(filePath) envFile, err := godotenv.Read(filePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logrus.Debugf("read '%s' from '%s'", envFile, filePath)
return envFile, nil return envFile, nil
} }
// ReadServerNames retrieves all server names.
func ReadServerNames() ([]string, error) { func ReadServerNames() ([]string, error) {
serverNames, err := getAllFoldersInDirectory(ABRA_SERVER_FOLDER) serverNames, err := getAllFoldersInDirectory(ABRA_SERVER_FOLDER)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logrus.Debugf("read '%s' from '%s'", strings.Join(serverNames, ","), ABRA_SERVER_FOLDER)
return serverNames, nil return serverNames, nil
} }
// getAllFilesInDirectory returns filenames of all files in directory // getAllFilesInDirectory returns filenames of all files in directory
func getAllFilesInDirectory(directory string) ([]fs.FileInfo, error) { func getAllFilesInDirectory(directory string) ([]fs.FileInfo, error) {
var realFiles []fs.FileInfo var realFiles []fs.FileInfo
files, err := ioutil.ReadDir(directory) files, err := ioutil.ReadDir(directory)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, file := range files { for _, file := range files {
// Follow any symlinks // Follow any symlinks
filePath := path.Join(directory, file.Name()) filePath := path.Join(directory, file.Name())
@ -71,14 +88,15 @@ func getAllFilesInDirectory(directory string) ([]fs.FileInfo, error) {
realFiles = append(realFiles, file) realFiles = append(realFiles, file)
} }
} }
} }
return realFiles, nil return realFiles, nil
} }
// getAllFoldersInDirectory returns both folder and symlink paths // getAllFoldersInDirectory returns both folder and symlink paths
func getAllFoldersInDirectory(directory string) ([]string, error) { func getAllFoldersInDirectory(directory string) ([]string, error) {
var folders []string var folders []string
files, err := ioutil.ReadDir(directory) files, err := ioutil.ReadDir(directory)
if err != nil { if err != nil {
return nil, err return nil, err
@ -86,6 +104,7 @@ func getAllFoldersInDirectory(directory string) ([]string, error) {
if len(files) == 0 { if len(files) == 0 {
return nil, fmt.Errorf("directory is empty: '%s'", directory) return nil, fmt.Errorf("directory is empty: '%s'", directory)
} }
for _, file := range files { for _, file := range files {
// Check if file is directory or symlink // Check if file is directory or symlink
if file.IsDir() || file.Mode()&fs.ModeSymlink != 0 { if file.IsDir() || file.Mode()&fs.ModeSymlink != 0 {
@ -99,12 +118,14 @@ func getAllFoldersInDirectory(directory string) ([]string, error) {
} }
} }
} }
return folders, nil return folders, nil
} }
// EnsureAbraDirExists checks for the abra config folder and throws error if not // EnsureAbraDirExists checks for the abra config folder and throws error if not
func EnsureAbraDirExists() error { func EnsureAbraDirExists() error {
if _, err := os.Stat(ABRA_DIR); os.IsNotExist(err) { if _, err := os.Stat(ABRA_DIR); os.IsNotExist(err) {
logrus.Debugf("'%s' does not exist, creating it", ABRA_DIR)
if err := os.Mkdir(ABRA_DIR, 0777); err != nil { if err := os.Mkdir(ABRA_DIR, 0777); err != nil {
return err return err
} }
@ -112,6 +133,7 @@ func EnsureAbraDirExists() error {
return nil return nil
} }
// ReadAbraShEnvVars reads env vars from an abra.sh recipe file.
func ReadAbraShEnvVars(abraSh string) (map[string]string, error) { func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
envVars := make(map[string]string) envVars := make(map[string]string)
@ -137,5 +159,7 @@ func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
} }
} }
logrus.Debugf("read '%s' from '%s'", envVars, abraSh)
return envVars, nil return envVars, nil
} }

33
pkg/git/clone.go Normal file
View File

@ -0,0 +1,33 @@
package git
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/sirupsen/logrus"
)
// Clone runs a git clone which accounts for different default branches.
func Clone(dir, url string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
logrus.Debugf("'%s' does not exist, attempting to git clone from '%s'", dir, url)
_, err := git.PlainClone(dir, false, &git.CloneOptions{URL: url, Tags: git.AllTags})
if err != nil {
logrus.Debugf("cloning from default branch failed, attempting from main branch")
// try with main branch because Git is being a Git
_, err := git.PlainClone(dir, false, &git.CloneOptions{
URL: url,
Tags: git.AllTags,
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
})
if err != nil {
return err
}
}
}
logrus.Debugf("'%s' has been git cloned successfully", dir)
return nil
}

View File

@ -2,7 +2,6 @@ package recipe
import ( import (
"fmt" "fmt"
"os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -11,9 +10,11 @@ import (
loader "coopcloud.tech/abra/pkg/client/stack" loader "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/compose" "coopcloud.tech/abra/pkg/compose"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
gitPkg "coopcloud.tech/abra/pkg/git"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"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/sirupsen/logrus"
) )
// Recipe represents a recipe. // Recipe represents a recipe.
@ -65,23 +66,10 @@ func Get(recipeName string) (Recipe, error) {
// EnsureExists checks whether a recipe has been cloned locally or not. // EnsureExists checks whether a recipe has been cloned locally or not.
func EnsureExists(recipe string) error { func EnsureExists(recipe string) error {
recipeDir := path.Join(config.ABRA_DIR, "apps", strings.ToLower(recipe)) recipeDir := path.Join(config.ABRA_DIR, "apps", strings.ToLower(recipe))
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe)
if _, err := os.Stat(recipeDir); os.IsNotExist(err) { if err := gitPkg.Clone(recipeDir, url); err != nil {
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe) return err
_, err := git.PlainClone(recipeDir, false, &git.CloneOptions{URL: url, Tags: git.AllTags})
if err != nil {
// try with main branch because Git is being a Git
_, err := git.PlainClone(recipeDir, false, &git.CloneOptions{
URL: url,
Tags: git.AllTags,
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
})
if err != nil {
return err
}
}
} }
return nil return nil
} }
@ -99,6 +87,8 @@ func EnsureVersion(recipeName, version string) error {
return nil return nil
} }
logrus.Debugf("read '%s' as tags for recipe '%s'", tags, recipeName)
var tagRef plumbing.ReferenceName var tagRef plumbing.ReferenceName
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) { if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
if ref.Name().Short() == version { if ref.Name().Short() == version {
@ -123,5 +113,7 @@ func EnsureVersion(recipeName, version string) error {
return err return err
} }
logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef, recipeDir)
return nil return nil
} }

View File

@ -19,6 +19,8 @@ func PassInsertSecret(secretValue, secretName, appName, server string) error {
secretValue, server, appName, secretName, secretValue, server, appName, secretName,
) )
logrus.Debugf("attempting to run '%s'", cmd)
if err := exec.Command("bash", "-c", cmd).Run(); err != nil { if err := exec.Command("bash", "-c", cmd).Run(); err != nil {
return err return err
} }
@ -39,6 +41,8 @@ func PassRmSecret(secretName, appName, server string) error {
server, appName, secretName, server, appName, secretName,
) )
logrus.Debugf("attempting to run '%s'", cmd)
if err := exec.Command("bash", "-c", cmd).Run(); err != nil { if err := exec.Command("bash", "-c", cmd).Run(); err != nil {
return err return err
} }

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"github.com/schultz-is/passgen" "github.com/schultz-is/passgen"
"github.com/sirupsen/logrus"
) )
// secretValue represents a parsed `SECRET_FOO=v1 # length=bar` env var config // secretValue represents a parsed `SECRET_FOO=v1 # length=bar` env var config
@ -33,6 +34,8 @@ func GeneratePasswords(count, length uint) ([]string, error) {
return nil, err return nil, err
} }
logrus.Debugf("generated '%s'", strings.Join(passwords, ", "))
return passwords, nil return passwords, nil
} }
@ -50,17 +53,24 @@ func GeneratePassphrases(count uint) ([]string, error) {
return nil, err return nil, err
} }
logrus.Debugf("generated '%s'", strings.Join(passphrases, ", "))
return passphrases, nil return passphrases, nil
} }
// ReadSecretEnvVars reads secret env vars from an app env var config.
func ReadSecretEnvVars(appEnv config.AppEnv) map[string]string { func ReadSecretEnvVars(appEnv config.AppEnv) map[string]string {
secretEnvVars := make(map[string]string) secretEnvVars := make(map[string]string)
for envVar := range appEnv { for envVar := range appEnv {
regex := regexp.MustCompile(`^SECRET.*VERSION.*`) regex := regexp.MustCompile(`^SECRET.*VERSION.*`)
if string(regex.Find([]byte(envVar))) != "" { if string(regex.Find([]byte(envVar))) != "" {
secretEnvVars[envVar] = appEnv[envVar] secretEnvVars[envVar] = appEnv[envVar]
} }
} }
logrus.Debugf("read '%s' as secrets from '%s'", secretEnvVars, appEnv)
return secretEnvVars return secretEnvVars
} }
@ -68,7 +78,9 @@ func ReadSecretEnvVars(appEnv config.AppEnv) map[string]string {
func ParseSecretEnvVarName(secretEnvVar string) string { func ParseSecretEnvVarName(secretEnvVar string) string {
withoutPrefix := strings.TrimPrefix(secretEnvVar, "SECRET_") withoutPrefix := strings.TrimPrefix(secretEnvVar, "SECRET_")
withoutSuffix := strings.TrimSuffix(withoutPrefix, "_VERSION") withoutSuffix := strings.TrimSuffix(withoutPrefix, "_VERSION")
return strings.ToLower(withoutSuffix) name := strings.ToLower(withoutSuffix)
logrus.Debugf("parsed '%s' as name from '%s'", name, secretEnvVar)
return name
} }
// TODO: should probably go in the config/app package? // TODO: should probably go in the config/app package?
@ -76,7 +88,9 @@ func ParseGeneratedSecretName(secret string, appEnv config.App) string {
name := fmt.Sprintf("%s_", appEnv.StackName()) name := fmt.Sprintf("%s_", appEnv.StackName())
withoutAppName := strings.TrimPrefix(secret, name) withoutAppName := strings.TrimPrefix(secret, name)
idx := strings.LastIndex(withoutAppName, "_") idx := strings.LastIndex(withoutAppName, "_")
return withoutAppName[:idx] parsed := withoutAppName[:idx]
logrus.Debugf("parsed '%s' as name from '%s'", parsed, secret)
return parsed
} }
// TODO: should probably go in the config/app package? // TODO: should probably go in the config/app package?
@ -85,19 +99,23 @@ func ParseSecretEnvVarValue(secret string) (secretValue, error) {
if len(values) == 0 { if len(values) == 0 {
return secretValue{}, fmt.Errorf("unable to parse '%s'", secret) return secretValue{}, fmt.Errorf("unable to parse '%s'", secret)
} }
if len(values) == 1 { if len(values) == 1 {
return secretValue{Version: values[0], Length: 0}, nil return secretValue{Version: values[0], Length: 0}, nil
} else {
split := strings.Split(values[1], "=")
parsed := split[len(split)-1]
stripped := strings.ReplaceAll(parsed, " ", "")
length, err := strconv.Atoi(stripped)
if err != nil {
return secretValue{}, err
}
version := strings.ReplaceAll(values[0], " ", "")
return secretValue{Version: version, Length: length}, nil
} }
split := strings.Split(values[1], "=")
parsed := split[len(split)-1]
stripped := strings.ReplaceAll(parsed, " ", "")
length, err := strconv.Atoi(stripped)
if err != nil {
return secretValue{}, err
}
version := strings.ReplaceAll(values[0], " ", "")
logrus.Debugf("parsed version '%s' and length '%s' from '%s'", version, length, secret)
return secretValue{Version: version, Length: length}, nil
} }
// GenerateSecrets generates secrets locally and sends them to a remote server for storage. // GenerateSecrets generates secrets locally and sends them to a remote server for storage.
@ -114,6 +132,7 @@ func GenerateSecrets(secretEnvVars map[string]string, appName, server string) (m
return return
} }
secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, secretValue.Version) secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, secretValue.Version)
logrus.Debugf("attempting to generate and store '%s' on '%s'", secretRemoteName, server)
if secretValue.Length > 0 { if secretValue.Length > 0 {
passwords, err := GeneratePasswords(1, uint(secretValue.Length)) passwords, err := GeneratePasswords(1, uint(secretValue.Length))
if err != nil { if err != nil {
@ -147,5 +166,7 @@ func GenerateSecrets(secretEnvVars map[string]string, appName, server string) (m
} }
} }
logrus.Debugf("generated and stored '%s' on '%s'", secrets, server)
return secrets, nil return secrets, nil
} }