Compare commits
37 Commits
0.3.1-alph
...
app-error
Author | SHA1 | Date | |
---|---|---|---|
ed859c0243
|
|||
236d0f5892
|
|||
6c87d501e6
|
|||
930c29f4a2 | |||
1d6c3e98e4 | |||
a90f3b7463 | |||
962f566228 | |||
9896c57399 | |||
748d607ddc | |||
3901258a96 | |||
4347083f98 | |||
4641a942d8 | |||
759a00eeb3 | |||
d1526fad21 | |||
6ef15e0a26
|
|||
dd0f328a65 | |||
aea5cc69c3 | |||
b02475eca5 | |||
d0a30f6b7b | |||
8635922b9f | |||
9d62fff074 | |||
711c4e5ee8 | |||
cb32e88cde | |||
a18729bf98 | |||
dbf84b7640 | |||
75db249053 | |||
fdf4fc6737 | |||
ef6a9abba9 | |||
ce57d5ed54 | |||
3b01b1bb2e | |||
fbdb792795 | |||
900f40f07a | |||
ecd2a63f0a | |||
304b70639f | |||
d821975aa2 | |||
1b836dbab6 | |||
fc51cf7775 |
@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
abraFormatter "coopcloud.tech/abra/cli/formatter"
|
abraFormatter "coopcloud.tech/abra/cli/formatter"
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/catalogue"
|
"coopcloud.tech/abra/pkg/catalogue"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/ssh"
|
"coopcloud.tech/abra/pkg/ssh"
|
||||||
@ -70,14 +71,18 @@ can take some time.
|
|||||||
}
|
}
|
||||||
sort.Sort(config.ByServerAndType(apps))
|
sort.Sort(config.ByServerAndType(apps))
|
||||||
|
|
||||||
|
alreadySeen := make(map[string]bool)
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
if err := ssh.EnsureHostKey(app.Server); err != nil {
|
if _, ok := alreadySeen[app.Server]; !ok {
|
||||||
logrus.Fatal(err)
|
if err := ssh.EnsureHostKey(app.Server); err != nil {
|
||||||
|
logrus.Fatal(fmt.Sprintf(internal.SSHFailMsg, app.Server))
|
||||||
|
}
|
||||||
|
alreadySeen[app.Server] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses := make(map[string]map[string]string)
|
statuses := make(map[string]map[string]string)
|
||||||
tableCol := []string{"Server", "Type", "Domain"}
|
tableCol := []string{"Server", "Type", "App Name", "Domain"}
|
||||||
if status {
|
if status {
|
||||||
tableCol = append(tableCol, "Status", "Version", "Updates")
|
tableCol = append(tableCol, "Status", "Version", "Updates")
|
||||||
statuses, err = config.GetAppStatuses(appFiles)
|
statuses, err = config.GetAppStatuses(appFiles)
|
||||||
@ -96,11 +101,19 @@ can take some time.
|
|||||||
canUpgradeCount int
|
canUpgradeCount int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
catl, err := catalogue.ReadRecipeCatalogue()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var appsCount int
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
var tableRow []string
|
var tableRow []string
|
||||||
if app.Type == appType || appType == "" {
|
if app.Type == appType || appType == "" {
|
||||||
|
appsCount++
|
||||||
|
|
||||||
// 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.StackName(), app.Domain}
|
||||||
if status {
|
if status {
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
status := "unknown"
|
status := "unknown"
|
||||||
@ -121,7 +134,8 @@ can take some time.
|
|||||||
|
|
||||||
var newUpdates []string
|
var newUpdates []string
|
||||||
if version != "unknown" {
|
if version != "unknown" {
|
||||||
updates, err := catalogue.GetRecipeCatalogueVersions(app.Type)
|
|
||||||
|
updates, err := catalogue.GetRecipeCatalogueVersions(app.Type, catl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -163,14 +177,19 @@ can take some time.
|
|||||||
table.Append(tableRow)
|
table.Append(tableRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := fmt.Sprintf(
|
var stats string
|
||||||
"Total apps: %v | Versioned: %v | Unversioned: %v | On latest: %v | Can upgrade: %v",
|
if status {
|
||||||
len(apps),
|
stats = fmt.Sprintf(
|
||||||
versionedAppsCount,
|
"Total apps: %v | Versioned: %v | Unversioned: %v | On latest: %v | Can upgrade: %v",
|
||||||
unversionedAppsCount,
|
appsCount,
|
||||||
onLatestCount,
|
versionedAppsCount,
|
||||||
canUpgradeCount,
|
unversionedAppsCount,
|
||||||
)
|
onLatestCount,
|
||||||
|
canUpgradeCount,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
stats = fmt.Sprintf("Total apps: %v", appsCount)
|
||||||
|
}
|
||||||
|
|
||||||
table.SetCaption(true, stats)
|
table.SetCaption(true, stats)
|
||||||
table.Render()
|
table.Render()
|
||||||
|
@ -4,74 +4,54 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stackLogs lists logs for all stack services
|
|
||||||
func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) {
|
|
||||||
filters := filters.NewArgs()
|
|
||||||
filters.Add("name", stackName)
|
|
||||||
serviceOpts := types.ServiceListOptions{Filters: filters}
|
|
||||||
services, err := client.ServiceList(c.Context, serviceOpts)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, service := range services {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(s string) {
|
|
||||||
logOpts := types.ContainerLogsOptions{
|
|
||||||
Details: true,
|
|
||||||
Follow: true,
|
|
||||||
ShowStderr: true,
|
|
||||||
ShowStdout: true,
|
|
||||||
Tail: "20",
|
|
||||||
Timestamps: true,
|
|
||||||
}
|
|
||||||
logs, err := client.ServiceLogs(c.Context, s, logOpts)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
// defer after err check as any err returns a nil io.ReadCloser
|
|
||||||
defer logs.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(os.Stdout, logs)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
}(service.ID)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var appLogsCommand = &cli.Command{
|
var appLogsCommand = &cli.Command{
|
||||||
Name: "logs",
|
Name: "logs",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
ArgsUsage: "[<service>]",
|
ArgsUsage: "[<service>]",
|
||||||
Usage: "Tail app logs",
|
Usage: "Tail app logs",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
internal.StderrFlag,
|
||||||
|
internal.StdoutFlag,
|
||||||
|
internal.HealthcheckFlag,
|
||||||
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
|
if !internal.Stderr && !internal.Stdout && !internal.Healthcheck {
|
||||||
|
internal.Stderr = true
|
||||||
|
internal.Stdout = true
|
||||||
|
internal.Healthcheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("flags parsed. --stderr: %t, --stdout: %t, --healthcheck: %t", internal.Stderr, internal.Stdout, internal.Healthcheck)
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOpts := types.ContainerLogsOptions{
|
||||||
|
Details: false,
|
||||||
|
Follow: true,
|
||||||
|
ShowStderr: internal.Stderr,
|
||||||
|
ShowStdout: internal.Stdout,
|
||||||
|
Tail: "20",
|
||||||
|
Timestamps: true,
|
||||||
|
}
|
||||||
|
|
||||||
serviceName := c.Args().Get(1)
|
serviceName := c.Args().Get(1)
|
||||||
if serviceName == "" {
|
if serviceName == "" {
|
||||||
logrus.Debug("tailing logs for all app services")
|
logrus.Debug("tailing logs for all app services")
|
||||||
stackLogs(c, app.StackName(), cl)
|
internal.StackLogs(c, app.StackName(), logOpts, cl)
|
||||||
}
|
}
|
||||||
logrus.Debugf("tailing logs for '%s'", serviceName)
|
logrus.Debugf("tailing logs for '%s'", serviceName)
|
||||||
|
|
||||||
@ -87,14 +67,6 @@ var appLogsCommand = &cli.Command{
|
|||||||
logrus.Fatalf("expected 1 service but got %v", len(services))
|
logrus.Fatalf("expected 1 service but got %v", len(services))
|
||||||
}
|
}
|
||||||
|
|
||||||
logOpts := types.ContainerLogsOptions{
|
|
||||||
Details: true,
|
|
||||||
Follow: true,
|
|
||||||
ShowStderr: true,
|
|
||||||
ShowStdout: true,
|
|
||||||
Tail: "20",
|
|
||||||
Timestamps: true,
|
|
||||||
}
|
|
||||||
logs, err := cl.ServiceLogs(c.Context, services[0].ID, logOpts)
|
logs, err := cl.ServiceLogs(c.Context, services[0].ID, logOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
|
@ -26,9 +26,10 @@ var watchFlag = &cli.BoolFlag{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appPsCommand = &cli.Command{
|
var appPsCommand = &cli.Command{
|
||||||
Name: "ps",
|
Name: "ps",
|
||||||
Usage: "Check app status",
|
Usage: "Check app status",
|
||||||
Aliases: []string{"p"},
|
Description: "This command shows a more detailed status output of a specific deployed app.",
|
||||||
|
Aliases: []string{"p"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
watchFlag,
|
watchFlag,
|
||||||
},
|
},
|
||||||
@ -75,7 +76,7 @@ func showPSOutput(c *cli.Context) {
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"image", "created", "status", "ports", "names"}
|
tableCol := []string{"image", "created", "status", "ports", "app name", "services"}
|
||||||
table := abraFormatter.CreateTable(tableCol)
|
table := abraFormatter.CreateTable(tableCol)
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
@ -90,6 +91,7 @@ func showPSOutput(c *cli.Context) {
|
|||||||
abraFormatter.HumanDuration(container.Created),
|
abraFormatter.HumanDuration(container.Created),
|
||||||
container.Status,
|
container.Status,
|
||||||
formatter.DisplayablePorts(container.Ports),
|
formatter.DisplayablePorts(container.Ports),
|
||||||
|
app.StackName(),
|
||||||
strings.Join(containerNames, "\n"),
|
strings.Join(containerNames, "\n"),
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
table.Append(tableRow)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"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"
|
||||||
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
@ -48,23 +49,18 @@ var appRemoveCommand = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appFiles, err := config.LoadAppFiles("")
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Force {
|
if !internal.Force {
|
||||||
// FIXME: only query for app we are interested in, not all of them!
|
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName())
|
||||||
statuses, err := config.GetAppStatuses(appFiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
if statuses[app.Name]["status"] == "deployed" {
|
if isDeployed {
|
||||||
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 undeploy %s \" or pass --force", app.Name, app.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,12 @@ recipes.
|
|||||||
logrus.Fatalf("'%s' is not deployed?", app.Name)
|
logrus.Fatalf("'%s' is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
|
catl, err := catalogue.ReadRecipeCatalogue()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type, catl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,12 @@ recipes.
|
|||||||
logrus.Fatalf("'%s' is not deployed?", app.Name)
|
logrus.Fatalf("'%s' is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
|
catl, err := catalogue.ReadRecipeCatalogue()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type, catl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ Example:
|
|||||||
|
|
||||||
Supported shells are as follows:
|
Supported shells are as follows:
|
||||||
|
|
||||||
fish
|
fizsh
|
||||||
zsh
|
zsh
|
||||||
bash
|
bash
|
||||||
`,
|
`,
|
||||||
@ -69,16 +69,16 @@ Supported shells are as follows:
|
|||||||
}
|
}
|
||||||
|
|
||||||
supportedShells := map[string]bool{
|
supportedShells := map[string]bool{
|
||||||
"bash": true,
|
"bash": true,
|
||||||
"zsh": true,
|
"zsh": true,
|
||||||
"fish": true,
|
"fizsh": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := supportedShells[shellType]; !ok {
|
if _, ok := supportedShells[shellType]; !ok {
|
||||||
logrus.Fatalf("%s is not a supported shell right now, sorry", shellType)
|
logrus.Fatalf("%s is not a supported shell right now, sorry", shellType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if shellType == "fish" {
|
if shellType == "fizsh" {
|
||||||
shellType = "zsh" // handled the same on the autocompletion side
|
shellType = "zsh" // handled the same on the autocompletion side
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,15 +40,18 @@ var CatalogueSkipList = map[string]bool{
|
|||||||
"docker-cp-deploy": true,
|
"docker-cp-deploy": true,
|
||||||
"docker-dind-bats-kcov": true,
|
"docker-dind-bats-kcov": true,
|
||||||
"docs.coopcloud.tech": true,
|
"docs.coopcloud.tech": true,
|
||||||
|
"drone-abra": true,
|
||||||
"example": true,
|
"example": true,
|
||||||
"gardening": true,
|
"gardening": true,
|
||||||
"go-abra": true,
|
"go-abra": true,
|
||||||
"organising": true,
|
"organising": true,
|
||||||
"pyabra": true,
|
"pyabra": true,
|
||||||
"radicle-seed-node": true,
|
"radicle-seed-node": true,
|
||||||
|
"recipes": true,
|
||||||
"stack-ssh-deploy": true,
|
"stack-ssh-deploy": true,
|
||||||
"swarm-cronjob": true,
|
"swarm-cronjob": true,
|
||||||
"tagcmp": true,
|
"tagcmp": true,
|
||||||
|
"traefik-cert-dumper": true,
|
||||||
"tyop": true,
|
"tyop": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +94,9 @@ A new catalogue copy can be published to the recipes repository by passing the
|
|||||||
ArgsUsage: "[<recipe>]",
|
ArgsUsage: "[<recipe>]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
recipeName := c.Args().First()
|
recipeName := c.Args().First()
|
||||||
|
if recipeName != "" {
|
||||||
|
internal.ValidateRecipe(c)
|
||||||
|
}
|
||||||
|
|
||||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
||||||
@ -130,6 +136,15 @@ A new catalogue copy can be published to the recipes repository by passing the
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isClean, err := gitPkg.IsClean(rm.Name)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isClean {
|
||||||
|
logrus.Fatalf("'%s' has locally unstaged changes", rm.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if err := gitPkg.EnsureUpToDate(recipeDir); err != nil {
|
if err := gitPkg.EnsureUpToDate(recipeDir); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -161,6 +176,11 @@ A new catalogue copy can be published to the recipes repository by passing the
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
features, category, err := catalogue.GetRecipeFeaturesAndCategory(recipeMeta.Name)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warn(err)
|
||||||
|
}
|
||||||
|
|
||||||
catl[recipeMeta.Name] = catalogue.RecipeMeta{
|
catl[recipeMeta.Name] = catalogue.RecipeMeta{
|
||||||
Name: recipeMeta.Name,
|
Name: recipeMeta.Name,
|
||||||
Repository: recipeMeta.CloneURL,
|
Repository: recipeMeta.CloneURL,
|
||||||
@ -169,8 +189,8 @@ A new catalogue copy can be published to the recipes repository by passing the
|
|||||||
Description: recipeMeta.Description,
|
Description: recipeMeta.Description,
|
||||||
Website: recipeMeta.Website,
|
Website: recipeMeta.Website,
|
||||||
Versions: versions,
|
Versions: versions,
|
||||||
// Category: ..., // FIXME: parse & load
|
Category: category,
|
||||||
// Features: ..., // FIXME: parse & load
|
Features: features,
|
||||||
}
|
}
|
||||||
catlBar.Add(1)
|
catlBar.Add(1)
|
||||||
}
|
}
|
||||||
|
@ -271,3 +271,48 @@ var DebugFlag = &cli.BoolFlag{
|
|||||||
Destination: &Debug,
|
Destination: &Debug,
|
||||||
Usage: "Show DEBUG messages",
|
Usage: "Show DEBUG messages",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHFailMsg is a hopefully helpful SSH failure message
|
||||||
|
var SSHFailMsg = `
|
||||||
|
Woops, Abra is unable to connect to connect to %s.
|
||||||
|
|
||||||
|
Here are a few tips for debugging your local SSH config. Abra uses plain 'ol
|
||||||
|
SSH to make connections to servers, so if your SSH config is working, Abra is
|
||||||
|
working.
|
||||||
|
|
||||||
|
In the first place, Abra will always try to read your Docker context connection
|
||||||
|
string for SSH connection details. You can view your server context configs
|
||||||
|
with the following command. Are they correct?
|
||||||
|
|
||||||
|
abra server ls
|
||||||
|
|
||||||
|
Is your ssh-agent running? You can start it by running the following command:
|
||||||
|
|
||||||
|
eval "$(ssh-agent)"
|
||||||
|
|
||||||
|
If your SSH private key loaded? You can check by running the following command:
|
||||||
|
|
||||||
|
ssh-add -L
|
||||||
|
|
||||||
|
If you are using a non-default public/private key, you can configure this in
|
||||||
|
your ~/.ssh/config file which Abra will read in order to figure out connection
|
||||||
|
details:
|
||||||
|
|
||||||
|
Host foo.coopcloud.tech
|
||||||
|
Hostname foo.coopcloud.tech
|
||||||
|
User bar
|
||||||
|
Port 12345
|
||||||
|
IdentityFile ~/.ssh/bar@foo.coopcloud.tech
|
||||||
|
|
||||||
|
If you're only using password authentication, you can use the following config:
|
||||||
|
|
||||||
|
Host foo.coopcloud.tech
|
||||||
|
Hostname foo.coopcloud.tech
|
||||||
|
User bar
|
||||||
|
Port 12345
|
||||||
|
PreferredAuthentications=password
|
||||||
|
PubkeyAuthentication=no
|
||||||
|
|
||||||
|
Good luck!
|
||||||
|
|
||||||
|
`
|
||||||
|
@ -26,7 +26,7 @@ func DeployAction(c *cli.Context) error {
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("checking whether '%s' is already deployed", stackName)
|
logrus.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
|
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -34,24 +34,26 @@ func DeployAction(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isDeployed {
|
if isDeployed {
|
||||||
if Force {
|
if Force || Chaos {
|
||||||
logrus.Warnf("'%s' already deployed but continuing (--force)", stackName)
|
logrus.Warnf("%s is already deployed but continuing (--force/--chaos)", stackName)
|
||||||
} else if Chaos {
|
|
||||||
logrus.Warnf("'%s' already deployed but continuing (--chaos)", stackName)
|
|
||||||
} else {
|
} else {
|
||||||
logrus.Fatalf("'%s' is already deployed", stackName)
|
logrus.Fatalf("%s is already deployed", stackName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
version := deployedVersion
|
version := deployedVersion
|
||||||
if version == "" && !Chaos {
|
if version == "" && !Chaos {
|
||||||
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
|
catl, err := catalogue.ReadRecipeCatalogue()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type, catl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(versions) > 0 {
|
if len(versions) > 0 {
|
||||||
version = versions[len(versions)-1]
|
version = versions[len(versions)-1]
|
||||||
logrus.Debugf("choosing '%s' as version to deploy", version)
|
logrus.Debugf("choosing %s as version to deploy", version)
|
||||||
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -65,7 +67,13 @@ func DeployAction(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if version == "" && !Chaos {
|
if version == "" && !Chaos {
|
||||||
logrus.Debugf("choosing '%s' as version to deploy", version)
|
logrus.Debugf("choosing %s as version to deploy", version)
|
||||||
|
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != "" && !Chaos {
|
||||||
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
68
cli/internal/logs.go
Normal file
68
cli/internal/logs.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
dockerClient "github.com/docker/docker/client"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Stderr bool
|
||||||
|
var StderrFlag = &cli.BoolFlag{
|
||||||
|
Name: "stderr",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Value: false,
|
||||||
|
Destination: &Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Stdout bool
|
||||||
|
var StdoutFlag = &cli.BoolFlag{
|
||||||
|
Name: "stdout",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Value: false,
|
||||||
|
Destination: &Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Healthcheck bool
|
||||||
|
var HealthcheckFlag = &cli.BoolFlag{
|
||||||
|
Name: "healthcheck",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Value: false,
|
||||||
|
Destination: &Healthcheck,
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackLogs lists logs for all stack services
|
||||||
|
func StackLogs(c *cli.Context, stackName string, logOpts types.ContainerLogsOptions, client *dockerClient.Client) {
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("name", stackName)
|
||||||
|
serviceOpts := types.ServiceListOptions{Filters: filters}
|
||||||
|
services, err := client.ServiceList(c.Context, serviceOpts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, service := range services {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(s string) {
|
||||||
|
logs, err := client.ServiceLogs(c.Context, s, logOpts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
// defer after err check as any err returns a nil io.ReadCloser
|
||||||
|
defer logs.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(os.Stdout, logs)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}(service.ID)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
@ -27,7 +27,11 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
|||||||
|
|
||||||
recipe, err := recipe.Get(recipeName)
|
recipe, err := recipe.Get(recipeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
if c.Command.Name == "generate" {
|
||||||
|
logrus.Warn(err)
|
||||||
|
} else {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("validated '%s' as recipe argument", recipeName)
|
logrus.Debugf("validated '%s' as recipe argument", recipeName)
|
||||||
@ -108,6 +112,35 @@ func ValidateApp(c *cli.Context) config.App {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAppByName ensures the app is valid and takes an app name as an argument, not context.
|
||||||
|
func ValidateAppByName(c *cli.Context, appName string) config.App {
|
||||||
|
if AppName != "" {
|
||||||
|
appName = AppName
|
||||||
|
logrus.Debugf("programmatically setting app name to %s", appName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if appName == "" {
|
||||||
|
ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := app.Get(appName)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := recipe.EnsureExists(app.Type); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ssh.EnsureHostKey(app.Server); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("validated '%s' as app argument", appName)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateDomain ensures the domain name arg is valid.
|
// ValidateDomain ensures the domain name arg is valid.
|
||||||
func ValidateDomain(c *cli.Context) (string, error) {
|
func ValidateDomain(c *cli.Context) (string, error) {
|
||||||
domainName := c.Args().First()
|
domainName := c.Args().First()
|
||||||
|
@ -236,7 +236,7 @@ func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *ssh.Client, d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("docker is installed on %s", domainName)
|
logrus.Infof("docker is already installed on %s", domainName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -276,7 +276,8 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error
|
|||||||
AdvertiseAddr: ipv4,
|
AdvertiseAddr: ipv4,
|
||||||
}
|
}
|
||||||
if _, err := cl.SwarmInit(c.Context, initReq); err != nil {
|
if _, err := cl.SwarmInit(c.Context, initReq); err != nil {
|
||||||
if !strings.Contains(err.Error(), "is already part of a swarm") {
|
if !strings.Contains(err.Error(), "is already part of a swarm") ||
|
||||||
|
!strings.Contains(err.Error(), "must specify a listening address") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logrus.Infof("swarm mode already initialised on %s", domainName)
|
logrus.Infof("swarm mode already initialised on %s", domainName)
|
||||||
|
101
cli/server/logs.go
Normal file
101
cli/server/logs.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Taillen string
|
||||||
|
var TaillenFlag = &cli.StringFlag{
|
||||||
|
Name: "tail",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: "5",
|
||||||
|
Destination: &Taillen,
|
||||||
|
Usage: "change how many lines are shown",
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverLogsCommand = &cli.Command{
|
||||||
|
Name: "logs",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
ArgsUsage: "<server>",
|
||||||
|
Usage: "show logs from all apps from server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
TaillenFlag,
|
||||||
|
internal.StderrFlag,
|
||||||
|
internal.StdoutFlag,
|
||||||
|
internal.HealthcheckFlag,
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
serverName, err := internal.ValidateServer(c)
|
||||||
|
serviceName := ""
|
||||||
|
if !internal.Stderr && !internal.Stdout && !internal.Healthcheck {
|
||||||
|
internal.Stderr = true
|
||||||
|
internal.Stdout = true
|
||||||
|
internal.Healthcheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("flags parsed. --stderr: %t, --stdout: %t, --healthcheck: %t", internal.Stderr, internal.Stdout, internal.Healthcheck)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
appMap, err := config.LoadAppFiles(serverName)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logOpts := types.ContainerLogsOptions{
|
||||||
|
Details: false,
|
||||||
|
Follow: false,
|
||||||
|
ShowStderr: internal.Stderr,
|
||||||
|
ShowStdout: internal.Stdout,
|
||||||
|
Tail: Taillen,
|
||||||
|
Timestamps: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var appFiles []config.App
|
||||||
|
for appname, _ := range appMap {
|
||||||
|
app := internal.ValidateAppByName(c, appname)
|
||||||
|
appFiles = append(appFiles, app)
|
||||||
|
}
|
||||||
|
for _, app := range appFiles {
|
||||||
|
fmt.Println(app)
|
||||||
|
logrus.Debugf("checking logs for: %s", app.Name)
|
||||||
|
cl, err := client.New(app.Server)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
logrus.Debugf("tailing logs for all services")
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("name", service)
|
||||||
|
serviceOpts := types.ServiceListOptions{Filters: filters}
|
||||||
|
services, err := cl.ServiceList(c.Context, serviceOpts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
logs, err := cl.ServiceLogs(c.Context, services[0].ID, logOpts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
logrus.Info(app.StackName())
|
||||||
|
for {
|
||||||
|
_, err = io.Copy(os.Stdout, logs)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
@ -23,5 +23,6 @@ apps, see available flags on "server add" for more.
|
|||||||
serverAddCommand,
|
serverAddCommand,
|
||||||
serverListCommand,
|
serverListCommand,
|
||||||
serverRemoveCommand,
|
serverRemoveCommand,
|
||||||
|
serverLogsCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -34,6 +34,7 @@ require (
|
|||||||
github.com/gliderlabs/ssh v0.3.3
|
github.com/gliderlabs/ssh v0.3.3
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.0
|
||||||
github.com/kevinburke/ssh_config v1.1.0
|
github.com/kevinburke/ssh_config v1.1.0
|
||||||
github.com/libdns/gandi v1.0.2
|
github.com/libdns/gandi v1.0.2
|
||||||
github.com/libdns/libdns v0.2.1
|
github.com/libdns/libdns v0.2.1
|
||||||
|
6
go.sum
6
go.sum
@ -444,8 +444,14 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc
|
|||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||||
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@ -46,6 +45,7 @@ type features struct {
|
|||||||
Image image `json:"image"`
|
Image image `json:"image"`
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Tests string `json:"tests"`
|
Tests string `json:"tests"`
|
||||||
|
SSO string `json:"sso"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// tag represents a git tag.
|
// tag represents a git tag.
|
||||||
@ -122,7 +122,7 @@ func (r ByRecipeName) Less(i, j int) bool {
|
|||||||
// recipeCatalogueFSIsLatest checks whether the recipe catalogue stored locally
|
// recipeCatalogueFSIsLatest checks whether the recipe catalogue stored locally
|
||||||
// is up to date.
|
// is up to date.
|
||||||
func recipeCatalogueFSIsLatest() (bool, error) {
|
func recipeCatalogueFSIsLatest() (bool, error) {
|
||||||
httpClient := &http.Client{Timeout: web.Timeout}
|
httpClient := web.NewHTTPRetryClient()
|
||||||
res, err := httpClient.Head(RecipeCatalogueURL)
|
res, err := httpClient.Head(RecipeCatalogueURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -374,6 +374,127 @@ func ReadReposMetadata() (RepoCatalogue, error) {
|
|||||||
return reposMeta, nil
|
return reposMeta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStringInBetween(str, start, end string) (result string, err error) {
|
||||||
|
// GetStringInBetween returns empty string if no start or end string found
|
||||||
|
s := strings.Index(str, start)
|
||||||
|
if s == -1 {
|
||||||
|
return "", fmt.Errorf("marker string '%s' not found", start)
|
||||||
|
}
|
||||||
|
s += len(start)
|
||||||
|
e := strings.Index(str[s:], end)
|
||||||
|
if e == -1 {
|
||||||
|
return "", fmt.Errorf("end marker '%s' not found", end)
|
||||||
|
}
|
||||||
|
return str[s : s+e], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetImageMetadata(imageRowString string) (image, error) {
|
||||||
|
img := image{}
|
||||||
|
|
||||||
|
imgFields := strings.Split(imageRowString, ",")
|
||||||
|
|
||||||
|
for i, elem := range imgFields {
|
||||||
|
imgFields[i] = strings.TrimSpace(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imgFields) < 3 {
|
||||||
|
logrus.Warnf("image string has incorrect format: %s", imageRowString)
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Rating = imgFields[1]
|
||||||
|
img.Source = imgFields[2]
|
||||||
|
|
||||||
|
imgString := imgFields[0]
|
||||||
|
|
||||||
|
imageName, err := GetStringInBetween(imgString, "[", "]")
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
img.Image = strings.ReplaceAll(imageName, "`", "")
|
||||||
|
|
||||||
|
imageURL, err := GetStringInBetween(imgString, "(", ")")
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
img.URL = imageURL
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRecipeFeaturesAndCategory(recipeName string) (features, string, error) {
|
||||||
|
feat := features{}
|
||||||
|
|
||||||
|
var category string
|
||||||
|
|
||||||
|
readmePath := path.Join(config.ABRA_DIR, "apps", recipeName, "README.md")
|
||||||
|
|
||||||
|
logrus.Debugf("attempting to open '%s'", readmePath)
|
||||||
|
|
||||||
|
readmeFS, err := ioutil.ReadFile(readmePath)
|
||||||
|
if err != nil {
|
||||||
|
return feat, category, err
|
||||||
|
}
|
||||||
|
|
||||||
|
readmeMetadata, err := GetStringInBetween( // Find text between delimiters
|
||||||
|
string(readmeFS),
|
||||||
|
"<!-- metadata -->", "<!-- endmetadata -->",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return feat, category, err
|
||||||
|
}
|
||||||
|
|
||||||
|
readmeLines := strings.Split( // Array item from lines
|
||||||
|
strings.ReplaceAll( // Remove \t tabs
|
||||||
|
readmeMetadata, "\t", "",
|
||||||
|
),
|
||||||
|
"\n")
|
||||||
|
|
||||||
|
for _, val := range readmeLines {
|
||||||
|
if strings.Contains(val, "**Category**") {
|
||||||
|
category = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Category**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**Backups**") {
|
||||||
|
feat.Backups = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Backups**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**Email**") {
|
||||||
|
feat.Email = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Email**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**SSO**") {
|
||||||
|
feat.SSO = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **SSO**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**Healthcheck**") {
|
||||||
|
feat.Healthcheck = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Healthcheck**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**Tests**") {
|
||||||
|
feat.Tests = strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Tests**:"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "**Image**") {
|
||||||
|
imageMetadata, err := GetImageMetadata(strings.TrimSpace(
|
||||||
|
strings.TrimPrefix(val, "* **Image**:"),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
feat.Image = imageMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return feat, category, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRecipeVersions retrieves all recipe versions.
|
// GetRecipeVersions retrieves all recipe versions.
|
||||||
func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
||||||
versions := RecipeVersions{}
|
versions := RecipeVersions{}
|
||||||
@ -404,7 +525,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
|||||||
|
|
||||||
checkOutOpts := &git.CheckoutOptions{
|
checkOutOpts := &git.CheckoutOptions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Force: true,
|
Keep: true,
|
||||||
Branch: plumbing.ReferenceName(ref.Name()),
|
Branch: plumbing.ReferenceName(ref.Name()),
|
||||||
}
|
}
|
||||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||||
@ -432,9 +553,21 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
|||||||
path = strings.Split(path, "/")[1]
|
path = strings.Split(path, "/")[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tag string
|
||||||
|
switch img.(type) {
|
||||||
|
case reference.NamedTagged:
|
||||||
|
tag = img.(reference.NamedTagged).Tag()
|
||||||
|
case reference.Named:
|
||||||
|
logrus.Warnf("%s service is missing image tag?", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("looking up image: '%s' from '%s'", img, path)
|
||||||
|
|
||||||
digest, err := client.GetTagDigest(img)
|
digest, err := client.GetTagDigest(img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
logrus.Warn(err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
versionMeta[service.Name] = ServiceMeta{
|
versionMeta[service.Name] = ServiceMeta{
|
||||||
@ -465,7 +598,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
|||||||
refName := fmt.Sprintf("refs/heads/%s", branch)
|
refName := fmt.Sprintf("refs/heads/%s", branch)
|
||||||
checkOutOpts := &git.CheckoutOptions{
|
checkOutOpts := &git.CheckoutOptions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Force: true,
|
Keep: true,
|
||||||
Branch: plumbing.ReferenceName(refName),
|
Branch: plumbing.ReferenceName(refName),
|
||||||
}
|
}
|
||||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||||
@ -480,14 +613,9 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRecipeCatalogueVersions list the recipe versions listed in the recipe catalogue.
|
// GetRecipeCatalogueVersions list the recipe versions listed in the recipe catalogue.
|
||||||
func GetRecipeCatalogueVersions(recipeName string) ([]string, error) {
|
func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]string, error) {
|
||||||
var versions []string
|
var versions []string
|
||||||
|
|
||||||
catl, err := ReadRecipeCatalogue()
|
|
||||||
if err != nil {
|
|
||||||
return versions, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if recipeMeta, exists := catl[recipeName]; exists {
|
if recipeMeta, exists := catl[recipeName]; exists {
|
||||||
for _, versionMeta := range recipeMeta.Versions {
|
for _, versionMeta := range recipeMeta.Versions {
|
||||||
for tag := range versionMeta {
|
for tag := range versionMeta {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/pkg/web"
|
"coopcloud.tech/abra/pkg/web"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RawTag struct {
|
type RawTag struct {
|
||||||
@ -35,12 +36,12 @@ func GetRegistryTags(image string) (RawTags, error) {
|
|||||||
func getRegv2Token(image reference.Named) (string, error) {
|
func getRegv2Token(image reference.Named) (string, error) {
|
||||||
img := reference.Path(image)
|
img := reference.Path(image)
|
||||||
authTokenURL := fmt.Sprintf("https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull", img)
|
authTokenURL := fmt.Sprintf("https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull", img)
|
||||||
req, err := http.NewRequest("GET", authTokenURL, nil)
|
req, err := retryablehttp.NewRequest("GET", authTokenURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: web.Timeout}
|
client := web.NewHTTPRetryClient()
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -78,7 +79,7 @@ func GetTagDigest(image reference.Named) (string, error) {
|
|||||||
tag := image.(reference.NamedTagged).Tag()
|
tag := image.(reference.NamedTagged).Tag()
|
||||||
manifestURL := fmt.Sprintf("https://index.docker.io/v2/%s/manifests/%s", img, tag)
|
manifestURL := fmt.Sprintf("https://index.docker.io/v2/%s/manifests/%s", img, tag)
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", manifestURL, nil)
|
req, err := retryablehttp.NewRequest("GET", manifestURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -96,7 +97,7 @@ func GetTagDigest(image reference.Named) (string, error) {
|
|||||||
"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
|
"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: web.Timeout}
|
client := web.NewHTTPRetryClient()
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -47,6 +47,16 @@ func EnsureUpToDate(dir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recipeName := filepath.Base(dir)
|
||||||
|
isClean, err := IsClean(recipeName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isClean {
|
||||||
|
return fmt.Errorf("'%s' has locally unstaged changes", recipeName)
|
||||||
|
}
|
||||||
|
|
||||||
branch := "master"
|
branch := "master"
|
||||||
if _, err := repo.Branch("master"); err != nil {
|
if _, err := repo.Branch("master"); err != nil {
|
||||||
if _, err := repo.Branch("main"); err != nil {
|
if _, err := repo.Branch("main"); err != nil {
|
||||||
@ -66,7 +76,7 @@ func EnsureUpToDate(dir string) error {
|
|||||||
refName := fmt.Sprintf("refs/heads/%s", branch)
|
refName := fmt.Sprintf("refs/heads/%s", branch)
|
||||||
checkOutOpts := &git.CheckoutOptions{
|
checkOutOpts := &git.CheckoutOptions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Force: true,
|
Keep: true,
|
||||||
Branch: plumbing.ReferenceName(refName),
|
Branch: plumbing.ReferenceName(refName),
|
||||||
}
|
}
|
||||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||||
|
104
pkg/git/read.go
104
pkg/git/read.go
@ -1,11 +1,17 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
|
gitConfigPkg "github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,6 +46,12 @@ func IsClean(recipeName string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patterns, err := GetExcludesFiles()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
worktree.Excludes = append(patterns, worktree.Excludes...)
|
||||||
|
|
||||||
status, err := worktree.Status()
|
status, err := worktree.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -53,3 +65,95 @@ func IsClean(recipeName string) (bool, error) {
|
|||||||
|
|
||||||
return status.IsClean(), nil
|
return status.IsClean(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExcludesFiles reads the exlude files from a global git ignore
|
||||||
|
func GetExcludesFiles() ([]gitignore.Pattern, error) {
|
||||||
|
var err error
|
||||||
|
var patterns []gitignore.Pattern
|
||||||
|
|
||||||
|
cfg, err := parseGitConfig()
|
||||||
|
if err != nil {
|
||||||
|
return patterns, err
|
||||||
|
}
|
||||||
|
|
||||||
|
excludesfile := getExcludesFile(cfg)
|
||||||
|
patterns, err = parseExcludesFile(excludesfile)
|
||||||
|
if err != nil {
|
||||||
|
return patterns, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return patterns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitConfig() (*gitConfigPkg.Config, error) {
|
||||||
|
cfg := gitConfigPkg.NewConfig()
|
||||||
|
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(usr.HomeDir + "/.gitconfig")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Unmarshal(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExcludesFile(cfg *gitConfigPkg.Config) string {
|
||||||
|
for _, sec := range cfg.Raw.Sections {
|
||||||
|
if sec.Name == "core" {
|
||||||
|
for _, opt := range sec.Options {
|
||||||
|
if opt.Key == "excludesfile" {
|
||||||
|
return opt.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
|
||||||
|
excludesfile, err := expandTilde(excludesfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(excludesfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ps []gitignore.Pattern
|
||||||
|
for _, s := range strings.Split(string(data), "\n") {
|
||||||
|
if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 {
|
||||||
|
ps = append(ps, gitignore.ParsePattern(s, nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandTilde(path string) (string, error) {
|
||||||
|
if !strings.HasPrefix(path, "~") {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
var paths []string
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, p := range strings.Split(path, string(filepath.Separator)) {
|
||||||
|
if p == "~" {
|
||||||
|
paths = append(paths, u.HomeDir)
|
||||||
|
} else {
|
||||||
|
paths = append(paths, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "/" + filepath.Join(paths...), nil
|
||||||
|
}
|
||||||
|
@ -84,7 +84,7 @@ func Get(recipeName string) (Recipe, error) {
|
|||||||
envSamplePath := path.Join(config.ABRA_DIR, "apps", recipeName, ".env.sample")
|
envSamplePath := path.Join(config.ABRA_DIR, "apps", recipeName, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return Recipe{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := stack.Deploy{Composefiles: composeFiles}
|
opts := stack.Deploy{Composefiles: composeFiles}
|
||||||
@ -157,7 +157,8 @@ func EnsureVersion(recipeName, version string) error {
|
|||||||
logrus.Debugf("read '%s' as tags for recipe '%s'", strings.Join(parsedTags, ", "), recipeName)
|
logrus.Debugf("read '%s' as tags for recipe '%s'", strings.Join(parsedTags, ", "), recipeName)
|
||||||
|
|
||||||
if tagRef.String() == "" {
|
if tagRef.String() == "" {
|
||||||
return fmt.Errorf("%s is not available?", version)
|
logrus.Warnf("%s recipe has no local tag: %s? this recipe version is not released?", recipeName, version)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
worktree, err := repo.Worktree()
|
worktree, err := repo.Worktree()
|
||||||
@ -168,7 +169,7 @@ func EnsureVersion(recipeName, version string) error {
|
|||||||
opts := &git.CheckoutOptions{
|
opts := &git.CheckoutOptions{
|
||||||
Branch: tagRef,
|
Branch: tagRef,
|
||||||
Create: false,
|
Create: false,
|
||||||
Force: true,
|
Keep: true,
|
||||||
}
|
}
|
||||||
if err := worktree.Checkout(opts); err != nil {
|
if err := worktree.Checkout(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -220,7 +221,7 @@ func EnsureLatest(recipeName string) error {
|
|||||||
refName := fmt.Sprintf("refs/heads/%s", branch)
|
refName := fmt.Sprintf("refs/heads/%s", branch)
|
||||||
checkOutOpts := &git.CheckoutOptions{
|
checkOutOpts := &git.CheckoutOptions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Force: true,
|
Keep: true,
|
||||||
Branch: plumbing.ReferenceName(refName),
|
Branch: plumbing.ReferenceName(refName),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,9 +330,9 @@ func HostKeyAddCallback(hostnameAndPort string, remote net.Addr, pubKey ssh.Publ
|
|||||||
|
|
||||||
fmt.Printf(fmt.Sprintf(`
|
fmt.Printf(fmt.Sprintf(`
|
||||||
You are attempting to make an SSH connection to a server but there is no entry
|
You are attempting to make an SSH connection to a server but there is no entry
|
||||||
in your ~/.ssh/known_hosts file which confirms that this is indeed the server
|
in your ~/.ssh/known_hosts file which confirms that you have already validated
|
||||||
you want to connect to. Please take a moment to validate the following SSH host
|
that this is indeed the server you want to connect to. Please take a moment to
|
||||||
key, it is important.
|
validate the following SSH host key, it is important.
|
||||||
|
|
||||||
Host: %s
|
Host: %s
|
||||||
Fingerprint: %s
|
Fingerprint: %s
|
||||||
|
@ -13,6 +13,11 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DontSkipValidation ensures validation is done for compose file loading
|
||||||
|
func DontSkipValidation(opts *loader.Options) {
|
||||||
|
opts.SkipValidation = false
|
||||||
|
}
|
||||||
|
|
||||||
// LoadComposefile parse the composefile specified in the cli and returns its Config and version.
|
// LoadComposefile parse the composefile specified in the cli and returns its Config and version.
|
||||||
func LoadComposefile(opts Deploy, appEnv map[string]string) (*composetypes.Config, error) {
|
func LoadComposefile(opts Deploy, appEnv map[string]string) (*composetypes.Config, error) {
|
||||||
configDetails, err := getConfigDetails(opts.Composefiles, appEnv)
|
configDetails, err := getConfigDetails(opts.Composefiles, appEnv)
|
||||||
@ -21,13 +26,12 @@ func LoadComposefile(opts Deploy, appEnv map[string]string) (*composetypes.Confi
|
|||||||
}
|
}
|
||||||
|
|
||||||
dicts := getDictsFrom(configDetails.ConfigFiles)
|
dicts := getDictsFrom(configDetails.ConfigFiles)
|
||||||
config, err := loader.Load(configDetails)
|
config, err := loader.Load(configDetails, DontSkipValidation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
|
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
|
||||||
return nil, fmt.Errorf("compose file contains unsupported options:\n\n%s",
|
return nil, fmt.Errorf("compose file contains unsupported options:\n\n%s",
|
||||||
propertyWarnings(fpe.Properties))
|
propertyWarnings(fpe.Properties))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ func deployServices(
|
|||||||
existingServiceMap[service.Spec.Name] = service
|
existingServiceMap[service.Spec.Name] = service
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceIDs []string
|
serviceIDs := make(map[string]string)
|
||||||
for internalName, serviceSpec := range services {
|
for internalName, serviceSpec := range services {
|
||||||
var (
|
var (
|
||||||
name = namespace.Scope(internalName)
|
name = namespace.Scope(internalName)
|
||||||
@ -410,7 +410,7 @@ func deployServices(
|
|||||||
return errors.Wrapf(err, "failed to update service %s", name)
|
return errors.Wrapf(err, "failed to update service %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceIDs = append(serviceIDs, service.ID)
|
serviceIDs[service.ID] = name
|
||||||
|
|
||||||
for _, warning := range response.Warnings {
|
for _, warning := range response.Warnings {
|
||||||
logrus.Warn(warning)
|
logrus.Warn(warning)
|
||||||
@ -430,15 +430,19 @@ func deployServices(
|
|||||||
return errors.Wrapf(err, "failed to create service %s", name)
|
return errors.Wrapf(err, "failed to create service %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceIDs = append(serviceIDs, serviceCreateResponse.ID)
|
serviceIDs[serviceCreateResponse.ID] = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("waiting for services to converge: %s", strings.Join(serviceIDs, ", "))
|
var serviceNames []string
|
||||||
|
for _, serviceName := range serviceIDs {
|
||||||
|
serviceNames = append(serviceNames, serviceName)
|
||||||
|
}
|
||||||
|
logrus.Infof("waiting for services to converge: %s", strings.Join(serviceNames, ", "))
|
||||||
|
|
||||||
ch := make(chan error, len(serviceIDs))
|
ch := make(chan error, len(serviceIDs))
|
||||||
for _, serviceID := range serviceIDs {
|
for serviceID, serviceName := range serviceIDs {
|
||||||
logrus.Debugf("waiting on %s to converge", serviceID)
|
logrus.Debugf("waiting on %s to converge", serviceName)
|
||||||
go func(s string) {
|
go func(s string) {
|
||||||
ch <- waitOnService(ctx, cl, s)
|
ch <- waitOnService(ctx, cl, s)
|
||||||
}(serviceID)
|
}(serviceID)
|
||||||
|
23
pkg/web/client.go
Normal file
23
pkg/web/client.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type customLeveledLogger struct {
|
||||||
|
retryablehttp.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l customLeveledLogger) Printf(msg string, args ...interface{}) {
|
||||||
|
logrus.Debugf(fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPRetryClient instantiates a new http client with retries baked in
|
||||||
|
func NewHTTPRetryClient() *retryablehttp.Client {
|
||||||
|
retryClient := retryablehttp.NewClient()
|
||||||
|
retryClient.Logger = customLeveledLogger{}
|
||||||
|
return retryClient
|
||||||
|
}
|
@ -3,7 +3,6 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ const Timeout = 10 * time.Second
|
|||||||
|
|
||||||
// ReadJSON reads JSON and parses it into your chosen interface pointer
|
// ReadJSON reads JSON and parses it into your chosen interface pointer
|
||||||
func ReadJSON(url string, target interface{}) error {
|
func ReadJSON(url string, target interface{}) error {
|
||||||
httpClient := &http.Client{Timeout: Timeout}
|
httpClient := NewHTTPRetryClient()
|
||||||
res, err := httpClient.Get(url)
|
res, err := httpClient.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -76,13 +76,13 @@ function install_abra_release {
|
|||||||
p=$HOME/.local/bin
|
p=$HOME/.local/bin
|
||||||
com="echo PATH=\$PATH:$p"
|
com="echo PATH=\$PATH:$p"
|
||||||
if [[ $SHELL =~ "bash" ]]; then
|
if [[ $SHELL =~ "bash" ]]; then
|
||||||
echo "echo $com >> $HOME/.bashrc"
|
echo "$com >> $HOME/.bashrc"
|
||||||
elif [[ $SHELL =~ "fizsh" ]]; then
|
elif [[ $SHELL =~ "fizsh" ]]; then
|
||||||
echo "echo $com >> $HOME/.fizsh/.fizshrc"
|
echo "$com >> $HOME/.fizsh/.fizshrc"
|
||||||
elif [[ $SHELL =~ "zsh" ]]; then
|
elif [[ $SHELL =~ "zsh" ]]; then
|
||||||
echo "echo $com >> $HOME/.zshrc"
|
echo "$com >> $HOME/.zshrc"
|
||||||
else
|
else
|
||||||
echo "echo $com >> $HOME/.profile"
|
echo "$com >> $HOME/.profile"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user