refactor: create compose package
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
decentral1se 2021-09-06 01:15:59 +02:00
parent e1a10723ce
commit b5d8fb1270
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
8 changed files with 172 additions and 152 deletions

View File

@ -19,9 +19,9 @@ var recipeCreateCommand = &cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipeName := internal.ValidateRecipe(c)
directory := path.Join(config.APPS_DIR, recipe) directory := path.Join(config.APPS_DIR, recipeName)
if _, err := os.Stat(directory); !os.IsNotExist(err) { if _, err := os.Stat(directory); !os.IsNotExist(err) {
logrus.Fatalf("'%s' recipe directory already exists?", directory) logrus.Fatalf("'%s' recipe directory already exists?", directory)
return nil return nil
@ -34,16 +34,16 @@ var recipeCreateCommand = &cli.Command{
return nil return nil
} }
gitRepo := path.Join(config.APPS_DIR, recipe, ".git") gitRepo := path.Join(config.APPS_DIR, recipeName, ".git")
if err := os.RemoveAll(gitRepo); err != nil { if err := os.RemoveAll(gitRepo); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil return nil
} }
toParse := []string{ toParse := []string{
path.Join(config.APPS_DIR, recipe, "README.md"), path.Join(config.APPS_DIR, recipeName, "README.md"),
path.Join(config.APPS_DIR, recipe, ".env.sample"), path.Join(config.APPS_DIR, recipeName, ".env.sample"),
path.Join(config.APPS_DIR, recipe, ".drone.yml"), path.Join(config.APPS_DIR, recipeName, ".drone.yml"),
} }
for _, path := range toParse { for _, path := range toParse {
file, err := os.OpenFile(path, os.O_RDWR, 0755) file, err := os.OpenFile(path, os.O_RDWR, 0755)
@ -64,7 +64,7 @@ var recipeCreateCommand = &cli.Command{
if err := tpl.Execute(file, struct { if err := tpl.Execute(file, struct {
Name string Name string
Description string Description string
}{recipe, "TODO"}); err != nil { }{recipeName, "TODO"}); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil return nil
} }
@ -72,7 +72,7 @@ var recipeCreateCommand = &cli.Command{
logrus.Infof( logrus.Infof(
"New recipe '%s' created in %s, happy hacking!\n", "New recipe '%s' created in %s, happy hacking!\n",
recipe, path.Join(config.APPS_DIR, recipe), recipeName, path.Join(config.APPS_DIR, recipeName),
) )
return nil return nil

View File

@ -23,9 +23,9 @@ var recipeLintCommand = &cli.Command{
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipeName := internal.ValidateRecipe(c)
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipe) pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipeName)
composeFiles, err := filepath.Glob(pattern) composeFiles, err := filepath.Glob(pattern)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -42,7 +42,7 @@ var recipeLintCommand = &cli.Command{
} }
envSampleProvided := false envSampleProvided := false
envSample := fmt.Sprintf("%s/%s/.env.sample", config.APPS_DIR, recipe) envSample := fmt.Sprintf("%s/%s/.env.sample", config.APPS_DIR, recipeName)
if _, err := os.Stat(envSample); !os.IsNotExist(err) { if _, err := os.Stat(envSample); !os.IsNotExist(err) {
envSampleProvided = true envSampleProvided = true
} }

View File

@ -5,8 +5,9 @@ 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/client/stack" "coopcloud.tech/abra/pkg/compose"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -28,36 +29,26 @@ the versioning metadata of up-and-running containers are.
`, `,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipeName := internal.ValidateRecipe(c)
appFiles, err := config.LoadAppFiles("") composeConfig, err := recipe.GetComposeConfig(recipeName)
if err != nil {
logrus.Fatal(err)
}
appEnv, err := config.GetApp(appFiles, recipe)
if err != nil {
logrus.Fatal(err)
}
compose, err := config.GetAppComposeConfig(recipe, stack.Deploy{}, appEnv.Env)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
hasAppService := false hasAppService := false
for _, service := range compose.Services { for _, service := range composeConfig.Services {
if service.Name == "app" { if service.Name == "app" {
hasAppService = true hasAppService = true
} }
} }
if !hasAppService { if !hasAppService {
logrus.Fatal(fmt.Sprintf("No 'app' service defined in '%s', cannot proceed", recipe)) logrus.Fatal(fmt.Sprintf("No 'app' service defined in '%s', cannot proceed", recipeName))
} }
for _, service := range compose.Services { for _, service := range composeConfig.Services {
img, _ := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -69,7 +60,8 @@ the versioning metadata of up-and-running containers are.
tag := img.(reference.NamedTagged).Tag() tag := img.(reference.NamedTagged).Tag()
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest) label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest)
if err := config.UpdateAppComposeLabel(recipe, service.Name, label, appEnv.Env); err != nil { pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipeName)
if err := compose.UpdateLabel(pattern, service.Name, label); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }

