mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
feat(build): print if no services to build, build dependencies --dep, check build configuration --check
This commit is contained in:
@@ -66,7 +66,10 @@ func runBuild(ctx context.Context, uncli *cli.CLI, opts cli.BuildOptions) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesToBuild := cli.GetServicesThatNeedBuild(project)
|
servicesToBuild, err := cli.ServicesThatNeedBuild(project, opts.Services, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("determine services to build: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(servicesToBuild) == 0 {
|
if len(servicesToBuild) == 0 {
|
||||||
fmt.Println("No services to build.")
|
fmt.Println("No services to build.")
|
||||||
|
|||||||
+18
-17
@@ -17,6 +17,8 @@ import (
|
|||||||
|
|
||||||
type buildOptions struct {
|
type buildOptions struct {
|
||||||
buildArgs []string
|
buildArgs []string
|
||||||
|
check bool
|
||||||
|
deps bool
|
||||||
files []string
|
files []string
|
||||||
noCache bool
|
noCache bool
|
||||||
profiles []string
|
profiles []string
|
||||||
@@ -44,10 +46,14 @@ func NewCBuildCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringArrayVar(&opts.buildArgs, "build-arg", nil,
|
cmd.Flags().StringArrayVar(&opts.buildArgs, "build-arg", nil,
|
||||||
"Set a build-time variable to pass to service Dockerfiles. Only used if declared with ARG.\n"+
|
"Set a build-time variable for services. Used in Dockerfiles that declare the variable with ARG.\n"+
|
||||||
"Can be specified multiple times. Format: --build-arg VAR=VALUE")
|
"Can be specified multiple times. Format: --build-arg VAR=VALUE")
|
||||||
|
cmd.Flags().BoolVar(&opts.check, "check", false,
|
||||||
|
"Check the build configuration for services without building them.")
|
||||||
|
cmd.Flags().BoolVar(&opts.deps, "deps", false,
|
||||||
|
"Also build services declared as dependencies of the selected services.")
|
||||||
cmd.Flags().StringSliceVarP(&opts.files, "file", "f", nil,
|
cmd.Flags().StringSliceVarP(&opts.files, "file", "f", nil,
|
||||||
"One or more Compose files to build (default compose.yaml)")
|
"One or more Compose files to build. (default compose.yaml)")
|
||||||
cmd.Flags().BoolVar(&opts.noCache, "no-cache", false,
|
cmd.Flags().BoolVar(&opts.noCache, "no-cache", false,
|
||||||
"Do not use cache when building images.")
|
"Do not use cache when building images.")
|
||||||
cmd.Flags().StringSliceVarP(&opts.profiles, "profile", "p", nil,
|
cmd.Flags().StringSliceVarP(&opts.profiles, "profile", "p", nil,
|
||||||
@@ -60,32 +66,27 @@ func NewCBuildCommand() *cobra.Command {
|
|||||||
|
|
||||||
// projectOptsFromCBuildOpts returns the project options for the Compose file(s).
|
// projectOptsFromCBuildOpts returns the project options for the Compose file(s).
|
||||||
func projectOptsFromCBuildOpts(opts buildOptions) []composecli.ProjectOptionsFn {
|
func projectOptsFromCBuildOpts(opts buildOptions) []composecli.ProjectOptionsFn {
|
||||||
var projectOpts []composecli.ProjectOptionsFn
|
var projOpts []composecli.ProjectOptionsFn
|
||||||
|
|
||||||
if len(opts.profiles) > 0 {
|
if len(opts.profiles) > 0 {
|
||||||
projectOpts = append(projectOpts, composecli.WithDefaultProfiles(opts.profiles...))
|
projOpts = append(projOpts, composecli.WithDefaultProfiles(opts.profiles...))
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectOpts
|
return projOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// runCBuild parses the Compose file(s) and builds the images for selected services.
|
// runCBuild parses the Compose file(s) and builds the images for selected services.
|
||||||
func runCBuild(ctx context.Context, _ *cli.CLI, opts buildOptions) error {
|
func runCBuild(ctx context.Context, _ *cli.CLI, opts buildOptions) error {
|
||||||
projectOpts := projectOptsFromCBuildOpts(opts)
|
projOpts := projectOptsFromCBuildOpts(opts)
|
||||||
project, err := compose.LoadProject(ctx, opts.files, projectOpts...)
|
project, err := compose.LoadProject(ctx, opts.files, projOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load compose file(s): %w", err)
|
return fmt.Errorf("load compose file(s): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.services) > 0 {
|
servicesToBuild, err := cli.ServicesThatNeedBuild(project, opts.services, opts.deps)
|
||||||
project, err = project.WithSelectedServices(opts.services)
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("determine services to build: %w", err)
|
||||||
return fmt.Errorf("select services: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesToBuild := cli.GetServicesThatNeedBuild(project)
|
|
||||||
|
|
||||||
if len(servicesToBuild) == 0 {
|
if len(servicesToBuild) == 0 {
|
||||||
fmt.Println("No services to build.")
|
fmt.Println("No services to build.")
|
||||||
return nil
|
return nil
|
||||||
@@ -105,6 +106,8 @@ func runCBuild(ctx context.Context, _ *cli.CLI, opts buildOptions) error {
|
|||||||
composeService := composev2.NewComposeService(dockerCli)
|
composeService := composev2.NewComposeService(dockerCli)
|
||||||
buildOpts := composeapi.BuildOptions{
|
buildOpts := composeapi.BuildOptions{
|
||||||
Args: composetypes.NewMappingWithEquals(opts.buildArgs),
|
Args: composetypes.NewMappingWithEquals(opts.buildArgs),
|
||||||
|
Check: opts.check,
|
||||||
|
Deps: opts.deps,
|
||||||
NoCache: opts.noCache,
|
NoCache: opts.noCache,
|
||||||
Pull: opts.pull,
|
Pull: opts.pull,
|
||||||
Services: opts.services,
|
Services: opts.services,
|
||||||
@@ -114,7 +117,5 @@ func runCBuild(ctx context.Context, _ *cli.CLI, opts buildOptions) error {
|
|||||||
return fmt.Errorf("build services: %w", err)
|
return fmt.Errorf("build services: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Build completed successfully.")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,20 +68,20 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
|
|
||||||
// projectOpts returns the project options for the Compose file(s).
|
// projectOpts returns the project options for the Compose file(s).
|
||||||
func projectOpts(opts deployOptions) []composecli.ProjectOptionsFn {
|
func projectOpts(opts deployOptions) []composecli.ProjectOptionsFn {
|
||||||
projectOpts := []composecli.ProjectOptionsFn{}
|
var projOpts []composecli.ProjectOptionsFn
|
||||||
|
|
||||||
if len(opts.profiles) > 0 {
|
if len(opts.profiles) > 0 {
|
||||||
projectOpts = append(projectOpts, composecli.WithDefaultProfiles(opts.profiles...))
|
projOpts = append(projOpts, composecli.WithDefaultProfiles(opts.profiles...))
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectOpts
|
return projOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// runDeploy parses the Compose file(s) and deploys the services.
|
// runDeploy parses the Compose file(s) and deploys the services.
|
||||||
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
||||||
projectOpts := projectOpts(opts)
|
projOpts := projectOpts(opts)
|
||||||
|
|
||||||
project, err := compose.LoadProject(ctx, opts.files, projectOpts...)
|
project, err := compose.LoadProject(ctx, opts.files, projOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load compose file(s): %w", err)
|
return fmt.Errorf("load compose file(s): %w", err)
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,10 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesToBuild := cli.GetServicesThatNeedBuild(project)
|
servicesToBuild, err := cli.ServicesThatNeedBuild(project, opts.services, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("determine services to build: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(servicesToBuild) > 0 {
|
if len(servicesToBuild) > 0 {
|
||||||
if opts.noBuild {
|
if opts.noBuild {
|
||||||
|
|||||||
+65
-8
@@ -6,8 +6,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
composetypes "github.com/compose-spec/compose-go/v2/types"
|
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||||
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/docker/docker/api/types/build"
|
||||||
@@ -27,16 +29,71 @@ type BuildOptions struct {
|
|||||||
NoCache bool
|
NoCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServicesThatNeedBuild returns a map of services that require building
|
// ServicesThatNeedBuild returns a map of services that require building.
|
||||||
func GetServicesThatNeedBuild(project *composetypes.Project) map[string]composetypes.ServiceConfig {
|
// deps indicates whether to include services that are dependencies of the selected services.
|
||||||
|
// Implementation is based on the logic from docker/compose/v2/pkg/compose/build.go.
|
||||||
|
func ServicesThatNeedBuild(
|
||||||
|
project *composetypes.Project, selectedServices []string, deps bool,
|
||||||
|
) (map[string]composetypes.ServiceConfig, error) {
|
||||||
servicesToBuild := make(map[string]composetypes.ServiceConfig, len(project.Services))
|
servicesToBuild := make(map[string]composetypes.ServiceConfig, len(project.Services))
|
||||||
for serviceName, service := range project.Services {
|
|
||||||
if service.Build == nil {
|
var policy composetypes.DependencyOption = composetypes.IgnoreDependencies
|
||||||
continue
|
if deps {
|
||||||
}
|
policy = composetypes.IncludeDependencies
|
||||||
servicesToBuild[serviceName] = service
|
|
||||||
}
|
}
|
||||||
return servicesToBuild
|
|
||||||
|
// Also include services used as build.additional_contexts with service: prefix.
|
||||||
|
selectedServices = includeAdditionalContextsServices(project, selectedServices)
|
||||||
|
// Some build dependencies we just introduced may not be enabled, enable them.
|
||||||
|
var err error
|
||||||
|
project, err = project.WithServicesEnabled(selectedServices...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err = project.WithSelectedServices(selectedServices)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = project.ForEachService(selectedServices, func(serviceName string, service *composetypes.ServiceConfig) error {
|
||||||
|
if service.Build != nil {
|
||||||
|
servicesToBuild[serviceName] = *service
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, policy)
|
||||||
|
|
||||||
|
for serviceName, service := range project.Services {
|
||||||
|
if service.Build != nil {
|
||||||
|
servicesToBuild[serviceName] = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servicesToBuild, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// includeAdditionalContextsServices adds services referenced in build.additional_contexts to the list
|
||||||
|
// of selected services.
|
||||||
|
func includeAdditionalContextsServices(project *composetypes.Project, selectedServices []string) []string {
|
||||||
|
servicesWithDependencies := mapset.NewSet(selectedServices...)
|
||||||
|
for _, service := range selectedServices {
|
||||||
|
s, ok := project.Services[service]
|
||||||
|
if !ok {
|
||||||
|
s = project.DisabledServices[service]
|
||||||
|
}
|
||||||
|
if s.Build != nil {
|
||||||
|
for _, target := range s.Build.AdditionalContexts {
|
||||||
|
if name, found := strings.CutPrefix(target, composetypes.ServicePrefix); found {
|
||||||
|
servicesWithDependencies.Add(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if servicesWithDependencies.Cardinality() > len(selectedServices) {
|
||||||
|
return includeAdditionalContextsServices(project, servicesWithDependencies.ToSlice())
|
||||||
|
}
|
||||||
|
|
||||||
|
return servicesWithDependencies.ToSlice()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildServices builds the services defined in the provided map.
|
// BuildServices builds the services defined in the provided map.
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ func TestComposeBuild(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
servicesToBuild := cliInternal.GetServicesThatNeedBuild(project)
|
servicesToBuild, err := cliInternal.ServicesThatNeedBuild(project, nil, false)
|
||||||
|
require.NoError(t, err)
|
||||||
serviceImage1 := fmt.Sprintf("127.0.0.1:%d/service-first", registryHostPort)
|
serviceImage1 := fmt.Sprintf("127.0.0.1:%d/service-first", registryHostPort)
|
||||||
serviceImage2 := fmt.Sprintf("127.0.0.1:%d/service-second:version2", registryHostPort)
|
serviceImage2 := fmt.Sprintf("127.0.0.1:%d/service-second:version2", registryHostPort)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
|||||||
Reference in New Issue
Block a user