Skip to content

Commit 4ac8195

Browse files
committed
Add context-aware versions of several functions and some methods
Introduce versions of a number of functions and some methods which take a context.Context, to least try to provide proper cancellation support. When copier.Get() returns an error, also write a non-NUL string after whatever it's already written, so that a tar.Reader that reads what it output will also flag the malformed header with an unexpected error. In main(), connect the global default context to use a timeout set by an experimental timeout flag, and to be canceled on the interrupt signal. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
1 parent 9d841c9 commit 4ac8195

47 files changed

Lines changed: 765 additions & 279 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

add.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ import (
2626
v1 "github.com/opencontainers/image-spec/specs-go/v1"
2727
"github.com/opencontainers/runtime-spec/specs-go"
2828
"github.com/sirupsen/logrus"
29-
"github.com/tonistiigi/dchapes-mode"
29+
mode "github.com/tonistiigi/dchapes-mode"
3030
"go.podman.io/buildah/copier"
3131
"go.podman.io/buildah/define"
32+
"go.podman.io/buildah/internal/ctxreader"
3233
"go.podman.io/buildah/internal/tmpdir"
3334
"go.podman.io/buildah/internal/urlsource"
3435
"go.podman.io/buildah/pkg/chrootuser"
@@ -129,7 +130,7 @@ type AddAndCopyOptions struct {
129130
}
130131

131132
// getURL writes a tar archive containing the named content
132-
func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod string, srcDigest digest.Digest, certPath string, insecureSkipTLSVerify types.OptionalBool, timestamp *time.Time) error {
133+
func getURL(ctx context.Context, src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod string, srcDigest digest.Digest, certPath string, insecureSkipTLSVerify types.OptionalBool, timestamp *time.Time) error {
133134
url, err := url.Parse(src)
134135
if err != nil {
135136
return err
@@ -152,7 +153,11 @@ func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string,
152153
Proxy: http.ProxyFromEnvironment,
153154
}
154155
httpClient := &http.Client{Transport: tr}
155-
response, err := httpClient.Get(src)
156+
req, err := http.NewRequestWithContext(ctx, "GET", src, nil)
157+
if err != nil {
158+
return err
159+
}
160+
response, err := httpClient.Do(req)
156161
if err != nil {
157162
return err
158163
}
@@ -184,7 +189,7 @@ func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string,
184189
}
185190
// Figure out the size of the content.
186191
size := response.ContentLength
187-
var responseBody io.Reader = response.Body
192+
responseBody := ctxreader.NewCancelableReader(ctx, response.Body)
188193
if size < 0 {
189194
// Create a temporary file and copy the content to it, so that
190195
// we can figure out how much content there is.
@@ -307,10 +312,15 @@ func getParentsPrefixToRemoveAndParentsToSkip(pattern string, contextDir string)
307312
return prefix, out
308313
}
309314

310-
// Add copies the contents of the specified sources into the container's root
315+
// Add() calls AddContext() with context.Background().
316+
func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, sources ...string) error {
317+
return b.AddContext(context.Background(), destination, extract, options, sources...)
318+
}
319+
320+
// AddContext copies the contents of the specified sources into the container's root
311321
// filesystem, optionally extracting contents of local files that look like
312322
// non-empty archives.
313-
func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, sources ...string) error {
323+
func (b *Builder) AddContext(ctx context.Context, destination string, extract bool, options AddAndCopyOptions, sources ...string) error {
314324
mountPoint, err := b.Mount(b.MountLabel)
315325
if err != nil {
316326
return err
@@ -374,7 +384,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
374384
DisallowWildcard: options.AllowWildcard == types.OptionalBoolFalse,
375385
AllowEmptyWildcard: options.AllowEmptyWildcard == types.OptionalBoolTrue,
376386
}
377-
localSourceStats, err = copier.Stat(contextDir, contextDir, statOptions, localSources)
387+
localSourceStats, err = copier.StatContext(ctx, contextDir, contextDir, statOptions, localSources)
378388
if err != nil {
379389
return fmt.Errorf("checking on sources under %q: %w", contextDir, err)
380390
}
@@ -466,7 +476,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
466476
statOptions := copier.StatOptions{
467477
CheckForArchives: extract,
468478
}
469-
destStats, err := copier.Stat(mountPoint, filepath.Join(mountPoint, b.WorkDir()), statOptions, []string{extractDirectory})
479+
destStats, err := copier.StatContext(ctx, mountPoint, filepath.Join(mountPoint, b.WorkDir()), statOptions, []string{extractDirectory})
470480
if err != nil {
471481
return fmt.Errorf("checking on destination %v: %w", extractDirectory, err)
472482
}
@@ -559,7 +569,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
559569
if !strings.HasPrefix(putDirAbs, stagingDirAbs+string(os.PathSeparator)) && putDirAbs != stagingDirAbs {
560570
return fmt.Errorf("destination path %q escapes staging directory", destination)
561571
}
562-
if err := copier.Mkdir(putRoot, putDirAbs, mkdirOptions); err != nil {
572+
if err := copier.MkdirContext(ctx, putRoot, putDirAbs, mkdirOptions); err != nil {
563573
return fmt.Errorf("ensuring target directory exists: %w", err)
564574
}
565575
tempPath := putDir
@@ -570,7 +580,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
570580
tempPath = filepath.Dir(tempPath)
571581
}
572582
} else {
573-
if err := copier.Mkdir(mountPoint, extractDirectory, mkdirOptions); err != nil {
583+
if err := copier.MkdirContext(ctx, mountPoint, extractDirectory, mkdirOptions); err != nil {
574584
return fmt.Errorf("ensuring target directory exists: %w", err)
575585
}
576586

@@ -599,7 +609,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
599609
defer wg.Done()
600610
defer pipeWriter.Close()
601611
var cloneDir, subdir string
602-
cloneDir, subdir, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src)
612+
cloneDir, subdir, getErr = define.TempDirForURLContext(ctx, tmpdir.GetTempDir(), "", src)
603613
if getErr != nil {
604614
return
605615
}
@@ -621,12 +631,12 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
621631
}
622632
writer := io.WriteCloser(pipeWriter)
623633
repositoryDir := filepath.Join(cloneDir, subdir)
624-
getErr = copier.Get(repositoryDir, repositoryDir, getOptions, []string{"."}, writer)
634+
getErr = copier.GetContext(ctx, repositoryDir, repositoryDir, getOptions, []string{"."}, writer)
625635
}()
626636
} else {
627637
go func() {
628-
getErr = retry.IfNecessary(context.TODO(), func() error {
629-
return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, options.Chmod, srcDigest, options.CertPath, options.InsecureSkipTLSVerify, options.Timestamp)
638+
getErr = retry.IfNecessary(ctx, func() error {
639+
return getURL(ctx, src, chownFiles, mountPoint, renameTarget, pipeWriter, options.Chmod, srcDigest, options.CertPath, options.InsecureSkipTLSVerify, options.Timestamp)
630640
}, &retry.Options{
631641
MaxRetry: options.MaxRetries,
632642
Delay: options.RetryDelay,
@@ -656,7 +666,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
656666
IgnoreDevices: userns.RunningInUserNS(),
657667
Timestamp: options.Timestamp,
658668
}
659-
putErr = copier.Put(putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher))
669+
putErr = copier.PutContext(ctx, putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher))
660670
}
661671
hashCloser.Close()
662672
pipeReader.Close()
@@ -788,7 +798,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
788798
AllowEmptyWildcard: options.AllowEmptyWildcard == types.OptionalBoolTrue,
789799
NoDerefSymlinks: options.FollowSymlink == types.OptionalBoolFalse,
790800
}
791-
getErr = copier.Get(contextDir, contextDir, getOptions, []string{globbedToGlobbable(globbed)}, writer)
801+
getErr = copier.GetContext(ctx, contextDir, contextDir, getOptions, []string{globbedToGlobbable(globbed)}, writer)
792802
closeErr = writer.Close()
793803
if renameTarget != "" && renamedItems > 1 {
794804
renameErr = fmt.Errorf("internal error: renamed %d items when we expected to only rename 1", renamedItems)
@@ -820,7 +830,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
820830
IgnoreDevices: userns.RunningInUserNS(),
821831
Timestamp: options.Timestamp,
822832
}
823-
putErr = copier.Put(putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher))
833+
putErr = copier.PutContext(ctx, putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher))
824834
}
825835
hashCloser.Close()
826836
pipeReader.Close()

