diff --git a/cmd/nerdctl/compose.go b/cmd/nerdctl/compose.go index 3a5fbed9d50..3b8269e3eff 100644 --- a/cmd/nerdctl/compose.go +++ b/cmd/nerdctl/compose.go @@ -17,23 +17,10 @@ package main import ( - "context" - "errors" - "fmt" - "github.com/containerd/containerd" - "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/platforms" "github.com/containerd/nerdctl/pkg/api/types" + "github.com/containerd/nerdctl/pkg/cmd/compose" "github.com/containerd/nerdctl/pkg/composer" - "github.com/containerd/nerdctl/pkg/composer/serviceparser" - "github.com/containerd/nerdctl/pkg/cosignutil" - "github.com/containerd/nerdctl/pkg/imgutil" - "github.com/containerd/nerdctl/pkg/ipfs" - "github.com/containerd/nerdctl/pkg/netutil" - "github.com/containerd/nerdctl/pkg/referenceutil" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -82,135 +69,44 @@ func newComposeCommand() *cobra.Command { } func getComposer(cmd *cobra.Command, client *containerd.Client, globalOptions *types.GlobalCommandOptions) (*composer.Composer, error) { - nerdctlCmd, nerdctlArgs := globalFlags(cmd) - projectDirectory, err := cmd.Flags().GetString("project-directory") + options, err := getComposeOptions(cmd) if err != nil { return nil, err } - envFile, err := cmd.Flags().GetString("env-file") + volStore, err := getVolumeStore(globalOptions) if err != nil { return nil, err } - projectName, err := cmd.Flags().GetString("project-name") + return compose.GetComposer(options, globalOptions, client, volStore, cmd.OutOrStdout(), cmd.ErrOrStderr()) +} + +func getComposeOptions(cmd *cobra.Command) (*composer.Options, error) { + options := &composer.Options{} + options.NerdctlCmd, options.NerdctlArgs = globalFlags(cmd) + var err error + options.ProjectDirectory, err = cmd.Flags().GetString("project-directory") if err != nil { return nil, err } - debugFull := globalOptions.DebugFull - snapshotter := globalOptions.Snapshotter - files, err := cmd.Flags().GetStringArray("file") + options.EnvFile, err = cmd.Flags().GetString("env-file") if err != nil { return nil, err } - insecure := globalOptions.InsecureRegistry - cniPath := globalOptions.CNIPath - cniNetconfpath := globalOptions.CNINetConfPath - hostsDirs := globalOptions.HostsDir - experimental := globalOptions.Experimental - - o := composer.Options{ - Project: projectName, - ProjectDirectory: projectDirectory, - ConfigPaths: files, - EnvFile: envFile, - NerdctlCmd: nerdctlCmd, - NerdctlArgs: nerdctlArgs, - DebugPrintFull: debugFull, - Experimental: experimental, - } - - cniEnv, err := netutil.NewCNIEnv(cniPath, cniNetconfpath, netutil.WithDefaultNetwork()) + options.Project, err = cmd.Flags().GetString("project-name") if err != nil { return nil, err } - networkConfigs, err := cniEnv.NetworkList() + options.DebugPrintFull, err = cmd.Flags().GetBool("debug-full") if err != nil { return nil, err } - - o.NetworkExists = func(netName string) (bool, error) { - for _, f := range networkConfigs { - if f.Name == netName { - return true, nil - } - } - return false, nil - } - - volStore, err := getVolumeStore(globalOptions) + options.ConfigPaths, err = cmd.Flags().GetStringArray("file") if err != nil { return nil, err } - - o.VolumeExists = func(volName string) (bool, error) { - if _, volGetErr := volStore.Get(volName, false); volGetErr == nil { - return true, nil - } else if errors.Is(volGetErr, errdefs.ErrNotFound) { - return false, nil - } else { - return false, volGetErr - } - } - - o.ImageExists = func(ctx context.Context, rawRef string) (bool, error) { - refNamed, err := referenceutil.ParseAny(rawRef) - if err != nil { - return false, err - } - ref := refNamed.String() - if _, err := client.ImageService().Get(ctx, ref); err != nil { - if errors.Is(err, errdefs.ErrNotFound) { - return false, nil - } - return false, err - } - return true, nil - } - - o.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error { - ocispecPlatforms := []ocispec.Platform{platforms.DefaultSpec()} - if platform != "" { - parsed, err := platforms.Parse(platform) - if err != nil { - return err - } - ocispecPlatforms = []ocispec.Platform{parsed} // no append - } - - // IPFS reference - if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(imageName); err == nil { - _, err = ipfs.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, scheme, ref, - pullMode, ocispecPlatforms, nil, quiet) - return err - } - - ref := imageName - if verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok { - switch verifier { - case "cosign": - if !o.Experimental { - return fmt.Errorf("cosign only work with enable experimental feature") - } - - // if key is given, use key mode, otherwise use keyless mode. - keyRef := "" - if keyVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok { - keyRef = keyVal.(string) - } - - ref, err = cosignutil.VerifyCosign(ctx, ref, keyRef, hostsDirs) - if err != nil { - return err - } - case "none": - logrus.Debugf("verification process skipped") - default: - return fmt.Errorf("no verifier found: %s", verifier) - } - } - _, err = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, ref, - pullMode, insecure, hostsDirs, ocispecPlatforms, nil, quiet) - return err + options.Experimental, err = cmd.Flags().GetBool("experimental") + if err != nil { + return nil, err } - - return composer.New(o, client) + return options, nil } diff --git a/cmd/nerdctl/compose_build.go b/cmd/nerdctl/compose_build.go index 85184445767..4c44936a098 100644 --- a/cmd/nerdctl/compose_build.go +++ b/cmd/nerdctl/compose_build.go @@ -17,6 +17,7 @@ package main import ( + "github.com/containerd/nerdctl/pkg/api/types" "github.com/containerd/nerdctl/pkg/clientutil" "github.com/containerd/nerdctl/pkg/composer" "github.com/spf13/cobra" @@ -42,15 +43,16 @@ func composeBuildAction(cmd *cobra.Command, args []string) error { if err != nil { return err } - buildArg, err := cmd.Flags().GetStringArray("build-arg") + options := &types.ComposeBuildCommandOptions{} + options.BuildArgs, err = cmd.Flags().GetStringArray("build-arg") if err != nil { return err } - noCache, err := cmd.Flags().GetBool("no-cache") + options.NoCache, err = cmd.Flags().GetBool("no-cache") if err != nil { return err } - progress, err := cmd.Flags().GetString("progress") + options.Progress, err = cmd.Flags().GetString("progress") if err != nil { return err } @@ -66,9 +68,9 @@ func composeBuildAction(cmd *cobra.Command, args []string) error { return err } bo := composer.BuildOptions{ - Args: buildArg, - NoCache: noCache, - Progress: progress, + Args: options.BuildArgs, + NoCache: options.NoCache, + Progress: options.Progress, } return c.Build(ctx, bo, args) } diff --git a/pkg/api/types/compose_types.go b/pkg/api/types/compose_types.go new file mode 100644 index 00000000000..cff9c163db0 --- /dev/null +++ b/pkg/api/types/compose_types.go @@ -0,0 +1,26 @@ +/* + Copyright The containerd 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 types + +type ComposeBuildCommandOptions struct { + // BuildArgs is a list of build-time variables for services. + BuildArgs []string + // NoCache is a flag to disable cache when building the image. + NoCache bool + // Progress is a flag to show the type of progress output (auto, plain, tty) + Progress string +} diff --git a/pkg/cmd/compose/compose.go b/pkg/cmd/compose/compose.go new file mode 100644 index 00000000000..d08567bafb0 --- /dev/null +++ b/pkg/cmd/compose/compose.go @@ -0,0 +1,133 @@ +/* + Copyright The containerd 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" + "fmt" + "io" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/platforms" + "github.com/containerd/nerdctl/pkg/api/types" + "github.com/containerd/nerdctl/pkg/composer" + "github.com/containerd/nerdctl/pkg/composer/serviceparser" + "github.com/containerd/nerdctl/pkg/cosignutil" + "github.com/containerd/nerdctl/pkg/imgutil" + "github.com/containerd/nerdctl/pkg/ipfs" + "github.com/containerd/nerdctl/pkg/mountutil/volumestore" + "github.com/containerd/nerdctl/pkg/netutil" + "github.com/containerd/nerdctl/pkg/referenceutil" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +func GetComposer(options *composer.Options, globalOptions *types.GlobalCommandOptions, client *containerd.Client, volStore volumestore.VolumeStore, stdout, stderr io.Writer) (*composer.Composer, error) { + var err error + cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithDefaultNetwork()) + if err != nil { + return nil, err + } + networkConfigs, err := cniEnv.NetworkList() + if err != nil { + return nil, err + } + + options.NetworkExists = func(netName string) (bool, error) { + for _, f := range networkConfigs { + if f.Name == netName { + return true, nil + } + } + return false, nil + } + + options.VolumeExists = func(volName string) (bool, error) { + if _, volGetErr := volStore.Get(volName, false); volGetErr == nil { + return true, nil + } else if errors.Is(volGetErr, errdefs.ErrNotFound) { + return false, nil + } else { + return false, volGetErr + } + } + + options.ImageExists = func(ctx context.Context, rawRef string) (bool, error) { + refNamed, err := referenceutil.ParseAny(rawRef) + if err != nil { + return false, err + } + ref := refNamed.String() + if _, err := client.ImageService().Get(ctx, ref); err != nil { + if errors.Is(err, errdefs.ErrNotFound) { + return false, nil + } + return false, err + } + return true, nil + } + + options.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error { + ocispecPlatforms := []ocispec.Platform{platforms.DefaultSpec()} + if platform != "" { + parsed, err := platforms.Parse(platform) + if err != nil { + return err + } + ocispecPlatforms = []ocispec.Platform{parsed} // no append + } + + // IPFS reference + if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(imageName); err == nil { + _, err = ipfs.EnsureImage(ctx, client, stdout, stderr, globalOptions.Snapshotter, scheme, ref, + pullMode, ocispecPlatforms, nil, quiet) + return err + } + + ref := imageName + if verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok { + switch verifier { + case "cosign": + if !options.Experimental { + return fmt.Errorf("cosign only work with enable experimental feature") + } + + // if key is given, use key mode, otherwise use keyless mode. + keyRef := "" + if keyVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok { + keyRef = keyVal.(string) + } + ref, err = cosignutil.VerifyCosign(ctx, ref, keyRef, globalOptions.HostsDir) + if err != nil { + return err + } + case "none": + logrus.Debugf("verification process skipped") + default: + return fmt.Errorf("no verifier found: %s", verifier) + } + } + _, err := imgutil.EnsureImage(ctx, client, stdout, stderr, globalOptions.Snapshotter, ref, + pullMode, globalOptions.InsecureRegistry, globalOptions.HostsDir, ocispecPlatforms, nil, quiet) + return err + } + + return composer.New(*options, client) +}