View File

@ -8,8 +8,9 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/catalogue" "coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/client/stack" "coopcloud.tech/abra/pkg/compose"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -35,25 +36,15 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
`, `,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipeName := internal.ValidateRecipe(c)
appFiles, err := config.LoadAppFiles("") composeConfig, err := recipe.GetComposeConfig(recipeName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
appEnv, err := config.GetApp(appFiles, recipe) for _, service := range composeConfig.Services {
if err != nil { catlVersions, err := catalogue.VersionsOfService(recipeName, service.Name)
logrus.Fatal(err)
}
compose, err := config.GetAppComposeConfig(recipe, stack.Deploy{}, appEnv.Env)
if err != nil {
logrus.Fatal(err)
}
for _, service := range compose.Services {
catlVersions, err := catalogue.VersionsOfService(recipe, service.Name)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -138,7 +129,8 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := config.UpdateAppComposeTag(recipe, image, upgradeTag, appEnv.Env); err != nil { pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipeName)
if err := compose.UpdateTag(pattern, image, upgradeTag); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }

View File

@ -14,7 +14,7 @@ var recipeVersionCommand = &cli.Command{
Aliases: []string{"v"}, Aliases: []string{"v"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipeName := internal.ValidateRecipe(c)
catalogue, err := catalogue.ReadRecipeCatalogue() catalogue, err := catalogue.ReadRecipeCatalogue()
if err != nil { if err != nil {
@ -22,17 +22,17 @@ var recipeVersionCommand = &cli.Command{
return nil return nil
} }
rec, ok := catalogue[recipe] recipe, ok := catalogue[recipeName]
if !ok { if !ok {
logrus.Fatalf("'%s' recipe doesn't exist?", recipe) logrus.Fatalf("'%s' recipe doesn't exist?", recipeName)
} }
tableCol := []string{"Version", "Service", "Image", "Digest"} tableCol := []string{"Version", "Service", "Image", "Digest"}
table := formatter.CreateTable(tableCol) table := formatter.CreateTable(tableCol)
for version := range rec.Versions { for version := range recipe.Versions {
for service := range rec.Versions[version] { for service := range recipe.Versions[version] {
meta := rec.Versions[version][service] meta := recipe.Versions[version][service]
table.Append([]string{version, service, meta.Image, meta.Digest}) table.Append([]string{version, service, meta.Image, meta.Digest})
} }
} }

115
pkg/compose/compose.go Normal file
View File

@ -0,0 +1,115 @@
package compose
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"coopcloud.tech/abra/pkg/client/stack"
loader "coopcloud.tech/abra/pkg/client/stack"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/distribution/reference"
"github.com/sirupsen/logrus"
)
// UpdateTag updates an image tag in-place on file system local compose files.
func UpdateTag(pattern, image, tag string) error {
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
emptyEnv := make(map[string]string)
compose, err := loader.LoadComposefile(opts, emptyEnv)
if err != nil {
return err
}
for _, service := range compose.Services {
if service.Image == "" {
continue // may be a compose.$optional.yml file
}
img, _ := reference.ParseNormalizedNamed(service.Image)
if err != nil {
logrus.Fatal(err)
}
composeImage := reference.Path(img)
if strings.Contains(composeImage, "library") {
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
// postgres:<tag>, i.e. images which do not have a username in the
// first position of the string
composeImage = strings.Split(composeImage, "/")[1]
}
composeTag := img.(reference.NamedTagged).Tag()
if image == composeImage {
bytes, err := ioutil.ReadFile(composeFile)
if err != nil {
logrus.Fatal(err)
}
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
new := fmt.Sprintf("%s:%s", composeImage, tag)
replacedBytes := strings.Replace(string(bytes), old, new, -1)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err
}
}
}
}
return nil
}
// UpdateLabel updates a label in-place on file system local compose files.
func UpdateLabel(pattern, serviceName, label string) error {
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
emptyEnv := make(map[string]string)
compose, err := loader.LoadComposefile(opts, emptyEnv)
if err != nil {
return err
}
serviceExists := false
var service composetypes.ServiceConfig
for _, s := range compose.Services {
if s.Name == serviceName {
service = s
serviceExists = true
}
}
if !serviceExists {
continue
}
for oldLabel, value := range service.Deploy.Labels {
if strings.HasPrefix(oldLabel, "coop-cloud") {
bytes, err := ioutil.ReadFile(composeFile)
if err != nil {
return err
}
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, value)
replacedBytes := strings.Replace(string(bytes), old, label, -1)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err
}
}
}
}
return nil
}

