github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/commit.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package container 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "strings" 24 25 "github.com/containerd/containerd" 26 "github.com/containerd/log" 27 "github.com/containerd/nerdctl/v2/pkg/api/types" 28 "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" 29 "github.com/containerd/nerdctl/v2/pkg/imgutil/commit" 30 "github.com/containerd/nerdctl/v2/pkg/referenceutil" 31 ) 32 33 // Commit will commit a container’s file changes or settings into a new image. 34 func Commit(ctx context.Context, client *containerd.Client, rawRef string, req string, options types.ContainerCommitOptions) error { 35 named, err := referenceutil.ParseDockerRef(rawRef) 36 if err != nil { 37 return err 38 } 39 40 changes, err := parseChanges(options.Change) 41 if err != nil { 42 return err 43 } 44 45 opts := &commit.Opts{ 46 Author: options.Author, 47 Message: options.Message, 48 Ref: named.String(), 49 Pause: options.Pause, 50 Changes: changes, 51 } 52 53 walker := &containerwalker.ContainerWalker{ 54 Client: client, 55 OnFound: func(ctx context.Context, found containerwalker.Found) error { 56 if found.MatchCount > 1 { 57 return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) 58 } 59 imageID, err := commit.Commit(ctx, client, found.Container, opts) 60 if err != nil { 61 return err 62 } 63 _, err = fmt.Fprintln(options.Stdout, imageID) 64 return err 65 }, 66 } 67 n, err := walker.Walk(ctx, req) 68 if err != nil { 69 return err 70 } else if n == 0 { 71 return fmt.Errorf("no such container %s", req) 72 } 73 return nil 74 } 75 76 func parseChanges(userChanges []string) (commit.Changes, error) { 77 const ( 78 // XXX: Where can I get a constants for this? 79 commandDirective = "CMD" 80 entrypointDirective = "ENTRYPOINT" 81 ) 82 if userChanges == nil { 83 return commit.Changes{}, nil 84 } 85 var changes commit.Changes 86 for _, change := range userChanges { 87 if change == "" { 88 return commit.Changes{}, fmt.Errorf("received an empty value in change flag") 89 } 90 changeFields := strings.Fields(change) 91 92 switch changeFields[0] { 93 case commandDirective: 94 var overrideCMD []string 95 if err := json.Unmarshal([]byte(change[len(changeFields[0]):]), &overrideCMD); err != nil { 96 return commit.Changes{}, fmt.Errorf("malformed json in change flag value %q", change) 97 } 98 if changes.CMD != nil { 99 log.L.Warn("multiple change flags supplied for the CMD directive, overriding with last supplied") 100 } 101 changes.CMD = overrideCMD 102 case entrypointDirective: 103 var overrideEntrypoint []string 104 if err := json.Unmarshal([]byte(change[len(changeFields[0]):]), &overrideEntrypoint); err != nil { 105 return commit.Changes{}, fmt.Errorf("malformed json in change flag value %q", change) 106 } 107 if changes.Entrypoint != nil { 108 log.L.Warnf("multiple change flags supplied for the Entrypoint directive, overriding with last supplied") 109 } 110 changes.Entrypoint = overrideEntrypoint 111 default: // TODO: Support the rest of the change directives 112 return commit.Changes{}, fmt.Errorf("unknown change directive %q", changeFields[0]) 113 } 114 } 115 return changes, nil 116 }