Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 19 additions & 123 deletions cmd/nerdctl/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
14 changes: 8 additions & 6 deletions cmd/nerdctl/compose_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -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)
}
26 changes: 26 additions & 0 deletions pkg/api/types/compose_types.go
Original file line number Diff line number Diff line change
@@ -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
}
133 changes: 133 additions & 0 deletions pkg/cmd/compose/compose.go
Original file line number Diff line number Diff line change
@@ -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)
}