From b5d8fb1270edf10d55173966426d9a8648b93068 Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 6 Sep 2021 01:15:59 +0200 Subject: [PATCH] refactor: create compose package --- cli/recipe/create.go | 16 +++--- cli/recipe/lint.go | 6 +-- cli/recipe/sync.go | 28 ++++------ cli/recipe/upgrade.go | 24 +++------ cli/recipe/version.go | 12 ++--- pkg/compose/compose.go | 115 +++++++++++++++++++++++++++++++++++++++++ pkg/config/app.go | 101 ------------------------------------ pkg/recipe/recipe.go | 22 ++++++++ 8 files changed, 172 insertions(+), 152 deletions(-) create mode 100644 pkg/compose/compose.go diff --git a/cli/recipe/create.go b/cli/recipe/create.go index f68f23d2..0f07f6c5 100644 --- a/cli/recipe/create.go +++ b/cli/recipe/create.go @@ -19,9 +19,9 @@ var recipeCreateCommand = &cli.Command{ Aliases: []string{"c"}, ArgsUsage: "", 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) { logrus.Fatalf("'%s' recipe directory already exists?", directory) return nil @@ -34,16 +34,16 @@ var recipeCreateCommand = &cli.Command{ 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 { logrus.Fatal(err) return nil } toParse := []string{ - path.Join(config.APPS_DIR, recipe, "README.md"), - path.Join(config.APPS_DIR, recipe, ".env.sample"), - path.Join(config.APPS_DIR, recipe, ".drone.yml"), + path.Join(config.APPS_DIR, recipeName, "README.md"), + path.Join(config.APPS_DIR, recipeName, ".env.sample"), + path.Join(config.APPS_DIR, recipeName, ".drone.yml"), } for _, path := range toParse { file, err := os.OpenFile(path, os.O_RDWR, 0755) @@ -64,7 +64,7 @@ var recipeCreateCommand = &cli.Command{ if err := tpl.Execute(file, struct { Name string Description string - }{recipe, "TODO"}); err != nil { + }{recipeName, "TODO"}); err != nil { logrus.Fatal(err) return nil } @@ -72,7 +72,7 @@ var recipeCreateCommand = &cli.Command{ logrus.Infof( "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 diff --git a/cli/recipe/lint.go b/cli/recipe/lint.go index 248c322f..25425230 100644 --- a/cli/recipe/lint.go +++ b/cli/recipe/lint.go @@ -23,9 +23,9 @@ var recipeLintCommand = &cli.Command{ Aliases: []string{"l"}, ArgsUsage: "", 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) if err != nil { logrus.Fatal(err) @@ -42,7 +42,7 @@ var recipeLintCommand = &cli.Command{ } 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) { envSampleProvided = true } diff --git a/cli/recipe/sync.go b/cli/recipe/sync.go index c4eb4ff0..e52856d4 100644 --- a/cli/recipe/sync.go +++ b/cli/recipe/sync.go @@ -5,8 +5,9 @@ import ( "coopcloud.tech/abra/cli/internal" "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/recipe" "github.com/docker/distribution/reference" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -28,36 +29,26 @@ the versioning metadata of up-and-running containers are. `, ArgsUsage: "", Action: func(c *cli.Context) error { - recipe := internal.ValidateRecipe(c) + recipeName := internal.ValidateRecipe(c) - appFiles, err := config.LoadAppFiles("") - 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) + composeConfig, err := recipe.GetComposeConfig(recipeName) if err != nil { logrus.Fatal(err) } hasAppService := false - for _, service := range compose.Services { + for _, service := range composeConfig.Services { if service.Name == "app" { hasAppService = true } } 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 { - img, _ := reference.ParseNormalizedNamed(service.Image) + for _, service := range composeConfig.Services { + img, err := reference.ParseNormalizedNamed(service.Image) if err != nil { logrus.Fatal(err) } @@ -69,7 +60,8 @@ the versioning metadata of up-and-running containers are. tag := img.(reference.NamedTagged).Tag() 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) } } diff --git a/cli/recipe/upgrade.go b/cli/recipe/upgrade.go index 260600f7..ca2c4a1b 100644 --- a/cli/recipe/upgrade.go +++ b/cli/recipe/upgrade.go @@ -8,8 +8,9 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/catalogue" "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/recipe" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" "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: "", 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 { - logrus.Fatal(err) - } - - for _, service := range compose.Services { - catlVersions, err := catalogue.VersionsOfService(recipe, service.Name) + for _, service := range composeConfig.Services { + catlVersions, err := catalogue.VersionsOfService(recipeName, service.Name) if err != nil { 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) } - 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) } } diff --git a/cli/recipe/version.go b/cli/recipe/version.go index 9c0d6897..897e2940 100644 --- a/cli/recipe/version.go +++ b/cli/recipe/version.go @@ -14,7 +14,7 @@ var recipeVersionCommand = &cli.Command{ Aliases: []string{"v"}, ArgsUsage: "", Action: func(c *cli.Context) error { - recipe := internal.ValidateRecipe(c) + recipeName := internal.ValidateRecipe(c) catalogue, err := catalogue.ReadRecipeCatalogue() if err != nil { @@ -22,17 +22,17 @@ var recipeVersionCommand = &cli.Command{ return nil } - rec, ok := catalogue[recipe] + recipe, ok := catalogue[recipeName] if !ok { - logrus.Fatalf("'%s' recipe doesn't exist?", recipe) + logrus.Fatalf("'%s' recipe doesn't exist?", recipeName) } tableCol := []string{"Version", "Service", "Image", "Digest"} table := formatter.CreateTable(tableCol) - for version := range rec.Versions { - for service := range rec.Versions[version] { - meta := rec.Versions[version][service] + for version := range recipe.Versions { + for service := range recipe.Versions[version] { + meta := recipe.Versions[version][service] table.Append([]string{version, service, meta.Image, meta.Digest}) } } diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go new file mode 100644 index 00000000..9c71c6be --- /dev/null +++ b/pkg/compose/compose.go @@ -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:, + // postgres:, 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 +} diff --git a/pkg/config/app.go b/pkg/config/app.go index 4f0f4862..8049b845 100644 --- a/pkg/config/app.go +++ b/pkg/config/app.go @@ -13,8 +13,6 @@ import ( loader "coopcloud.tech/abra/pkg/client/stack" stack "coopcloud.tech/abra/pkg/client/stack" 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 @@ -258,102 +256,3 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv AppEnv) (*comp } 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:, - // postgres:, 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 -} diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index bf8c9977..9b098f98 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -4,9 +4,13 @@ import ( "fmt" "os" "path" + "path/filepath" "strings" + "coopcloud.tech/abra/pkg/client/stack" + loader "coopcloud.tech/abra/pkg/client/stack" "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/plumbing" ) @@ -66,3 +70,21 @@ func EnsureVersion(version string) error { 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 +}