204 lines
5.4 KiB
Go
204 lines
5.4 KiB
Go
package recipe
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/pkg/formatter"
|
|
"coopcloud.tech/abra/pkg/log"
|
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
|
loader "coopcloud.tech/abra/pkg/upstream/stack"
|
|
"github.com/distribution/reference"
|
|
composetypes "github.com/docker/cli/cli/compose/types"
|
|
)
|
|
|
|
// GetComposeFiles gets the list of compose files for an app (or recipe if you
|
|
// don't already have an app) which should be merged into a composetypes.Config
|
|
// while respecting the COMPOSE_FILE env var.
|
|
func (r Recipe2) GetComposeFiles(appEnv map[string]string) ([]string, error) {
|
|
composeFileEnvVar, ok := appEnv["COMPOSE_FILE"]
|
|
if !ok {
|
|
if err := ensurePathExists(r.ComposePath); err != nil {
|
|
return []string{}, err
|
|
}
|
|
log.Debugf("no COMPOSE_FILE detected, loading default: %s", r.ComposePath)
|
|
return []string{r.ComposePath}, nil
|
|
}
|
|
|
|
if !strings.Contains(composeFileEnvVar, ":") {
|
|
path := fmt.Sprintf("%s/%s", r.Dir, composeFileEnvVar)
|
|
if err := ensurePathExists(path); err != nil {
|
|
return []string{}, err
|
|
}
|
|
log.Debugf("COMPOSE_FILE detected, loading %s", path)
|
|
return []string{path}, nil
|
|
}
|
|
|
|
var composeFiles []string
|
|
|
|
numComposeFiles := strings.Count(composeFileEnvVar, ":") + 1
|
|
envVars := strings.SplitN(composeFileEnvVar, ":", numComposeFiles)
|
|
if len(envVars) != numComposeFiles {
|
|
return composeFiles, fmt.Errorf("COMPOSE_FILE (=\"%s\") parsing failed?", composeFileEnvVar)
|
|
}
|
|
|
|
for _, file := range envVars {
|
|
path := fmt.Sprintf("%s/%s", r.Dir, file)
|
|
if err := ensurePathExists(path); err != nil {
|
|
return composeFiles, err
|
|
}
|
|
composeFiles = append(composeFiles, path)
|
|
}
|
|
|
|
log.Debugf("COMPOSE_FILE detected (%s), loading %s", composeFileEnvVar, strings.Join(envVars, ", "))
|
|
log.Debugf("retrieved %s configs for %s", strings.Join(composeFiles, ", "), r.Name)
|
|
|
|
return composeFiles, nil
|
|
}
|
|
|
|
// UpdateTag updates an image tag in-place on file system local compose files.
|
|
func (r Recipe2) UpdateTag(image, tag string) (bool, error) {
|
|
fullPattern := fmt.Sprintf("%s/compose**yml", r.Dir)
|
|
image = formatter.StripTagMeta(image)
|
|
|
|
composeFiles, err := filepath.Glob(fullPattern)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
log.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))
|
|
|
|
for _, composeFile := range composeFiles {
|
|
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
|
|
|
sampleEnv, err := r.SampleEnv()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
compose, err := loader.LoadComposefile(opts, sampleEnv)
|
|
if err != nil {
|
|
return false, 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 {
|
|
return false, err
|
|
}
|
|
|
|
var composeTag string
|
|
switch img.(type) {
|
|
case reference.NamedTagged:
|
|
composeTag = img.(reference.NamedTagged).Tag()
|
|
default:
|
|
log.Debugf("unable to parse %s, skipping", img)
|
|
continue
|
|
}
|
|
|
|
composeImage := formatter.StripTagMeta(reference.Path(img))
|
|
|
|
log.Debugf("parsed %s from %s", composeTag, service.Image)
|
|
|
|
if image == composeImage {
|
|
bytes, err := ioutil.ReadFile(composeFile)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
|
|
new := fmt.Sprintf("%s:%s", composeImage, tag)
|
|
replacedBytes := strings.Replace(string(bytes), old, new, -1)
|
|
|
|
log.Debugf("updating %s to %s in %s", old, new, compose.Filename)
|
|
|
|
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// UpdateLabel updates a label in-place on file system local compose files.
|
|
func (r Recipe2) UpdateLabel(pattern, serviceName, label string) error {
|
|
fullPattern := fmt.Sprintf("%s/%s", r.Dir, pattern)
|
|
composeFiles, err := filepath.Glob(fullPattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("considering %s config(s) for label update", strings.Join(composeFiles, ", "))
|
|
|
|
for _, composeFile := range composeFiles {
|
|
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
|
|
|
sampleEnv, err := r.SampleEnv()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
compose, err := loader.LoadComposefile(opts, sampleEnv)
|
|
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
|
|
}
|
|
|
|
discovered := false
|
|
for oldLabel, value := range service.Deploy.Labels {
|
|
if strings.HasPrefix(oldLabel, "coop-cloud") && strings.Contains(oldLabel, "version") {
|
|
discovered = true
|
|
|
|
bytes, err := ioutil.ReadFile(composeFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value)
|
|
replacedBytes := strings.Replace(string(bytes), old, label, -1)
|
|
|
|
if old == label {
|
|
log.Warnf("%s is already set, nothing to do?", label)
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("updating %s to %s in %s", old, label, compose.Filename)
|
|
|
|
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("synced label %s to service %s", label, serviceName)
|
|
}
|
|
}
|
|
|
|
if !discovered {
|
|
log.Warn("no existing label found, automagic insertion not supported yet")
|
|
log.Fatalf("add '- \"%s\"' manually to the 'app' service in %s", label, composeFile)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|