Files
compose/pkg/compose/loader.go
Sebastiaan van Stijn 89d3944837 fix linting issues
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 14:54:58 +01:00

156 lines
4.8 KiB
Go

/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"errors"
"os"
"strings"
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/remote"
)
// LoadProject implements api.Compose.LoadProject
// It loads and validates a Compose project from configuration files.
func (s *composeService) LoadProject(ctx context.Context, options api.ProjectLoadOptions) (*types.Project, error) {
// Setup remote loaders (Git, OCI)
remoteLoaders := s.createRemoteLoaders(options)
projectOptions, err := s.buildProjectOptions(options, remoteLoaders)
if err != nil {
return nil, err
}
// Register all user-provided listeners (e.g., for metrics collection)
for _, listener := range options.LoadListeners {
if listener != nil {
projectOptions.WithListeners(listener)
}
}
if options.Compatibility {
api.Separator = "_"
}
project, err := projectOptions.LoadProject(ctx)
if err != nil {
return nil, err
}
// Post-processing: service selection, environment resolution, etc.
project, err = s.postProcessProject(project, options)
if err != nil {
return nil, err
}
return project, nil
}
// createRemoteLoaders creates Git and OCI remote loaders if not in offline mode
func (s *composeService) createRemoteLoaders(options api.ProjectLoadOptions) []loader.ResourceLoader {
if options.Offline {
return nil
}
git := remote.NewGitRemoteLoader(s.dockerCli, options.Offline)
oci := remote.NewOCIRemoteLoader(s.dockerCli, options.Offline, options.OCI)
return []loader.ResourceLoader{git, oci}
}
// buildProjectOptions constructs compose-go ProjectOptions from API options
func (s *composeService) buildProjectOptions(options api.ProjectLoadOptions, remoteLoaders []loader.ResourceLoader) (*cli.ProjectOptions, error) {
opts := []cli.ProjectOptionsFn{
cli.WithWorkingDirectory(options.WorkingDir),
cli.WithOsEnv,
}
// Add PWD if not present
if _, present := os.LookupEnv("PWD"); !present {
if pwd, err := os.Getwd(); err == nil {
opts = append(opts, cli.WithEnv([]string{"PWD=" + pwd}))
}
}
// Add remote loaders
for _, r := range remoteLoaders {
opts = append(opts, cli.WithResourceLoader(r))
}
opts = append(opts,
// Load PWD/.env if present and no explicit --env-file has been set
cli.WithEnvFiles(options.EnvFiles...),
// read dot env file to populate project environment
cli.WithDotEnv,
// get compose file path set by COMPOSE_FILE
cli.WithConfigFileEnv,
// if none was selected, get default compose.yaml file from current dir or parent folder
cli.WithDefaultConfigPath,
// .. and then, a project directory != PWD maybe has been set so let's load .env file
cli.WithEnvFiles(options.EnvFiles...),
cli.WithDotEnv,
// eventually COMPOSE_PROFILES should have been set
cli.WithDefaultProfiles(options.Profiles...),
cli.WithName(options.ProjectName),
)
return cli.NewProjectOptions(options.ConfigPaths, append(options.ProjectOptionsFns, opts...)...)
}
// postProcessProject applies post-loading transformations to the project
func (s *composeService) postProcessProject(project *types.Project, options api.ProjectLoadOptions) (*types.Project, error) {
if project.Name == "" {
return nil, errors.New("project name can't be empty. Use ProjectName option to set a valid name")
}
project, err := project.WithServicesEnabled(options.Services...)
if err != nil {
return nil, err
}
// Add custom labels
for name, s := range project.Services {
s.CustomLabels = map[string]string{
api.ProjectLabel: project.Name,
api.ServiceLabel: name,
api.VersionLabel: api.ComposeVersion,
api.WorkingDirLabel: project.WorkingDir,
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
api.OneoffLabel: "False",
}
if len(options.EnvFiles) != 0 {
s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(options.EnvFiles, ",")
}
project.Services[name] = s
}
project, err = project.WithSelectedServices(options.Services)
if err != nil {
return nil, err
}
// Remove unnecessary resources if not All
if !options.All {
project = project.WithoutUnnecessaryResources()
}
return project, nil
}