View File

@ -13,8 +13,6 @@ import (
loader "coopcloud.tech/abra/pkg/client/stack" loader "coopcloud.tech/abra/pkg/client/stack"
stack "coopcloud.tech/abra/pkg/client/stack" stack "coopcloud.tech/abra/pkg/client/stack"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/distribution/reference"
"github.com/sirupsen/logrus"
) )
// Type aliases to make code hints easier to understand // Type aliases to make code hints easier to understand
@ -258,102 +256,3 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv AppEnv) (*comp
} }
return compose, nil return compose, nil
} }
func UpdateAppComposeTag(recipe, image, tag string, appEnv AppEnv) error {
pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe)
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
compose, err := loader.LoadComposefile(opts, appEnv)
if err != nil {
return err
}
for _, service := range compose.Services {
if service.Image == "" {
continue // may be a compose.$optional.yml file
}
img, _ := reference.ParseNormalizedNamed(service.Image)
if err != nil {
logrus.Fatal(err)
}
composeImage := reference.Path(img)
if strings.Contains(composeImage, "library") {
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
// postgres:<tag>, i.e. images which do not have a username in the
// first position of the string
composeImage = strings.Split(composeImage, "/")[1]
}
composeTag := img.(reference.NamedTagged).Tag()
if image == composeImage {
bytes, err := ioutil.ReadFile(composeFile)
if err != nil {
logrus.Fatal(err)
}
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
new := fmt.Sprintf("%s:%s", composeImage, tag)
replacedBytes := strings.Replace(string(bytes), old, new, -1)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err
}
}
}
}
return nil
}
func UpdateAppComposeLabel(recipe, serviceName, newLabel string, appEnv AppEnv) error {
pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe)
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
compose, err := loader.LoadComposefile(opts, appEnv)
if err != nil {
return err
}
serviceExists := false
var service composetypes.ServiceConfig
for _, s := range compose.Services {
if s.Name == serviceName {
service = s
serviceExists = true
}
}
if !serviceExists {
continue
}
for oldLabel, value := range service.Deploy.Labels {
if strings.HasPrefix(oldLabel, "coop-cloud") {
bytes, err := ioutil.ReadFile(composeFile)
if err != nil {
return err
}
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, value)
replacedBytes := strings.Replace(string(bytes), old, newLabel, -1)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil {
return err
}
}
}
}
return nil
}

View File

@ -4,9 +4,13 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"coopcloud.tech/abra/pkg/client/stack"
loader "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
) )
@ -66,3 +70,21 @@ func EnsureVersion(version string) error {
return nil return nil
} }
// GetComposeConfig merges and loads a recipe compose configuration.
func GetComposeConfig(recipeName string) (*composetypes.Config, error) {
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipeName)
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return &composetypes.Config{}, err
}
opts := stack.Deploy{Composefiles: composeFiles}
emptyEnv := make(map[string]string)
compose, err := loader.LoadComposefile(opts, emptyEnv)
if err != nil {
return &composetypes.Config{}, err
}
return compose, nil
}