Compare commits

...

10 Commits

20 changed files with 98 additions and 95 deletions

View File

@ -5,7 +5,6 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -38,7 +37,7 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if err := recipe.EnsureExists(app.Recipe); err != nil { if err := recipePkg.EnsureExists(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -68,9 +67,9 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
for _, envVar := range envVars { for _, envVar := range envVars {
if envVar.Present { if envVar.Present {
table.Append([]string{envVar.Name, "✅"}) table.Append([]string{envVar.Name, "✅ OK"})
} else { } else {
table.Append([]string{envVar.Name, "❌"}) table.Append([]string{envVar.Name, "❌ Missing"})
} }
} }

View File

@ -14,7 +14,6 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -60,7 +59,7 @@ Example:
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if err := recipe.EnsureExists(app.Recipe); err != nil { if err := recipePkg.EnsureExists(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -228,7 +227,7 @@ var appCmdListCommand = cli.Command{
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if err := recipe.EnsureExists(app.Recipe); err != nil { if err := recipePkg.EnsureExists(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -270,17 +270,17 @@ can take some time.
table.Render() table.Render()
if status { if status {
fmt.Println(fmt.Sprintf( fmt.Printf(
"server: %s | total apps: %v | versioned: %v | unversioned: %v | latest: %v | upgrade: %v", "server: %s | total apps: %v | versioned: %v | unversioned: %v | latest: %v | upgrade: %v\n",
app.Server, app.Server,
serverStat.AppCount, serverStat.AppCount,
serverStat.VersionCount, serverStat.VersionCount,
serverStat.UnversionedCount, serverStat.UnversionedCount,
serverStat.LatestCount, serverStat.LatestCount,
serverStat.UpgradeCount, serverStat.UpgradeCount,
)) )
} else { } else {
fmt.Println(fmt.Sprintf("server: %s | total apps: %v", app.Server, serverStat.AppCount)) fmt.Printf("server: %s | total apps: %v\n", app.Server, serverStat.AppCount)
} }
} }
@ -292,7 +292,7 @@ can take some time.
} }
if len(allStats) > 1 { if len(allStats) > 1 {
fmt.Println(fmt.Sprintf("total servers: %v | total apps: %v ", totalServersCount, totalAppsCount)) fmt.Printf("total servers: %v | total apps: %v\n", totalServersCount, totalAppsCount)
} }
return nil return nil

View File

@ -10,7 +10,6 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/jsontable" "coopcloud.tech/abra/pkg/jsontable"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -142,15 +141,15 @@ var appNewCommand = cli.Command{
table := formatter.CreateTable(tableCol) table := formatter.CreateTable(tableCol)
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain}) table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) fmt.Printf("A new %s app has been created! Here is an overview:\n", recipe.Name)
fmt.Println("") fmt.Println("")
table.Render() table.Render()
fmt.Println("") fmt.Println("")
fmt.Println("You can configure this app by running the following:") fmt.Println("You can configure this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain)) fmt.Printf("\n abra app config %s\n", internal.Domain)
fmt.Println("") fmt.Println("")
fmt.Println("You can deploy this app by running the following:") fmt.Println("You can deploy this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain)) fmt.Printf("\n abra app deploy %s\n", internal.Domain)
if len(secrets) > 0 { if len(secrets) > 0 {
fmt.Println("") fmt.Println("")
@ -192,7 +191,7 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
} }
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/ // ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
func ensureDomainFlag(recipe recipe.Recipe, server string) error { func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
if internal.Domain == "" && !internal.NoInput { if internal.Domain == "" && !internal.NoInput {
prompt := &survey.Input{ prompt := &survey.Input{
Message: "Specify app domain", Message: "Specify app domain",

View File

@ -110,7 +110,7 @@ flag.
logrus.Fatal(err) logrus.Fatal(err)
} }
volumeListOptions := volume.ListOptions{fs} volumeListOptions := volume.ListOptions{Filters: fs}
volumeListOKBody, err := cl.VolumeList(context.Background(), volumeListOptions) volumeListOKBody, err := cl.VolumeList(context.Background(), volumeListOptions)
volumeList := volumeListOKBody.Volumes volumeList := volumeListOKBody.Volumes
if err != nil { if err != nil {

View File

@ -11,7 +11,6 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
containerPkg "coopcloud.tech/abra/pkg/container" containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/container" "coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@ -60,7 +59,7 @@ Example:
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
recipe, err := recipe.Get(app.Recipe, internal.Offline) recipe, err := recipePkg.Get(app.Recipe, internal.Offline)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -9,7 +9,6 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
@ -62,17 +61,17 @@ recipes.
} }
if !internal.Chaos { if !internal.Chaos {
if err := recipe.EnsureIsClean(app.Recipe); err != nil { if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if !internal.Offline { if !internal.Offline {
if err := recipe.EnsureUpToDate(app.Recipe); err != nil { if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }
if err := recipe.EnsureLatest(app.Recipe); err != nil { if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }

View File

@ -80,29 +80,26 @@ Example:
switch shellType { switch shellType {
case "bash": case "bash":
fmt.Println(fmt.Sprintf(` fmt.Printf(`# Run the following commands to install auto-completion
# Run the following commands to install auto-completion
sudo mkdir /etc/bash_completion.d/ sudo mkdir /etc/bash_completion.d/
sudo cp %s /etc/bash_completion.d/abra sudo cp %s /etc/bash_completion.d/abra
echo "source /etc/bash_completion.d/abra" >> ~/.bashrc echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
# To test, run the following: "abra app <hit tab key>" - you should see command completion! # To test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile)) `, autocompletionFile)
case "zsh": case "zsh":
fmt.Println(fmt.Sprintf(` fmt.Printf(`# Run the following commands to install auto-completion
# Run the following commands to install auto-completion
sudo mkdir /etc/zsh/completion.d/ sudo mkdir /etc/zsh/completion.d/
sudo cp %s /etc/zsh/completion.d/abra sudo cp %s /etc/zsh/completion.d/abra
echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc
# To test, run the following: "abra app <hit tab key>" - you should see command completion! # To test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile)) `, autocompletionFile)
case "fish": case "fish":
fmt.Println(fmt.Sprintf(` fmt.Printf(`# Run the following commands to install auto-completion
# Run the following commands to install auto-completion
sudo mkdir -p /etc/fish/completions sudo mkdir -p /etc/fish/completions
sudo cp %s /etc/fish/completions/abra sudo cp %s /etc/fish/completions/abra
echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
# To test, run the following: "abra app <hit tab key>" - you should see command completion! # To test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile)) `, autocompletionFile)
} }
return nil return nil
@ -117,6 +114,10 @@ var UpgradeCommand = cli.Command{
Description: ` Description: `
Upgrade Abra in-place with the latest stable or release candidate. Upgrade Abra in-place with the latest stable or release candidate.
This command uses wget and bash. The install script is pulled from:
- default stable release: https://install.abra.coopcloud.tech
- release candidate: https://git.coopcloud.tech/coop-cloud/abra/
Pass "-r/--rc" to install the latest release candidate. Please bear in mind Pass "-r/--rc" to install the latest release candidate. Please bear in mind
that it may contain catastrophic bugs. Thank you very much for the testing that it may contain catastrophic bugs. Thank you very much for the testing
efforts! efforts!

View File

@ -162,14 +162,14 @@ var NewAppServer string
var NewAppServerFlag = &cli.StringFlag{ var NewAppServerFlag = &cli.StringFlag{
Name: "server, s", Name: "server, s",
Value: "", Value: "",
Usage: "Show apps of a specific server", Usage: "Use a specific server",
Destination: &NewAppServer, Destination: &NewAppServer,
} }
var NoDomainChecks bool var NoDomainChecks bool
var NoDomainChecksFlag = &cli.BoolFlag{ var NoDomainChecksFlag = &cli.BoolFlag{
Name: "no-domain-checks, D", Name: "no-domain-checks, D",
Usage: "Disable app domain sanity checks", Usage: "Disable domain sanity checks",
Destination: &NoDomainChecks, Destination: &NoDomainChecks,
} }

View File

@ -40,7 +40,7 @@ Here is a semver cheat sheet (more on https://semver.org):
var chosenBumpType string var chosenBumpType string
prompt := &survey.Select{ prompt := &survey.Select{
Message: fmt.Sprintf("select recipe version increment type"), Message: "select recipe version increment type",
Options: []string{"major", "minor", "patch"}, Options: []string{"major", "minor", "patch"},
} }

View File

@ -103,7 +103,7 @@ recipe and domain in the sample environment config).
logrus.Fatal(err) logrus.Fatal(err)
} }
fmt.Print(fmt.Sprintf(` fmt.Printf(`
Your new %s recipe has been created in %s. Your new %s recipe has been created in %s.
In order to share your recipe, you can upload it the git repository to: In order to share your recipe, you can upload it the git repository to:
@ -118,7 +118,7 @@ See "abra recipe -h" for additional recipe maintainer commands.
Happy Hacking! Happy Hacking!
`, recipeName, path.Join(config.RECIPES_DIR, recipeName), recipeName)) `, recipeName, path.Join(config.RECIPES_DIR, recipeName), recipeName)
return nil return nil
}, },

View File

@ -68,7 +68,7 @@ local file system.
if internal.NoInput { if internal.NoInput {
logrus.Fatalf("unable to continue, input required for initial version") logrus.Fatalf("unable to continue, input required for initial version")
} }
fmt.Println(fmt.Sprintf(` fmt.Printf(`
The following options are two types of initial semantic version that you can The following options are two types of initial semantic version that you can
pick for %s that will be published in the recipe catalogue. This follows the pick for %s that will be published in the recipe catalogue. This follows the
semver convention (more on https://semver.org), here is a short cheatsheet semver convention (more on https://semver.org), here is a short cheatsheet
@ -85,7 +85,7 @@ If you want people to be able alpha test your current config for %s but don't
think it is quite reliable, go with 0.1.0 and people will know that things are think it is quite reliable, go with 0.1.0 and people will know that things are
likely to change. likely to change.
`, recipe.Name, recipe.Name)) `, recipe.Name, recipe.Name)
var chosenVersion string var chosenVersion string
edPrompt := &survey.Select{ edPrompt := &survey.Select{
Message: "which version do you want to begin with?", Message: "which version do you want to begin with?",

View File

@ -53,7 +53,7 @@ func cleanUp(domainName string) {
// Docker manages SSH connection details. These are stored to disk in // Docker manages SSH connection details. These are stored to disk in
// ~/.docker. Abra can manage this completely for the user, so it's an // ~/.docker. Abra can manage this completely for the user, so it's an
// implementation detail. // implementation detail.
func newContext(c *cli.Context, domainName, username, port string) error { func newContext(c *cli.Context, name, host, username, port string) error {
store := contextPkg.NewDefaultDockerContextStore() store := contextPkg.NewDefaultDockerContextStore()
contexts, err := store.Store.List() contexts, err := store.Store.List()
if err != nil { if err != nil {
@ -61,15 +61,15 @@ func newContext(c *cli.Context, domainName, username, port string) error {
} }
for _, context := range contexts { for _, context := range contexts {
if context.Name == domainName { if context.Name == name {
logrus.Debugf("context for %s already exists", domainName) logrus.Debugf("context for %s already exists", name)
return nil return nil
} }
} }
logrus.Debugf("creating context with domain %s, username %s and port %s", domainName, username, port) logrus.Debugf("creating context with name %s, host %s, username %s and port %s", name, host, username, port)
if err := client.CreateContext(domainName, username, port); err != nil { if err := client.CreateContext(name, host, username, port); err != nil {
return err return err
} }
@ -112,11 +112,16 @@ Abra can then load SSH connection details from this configuratiion with:
If "--local" is passed, then Abra assumes that the current local server is If "--local" is passed, then Abra assumes that the current local server is
intended as the target server. This is useful when you want to have your entire intended as the target server. This is useful when you want to have your entire
Co-op Cloud config located on the server itself, and not on your local Co-op Cloud config located on the server itself, and not on your local
developer machine. developer machine. The domain is then set to "default".
You can also pass "--no-domain-checks" flag to use any arbitrary name instead
of a real domain. Host will be resolved with the "hostname" entry of your SSH
configuration. Checks for a valid online domain will be skipped.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.NoDomainChecksFlag,
localFlag, localFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
@ -150,23 +155,29 @@ developer machine.
return nil return nil
} }
if _, err := dns.EnsureIPv4(domainName); err != nil { if !internal.NoDomainChecks {
logrus.Fatal(err) if _, err := dns.EnsureIPv4(domainName); err != nil {
} logrus.Fatal(err)
}
if err := createServerDir(domainName); err != nil {
logrus.Fatal(err)
} }
hostConfig, err := sshPkg.GetHostConfig(domainName) hostConfig, err := sshPkg.GetHostConfig(domainName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if hostConfig.Host == "" {
hostConfig.Host = domainName
}
if err := newContext(c, domainName, hostConfig.User, hostConfig.Port); err != nil { if err := createServerDir(domainName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := newContext(c, domainName, hostConfig.Host, hostConfig.User, hostConfig.Port); err != nil {
logrus.Fatal(err)
cleanUp(domainName)
}
logrus.Infof("attempting to create client for %s", domainName) logrus.Infof("attempting to create client for %s", domainName)
if _, err := client.New(domainName); err != nil { if _, err := client.New(domainName); err != nil {
cleanUp(domainName) cleanUp(domainName)

View File

@ -14,8 +14,7 @@ import (
type Context = contextStore.Metadata type Context = contextStore.Metadata
func CreateContext(contextName string, user string, port string) error { func CreateContext(contextName string, host string, user string, port string) error {
host := contextName
if user != "" { if user != "" {
host = fmt.Sprintf("%s@%s", user, host) host = fmt.Sprintf("%s@%s", user, host)
} }

View File

@ -9,7 +9,7 @@ import (
) )
func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) { func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) {
volumeListOptions := volume.ListOptions{fs} volumeListOptions := volume.ListOptions{Filters: fs}
volumeListOKBody, err := cl.VolumeList(ctx, volumeListOptions) volumeListOKBody, err := cl.VolumeList(ctx, volumeListOptions)
volumeList := volumeListOKBody.Volumes volumeList := volumeListOKBody.Volumes
if err != nil { if err != nil {

View File

@ -16,25 +16,23 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// getBaseDir retrieves the Abra base directory. func getFromEnvOrDefault(envStr string, defaultStr string) string {
func getBaseDir() string { if envVar, exists := os.LookupEnv(envStr); exists && envVar != "" {
home := os.ExpandEnv("$HOME/.abra") return envVar
if customAbraDir, exists := os.LookupEnv("ABRA_DIR"); exists && customAbraDir != "" {
home = customAbraDir
} }
return home return defaultStr
} }
var ABRA_DIR = getBaseDir() var ABRA_DIR = getFromEnvOrDefault("ABRA_DIR", os.ExpandEnv("$HOME/.abra"))
var SERVERS_DIR = path.Join(ABRA_DIR, "servers") var SERVERS_DIR = path.Join(ABRA_DIR, "servers")
var RECIPES_DIR = path.Join(ABRA_DIR, "recipes") var RECIPES_DIR = path.Join(ABRA_DIR, "recipes")
var VENDOR_DIR = path.Join(ABRA_DIR, "vendor") var VENDOR_DIR = path.Join(ABRA_DIR, "vendor")
var BACKUP_DIR = path.Join(ABRA_DIR, "backups") var BACKUP_DIR = path.Join(ABRA_DIR, "backups")
var CATALOGUE_DIR = path.Join(ABRA_DIR, "catalogue") var CATALOGUE_DIR = path.Join(ABRA_DIR, "catalogue")
var RECIPES_JSON = path.Join(ABRA_DIR, "catalogue", "recipes.json") var RECIPES_JSON = path.Join(ABRA_DIR, "catalogue", "recipes.json")
var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud" var REPOS_BASE_URL = getFromEnvOrDefault("ABRA_REPOS_BASE_URL", "https://git.coopcloud.tech/coop-cloud")
var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json" var CATALOGUE_JSON_REPO_NAME = getFromEnvOrDefault("ABRA_CATALOGUE_JSON_REPO_NAME", "recipes-catalogue-json")
var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git" var SSH_URL_TEMPLATE = getFromEnvOrDefault("ABRA_SSH_URL_TEMPLATE", "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git")
// envVarModifiers is a list of env var modifier strings. These are added to // envVarModifiers is a list of env var modifier strings. These are added to
// env vars as comments and modify their processing by Abra, e.g. determining // env vars as comments and modify their processing by Abra, e.g. determining

View File

@ -9,7 +9,7 @@ import (
func EnsureIPv4(domainName string) (string, error) { func EnsureIPv4(domainName string) (string, error) {
ipv4, err := net.ResolveIPAddr("ip4", domainName) ipv4, err := net.ResolveIPAddr("ip4", domainName)
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("unable to resolve ipv4 address for %s, %s", domainName, err)
} }
// NOTE(d1): e.g. when there is only an ipv6 record available // NOTE(d1): e.g. when there is only an ipv6 record available

View File

@ -6,7 +6,7 @@ type Limiter struct{ sem chan struct{} }
// New returns a new Limiter. The limit param is the maximum number of // New returns a new Limiter. The limit param is the maximum number of
// concurrent operations. // concurrent operations.
func New(limit int) *Limiter { func New(limit int) *Limiter {
return &Limiter{make(chan struct{}, limit)} return &Limiter{sem: make(chan struct{}, limit)}
} }
// Begin an operation. // Begin an operation.

View File

@ -7,7 +7,6 @@ import (
"path" "path"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -19,13 +18,13 @@ import (
var Warn = "warn" var Warn = "warn"
var Critical = "critical" var Critical = "critical"
type LintFunction func(recipe.Recipe) (bool, error) type LintFunction func(recipePkg.Recipe) (bool, error)
// SkipFunction determines whether the LintFunction is run or not. It should // SkipFunction determines whether the LintFunction is run or not. It should
// not take the lint rule level into account because some rules are always an // not take the lint rule level into account because some rules are always an
// error but may depend on some additional context of the recipe configuration. // error but may depend on some additional context of the recipe configuration.
// This function aims to cover those additional cases. // This function aims to cover those additional cases.
type SkipFunction func(recipe.Recipe) (bool, error) type SkipFunction func(recipePkg.Recipe) (bool, error)
// LintRule is a linting rule which helps a recipe maintainer avoid common // LintRule is a linting rule which helps a recipe maintainer avoid common
// problems in their recipe configurations. We aim to highlight things that // problems in their recipe configurations. We aim to highlight things that
@ -42,7 +41,7 @@ type LintRule struct {
} }
// Skip implements the SkipFunction for the lint rule. // Skip implements the SkipFunction for the lint rule.
func (l LintRule) Skip(recipe recipe.Recipe) bool { func (l LintRule) Skip(recipe recipePkg.Recipe) bool {
if l.SkipCondition != nil { if l.SkipCondition != nil {
ok, err := l.SkipCondition(recipe) ok, err := l.SkipCondition(recipe)
if err != nil { if err != nil {
@ -166,7 +165,7 @@ var LintRules = map[string][]LintRule{
// LintForErrors lints specifically for errors and not other levels. This is // LintForErrors lints specifically for errors and not other levels. This is
// used in code paths such as "app deploy" to avoid nasty surprises but not for // used in code paths such as "app deploy" to avoid nasty surprises but not for
// the typical linting commands, which do handle other levels. // the typical linting commands, which do handle other levels.
func LintForErrors(recipe recipe.Recipe) error { func LintForErrors(recipe recipePkg.Recipe) error {
logrus.Debugf("linting for critical errors in %s configs", recipe.Name) logrus.Debugf("linting for critical errors in %s configs", recipe.Name)
for level := range LintRules { for level := range LintRules {
@ -194,7 +193,7 @@ func LintForErrors(recipe recipe.Recipe) error {
return nil return nil
} }
func LintComposeVersion(recipe recipe.Recipe) (bool, error) { func LintComposeVersion(recipe recipePkg.Recipe) (bool, error) {
if recipe.Config.Version == "3.8" { if recipe.Config.Version == "3.8" {
return true, nil return true, nil
} }
@ -202,7 +201,7 @@ func LintComposeVersion(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintEnvConfigPresent(recipe recipe.Recipe) (bool, error) { func LintEnvConfigPresent(recipe recipePkg.Recipe) (bool, error) {
envSample := fmt.Sprintf("%s/%s/.env.sample", config.RECIPES_DIR, recipe.Name) envSample := fmt.Sprintf("%s/%s/.env.sample", config.RECIPES_DIR, recipe.Name)
if _, err := os.Stat(envSample); !os.IsNotExist(err) { if _, err := os.Stat(envSample); !os.IsNotExist(err) {
return true, nil return true, nil
@ -211,7 +210,7 @@ func LintEnvConfigPresent(recipe recipe.Recipe) (bool, error) {
return false, nil return false, nil
} }
func LintAppService(recipe recipe.Recipe) (bool, error) { func LintAppService(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
if service.Name == "app" { if service.Name == "app" {
return true, nil return true, nil
@ -225,7 +224,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
// confirms that there is no "DOMAIN=..." in the .env.sample configuration of // confirms that there is no "DOMAIN=..." in the .env.sample configuration of
// the recipe. This typically means that no domain is required to deploy and // the recipe. This typically means that no domain is required to deploy and
// therefore no matching traefik deploy label will be present. // therefore no matching traefik deploy label will be present.
func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) { func LintTraefikEnabledSkipCondition(recipe recipePkg.Recipe) (bool, error) {
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample") envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
sampleEnv, err := config.ReadEnv(envSamplePath) sampleEnv, err := config.ReadEnv(envSamplePath)
if err != nil { if err != nil {
@ -239,7 +238,7 @@ func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
return false, nil return false, nil
} }
func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) { func LintTraefikEnabled(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
for label := range service.Deploy.Labels { for label := range service.Deploy.Labels {
if label == "traefik.enable" { if label == "traefik.enable" {
@ -253,7 +252,7 @@ func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) {
return false, nil return false, nil
} }
func LintHealthchecks(recipe recipe.Recipe) (bool, error) { func LintHealthchecks(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
if service.HealthCheck == nil { if service.HealthCheck == nil {
return false, nil return false, nil
@ -263,7 +262,7 @@ func LintHealthchecks(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintAllImagesTagged(recipe recipe.Recipe) (bool, error) { func LintAllImagesTagged(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
@ -277,7 +276,7 @@ func LintAllImagesTagged(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintNoUnstableTags(recipe recipe.Recipe) (bool, error) { func LintNoUnstableTags(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
@ -300,7 +299,7 @@ func LintNoUnstableTags(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintSemverLikeTags(recipe recipe.Recipe) (bool, error) { func LintSemverLikeTags(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
@ -323,7 +322,7 @@ func LintSemverLikeTags(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintImagePresent(recipe recipe.Recipe) (bool, error) { func LintImagePresent(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
if service.Image == "" { if service.Image == "" {
return false, nil return false, nil
@ -332,7 +331,7 @@ func LintImagePresent(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) { func LintHasPublishedVersion(recipe recipePkg.Recipe) (bool, error) {
catl, err := recipePkg.ReadRecipeCatalogue(false) catl, err := recipePkg.ReadRecipeCatalogue(false)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -350,8 +349,8 @@ func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) { func LintMetadataFilledIn(recipe recipePkg.Recipe) (bool, error) {
features, category, err := recipe.GetRecipeFeaturesAndCategory(r.Name) features, category, err := recipePkg.GetRecipeFeaturesAndCategory(recipe.Name)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -371,7 +370,7 @@ func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintAbraShVendors(recipe recipe.Recipe) (bool, error) { func LintAbraShVendors(recipe recipePkg.Recipe) (bool, error) {
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
if len(service.Configs) > 0 { if len(service.Configs) > 0 {
abraSh := path.Join(config.RECIPES_DIR, recipe.Name, "abra.sh") abraSh := path.Join(config.RECIPES_DIR, recipe.Name, "abra.sh")
@ -386,7 +385,7 @@ func LintAbraShVendors(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) { func LintHasRecipeRepo(recipe recipePkg.Recipe) (bool, error) {
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe.Name) url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe.Name)
res, err := http.Get(url) res, err := http.Get(url)
@ -401,7 +400,7 @@ func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) {
return true, nil return true, nil
} }
func LintValidTags(recipe recipe.Recipe) (bool, error) { func LintValidTags(recipe recipePkg.Recipe) (bool, error) {
recipeDir := path.Join(config.RECIPES_DIR, recipe.Name) recipeDir := path.Join(config.RECIPES_DIR, recipe.Name)
repo, err := git.PlainOpen(recipeDir) repo, err := git.PlainOpen(recipeDir)

View File

@ -66,19 +66,19 @@ func GetDeployedServicesByLabel(cl *dockerClient.Client, contextName string, lab
filters.Add("label", label) filters.Add("label", label)
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filters}) services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filters})
if err != nil { if err != nil {
return StackStatus{[]swarm.Service{}, err} return StackStatus{Services: []swarm.Service{}, Err: err}
} }
return StackStatus{services, nil} return StackStatus{Services: services, Err: nil}
} }
func GetAllDeployedServices(cl *dockerClient.Client, contextName string) StackStatus { func GetAllDeployedServices(cl *dockerClient.Client, contextName string) StackStatus {
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: getAllStacksFilter()}) services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil { if err != nil {
return StackStatus{[]swarm.Service{}, err} return StackStatus{Services: []swarm.Service{}, Err: err}
} }
return StackStatus{services, nil} return StackStatus{Services: services, Err: nil}
} }
// GetDeployedServicesByName filters services by name // GetDeployedServicesByName filters services by name
@ -88,10 +88,10 @@ func GetDeployedServicesByName(ctx context.Context, cl *dockerClient.Client, sta
services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filters}) services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filters})
if err != nil { if err != nil {
return StackStatus{[]swarm.Service{}, err} return StackStatus{Services: []swarm.Service{}, Err: err}
} }
return StackStatus{services, nil} return StackStatus{Services: services, Err: nil}
} }
// IsDeployed chekcks whether an appp is deployed or not. // IsDeployed chekcks whether an appp is deployed or not.