func (c *configLoader) processExtends(
	s *serviceInternal,
	dcFile *dockerComposeFile) error {
	if s.visited {
		if s.recStack {
			if dcFile.resolvedFile != "" {
				return fmt.Errorf("cannot extend service %s of file %#v because this would cause an infinite loop. Please ensure your "+
					"docker compose services do not have a cyclical extends relationship", s.name, dcFile.resolvedFile)
			}
			return fmt.Errorf("cannot extend service %s (of the merged docker compose file) because this would cause an infinite loop. "+
				"Please ensure your docker compose services do not have a cyclical extends relationship", s.name)
		}
		return nil
	}
	s.visited = true
	if s.Extends == nil {
		return nil
	}
	s.recStack = true
	defer s.clearRecStack()
	sExtended, err := c.resolveExtends(s, dcFile)
	if err != nil {
		return err
	}
	merge(s, sExtended, false)
	return nil
}
processExtends process the extends field of a docker compose service. That is: given a docker compose service X,
if X extends another service Y then processExtends copies inherited configuration Y into the representation of X.
package config
import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	version "github.com/hashicorp/go-version"
	"github.com/kube-compose/kube-compose/internal/pkg/fs"
	"github.com/kube-compose/kube-compose/internal/pkg/util"
	"github.com/pkg/errors"
	"github.com/uber-go/mapdecode"
	yaml "gopkg.in/yaml.v2"
)
var (
	v1   = version.Must(version.NewVersion("1"))
	v2_1 = version.Must(version.NewVersion("2.1"))
	v3_1 = version.Must(version.NewVersion("3.1"))
	v3_3 = version.Must(version.NewVersion("3.3"))
)
type genericMap map[interface{}]interface{}
type XProperties map[string]interface{}
type CanonicalDockerComposeConfig struct {
	Services map[string]*Service
	XProperties []XProperties
}
type Service struct {
	Command []string
	DependsOn           map[string]ServiceHealthiness
	Entrypoint          []string
	Environment         map[string]string
	Healthcheck         *Healthcheck
	HealthcheckDisabled bool
	Image               string
	Name                string
	Ports               []PortBinding
	Privileged          bool
	Restart             string
	User                *string
	Volumes             []ServiceVolume
	WorkingDir          string
}
type serviceInternal struct {
	Command   *stringOrStringSlice `mapdecode:"command"`
	DependsOn *dependsOn           `mapdecode:"depends_on"`
	Entrypoint        *stringOrStringSlice `mapdecode:"entrypoint"`
	Environment       *environment         `mapdecode:"environment"`
	environmentParsed map[string]string
	Extends           *extends `mapdecode:"extends"`
	finalService *Service
	Healthcheck  *healthcheckInternal `mapdecode:"healthcheck"`
	Image        *string              `mapdecode:"image"`
	name        string
	Ports       []port `mapdecode:"ports"`
	portsParsed []PortBinding
	Privileged  *bool `mapdecode:"privileged"`
	recStack bool
	Restart  *string `mapdecode:"restart"`
	User     *string `mapdecode:"user"`
	visited    bool
	Volumes    []ServiceVolume `mapdecode:"volumes"`
	WorkingDir *string         `mapdecode:"working_dir"`
}
func (c *serviceInternal) clearRecStack() {
	c.recStack = false
}
type dockerComposeFile struct {
	Services map[string]*serviceInternal `mapdecode:"services"`
	version  *version.Version
	xProperties XProperties
	resolvedFile string
}
type loadResolvedFileCacheItem struct {
	parsed *dockerComposeFile
	err    error
}
type configLoader struct {
	environmentGetter ValueGetter
	loadResolvedFileCache map[string]*loadResolvedFileCacheItem
}
func (c *configLoader) loadFile(file string) (*dockerComposeFile, error) {
	resolvedFile, err := fs.OS.EvalSymlinks(file)
	if err != nil {
		return nil, errors.Wrapf(err, "error when evaluating symlinks %#v", file)
	}
	return c.loadResolvedFile(resolvedFile)
}
func getVersion(dataMap genericMap) (*version.Version, error) {
	var v *version.Version
	vRaw, hasVersion := dataMap["version"]
	if !hasVersion {
		v = v1
	} else if vString, ok := vRaw.(string); ok {
		var err error
		v, err = version.NewVersion(vString)
		if err != nil {
			return nil, fmt.Errorf("could not parse version: %#v", vString)
		}
	} else {
		return nil, fmt.Errorf("version must be a string")
	}
	return v, nil
}
func (c *configLoader) loadResolvedFile(resolvedFile string) (*dockerComposeFile, error) {
	cacheItem := c.loadResolvedFileCache[resolvedFile]
	if cacheItem == nil {
		cacheItem = &loadResolvedFileCacheItem{
			parsed: &dockerComposeFile{},
		}
		c.loadResolvedFileCache[resolvedFile] = cacheItem
		cacheItem.err = c.loadResolvedFileCore(resolvedFile, cacheItem.parsed)
	}
	return cacheItem.parsed, cacheItem.err
}
func loadYamlFileAsGenericMap(file string) (genericMap, error) {
	reader, err := fs.OS.Open(file)
	if err != nil {
		return nil, err
	}
	defer util.CloseAndLogError(reader)
	decoder := yaml.NewDecoder(reader)
	var dataMap genericMap
	err = decoder.Decode(&dataMap)
	return dataMap, err
}
func (c *configLoader) loadResolvedFileCore(resolvedFile string, dcFile *dockerComposeFile) error {
	dcFile.resolvedFile = resolvedFile
	dataMap, err := loadYamlFileAsGenericMap(resolvedFile)
	if err != nil {
		return err
	}
	dcFile.version, err = getVersion(dataMap)
	if err != nil {
		return err
	}
	err = InterpolateConfig(dataMap, c.environmentGetter, dcFile.version)
	if err != nil {
		return err
	}
	if !dcFile.version.Equal(v1) {
		dcFile.xProperties = getXProperties(dataMap)
	} else {
		dataMap = map[interface{}]interface{}{
			"services": dataMap,
		}
	}
	err = mapdecode.Decode(dcFile, dataMap, mapdecode.IgnoreUnused(true))
	if err != nil {
		return err
	}
	return c.parseDockerComposeFile(dcFile)
}
func (c *configLoader) loadStandardFiles() ([]string, error) {
	var resolvedFileSlice []string
	cwd, err := fs.OS.Getwd()
	if err != nil {
		return nil, err
	}
	dir := ""
	resolvedDir, err := fs.OS.EvalSymlinks(cwd)
	if err != nil {
		return nil, err
	}
	for {
		resolvedFile, err := c.loadStandardFilesTry(dir, resolvedDir, "")
		if err == nil {
			resolvedFileSlice = append(resolvedFileSlice, resolvedFile)
			resolvedFile, err = c.loadStandardFilesTry(dir, resolvedDir, ".override")
			if err == nil {
				resolvedFileSlice = append(resolvedFileSlice, resolvedFile)
			}
			if err == nil || os.IsNotExist(err) {
				return resolvedFileSlice, nil
			}
		}
		if !os.IsNotExist(err) {
			return nil, err
		}
		resolvedDirParent := filepath.Dir(resolvedDir)
		if resolvedDirParent == resolvedDir {
			break
		}
		resolvedDir = resolvedDirParent
		dir = ".." + string(filepath.Separator) + dir
	}
	return nil, fmt.Errorf("could not find file docker-compose.yml or docker-compose.yaml in (parents of) the current directory %#v", cwd)
}
func (c *configLoader) loadStandardFilesTry(dir, resolvedDir, override string) (resolvedFile string, err error) {
	for _, suffix := range []string{".yml", ".yaml"} {
		basename := "docker-compose" + override + suffix
		file := dir + basename
		resolvedFile, err = fs.OS.EvalSymlinks(resolvedDir + "/" + basename)
		if err == nil {
			_, err = c.loadResolvedFile(resolvedFile)
			if err != nil {
				return "", errors.Wrapf(err, "error while loading docker compose file %s (%#v)", dir+file, resolvedFile)
			}
			return resolvedFile, nil
		}
		if !os.IsNotExist(err) {
			return "", errors.Wrapf(err, "error when evaluating symlinks %s (%#v)", dir+file, resolvedDir+"/"+basename)
		}
	}
	return "", err
}