buildah_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package buildah
22

33
import (
4-
"context"
54
"flag"
65
"os"
76
"testing"
@@ -46,7 +45,7 @@ func TestOpenBuilderCommonBuildOpts(t *testing.T) {
4645
// or builder must enable sometime of locking mechanism i.e if
4746
// routine is creating Builder other's must wait for it.
4847
// Tracked here: https://github.com/containers/buildah/issues/5967
49-
ctx := context.TODO()
48+
ctx := t.Context()
5049
store, err := storage.GetStore(types.StoreOptions{
5150
RunRoot: t.TempDir(),
5251
GraphRoot: t.TempDir(),

chroot/run_common.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package chroot
44

55
import (
66
"bytes"
7+
"context"
78
"encoding/json"
89
"fmt"
910
"io"
@@ -51,10 +52,15 @@ type runUsingChrootExecSubprocOptions struct {
5152
NoPivot bool
5253
}
5354

54-
// RunUsingChroot runs a chrooted process, using some of the settings from the
55+
// RunUsingChroot() calls RunUsingChrootContext() with context.Background().
56+
func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer, noPivot bool) (err error) {
57+
return RunUsingChrootContext(context.Background(), spec, bundlePath, homeDir, stdin, stdout, stderr, noPivot)
58+
}
59+
60+
// RunUsingChrootContext runs a chrooted process, using some of the settings from the
5561
// passed-in spec, and using the specified bundlePath to hold temporary files,
5662
// directories, and mountpoints.
57-
func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer, noPivot bool) (err error) {
63+
func RunUsingChrootContext(ctx context.Context, spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer, noPivot bool) (err error) {
5864
var confwg sync.WaitGroup
5965
var homeFound bool
6066
for _, env := range spec.Process.Env {
@@ -127,6 +133,7 @@ func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reade
127133

128134
// Start the grandparent subprocess.
129135
cmd := unshare.Command(runUsingChrootCommand)
136+
cmd.Cmd = reexec.CommandContext(ctx, runUsingChrootCommand) // TODO: add an unshare.CommandContext()
130137
setPdeathsig(cmd.Cmd)
131138
cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr
132139
cmd.Dir = "/"

chroot/unsupported.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package chroot
44

55
import (
6+
"context"
67
"fmt"
78
"io"
89

@@ -11,5 +12,10 @@ import (
1112

1213
// RunUsingChroot is not supported.
1314
func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer) (err error) {
15+
return RunUsingChrootContext(context.Background(), spec, bundlePath, homeDir, stdin, stdout, stderr)
16+
}
17+
18+
// RunUsingChrootContext is not supported.
19+
func RunUsingChrootContext(ctx context.Context, spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer) (err error) {
1420
return fmt.Errorf("--isolation chroot is not supported on this platform")
1521
}

cmd/buildah/addcopy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func addAndCopyCmd(c *cobra.Command, args []string, verb string, iopts addCopyRe
322322
}
323323

324324
extractLocalArchives := verb == "ADD"
325-
err = builder.Add(dest, extractLocalArchives, options, args...)
325+
err = builder.AddContext(getContext(), dest, extractLocalArchives, options, args...)
326326
if err != nil {
327327
return fmt.Errorf("adding content to container %q: %w", builder.Container, err)
328328
}

cmd/buildah/common.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"os/signal"
9+
"sync"
810

911
"github.com/spf13/cobra"
1012
"github.com/spf13/pflag"
@@ -157,11 +159,31 @@ func openImage(ctx context.Context, sc *types.SystemContext, store storage.Store
157159
return builder, nil
158160
}
159161

160-
// getContext returns a context.TODO
162+
// getContext returns a context that may have a timeout, and which cancels if
163+
// it receives os.Interrupt
161164
func getContext() context.Context {
162-
return context.TODO()
165+
ctx, _ := getContextWithCancel()
166+
return ctx
163167
}
164168

169+
func getContextCancel() context.CancelFunc {
170+
_, cancel := getContextWithCancel()
171+
return cancel
172+
}
173+
174+
var getContextWithCancel = sync.OnceValues(func() (context.Context, context.CancelFunc) {
175+
var ctx context.Context
176+
var cancel1, cancel2 func()
177+
if rootCmd.PersistentFlags().Changed("experimental-timeout") {
178+
ctx, cancel1 = context.WithTimeout(context.Background(), globalFlagResults.ExperimentalTimeout)
179+
} else {
180+
ctx = context.Background()
181+
cancel1 = func() {}
182+
}
183+
ctx, cancel2 = signal.NotifyContext(ctx, os.Interrupt)
184+
return ctx, func() { cancel1(); cancel2() }
185+
})
186+
165187
func getUserFlags() pflag.FlagSet {
166188
fs := pflag.FlagSet{}
167189
fs.String("user", "", "`user[:group]` to run the command as")

cmd/buildah/from.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func onBuild(builder *buildah.Builder, quiet bool) error {
129129
dest = args[size-1]
130130
args = args[:size-1]
131131
}
132-
if err := builder.Add(dest, command == "ADD", buildah.AddAndCopyOptions{}, args...); err != nil {
132+
if err := builder.AddContext(getContext(), dest, command == "ADD", buildah.AddAndCopyOptions{}, args...); err != nil {
133133
return err
134134
}
135135
case "ANNOTATION":

cmd/buildah/images.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"context"
54
"encoding/json"
65
"errors"
76
"fmt"
@@ -133,7 +132,7 @@ func imagesCmd(c *cobra.Command, args []string, iopts *imageResults) error {
133132
return err
134133
}
135134

136-
ctx := context.Background()
135+
ctx := getContext()
137136

138137
options := &libimage.ListImagesOptions{}
139138
if len(iopts.filter) > 0 {

cmd/buildah/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"runtime/pprof"
1010
"strings"
1111
"syscall"
12+
"time"
1213

1314
ispecs "github.com/opencontainers/image-spec/specs-go"
1415
rspecs "github.com/opencontainers/runtime-spec/specs-go"
@@ -58,6 +59,7 @@ type globalFlags struct {
5859
MemoryProfile string
5960
UserShortNameAliasConfPath string
6061
CgroupManager string
62+
ExperimentalTimeout time.Duration
6163
}
6264

6365
var rootCmd = &cobra.Command{
@@ -107,6 +109,7 @@ func mainInit() {
107109
rootCmd.CompletionOptions.HiddenDefaultCmd = true
108110
// rootCmd.TraverseChildren = true
109111
rootCmd.Version = fmt.Sprintf("%s (image-spec %s, runtime-spec %s)", define.Version, ispecs.Version, rspecs.Version)
112+
rootCmd.PersistentFlags().DurationVar(&globalFlagResults.ExperimentalTimeout, "experimental-timeout", 0, "global timeout") // maybe rename this at some point?
110113
rootCmd.PersistentFlags().BoolVar(&globalFlagResults.Debug, "debug", false, "print debugging information")
111114
// TODO Need to allow for environment variable
112115
rootCmd.PersistentFlags().StringVar(&globalFlagResults.RegistriesConf, "registries-conf", "", "path to registries.conf file (not usually used)")
@@ -138,6 +141,9 @@ func mainInit() {
138141
if err := rootCmd.PersistentFlags().MarkHidden("memory-profile"); err != nil {
139142
logrus.Fatalf("unable to mark memory-profile flag as hidden: %v", err)
140143
}
144+
if err := rootCmd.PersistentFlags().MarkHidden("experimental-timeout"); err != nil {
145+
logrus.Fatalf("unable to mark experimental-timeout flag as hidden: %v", err)
146+
}
141147
rootCmd.AddGroup(commandGroups...)
142148
}
143149

@@ -293,7 +299,9 @@ func main() {
293299
// Hard code TMPDIR functions to use $TMPDIR or /var/tmp
294300
os.Setenv("TMPDIR", parse.GetTempDir())
295301

296-
if err := rootCmd.Execute(); err != nil {
302+
err := rootCmd.Execute()
303+
getContextCancel()()
304+
if err != nil {
297305
if logrus.IsLevelEnabled(logrus.TraceLevel) {
298306
fmt.Fprintf(os.Stderr, "Error: %+v\n", err)
299307
} else {

cmd/buildah/manifest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ func manifestRmCmd(c *cobra.Command, args []string) error {
741741
Filters: []string{"readonly=false"},
742742
LookupManifest: true,
743743
}
744-
rmiReports, rmiErrors := runtime.RemoveImages(context.Background(), args, options)
744+
rmiReports, rmiErrors := runtime.RemoveImages(getContext(), args, options)
745745
for _, r := range rmiReports {
746746
for _, u := range r.Untagged {
747747
fmt.Printf("untagged: %s\n", u)

0 commit comments

Comments
 (0)