forked from toolshed/abra
		
	refactor: create compose package
This commit is contained in:
		| @ -19,9 +19,9 @@ var recipeCreateCommand = &cli.Command{ | ||||
| 	Aliases:   []string{"c"}, | ||||
| 	ArgsUsage: "<recipe>", | ||||
| 	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 | ||||
|  | ||||
| @ -23,9 +23,9 @@ var recipeLintCommand = &cli.Command{ | ||||
| 	Aliases:   []string{"l"}, | ||||
| 	ArgsUsage: "<recipe>", | ||||
| 	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 | ||||
| 		} | ||||
|  | ||||
| @ -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: "<recipe>", | ||||
| 	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) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -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: "<recipe>", | ||||
| 	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) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -14,7 +14,7 @@ var recipeVersionCommand = &cli.Command{ | ||||
| 	Aliases:   []string{"v"}, | ||||
| 	ArgsUsage: "<recipe>", | ||||
| 	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}) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										115
									
								
								pkg/compose/compose.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								pkg/compose/compose.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @ -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:<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 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user