update github.com/compose-spec/compose-go to v1.9.0

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2023-02-03 03:19:59 +01:00
parent d2fa4a5724
commit 21ac4c34fb
14 changed files with 284 additions and 233 deletions

View File

@ -17,9 +17,7 @@
package loader
import (
"bytes"
"fmt"
"io"
"os"
paths "path"
"path/filepath"
@ -30,7 +28,6 @@ import (
"time"
"github.com/compose-spec/compose-go/consts"
"github.com/compose-spec/compose-go/dotenv"
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/schema"
"github.com/compose-spec/compose-go/template"
@ -67,6 +64,8 @@ type Options struct {
projectName string
// Indicates when the projectName was imperatively set or guessed from path
projectNameImperativelySet bool
// Profiles set profiles to enable
Profiles []string
}
func (o *Options) SetProjectName(name string, imperativelySet bool) {
@ -125,6 +124,13 @@ func WithSkipValidation(opts *Options) {
opts.SkipValidation = true
}
// WithProfiles sets profiles to be activated
func WithProfiles(profiles []string) func(*Options) {
return func(opts *Options) {
opts.Profiles = profiles
}
}
// ParseYAML reads the bytes from a file, parses the bytes into a mapping
// structure, and returns it.
func ParseYAML(source []byte) (map[string]interface{}, error) {
@ -161,12 +167,22 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
op(opts)
}
projectName := projectName(configDetails, opts)
projectName, err := projectName(configDetails, opts)
if err != nil {
return nil, err
}
var configs []*types.Config
for i, file := range configDetails.ConfigFiles {
configDict := file.Config
if configDict == nil {
if len(file.Content) == 0 {
content, err := os.ReadFile(file.Filename)
if err != nil {
return nil, err
}
file.Content = content
}
dict, err := parseConfig(file.Content, opts)
if err != nil {
return nil, err
@ -188,12 +204,6 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
if err != nil {
return nil, err
}
if opts.discardEnvFiles {
for i := range cfg.Services {
cfg.Services[i].EnvFile = nil
}
}
configs = append(configs, cfg)
}
@ -236,22 +246,35 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
}
return project, nil
if len(opts.Profiles) > 0 {
project.ApplyProfiles(opts.Profiles)
}
err = project.ResolveServicesEnvironment(opts.discardEnvFiles)
return project, err
}
func projectName(details types.ConfigDetails, opts *Options) string {
func projectName(details types.ConfigDetails, opts *Options) (string, error) {
projectName, projectNameImperativelySet := opts.GetProjectName()
var pjNameFromConfigFile string
for _, configFile := range details.ConfigFiles {
yml, err := ParseYAML(configFile.Content)
if err != nil {
return ""
return "", nil
}
if val, ok := yml["name"]; ok && val != "" {
pjNameFromConfigFile = yml["name"].(string)
}
}
if !opts.SkipInterpolation {
interpolated, err := interp.Interpolate(map[string]interface{}{"name": pjNameFromConfigFile}, *opts.Interpolate)
if err != nil {
return "", err
}
pjNameFromConfigFile = interpolated["name"].(string)
}
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
if !projectNameImperativelySet && pjNameFromConfigFile != "" {
projectName = pjNameFromConfigFile
@ -260,7 +283,7 @@ func projectName(details types.ConfigDetails, opts *Options) string {
if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
details.Environment[consts.ComposeProjectName] = projectName
}
return projectName
return projectName, nil
}
func NormalizeProjectName(s string) string {
@ -508,6 +531,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
}
if target == nil {
target = map[string]interface{}{}
}
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths)
if err != nil {
return nil, err
@ -547,16 +574,14 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
// absolute path.
baseFileParent := filepath.Dir(*file)
if baseService.Build != nil {
// Note that the Dockerfile is always defined relative to the
// build context, so there's no need to update the Dockerfile field.
baseService.Build.Context = absPath(baseFileParent, baseService.Build.Context)
baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context)
}
for i, vol := range baseService.Volumes {
if vol.Type != types.VolumeTypeBind {
continue
}
baseService.Volumes[i].Source = absPath(baseFileParent, vol.Source)
baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv)
}
}
@ -569,6 +594,19 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return serviceConfig, nil
}
func resolveBuildContextPath(baseFileParent string, context string) string {
// Checks if the context is an HTTP(S) URL or a remote git repository URL
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
if strings.HasPrefix(context, prefix) {
return context
}
}
// Note that the Dockerfile is always defined relative to the
// build context, so there's no need to update the Dockerfile field.
return absPath(baseFileParent, context)
}
// LoadService produces a single ServiceConfig from a compose file Dict
// the serviceDict is not validated if directly used. Use Load() to enable validation
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) {
@ -580,10 +618,6 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
}
serviceConfig.Name = name
if err := resolveEnvironment(serviceConfig, workingDir, lookupEnv); err != nil {
return nil, err
}
for i, volume := range serviceConfig.Volumes {
if volume.Type != types.VolumeTypeBind {
continue
@ -620,48 +654,8 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf
return volume
}
func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, lookupEnv template.Mapping) error {
environment := types.MappingWithEquals{}
if len(serviceConfig.EnvFile) > 0 {
if serviceConfig.Environment == nil {
serviceConfig.Environment = types.MappingWithEquals{}
}
for _, envFile := range serviceConfig.EnvFile {
filePath := absPath(workingDir, envFile)
file, err := os.Open(filePath)
if err != nil {
return err
}
b, err := io.ReadAll(file)
if err != nil {
return err
}
// Do not defer to avoid it inside a loop
file.Close() //nolint:errcheck
fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), dotenv.LookupFn(lookupEnv))
if err != nil {
return err
}
env := types.MappingWithEquals{}
for k, v := range fileVars {
v := v
env[k] = &v
}
environment.OverrideBy(env.Resolve(lookupEnv).RemoveEmpty())
}
}
environment.OverrideBy(serviceConfig.Environment.Resolve(lookupEnv))
serviceConfig.Environment = environment
return nil
}
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
filePath := expandUser(volume.Source, lookupEnv)
func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Mapping) string {
filePath := expandUser(path, lookupEnv)
// Check if source is an absolute path (either Unix or Windows), to
// handle a Windows client with a Unix daemon or vice-versa.
//
@ -671,10 +665,21 @@ func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, look
if !paths.IsAbs(filePath) && !isAbs(filePath) {
filePath = absPath(workingDir, filePath)
}
volume.Source = filePath
return filePath
}
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
volume.Source = resolveMaybeUnixPath(volume.Source, workingDir, lookupEnv)
return volume
}
func resolveSecretsPath(secret types.SecretConfig, workingDir string, lookupEnv template.Mapping) types.SecretConfig {
if !secret.External.External && secret.File != "" {
secret.File = resolveMaybeUnixPath(secret.File, workingDir, lookupEnv)
}
return secret
}
// TODO: make this more robust
func expandUser(path string, lookupEnv template.Mapping) string {
if strings.HasPrefix(path, "~") {
@ -723,7 +728,7 @@ func LoadNetworks(source map[string]interface{}) (map[string]types.NetworkConfig
if network.Name != "" {
return nil, errors.Errorf("network %s: network.external.name and network.name conflict; only use network.name", name)
}
logrus.Warnf("network %s: network.external.name is deprecated in favor of network.name", name)
logrus.Warnf("network %s: network.external.name is deprecated. Please set network.name with external: true", name)
network.Name = network.External.Name
network.External.Name = ""
case network.Name == "":
@ -782,11 +787,14 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, res
return secrets, err
}
for name, secret := range secrets {
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, resolvePaths)
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, false)
if err != nil {
return nil, err
}
secretConfig := types.SecretConfig(obj)
if resolvePaths {
secretConfig = resolveSecretsPath(secretConfig, details.WorkingDir, details.LookupEnv)
}
secrets[name] = secretConfig
}
return secrets, nil