diff --git a/cmd/entrypoint/main.go b/cmd/entrypoint/main.go index db0df30e..e4666122 100644 --- a/cmd/entrypoint/main.go +++ b/cmd/entrypoint/main.go @@ -25,6 +25,25 @@ import ( "k8s.io/test-infra/prow/logrusutil" ) +/* +The tool is used to rewrite the entrypoint of a container image. +To override the base shell image update `.ko.yaml` file. + +To use it, run +``` +image: github.com/knative/build/cmd/entrypoint +``` + +It used in knative/build as a method of running containers in +order that are in the same pod this is done by: +1) for the Pod(containing user Steps) created by a Build, +create a shared directory with the entrypoint binary +2) change the entrypoint of all the user specified containers in Steps to be the +entrypoint binary with configuration to run the user specified entrypoint with some custom logic +3) one piece of "custom logic" is having the entrypoint binary wait for the previous step +as seen in knative/build/pkg/entrypoint/run.go -- waitForPrevStep() +*/ + func main() { o := entrypoint.NewOptions() if err := options.Load(o); err != nil { diff --git a/pkg/entrypoint/options.go b/pkg/entrypoint/options.go index 4890fc50..e0e34e25 100644 --- a/pkg/entrypoint/options.go +++ b/pkg/entrypoint/options.go @@ -20,7 +20,6 @@ import ( "encoding/json" "errors" "flag" - "time" "github.com/knative/build/pkg/entrypoint/wrapper" ) @@ -38,18 +37,6 @@ func NewOptions() *Options { type Options struct { // Args is the process and args to run Args []string `json:"args"` - // Timeout determines how long to wait before the - // entrypoint sends SIGINT to the process - Timeout time.Duration `json:"timeout"` - // GracePeriod determines how long to wait after - // sending SIGINT before the entrypoint sends - // SIGKILL. - GracePeriod time.Duration `json:"grace_period"` - // ArtifactDir is a directory where test processes can dump artifacts - // for upload to persistent storage (courtesy of sidecar). - // If specified, it is created by entrypoint before starting the test process. - // May be ignored if not using sidecar. - ArtifactDir string `json:"artifact_dir,omitempty"` *wrapper.Options } @@ -84,21 +71,14 @@ func (o *Options) LoadConfig(config string) error { // AddFlags binds flags to options func (o *Options) AddFlags(flags *flag.FlagSet) { - flags.DurationVar(&o.Timeout, "timeout", - DefaultTimeout, "Timeout for the test command.") - flags.DurationVar(&o.GracePeriod, "grace-period", - DefaultGracePeriod, "Grace period after timeout for the test command.") - flags.StringVar(&o.ArtifactDir, "artifact-dir", - "", "directory where test artifacts should be placed for upload "+ - "to persistent storage") flags.BoolVar(&o.ShouldWaitForPrevStep, "should-wait-for-prev-step", DefaultShouldWaitForPrevStep, "If we should wait for prev step.") flags.BoolVar(&o.ShouldRunPostRun, "should-run-post-run", - DefaultShouldRunPostRun, "If post run step should be run.") + DefaultShouldRunPostRun, "If the post run step should be run after execution finishes.") flags.StringVar(&o.PreRunFile, "prerun-file", - DefaultPreRunFile, "The prerun file to wait for.") + DefaultPreRunFile, "The path of the file that acts as a lock for the entrypoint. The entrypoint binary will wait until that file is present to launch the specified command.") flags.StringVar(&o.PostRunFile, "postrun-file", - DefaultPostRunFile, "If postrun file to write.") + DefaultPostRunFile, "The path of the file that will be written once the command finishes for the entrypoint. This can act as a lock for other entrypoint rewritten containers.") o.Options.AddFlags(flags) } diff --git a/pkg/entrypoint/options/load_test.go b/pkg/entrypoint/options/load_test.go new file mode 100644 index 00000000..1f7dbeb4 --- /dev/null +++ b/pkg/entrypoint/options/load_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2017 The Knative 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 options + +import ( + "flag" + "os" + "testing" + + "github.com/knative/build/pkg/entrypoint" +) + +const ( + TestEnvVar = "TEST_ENV_VAR" +) + +type TestOptions struct { + *entrypoint.Options +} + +func (o *TestOptions) ConfigVar() string { + return TestEnvVar +} + +func (o *TestOptions) AddFlags(flags *flag.FlagSet) { + // Required to reset os.Args[1:] values used in Load() + os.Args[1] = "" + return +} + +func (o *TestOptions) Complete(args []string) { + return +} + +func TestOptions_Load(t *testing.T) { + tt := []struct { + name string + envmap map[string]string + in OptionLoader + err error + }{ + {"successful load", map[string]string{TestEnvVar: "hello"}, &TestOptions{}, nil}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := Load(tc.in) + if tc.err != err { + t.Errorf("expected err to be %v; got %v", tc.err, err) + } + }) + } +} diff --git a/pkg/entrypoint/options_test.go b/pkg/entrypoint/options_test.go index ccb15700..b4dcdeab 100644 --- a/pkg/entrypoint/options_test.go +++ b/pkg/entrypoint/options_test.go @@ -27,37 +27,29 @@ func TestOptions_Validate(t *testing.T) { name string input Options expectedErr bool - }{ - { - name: "all ok", - input: Options{ - Args: []string{"/usr/bin/true"}, - Options: &wrapper.Options{ - ProcessLog: "output.txt", - MarkerFile: "marker.txt", - }, + }{{ + name: "all ok", + input: Options{ + Args: []string{"/usr/bin/true"}, + Options: &wrapper.Options{ + ProcessLog: "output.txt", + MarkerFile: "marker.txt", }, - expectedErr: false, }, - { - name: "missing args", - input: Options{ - Options: &wrapper.Options{ - ProcessLog: "output.txt", - MarkerFile: "marker.txt", - }, + }, { + name: "missing args", + input: Options{ + Options: &wrapper.Options{ + ProcessLog: "output.txt", + MarkerFile: "marker.txt", }, - expectedErr: true, }, - } + expectedErr: true, + }} for _, testCase := range testCases { - err := testCase.input.Validate() - if testCase.expectedErr && err == nil { - t.Errorf("%s: expected an error but got none", testCase.name) - } - if !testCase.expectedErr && err != nil { - t.Errorf("%s: expected no error but got one: %v", testCase.name, err) + if err := testCase.input.Validate(); testCase.expectedErr != (err != nil) { + t.Errorf("%s: expected error to be %v but got %v", testCase.name, testCase.expectedErr, err) } } } diff --git a/pkg/entrypoint/run.go b/pkg/entrypoint/run.go index cdaa4d02..abdfc128 100644 --- a/pkg/entrypoint/run.go +++ b/pkg/entrypoint/run.go @@ -19,11 +19,9 @@ package entrypoint import ( "errors" "fmt" - "io" "io/ioutil" "os" "os/exec" - "os/signal" "path/filepath" "strconv" "syscall" @@ -36,28 +34,23 @@ const ( // InternalErrorCode is what we write to the marker file to // indicate that we failed to start the wrapped command InternalErrorCode = 127 - // AbortedErrorCode is what we write to the marker file to - // indicate that we were terminated via a signal. - AbortedErrorCode = 130 - // DefaultTimeout is the default timeout for the test - // process before SIGINT is sent - DefaultTimeout = 120 * time.Minute - - // DefaultGracePeriod is the default timeout for the test - // process after SIGINT is sent before SIGKILL is sent - DefaultGracePeriod = 15 * time.Second - - // DefaultShouldWaitForPrevStep is ... + // DefaultShouldWaitForPrevStep is the default value for whether the + // command the entrypoint binary will launch should wait for a "finished" + // signal from another job. This allows ordering steps DefaultShouldWaitForPrevStep = false - // DefaultShouldRunPostRun is ... + // DefaultShouldRunPostRun is the default value for whether after the + // command finishes, it should send a "finished" signal that other waiting + // jobs might be relying on to begin. This allows ordering steps DefaultShouldRunPostRun = false - // DefaultPreRunFile is ... + // DefaultPreRunFile is the name of the file that a + // waiting job will be waiting to read before it runs. DefaultPreRunFile = "0" - // DefaultPostRunFile is ... + // DefaultPostRunFile is the name of the file that a + // finishing job will be write after it successfully completes. DefaultPostRunFile = "1" ) @@ -77,63 +70,19 @@ func (o Options) Run() int { if err != nil { logrus.WithError(err).Error("Error executing test process") } - if err := o.mark(code); err != nil { - logrus.WithError(err).Error("Error writing exit code to marker file") - return InternalErrorCode - } return code } // ExecuteProcess creates the artifact directory then executes the process as // configured, writing the output to the process log. func (o Options) ExecuteProcess() (int, error) { - if o.ArtifactDir != "" { - if err := os.MkdirAll(o.ArtifactDir, os.ModePerm); err != nil { - return InternalErrorCode, fmt.Errorf("could not create artifact directory(%s): %v", o.ArtifactDir, err) - } - } - processLogFile, err := os.Create(o.ProcessLog) - if err != nil { - return InternalErrorCode, fmt.Errorf("could not create process logfile(%s): %v", o.ProcessLog, err) - } - defer processLogFile.Close() - - output := io.MultiWriter(os.Stdout, processLogFile) - logrus.SetOutput(output) - defer logrus.SetOutput(os.Stdout) - // --- - timeout := optionOrDefault(o.Timeout, DefaultTimeout) - gracePeriod := optionOrDefault(o.GracePeriod, DefaultGracePeriod) var commandErr error - cancelled, aborted := false, false done := make(chan error) go func() { done <- o.waitForPrevStep() }() - select { - case err := <-done: - commandErr = err - if err != nil { - cancelled = true - } - case <-time.After(timeout): - logrus.Errorf("Previous step did not finish within %s timeout", timeout) - cancelled = true - } - - var returnCode int - if cancelled { - if aborted { - commandErr = errAborted - returnCode = AbortedErrorCode - } else { - commandErr = errTimedOut - returnCode = InternalErrorCode - } - return returnCode, commandErr - } // --- executable := o.Args[0] @@ -142,17 +91,12 @@ func (o Options) ExecuteProcess() (int, error) { arguments = o.Args[1:] } command := exec.Command(executable, arguments...) - command.Stderr = output - command.Stdout = output if err := command.Start(); err != nil { return InternalErrorCode, fmt.Errorf("could not start the process: %v", err) } // if we get asked to terminate we need to forward // that to the wrapped process as if it timed out - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) - done = make(chan error) go func() { done <- command.Wait() @@ -163,67 +107,21 @@ func (o Options) ExecuteProcess() (int, error) { if o.ShouldRunPostRun { o.postRunWriteFile(0) } - - case <-time.After(timeout): - logrus.Errorf("Process did not finish before %s timeout", timeout) - cancelled = true - gracefullyTerminate(command, done, gracePeriod) - case s := <-interrupt: - logrus.Errorf("Entrypoint received interrupt: %v", s) - cancelled = true - aborted = true - gracefullyTerminate(command, done, gracePeriod) } - if cancelled { - if aborted { - commandErr = errAborted - returnCode = AbortedErrorCode - } else { - commandErr = errTimedOut - returnCode = InternalErrorCode - } + var returnCode int + if status, ok := command.ProcessState.Sys().(syscall.WaitStatus); ok { + returnCode = status.ExitStatus() + } else if commandErr == nil { + returnCode = 0 } else { - if status, ok := command.ProcessState.Sys().(syscall.WaitStatus); ok { - returnCode = status.ExitStatus() - } else if commandErr == nil { - returnCode = 0 - } else { - returnCode = 1 - } - - if returnCode != 0 { - commandErr = fmt.Errorf("wrapped process failed: %v", commandErr) - } + returnCode = 1 } - return returnCode, commandErr -} -func (o *Options) mark(exitCode int) error { - content := []byte(strconv.Itoa(exitCode)) - - // create temp file in the same directory as the desired marker file - dir := filepath.Dir(o.MarkerFile) - tempFile, err := ioutil.TempFile(dir, "temp-marker") - if err != nil { - return fmt.Errorf("could not create temp marker file in %s: %v", dir, err) - } - // write the exit code to the tempfile, sync to disk and close - if _, err = tempFile.Write(content); err != nil { - return fmt.Errorf("could not write to temp marker file (%s): %v", tempFile.Name(), err) + if returnCode != 0 { + commandErr = fmt.Errorf("wrapped process failed: %v", commandErr) } - if err = tempFile.Sync(); err != nil { - return fmt.Errorf("could not sync temp marker file (%s): %v", tempFile.Name(), err) - } - tempFile.Close() - // set desired permission bits, then rename to the desired file name - if err = os.Chmod(tempFile.Name(), os.ModePerm); err != nil { - return fmt.Errorf("could not chmod (%x) temp marker file (%s): %v", os.ModePerm, tempFile.Name(), err) - } - if err := os.Rename(tempFile.Name(), o.MarkerFile); err != nil { - return fmt.Errorf("could not move marker file to destination path (%s): %v", o.MarkerFile, err) - } - return nil + return returnCode, commandErr } // optionOrDefault defaults to a value if option @@ -240,6 +138,8 @@ func (o *Options) waitForPrevStep() error { // wait for a file to exist that the last step wrote in a mounted shared dir if o.ShouldWaitForPrevStep { for { + // TODO(aaron-prindle) check for non-zero returnCode only + // as PreRunFile will have returnCode as it's contents? _, err := os.Stat(o.PreRunFile) if err == nil { break @@ -277,19 +177,3 @@ func (o *Options) postRunWriteFile(exitCode int) error { } return nil } - -func gracefullyTerminate(command *exec.Cmd, done <-chan error, gracePeriod time.Duration) { - if err := command.Process.Signal(os.Interrupt); err != nil { - logrus.WithError(err).Error("Could not interrupt process after timeout") - } - select { - case <-done: - logrus.Errorf("Process gracefully exited before %s grace period", gracePeriod) - // but we ignore the output error as we will want errTimedOut - case <-time.After(gracePeriod): - logrus.Errorf("Process did not exit before %s grace period", gracePeriod) - if err := command.Process.Kill(); err != nil { - logrus.WithError(err).Error("Could not kill process after grace period") - } - } -} diff --git a/pkg/entrypoint/run_test.go b/pkg/entrypoint/run_test.go index 3c99d052..28ca712a 100644 --- a/pkg/entrypoint/run_test.go +++ b/pkg/entrypoint/run_test.go @@ -16,116 +16,100 @@ limitations under the License. package entrypoint -// func TestOptions_Run(t *testing.T) { -// var testCases = []struct { -// name string -// args []string -// timeout time.Duration -// gracePeriod time.Duration -// expectedLog string -// expectedMarker string -// expectedShouldWaitForPrevStep bool -// expectedPreRunFile string -// expectedPostRunFile string -// expectedShouldRunPostRun bool -// }{ -// { -// name: "successful command", -// args: []string{"sh", "-c", "exit 0"}, -// expectedLog: "", -// expectedMarker: "0", -// expectedShouldRunPostRun: true, -// expectedPostRunFile: "0", -// }, -// { -// name: "successful command with output", -// args: []string{"echo", "test"}, -// expectedLog: "test\n", -// expectedMarker: "0", -// }, -// { -// name: "unsuccessful command", -// args: []string{"sh", "-c", "exit 12"}, -// expectedLog: "", -// expectedMarker: "12", -// }, -// { -// name: "unsuccessful command with output", -// args: []string{"sh", "-c", "echo test && exit 12"}, -// expectedLog: "test\n", -// expectedMarker: "12", -// }, -// { -// name: "command times out", -// args: []string{"sleep", "10"}, -// timeout: 1 * time.Second, -// gracePeriod: 1 * time.Second, -// expectedLog: "level=error msg=\"Process did not finish before 1s timeout\" \nlevel=error msg=\"Process gracefully exited before 1s grace period\" \n", -// expectedMarker: strconv.Itoa(InternalErrorCode), -// }, -// { -// name: "command times out and ignores interrupt", -// args: []string{"bash", "-c", "trap 'sleep 10' EXIT; sleep 10"}, -// timeout: 1 * time.Second, -// gracePeriod: 1 * time.Second, -// expectedLog: "level=error msg=\"Process did not finish before 1s timeout\" \nlevel=error msg=\"Process did not exit before 1s grace period\" \n", -// expectedMarker: strconv.Itoa(InternalErrorCode), -// }, -// } +import ( + "io/ioutil" + "os" + "path" + "testing" + "time" -// // we write logs to the process log if wrapping fails -// // and cannot write timestamps or we can't match text -// logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true}) + "github.com/knative/build/pkg/entrypoint/wrapper" + "github.com/sirupsen/logrus" +) -// for _, testCase := range testCases { -// t.Run(testCase.name, func(t *testing.T) { -// tmpDir, err := ioutil.TempDir("", testCase.name) -// if err != nil { -// t.Errorf("%s: error creating temp dir: %v", testCase.name, err) -// } -// defer func() { -// if err := os.RemoveAll(tmpDir); err != nil { -// t.Errorf("%s: error cleaning up temp dir: %v", testCase.name, err) -// } -// }() +func TestOptions_Run(t *testing.T) { + var testCases = []struct { + name string + args []string + timeout time.Duration + gracePeriod time.Duration + expectedLog string + expectedShouldWaitForPrevStep bool + expectedPreRunFile string + expectedPostRunFile string + expectedShouldRunPostRun bool + }{ + { + name: "successful command", + args: []string{"sh", "-c", "exit 0"}, + expectedLog: "", + expectedShouldRunPostRun: true, + expectedPostRunFile: "0", + }, + { + name: "successful command with output", + args: []string{"echo", "test"}, + expectedLog: "test\n", + }, + { + name: "unsuccessful command", + args: []string{"sh", "-c", "exit 12"}, + expectedLog: "", + }, + { + name: "unsuccessful command with output", + args: []string{"sh", "-c", "echo test && exit 12"}, + expectedLog: "test\n", + }, + } -// options := Options{ -// Args: testCase.args, -// Timeout: testCase.timeout, -// GracePeriod: testCase.gracePeriod, -// Options: &wrapper.Options{ -// ProcessLog: path.Join(tmpDir, "process-log.txt"), -// MarkerFile: path.Join(tmpDir, "marker-file.txt"), -// ShouldWaitForPrevStep: false, -// PreRunFile: path.Join(tmpDir, "0"), -// PostRunFile: path.Join(tmpDir, "0"), -// }, -// } + // we write logs to the process log if wrapping fails + // and cannot write timestamps or we can't match text + logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true}) -// if code := strconv.Itoa(options.Run()); code != testCase.expectedMarker { -// t.Errorf("%s: exit code %q does not match expected marker file contents %q", testCase.name, code, testCase.expectedMarker) -// } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", testCase.name) + if err != nil { + t.Errorf("%s: error creating temp dir: %v", testCase.name, err) + } + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + t.Errorf("%s: error cleaning up temp dir: %v", testCase.name, err) + } + }() -// compareFileContents(testCase.name, options.ProcessLog, testCase.expectedLog, t) -// compareFileContents(testCase.name, options.MarkerFile, testCase.expectedMarker, t) -// if options.ShouldWaitForPrevStep { -// compareFileContents(testCase.name, options.PreRunFile, -// testCase.expectedPreRunFile, t) -// } -// if options.ShouldRunPostRun { -// compareFileContents(testCase.name, options.PostRunFile, -// testCase.expectedPostRunFile, t) -// } -// }) -// } -// } + options := Options{ + Args: testCase.args, + Timeout: testCase.timeout, + GracePeriod: testCase.gracePeriod, + Options: &wrapper.Options{ + ProcessLog: path.Join(tmpDir, "process-log.txt"), + ShouldWaitForPrevStep: false, + PreRunFile: path.Join(tmpDir, "0"), + PostRunFile: path.Join(tmpDir, "0"), + }, + } -// func compareFileContents(name, file, expected string, t *testing.T) { -// data, err := ioutil.ReadFile(file) -// if err != nil { -// t.Fatalf("%s: could not read file: %v", name, err) -// } -// if string(data) != expected { -// t.Errorf("%s: expected contents: %q, got %q", name, expected, data) -// } -// } + compareFileContents(testCase.name, options.ProcessLog, testCase.expectedLog, t) + if options.ShouldWaitForPrevStep { + compareFileContents(testCase.name, options.PreRunFile, + testCase.expectedPreRunFile, t) + } + if options.ShouldRunPostRun { + compareFileContents(testCase.name, options.PostRunFile, + testCase.expectedPostRunFile, t) + } + }) + } +} + +func compareFileContents(name, file, expected string, t *testing.T) { + data, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("%s: could not read file: %v", name, err) + } + if string(data) != expected { + t.Errorf("%s: expected contents: %q, got %q", name, expected, data) + } +} diff --git a/pkg/entrypoint/wrapper/options.go b/pkg/entrypoint/wrapper/options.go index c2d38669..1cbfe39b 100644 --- a/pkg/entrypoint/wrapper/options.go +++ b/pkg/entrypoint/wrapper/options.go @@ -17,22 +17,12 @@ limitations under the License. package wrapper import ( - "errors" "flag" ) // Options exposes the configuration options // used when wrapping test execution type Options struct { - // ProcessLog will contain std{out,err} from the - // wrapped test process - ProcessLog string `json:"process_log"` - - // MarkerFile will be written with the exit code - // of the test process or an internal error code - // if the entrypoint fails. - MarkerFile string `json:"marker_file"` - // ShouldWaitForPrevStep will be written with the exit code // of the test process or an internal error code // if the entrypoint fails. @@ -48,29 +38,20 @@ type Options struct { // if the entrypoint fails. ShouldRunPostRun bool `json:"shouldRunPostRun"` - // MarkerFile will be written with the exit code + // PostRunFile will be written with the exit code // of the test process or an internal error code - // if the entrypoint fails. + // if the entrypoint fails or if it succeeds PostRunFile string `json:"postRunFile"` } // AddFlags adds flags to the FlagSet that populate // the wrapper options struct provided. func (o *Options) AddFlags(fs *flag.FlagSet) { - fs.StringVar(&o.ProcessLog, "process-log", "", "path to the log where stdout and stderr are streamed for the process we execute") - fs.StringVar(&o.MarkerFile, "marker-file", "", "file we write the return code of the process we execute once it has finished running") + return } // Validate ensures that the set of options are // self-consistent and valid func (o *Options) Validate() error { - if o.ProcessLog == "" { - return errors.New("no log file specified with --process-log") - } - - if o.MarkerFile == "" { - return errors.New("no marker file specified with --marker-file") - } - return nil } diff --git a/pkg/reconciler/build/resources/pod.go b/pkg/reconciler/build/resources/pod.go index e2a67bfe..a1ddecf9 100644 --- a/pkg/reconciler/build/resources/pod.go +++ b/pkg/reconciler/build/resources/pod.go @@ -51,6 +51,8 @@ import ( "github.com/knative/build/pkg/credentials" "github.com/knative/build/pkg/credentials/dockercreds" "github.com/knative/build/pkg/credentials/gitcreds" + + "github.com/knative/build/pkg/entrypoint" "github.com/knative/pkg/apis" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "golang.org/x/oauth2/google" @@ -534,7 +536,6 @@ const ( MountName = "tools" MountPoint = "/tools" BinaryLocation = MountPoint + "/entrypoint" - JSONConfigEnvVar = "ENTRYPOINT_OPTIONS" InitContainerName = "place-tools" // TODO(aaron-prindle) change this to wherever is sensible DefaultEntrypointImage = "gcr.io/aprindle-vm-test/entrypoint:latest" @@ -690,6 +691,13 @@ func GetRemoteEntrypoint(cache *Cache, image string, kubeclient kubernetes.Inter return nil, err // TODO(aaron-prindle) better err msg? } for _, secret := range sa.ImagePullSecrets { + // TODO(aaron-prindle) see if there is a better way than blocking + WaitForSecret(kubeclient, secret.Name, build.Namespace, "desc") + _, err = kubeclient.CoreV1().Secrets(build.Namespace).Get(secret.Name, metav1.GetOptions{}) + if err != nil { + return nil, err // TODO(aaron-prindle) better err msg? wrap errors? + } + kc, err = k8schain.New(kubeclient, k8schain.Options{ ImagePullSecrets: []string{secret.Name}, Namespace: build.Namespace, @@ -763,7 +771,7 @@ func RedirectSteps(steps []corev1.Container, kubeclient kubernetes.Interface, bu step.Args = []string{} step.Env = append(step.Env, corev1.EnvVar{ - Name: JSONConfigEnvVar, + Name: entrypoint.JSONConfigEnvVar, Value: e, }) step.VolumeMounts = append(step.VolumeMounts, toolsMount) diff --git a/test/serviceaccount/secret.yaml b/test/serviceaccount/secret.yaml index d75e32e4..f4f9ac49 100644 --- a/test/serviceaccount/secret.yaml +++ b/test/serviceaccount/secret.yaml @@ -15,12 +15,11 @@ apiVersion: v1 kind: Secret metadata: name: test-readonly-credentials -type: kubernetes.io/dockerconfigjson +type: kubernetes.io/dockercfg data: # Generated by: # kubectl create secret docker-registry regsecret --docker-server=https://gcr.io \ # --docker-username=_json_key --docker-password="$(cat /tmp/key.json)" \ # --docker-email=noreply@google.com --dry-run -o yaml # This service account is JUST a storage reader on gcr.io/build-crd-testing - # .dockercfg: eyJodHRwczovL2djci5pbyI6eyJ1c2VybmFtZSI6Il9qc29uX2tleSIsInBhc3N3b3JkIjoie1xuICBcInR5cGVcIjogXCJzZXJ2aWNlX2FjY291bnRcIixcbiAgXCJwcm9qZWN0X2lkXCI6IFwiYnVpbGQtY3JkLXRlc3RpbmdcIixcbiAgXCJwcml2YXRlX2tleV9pZFwiOiBcIjA1MDJhNDFhODEyZmI2NGNlNTZhNjhlYzU4MzJhYjBiYTExYzExZTZcIixcbiAgXCJwcml2YXRlX2tleVwiOiBcIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxcbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzlYNEVZT0FSYnhRTThcXG5EMnhYY2FaVGsrZ1k4ZWp1OTh0THFDUXFUckdNVzlSZVQyeE9ZNUF5Z2FsUFArcDd5WEVja3dCRC9IaE0wZ2xJXFxuN01UTGRlZUtXcityQTFMd0haeVdGVzdIME9uZjd3bllIRUhMV1VtYzNCQ09SRUR0SFJaN1pyUEJmMUhUQUEvM1xcbk1uVzVsWkhTTjlvanpTU0Z3NkFWdTZqNmF4YkJJSUo3NTRMcmdLZUFZdXJ3ZklRMlJMVHUyMDFrMklxTFliaGJcXG4zbVNWRzVSK3RiS3oxQ3ZNNTNuSENiN0NmdVZlV3NyQThrazd4SHJyTFFLTW1JOXYyc2dSdWd5TUF6d3ovNnpOXFxuaDUvaU14eGdlcTVXOHhrVngzSjJuWThKSmRIYWYvVDZBR3NPTkVvNDNweGVpUVZqblJmL0tuMTBUQ2MyRXNJWVxcblM0OVVzWjdCQWdNQkFBRUNnZ0VBQXVwbGR1a0NRUXVENVUvZ2FtSHQ3R2dXM0FNVjE4ZXFuSG5DYTJqbGFoK1NcXG5BZVVHbmhnSmpOdkUrcE1GbFN2NXVmMnAySzRlZC9veEQ2K0NwOVpYRFJqZ3ZmdEl5cWpsemJ3dkZjZ3p3TnVEXFxueWdVa3VwN0hlY0RzRDhUdGVBb2JUL1Zwd3E2ektNckJ3Q3ZOa3Z5NmJWbG9FajV4M2JYc2F4ZTk1RE8veXB1NlxcbncwVzk3enh3d0RKWTZLUWNJV01qaHJHeHZ3WDduaVVDZU00bGVXQkR5R3R3MXplSm40aEVjNk4zYWpRYWNYS2NcXG4rNFFseGNpYW1ZcVFXYlBudHhXUWhoUXpjSFdMaTJsOWNGYlpENyt1SkxGNGlONnk4bVZOVTNLM0sxYlJZclNEXFxuUlVwM2FVVkJYbUZnK1ovMnB1VkwrbVUzajNMTFdZeUJPa2V2dU9tZGdRS0JnUURlM0dJUWt5V0lTMTRUZE1PU1xcbkJpS0JDRHk4aDk2ZWhMMEhrRGJ5T2tTdFBLZEY5cHVFeFp4aHk3b2pIQ0lNNUZWcnBSTjI1cDRzRXp3RmFjK3ZcXG5KSUZnRXZxN21YZm1YaVhJTmllUG9FUWFDbm54RHhXZ21yMEhVS0VtUzlvTWRnTGNHVStrQ1ZHTnN6N0FPdW0wXFxuS3FZM3MyMlE5bFE2N0ZPeXFpdThXRlE3UVFLQmdRRFppRmhURVprUEVjcVpqbndKcFRCNTZaV1A5S1RzbFpQN1xcbndVNGJ6aTZ5K21leWYzTUorNEwyU3lIYzNjcFNNYmp0Tk9aQ3Q0N2I5MDhGVW1MWFVHTmhjd3VaakVReEZleTBcXG5tNDFjUzVlNFA0OWI5bjZ5TEJqQnJCb3FzMldCYWwyZWdkaE5KU3NDV29pWlA4L1pUOGVnWHZoN2I5MWp6b0syXFxucTJQVW1BNERnUUtCZ0FXTDJJanZFSTBPeXgyUzExY24vZTNXSmFUUGdOUFRHOTAzVXBhK3FuemhPSXgrTWFxaFxcblBGNFdzdUF5MEFvZ0dKd2dOSmJOOEh2S1VzRVR2QTV3eXlOMzlYTjd3MGNoYXJGTDM3b3NVK1dPQXpEam5qY3NcXG5BcTVPN0dQR21YdWI2RUJRQlBKaEpQMXd5NHYvSzFmSGcvRjQ3cTRmNDBMQUpPa2FZUkpENUh6QkFvR0JBTlVoXFxubklCUEpxcTRJTXZRNmNDOWc4QisxeFlEZWE5L1lrMXcrU21QR3Z3ckVYeTNHS3g0SzdsS3BiUHo3bTRYMzNzeFxcbnNFVS8rWTJWUW13UmExeFFtLzUzcks3VjJsNUpmL0Q0MDBqUm02WmZTQU92Z0RUcnRablVHSk1yejlFN3VOdzdcXG5sZ1VIM0pyaXZ5Ri9meE1JOHFzelFid1hQMCt4bnlxQXhFQWdkdUtCQW9HQUlNK1BTTllXQ1pYeERwU0hJMThkXFxuaktrb0FidzJNb3l3UUlsa2V1QW4xZFhGYWQxenNYUUdkVHJtWHl2N05QUCs4R1hCa25CTGkzY3Z4VGlsSklTeVxcbnVjTnJDTWlxTkFTbi9kcTdjV0RGVUFCZ2pYMTZKSDJETkZaL2wvVVZGM05EQUpqWENzMVg3eUlKeVhCNm94L3pcXG5hU2xxbElNVjM1REJEN3F4Unl1S3Nnaz1cXG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXFxuXCIsXG4gIFwiY2xpZW50X2VtYWlsXCI6IFwicHVsbC1zZWNyZXQtdGVzdGluZ0BidWlsZC1jcmQtdGVzdGluZy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbVwiLFxuICBcImNsaWVudF9pZFwiOiBcIjEwNzkzNTg2MjAzMzAyNTI1MTM1MlwiLFxuICBcImF1dGhfdXJpXCI6IFwiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGhcIixcbiAgXCJ0b2tlbl91cmlcIjogXCJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvdG9rZW5cIixcbiAgXCJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmxcIjogXCJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHNcIixcbiAgXCJjbGllbnRfeDUwOV9jZXJ0X3VybFwiOiBcImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvcHVsbC1zZWNyZXQtdGVzdGluZyU0MGJ1aWxkLWNyZC10ZXN0aW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tXCJcbn0iLCJlbWFpbCI6Im5vcmVwbHlAZ29vZ2xlLmNvbSIsImF1dGgiOiJYMnB6YjI1ZmEyVjVPbnNLSUNBaWRIbHdaU0k2SUNKelpYSjJhV05sWDJGalkyOTFiblFpTEFvZ0lDSndjbTlxWldOMFgybGtJam9nSW1KMWFXeGtMV055WkMxMFpYTjBhVzVuSWl3S0lDQWljSEpwZG1GMFpWOXJaWGxmYVdRaU9pQWlNRFV3TW1FME1XRTRNVEptWWpZMFkyVTFObUUyT0dWak5UZ3pNbUZpTUdKaE1URmpNVEZsTmlJc0NpQWdJbkJ5YVhaaGRHVmZhMlY1SWpvZ0lpMHRMUzB0UWtWSFNVNGdVRkpKVmtGVVJTQkxSVmt0TFMwdExWeHVUVWxKUlhaUlNVSkJSRUZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVTBOQ1MyTjNaMmRUYWtGblJVRkJiMGxDUVZGRE9WZzBSVmxQUVZKaWVGRk5PRnh1UkRKNFdHTmhXbFJySzJkWk9HVnFkVGs0ZEV4eFExRnhWSEpIVFZjNVVtVlVNbmhQV1RWQmVXZGhiRkJRSzNBM2VWaEZZMnQzUWtRdlNHaE5NR2RzU1Z4dU4wMVVUR1JsWlV0WGNpdHlRVEZNZDBoYWVWZEdWemRJTUU5dVpqZDNibGxJUlVoTVYxVnRZek5DUTA5U1JVUjBTRkphTjFweVVFSm1NVWhVUVVFdk0xeHVUVzVYTld4YVNGTk9PVzlxZWxOVFJuYzJRVloxTm1vMllYaGlRa2xKU2pjMU5FeHlaMHRsUVZsMWNuZG1TVkV5VWt4VWRUSXdNV3N5U1hGTVdXSm9ZbHh1TTIxVFZrYzFVaXQwWWt0Nk1VTjJUVFV6YmtoRFlqZERablZXWlZkemNrRTRhMnMzZUVoeWNreFJTMDF0U1RsMk1uTm5VblZuZVUxQmVuZDZMelo2VGx4dWFEVXZhVTE0ZUdkbGNUVlhPSGhyVm5nelNqSnVXVGhLU21SSVlXWXZWRFpCUjNOUFRrVnZORE53ZUdWcFVWWnFibEptTDB0dU1UQlVRMk15UlhOSldWeHVVelE1VlhOYU4wSkJaMDFDUVVGRlEyZG5SVUZCZFhCc1pIVnJRMUZSZFVRMVZTOW5ZVzFJZERkSFoxY3pRVTFXTVRobGNXNUlia05oTW1wc1lXZ3JVMXh1UVdWVlIyNW9aMHBxVG5aRkszQk5SbXhUZGpWMVpqSndNa3MwWldRdmIzaEVOaXREY0RsYVdFUlNhbWQyWm5SSmVYRnFiSHBpZDNaR1kyZDZkMDUxUkZ4dWVXZFZhM1Z3TjBobFkwUnpSRGhVZEdWQmIySlVMMVp3ZDNFMmVrdE5ja0ozUTNaT2EzWjVObUpXYkc5RmFqVjRNMkpZYzJGNFpUazFSRTh2ZVhCMU5seHVkekJYT1RkNmVIZDNSRXBaTmt0UlkwbFhUV3BvY2tkNGRuZFlOMjVwVlVObFRUUnNaVmRDUkhsSGRIY3hlbVZLYmpSb1JXTTJUak5oYWxGaFkxaExZMXh1S3pSUmJIaGphV0Z0V1hGUlYySlFiblI0VjFGb2FGRjZZMGhYVEdreWJEbGpSbUphUkRjcmRVcE1SalJwVGpaNU9HMVdUbFV6U3pOTE1XSlNXWEpUUkZ4dVVsVndNMkZWVmtKWWJVWm5LMW92TW5CMVZrd3JiVlV6YWpOTVRGZFplVUpQYTJWMmRVOXRaR2RSUzBKblVVUmxNMGRKVVd0NVYwbFRNVFJVWkUxUFUxeHVRbWxMUWtORWVUaG9PVFpsYUV3d1NHdEVZbmxQYTFOMFVFdGtSamx3ZFVWNFduaG9lVGR2YWtoRFNVMDFSbFp5Y0ZKT01qVndOSE5GZW5kR1lXTXJkbHh1U2tsR1owVjJjVGR0V0dadFdHbFlTVTVwWlZCdlJWRmhRMjV1ZUVSNFYyZHRjakJJVlV0RmJWTTViMDFrWjB4alIxVXJhME5XUjA1emVqZEJUM1Z0TUZ4dVMzRlpNM015TWxFNWJGRTJOMFpQZVhGcGRUaFhSbEUzVVZGTFFtZFJSRnBwUm1oVVJWcHJVRVZqY1ZwcWJuZEtjRlJDTlRaYVYxQTVTMVJ6YkZwUU4xeHVkMVUwWW5wcE5ua3JiV1Y1WmpOTlNpczBUREpUZVVoak0yTndVMDFpYW5ST1QxcERkRFEzWWprd09FWlZiVXhZVlVkT2FHTjNkVnBxUlZGNFJtVjVNRnh1YlRReFkxTTFaVFJRTkRsaU9XNDJlVXhDYWtKeVFtOXhjekpYUW1Gc01tVm5aR2hPU2xOelExZHZhVnBRT0M5YVZEaGxaMWgyYURkaU9URnFlbTlMTWx4dWNUSlFWVzFCTkVSblVVdENaMEZYVERKSmFuWkZTVEJQZVhneVV6RXhZMjR2WlROWFNtRlVVR2RPVUZSSE9UQXpWWEJoSzNGdWVtaFBTWGdyVFdGeGFGeHVVRVkwVjNOMVFYa3dRVzluUjBwM1owNUtZazQ0U0haTFZYTkZWSFpCTlhkNWVVNHpPVmhPTjNjd1kyaGhja1pNTXpkdmMxVXJWMDlCZWtScWJtcGpjMXh1UVhFMVR6ZEhVRWR0V0hWaU5rVkNVVUpRU21oS1VERjNlVFIyTDBzeFpraG5MMFkwTjNFMFpqUXdURUZLVDJ0aFdWSktSRFZJZWtKQmIwZENRVTVWYUZ4dWJrbENVRXB4Y1RSSlRYWlJObU5ET1djNFFpc3hlRmxFWldFNUwxbHJNWGNyVTIxUVIzWjNja1ZZZVROSFMzZzBTemRzUzNCaVVIbzNiVFJZTXpOemVGeHVjMFZWTHl0Wk1sWlJiWGRTWVRGNFVXMHZOVE55U3pkV01tdzFTbVl2UkRRd01HcFNiVFphWmxOQlQzWm5SRlJ5ZEZwdVZVZEtUWEo2T1VVM2RVNTNOMXh1YkdkVlNETktjbWwyZVVZdlpuaE5TVGh4YzNwUlluZFlVREFyZUc1NWNVRjRSVUZuWkhWTFFrRnZSMEZKVFN0UVUwNVpWME5hV0hoRWNGTklTVEU0WkZ4dWFrdHJiMEZpZHpKTmIzbDNVVWxzYTJWMVFXNHhaRmhHWVdReGVuTllVVWRrVkhKdFdIbDJOMDVRVUNzNFIxaENhMjVDVEdrelkzWjRWR2xzU2tsVGVWeHVkV05PY2tOTmFYRk9RVk51TDJSeE4yTlhSRVpWUVVKbmFsZ3hOa3BJTWtST1Jsb3ZiQzlWVmtZelRrUkJTbXBZUTNNeFdEZDVTVXA1V0VJMmIzZ3ZlbHh1WVZOc2NXeEpUVll6TlVSQ1JEZHhlRko1ZFV0eloyczlYRzR0TFMwdExVVk9SQ0JRVWtsV1FWUkZJRXRGV1MwdExTMHRYRzRpTEFvZ0lDSmpiR2xsYm5SZlpXMWhhV3dpT2lBaWNIVnNiQzF6WldOeVpYUXRkR1Z6ZEdsdVowQmlkV2xzWkMxamNtUXRkR1Z6ZEdsdVp5NXBZVzB1WjNObGNuWnBZMlZoWTJOdmRXNTBMbU52YlNJc0NpQWdJbU5zYVdWdWRGOXBaQ0k2SUNJeE1EYzVNelU0TmpJd016TXdNalV5TlRFek5USWlMQW9nSUNKaGRYUm9YM1Z5YVNJNklDSm9kSFJ3Y3pvdkwyRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMHZieTl2WVhWMGFESXZZWFYwYUNJc0NpQWdJblJ2YTJWdVgzVnlhU0k2SUNKb2RIUndjem92TDJGalkyOTFiblJ6TG1kdmIyZHNaUzVqYjIwdmJ5OXZZWFYwYURJdmRHOXJaVzRpTEFvZ0lDSmhkWFJvWDNCeWIzWnBaR1Z5WDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2YjJGMWRHZ3lMM1l4TDJObGNuUnpJaXdLSUNBaVkyeHBaVzUwWDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2Y205aWIzUXZkakV2YldWMFlXUmhkR0V2ZURVd09TOXdkV3hzTFhObFkzSmxkQzEwWlhOMGFXNW5KVFF3WW5WcGJHUXRZM0prTFhSbGMzUnBibWN1YVdGdExtZHpaWEoyYVdObFlXTmpiM1Z1ZEM1amIyMGlDbjA9In19 - .dockerconfigjson: eyAiYXV0aHMiOiB7ICJnY3IuaW8iOiB7ICJhdXRoIjogIlgycHpiMjVmYTJWNU9uc2dJQ0owZVhCbElqb2dJbk5sY25acFkyVmZZV05qYjNWdWRDSXNJQ0FpY0hKdmFtVmpkRjlwWkNJNklDSmlkV2xzWkMxamNtUXRkR1Z6ZEdsdVp5SXNJQ0FpY0hKcGRtRjBaVjlyWlhsZmFXUWlPaUFpTURVd01tRTBNV0U0TVRKbVlqWTBZMlUxTm1FMk9HVmpOVGd6TW1GaU1HSmhNVEZqTVRGbE5pSXNJQ0FpY0hKcGRtRjBaVjlyWlhraU9pQWlMUzB0TFMxQ1JVZEpUaUJRVWtsV1FWUkZJRXRGV1MwdExTMHRYRzVOU1VsRmRsRkpRa0ZFUVU1Q1oydHhhR3RwUnpsM01FSkJVVVZHUVVGVFEwSkxZM2RuWjFOcVFXZEZRVUZ2U1VKQlVVTTVXRFJGV1U5QlVtSjRVVTA0WEc1RU1uaFlZMkZhVkdzcloxazRaV3AxT1RoMFRIRkRVWEZVY2tkTlZ6bFNaVlF5ZUU5Wk5VRjVaMkZzVUZBcmNEZDVXRVZqYTNkQ1JDOUlhRTB3WjJ4SlhHNDNUVlJNWkdWbFMxZHlLM0pCTVV4M1NGcDVWMFpYTjBnd1QyNW1OM2R1V1VoRlNFeFhWVzFqTTBKRFQxSkZSSFJJVWxvM1duSlFRbVl4U0ZSQlFTOHpYRzVOYmxjMWJGcElVMDQ1YjJwNlUxTkdkelpCVm5VMmFqWmhlR0pDU1VsS056VTBUSEpuUzJWQldYVnlkMlpKVVRKU1RGUjFNakF4YXpKSmNVeFpZbWhpWEc0emJWTldSelZTSzNSaVMzb3hRM1pOTlROdVNFTmlOME5tZFZabFYzTnlRVGhyYXpkNFNISnlURkZMVFcxSk9YWXljMmRTZFdkNVRVRjZkM292Tm5wT1hHNW9OUzlwVFhoNFoyVnhOVmM0ZUd0V2VETktNbTVaT0VwS1pFaGhaaTlVTmtGSGMwOU9SVzgwTTNCNFpXbFJWbXB1VW1ZdlMyNHhNRlJEWXpKRmMwbFpYRzVUTkRsVmMxbzNRa0ZuVFVKQlFVVkRaMmRGUVVGMWNHeGtkV3REVVZGMVJEVlZMMmRoYlVoME4wZG5Wek5CVFZZeE9HVnhia2h1UTJFeWFteGhhQ3RUWEc1QlpWVkhibWhuU21wT2RrVXJjRTFHYkZOMk5YVm1NbkF5U3pSbFpDOXZlRVEySzBOd09WcFlSRkpxWjNabWRFbDVjV3BzZW1KM2RrWmpaM3AzVG5WRVhHNTVaMVZyZFhBM1NHVmpSSE5FT0ZSMFpVRnZZbFF2Vm5CM2NUWjZTMDF5UW5kRGRrNXJkbmsyWWxac2IwVnFOWGd6WWxoellYaGxPVFZFVHk5NWNIVTJYRzUzTUZjNU4zcDRkM2RFU2xrMlMxRmpTVmROYW1oeVIzaDJkMWczYm1sVlEyVk5OR3hsVjBKRWVVZDBkekY2WlVwdU5HaEZZelpPTTJGcVVXRmpXRXRqWEc0ck5GRnNlR05wWVcxWmNWRlhZbEJ1ZEhoWFVXaG9VWHBqU0ZkTWFUSnNPV05HWWxwRU55dDFTa3hHTkdsT05uazRiVlpPVlROTE0wc3hZbEpaY2xORVhHNVNWWEF6WVZWV1FsaHRSbWNyV2k4eWNIVldUQ3R0VlROcU0weE1WMWw1UWs5clpYWjFUMjFrWjFGTFFtZFJSR1V6UjBsUmEzbFhTVk14TkZSa1RVOVRYRzVDYVV0Q1EwUjVPR2c1Tm1Wb1REQklhMFJpZVU5clUzUlFTMlJHT1hCMVJYaGFlR2g1TjI5cVNFTkpUVFZHVm5Kd1VrNHlOWEEwYzBWNmQwWmhZeXQyWEc1S1NVWm5SWFp4TjIxWVptMVlhVmhKVG1sbFVHOUZVV0ZEYm01NFJIaFhaMjF5TUVoVlMwVnRVemx2VFdSblRHTkhWU3RyUTFaSFRuTjZOMEZQZFcwd1hHNUxjVmt6Y3pJeVVUbHNVVFkzUms5NWNXbDFPRmRHVVRkUlVVdENaMUZFV21sR2FGUkZXbXRRUldOeFdtcHVkMHB3VkVJMU5scFhVRGxMVkhOc1dsQTNYRzUzVlRSaWVtazJlU3R0WlhsbU0wMUtLelJNTWxONVNHTXpZM0JUVFdKcWRFNVBXa04wTkRkaU9UQTRSbFZ0VEZoVlIwNW9ZM2QxV21wRlVYaEdaWGt3WEc1dE5ERmpVelZsTkZBME9XSTVialo1VEVKcVFuSkNiM0Z6TWxkQ1lXd3laV2RrYUU1S1UzTkRWMjlwV2xBNEwxcFVPR1ZuV0hab04ySTVNV3A2YjBzeVhHNXhNbEJWYlVFMFJHZFJTMEpuUVZkTU1rbHFka1ZKTUU5NWVESlRNVEZqYmk5bE0xZEtZVlJRWjA1UVZFYzVNRE5WY0dFcmNXNTZhRTlKZUN0TllYRm9YRzVRUmpSWGMzVkJlVEJCYjJkSFNuZG5Ua3BpVGpoSWRrdFZjMFZVZGtFMWQzbDVUak01V0U0M2R6QmphR0Z5Umt3ek4yOXpWU3RYVDBGNlJHcHVhbU56WEc1QmNUVlBOMGRRUjIxWWRXSTJSVUpSUWxCS2FFcFFNWGQ1TkhZdlN6Rm1TR2N2UmpRM2NUUm1OREJNUVVwUGEyRlpVa3BFTlVoNlFrRnZSMEpCVGxWb1hHNXVTVUpRU25GeE5FbE5kbEUyWTBNNVp6aENLekY0V1VSbFlUa3ZXV3N4ZHl0VGJWQkhkbmR5UlZoNU0wZExlRFJMTjJ4TGNHSlFlamR0TkZnek0zTjRYRzV6UlZVdksxa3lWbEZ0ZDFKaE1YaFJiUzgxTTNKTE4xWXliRFZLWmk5RU5EQXdhbEp0TmxwbVUwRlBkbWRFVkhKMFdtNVZSMHBOY25vNVJUZDFUbmMzWEc1c1oxVklNMHB5YVhaNVJpOW1lRTFKT0hGemVsRmlkMWhRTUN0NGJubHhRWGhGUVdka2RVdENRVzlIUVVsTksxQlRUbGxYUTFwWWVFUndVMGhKTVRoa1hHNXFTMnR2UVdKM01rMXZlWGRSU1d4clpYVkJiakZrV0VaaFpERjZjMWhSUjJSVWNtMVllWFkzVGxCUUt6aEhXRUpyYmtKTWFUTmpkbmhVYVd4S1NWTjVYRzUxWTA1eVEwMXBjVTVCVTI0dlpIRTNZMWRFUmxWQlFtZHFXREUyU2tneVJFNUdXaTlzTDFWV1JqTk9SRUZLYWxoRGN6RllOM2xKU25sWVFqWnZlQzk2WEc1aFUyeHhiRWxOVmpNMVJFSkVOM0Y0VW5sMVMzTm5hejFjYmkwdExTMHRSVTVFSUZCU1NWWkJWRVVnUzBWWkxTMHRMUzFjYmlJc0lDQWlZMnhwWlc1MFgyVnRZV2xzSWpvZ0luQjFiR3d0YzJWamNtVjBMWFJsYzNScGJtZEFZblZwYkdRdFkzSmtMWFJsYzNScGJtY3VhV0Z0TG1kelpYSjJhV05sWVdOamIzVnVkQzVqYjIwaUxDQWdJbU5zYVdWdWRGOXBaQ0k2SUNJeE1EYzVNelU0TmpJd016TXdNalV5TlRFek5USWlMQ0FnSW1GMWRHaGZkWEpwSWpvZ0ltaDBkSEJ6T2k4dllXTmpiM1Z1ZEhNdVoyOXZaMnhsTG1OdmJTOXZMMjloZFhSb01pOWhkWFJvSWl3Z0lDSjBiMnRsYmw5MWNta2lPaUFpYUhSMGNITTZMeTloWTJOdmRXNTBjeTVuYjI5bmJHVXVZMjl0TDI4dmIyRjFkR2d5TDNSdmEyVnVJaXdnSUNKaGRYUm9YM0J5YjNacFpHVnlYM2cxTURsZlkyVnlkRjkxY213aU9pQWlhSFIwY0hNNkx5OTNkM2N1WjI5dloyeGxZWEJwY3k1amIyMHZiMkYxZEdneUwzWXhMMk5sY25Seklpd2dJQ0pqYkdsbGJuUmZlRFV3T1Y5alpYSjBYM1Z5YkNJNklDSm9kSFJ3Y3pvdkwzZDNkeTVuYjI5bmJHVmhjR2x6TG1OdmJTOXliMkp2ZEM5Mk1TOXRaWFJoWkdGMFlTOTROVEE1TDNCMWJHd3RjMlZqY21WMExYUmxjM1JwYm1jbE5EQmlkV2xzWkMxamNtUXRkR1Z6ZEdsdVp5NXBZVzB1WjNObGNuWnBZMlZoWTJOdmRXNTBMbU52YlNKOSIgfSB9LCAiSHR0cEhlYWRlcnMiOiB7ICJVc2VyLUFnZW50IjogIkRvY2tlci1DbGllbnQvMTguMDYuMS1jZSAobGludXgpIiB9fQ== + .dockercfg: eyJodHRwczovL2djci5pbyI6eyJ1c2VybmFtZSI6Il9qc29uX2tleSIsInBhc3N3b3JkIjoie1xuICBcInR5cGVcIjogXCJzZXJ2aWNlX2FjY291bnRcIixcbiAgXCJwcm9qZWN0X2lkXCI6IFwiYnVpbGQtY3JkLXRlc3RpbmdcIixcbiAgXCJwcml2YXRlX2tleV9pZFwiOiBcIjA1MDJhNDFhODEyZmI2NGNlNTZhNjhlYzU4MzJhYjBiYTExYzExZTZcIixcbiAgXCJwcml2YXRlX2tleVwiOiBcIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxcbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzlYNEVZT0FSYnhRTThcXG5EMnhYY2FaVGsrZ1k4ZWp1OTh0THFDUXFUckdNVzlSZVQyeE9ZNUF5Z2FsUFArcDd5WEVja3dCRC9IaE0wZ2xJXFxuN01UTGRlZUtXcityQTFMd0haeVdGVzdIME9uZjd3bllIRUhMV1VtYzNCQ09SRUR0SFJaN1pyUEJmMUhUQUEvM1xcbk1uVzVsWkhTTjlvanpTU0Z3NkFWdTZqNmF4YkJJSUo3NTRMcmdLZUFZdXJ3ZklRMlJMVHUyMDFrMklxTFliaGJcXG4zbVNWRzVSK3RiS3oxQ3ZNNTNuSENiN0NmdVZlV3NyQThrazd4SHJyTFFLTW1JOXYyc2dSdWd5TUF6d3ovNnpOXFxuaDUvaU14eGdlcTVXOHhrVngzSjJuWThKSmRIYWYvVDZBR3NPTkVvNDNweGVpUVZqblJmL0tuMTBUQ2MyRXNJWVxcblM0OVVzWjdCQWdNQkFBRUNnZ0VBQXVwbGR1a0NRUXVENVUvZ2FtSHQ3R2dXM0FNVjE4ZXFuSG5DYTJqbGFoK1NcXG5BZVVHbmhnSmpOdkUrcE1GbFN2NXVmMnAySzRlZC9veEQ2K0NwOVpYRFJqZ3ZmdEl5cWpsemJ3dkZjZ3p3TnVEXFxueWdVa3VwN0hlY0RzRDhUdGVBb2JUL1Zwd3E2ektNckJ3Q3ZOa3Z5NmJWbG9FajV4M2JYc2F4ZTk1RE8veXB1NlxcbncwVzk3enh3d0RKWTZLUWNJV01qaHJHeHZ3WDduaVVDZU00bGVXQkR5R3R3MXplSm40aEVjNk4zYWpRYWNYS2NcXG4rNFFseGNpYW1ZcVFXYlBudHhXUWhoUXpjSFdMaTJsOWNGYlpENyt1SkxGNGlONnk4bVZOVTNLM0sxYlJZclNEXFxuUlVwM2FVVkJYbUZnK1ovMnB1VkwrbVUzajNMTFdZeUJPa2V2dU9tZGdRS0JnUURlM0dJUWt5V0lTMTRUZE1PU1xcbkJpS0JDRHk4aDk2ZWhMMEhrRGJ5T2tTdFBLZEY5cHVFeFp4aHk3b2pIQ0lNNUZWcnBSTjI1cDRzRXp3RmFjK3ZcXG5KSUZnRXZxN21YZm1YaVhJTmllUG9FUWFDbm54RHhXZ21yMEhVS0VtUzlvTWRnTGNHVStrQ1ZHTnN6N0FPdW0wXFxuS3FZM3MyMlE5bFE2N0ZPeXFpdThXRlE3UVFLQmdRRFppRmhURVprUEVjcVpqbndKcFRCNTZaV1A5S1RzbFpQN1xcbndVNGJ6aTZ5K21leWYzTUorNEwyU3lIYzNjcFNNYmp0Tk9aQ3Q0N2I5MDhGVW1MWFVHTmhjd3VaakVReEZleTBcXG5tNDFjUzVlNFA0OWI5bjZ5TEJqQnJCb3FzMldCYWwyZWdkaE5KU3NDV29pWlA4L1pUOGVnWHZoN2I5MWp6b0syXFxucTJQVW1BNERnUUtCZ0FXTDJJanZFSTBPeXgyUzExY24vZTNXSmFUUGdOUFRHOTAzVXBhK3FuemhPSXgrTWFxaFxcblBGNFdzdUF5MEFvZ0dKd2dOSmJOOEh2S1VzRVR2QTV3eXlOMzlYTjd3MGNoYXJGTDM3b3NVK1dPQXpEam5qY3NcXG5BcTVPN0dQR21YdWI2RUJRQlBKaEpQMXd5NHYvSzFmSGcvRjQ3cTRmNDBMQUpPa2FZUkpENUh6QkFvR0JBTlVoXFxubklCUEpxcTRJTXZRNmNDOWc4QisxeFlEZWE5L1lrMXcrU21QR3Z3ckVYeTNHS3g0SzdsS3BiUHo3bTRYMzNzeFxcbnNFVS8rWTJWUW13UmExeFFtLzUzcks3VjJsNUpmL0Q0MDBqUm02WmZTQU92Z0RUcnRablVHSk1yejlFN3VOdzdcXG5sZ1VIM0pyaXZ5Ri9meE1JOHFzelFid1hQMCt4bnlxQXhFQWdkdUtCQW9HQUlNK1BTTllXQ1pYeERwU0hJMThkXFxuaktrb0FidzJNb3l3UUlsa2V1QW4xZFhGYWQxenNYUUdkVHJtWHl2N05QUCs4R1hCa25CTGkzY3Z4VGlsSklTeVxcbnVjTnJDTWlxTkFTbi9kcTdjV0RGVUFCZ2pYMTZKSDJETkZaL2wvVVZGM05EQUpqWENzMVg3eUlKeVhCNm94L3pcXG5hU2xxbElNVjM1REJEN3F4Unl1S3Nnaz1cXG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXFxuXCIsXG4gIFwiY2xpZW50X2VtYWlsXCI6IFwicHVsbC1zZWNyZXQtdGVzdGluZ0BidWlsZC1jcmQtdGVzdGluZy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbVwiLFxuICBcImNsaWVudF9pZFwiOiBcIjEwNzkzNTg2MjAzMzAyNTI1MTM1MlwiLFxuICBcImF1dGhfdXJpXCI6IFwiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGhcIixcbiAgXCJ0b2tlbl91cmlcIjogXCJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvdG9rZW5cIixcbiAgXCJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmxcIjogXCJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHNcIixcbiAgXCJjbGllbnRfeDUwOV9jZXJ0X3VybFwiOiBcImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvcHVsbC1zZWNyZXQtdGVzdGluZyU0MGJ1aWxkLWNyZC10ZXN0aW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tXCJcbn0iLCJlbWFpbCI6Im5vcmVwbHlAZ29vZ2xlLmNvbSIsImF1dGgiOiJYMnB6YjI1ZmEyVjVPbnNLSUNBaWRIbHdaU0k2SUNKelpYSjJhV05sWDJGalkyOTFiblFpTEFvZ0lDSndjbTlxWldOMFgybGtJam9nSW1KMWFXeGtMV055WkMxMFpYTjBhVzVuSWl3S0lDQWljSEpwZG1GMFpWOXJaWGxmYVdRaU9pQWlNRFV3TW1FME1XRTRNVEptWWpZMFkyVTFObUUyT0dWak5UZ3pNbUZpTUdKaE1URmpNVEZsTmlJc0NpQWdJbkJ5YVhaaGRHVmZhMlY1SWpvZ0lpMHRMUzB0UWtWSFNVNGdVRkpKVmtGVVJTQkxSVmt0TFMwdExWeHVUVWxKUlhaUlNVSkJSRUZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVTBOQ1MyTjNaMmRUYWtGblJVRkJiMGxDUVZGRE9WZzBSVmxQUVZKaWVGRk5PRnh1UkRKNFdHTmhXbFJySzJkWk9HVnFkVGs0ZEV4eFExRnhWSEpIVFZjNVVtVlVNbmhQV1RWQmVXZGhiRkJRSzNBM2VWaEZZMnQzUWtRdlNHaE5NR2RzU1Z4dU4wMVVUR1JsWlV0WGNpdHlRVEZNZDBoYWVWZEdWemRJTUU5dVpqZDNibGxJUlVoTVYxVnRZek5DUTA5U1JVUjBTRkphTjFweVVFSm1NVWhVUVVFdk0xeHVUVzVYTld4YVNGTk9PVzlxZWxOVFJuYzJRVloxTm1vMllYaGlRa2xKU2pjMU5FeHlaMHRsUVZsMWNuZG1TVkV5VWt4VWRUSXdNV3N5U1hGTVdXSm9ZbHh1TTIxVFZrYzFVaXQwWWt0Nk1VTjJUVFV6YmtoRFlqZERablZXWlZkemNrRTRhMnMzZUVoeWNreFJTMDF0U1RsMk1uTm5VblZuZVUxQmVuZDZMelo2VGx4dWFEVXZhVTE0ZUdkbGNUVlhPSGhyVm5nelNqSnVXVGhLU21SSVlXWXZWRFpCUjNOUFRrVnZORE53ZUdWcFVWWnFibEptTDB0dU1UQlVRMk15UlhOSldWeHVVelE1VlhOYU4wSkJaMDFDUVVGRlEyZG5SVUZCZFhCc1pIVnJRMUZSZFVRMVZTOW5ZVzFJZERkSFoxY3pRVTFXTVRobGNXNUlia05oTW1wc1lXZ3JVMXh1UVdWVlIyNW9aMHBxVG5aRkszQk5SbXhUZGpWMVpqSndNa3MwWldRdmIzaEVOaXREY0RsYVdFUlNhbWQyWm5SSmVYRnFiSHBpZDNaR1kyZDZkMDUxUkZ4dWVXZFZhM1Z3TjBobFkwUnpSRGhVZEdWQmIySlVMMVp3ZDNFMmVrdE5ja0ozUTNaT2EzWjVObUpXYkc5RmFqVjRNMkpZYzJGNFpUazFSRTh2ZVhCMU5seHVkekJYT1RkNmVIZDNSRXBaTmt0UlkwbFhUV3BvY2tkNGRuZFlOMjVwVlVObFRUUnNaVmRDUkhsSGRIY3hlbVZLYmpSb1JXTTJUak5oYWxGaFkxaExZMXh1S3pSUmJIaGphV0Z0V1hGUlYySlFiblI0VjFGb2FGRjZZMGhYVEdreWJEbGpSbUphUkRjcmRVcE1SalJwVGpaNU9HMVdUbFV6U3pOTE1XSlNXWEpUUkZ4dVVsVndNMkZWVmtKWWJVWm5LMW92TW5CMVZrd3JiVlV6YWpOTVRGZFplVUpQYTJWMmRVOXRaR2RSUzBKblVVUmxNMGRKVVd0NVYwbFRNVFJVWkUxUFUxeHVRbWxMUWtORWVUaG9PVFpsYUV3d1NHdEVZbmxQYTFOMFVFdGtSamx3ZFVWNFduaG9lVGR2YWtoRFNVMDFSbFp5Y0ZKT01qVndOSE5GZW5kR1lXTXJkbHh1U2tsR1owVjJjVGR0V0dadFdHbFlTVTVwWlZCdlJWRmhRMjV1ZUVSNFYyZHRjakJJVlV0RmJWTTViMDFrWjB4alIxVXJhME5XUjA1emVqZEJUM1Z0TUZ4dVMzRlpNM015TWxFNWJGRTJOMFpQZVhGcGRUaFhSbEUzVVZGTFFtZFJSRnBwUm1oVVJWcHJVRVZqY1ZwcWJuZEtjRlJDTlRaYVYxQTVTMVJ6YkZwUU4xeHVkMVUwWW5wcE5ua3JiV1Y1WmpOTlNpczBUREpUZVVoak0yTndVMDFpYW5ST1QxcERkRFEzWWprd09FWlZiVXhZVlVkT2FHTjNkVnBxUlZGNFJtVjVNRnh1YlRReFkxTTFaVFJRTkRsaU9XNDJlVXhDYWtKeVFtOXhjekpYUW1Gc01tVm5aR2hPU2xOelExZHZhVnBRT0M5YVZEaGxaMWgyYURkaU9URnFlbTlMTWx4dWNUSlFWVzFCTkVSblVVdENaMEZYVERKSmFuWkZTVEJQZVhneVV6RXhZMjR2WlROWFNtRlVVR2RPVUZSSE9UQXpWWEJoSzNGdWVtaFBTWGdyVFdGeGFGeHVVRVkwVjNOMVFYa3dRVzluUjBwM1owNUtZazQ0U0haTFZYTkZWSFpCTlhkNWVVNHpPVmhPTjNjd1kyaGhja1pNTXpkdmMxVXJWMDlCZWtScWJtcGpjMXh1UVhFMVR6ZEhVRWR0V0hWaU5rVkNVVUpRU21oS1VERjNlVFIyTDBzeFpraG5MMFkwTjNFMFpqUXdURUZLVDJ0aFdWSktSRFZJZWtKQmIwZENRVTVWYUZ4dWJrbENVRXB4Y1RSSlRYWlJObU5ET1djNFFpc3hlRmxFWldFNUwxbHJNWGNyVTIxUVIzWjNja1ZZZVROSFMzZzBTemRzUzNCaVVIbzNiVFJZTXpOemVGeHVjMFZWTHl0Wk1sWlJiWGRTWVRGNFVXMHZOVE55U3pkV01tdzFTbVl2UkRRd01HcFNiVFphWmxOQlQzWm5SRlJ5ZEZwdVZVZEtUWEo2T1VVM2RVNTNOMXh1YkdkVlNETktjbWwyZVVZdlpuaE5TVGh4YzNwUlluZFlVREFyZUc1NWNVRjRSVUZuWkhWTFFrRnZSMEZKVFN0UVUwNVpWME5hV0hoRWNGTklTVEU0WkZ4dWFrdHJiMEZpZHpKTmIzbDNVVWxzYTJWMVFXNHhaRmhHWVdReGVuTllVVWRrVkhKdFdIbDJOMDVRVUNzNFIxaENhMjVDVEdrelkzWjRWR2xzU2tsVGVWeHVkV05PY2tOTmFYRk9RVk51TDJSeE4yTlhSRVpWUVVKbmFsZ3hOa3BJTWtST1Jsb3ZiQzlWVmtZelRrUkJTbXBZUTNNeFdEZDVTVXA1V0VJMmIzZ3ZlbHh1WVZOc2NXeEpUVll6TlVSQ1JEZHhlRko1ZFV0eloyczlYRzR0TFMwdExVVk9SQ0JRVWtsV1FWUkZJRXRGV1MwdExTMHRYRzRpTEFvZ0lDSmpiR2xsYm5SZlpXMWhhV3dpT2lBaWNIVnNiQzF6WldOeVpYUXRkR1Z6ZEdsdVowQmlkV2xzWkMxamNtUXRkR1Z6ZEdsdVp5NXBZVzB1WjNObGNuWnBZMlZoWTJOdmRXNTBMbU52YlNJc0NpQWdJbU5zYVdWdWRGOXBaQ0k2SUNJeE1EYzVNelU0TmpJd016TXdNalV5TlRFek5USWlMQW9nSUNKaGRYUm9YM1Z5YVNJNklDSm9kSFJ3Y3pvdkwyRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMHZieTl2WVhWMGFESXZZWFYwYUNJc0NpQWdJblJ2YTJWdVgzVnlhU0k2SUNKb2RIUndjem92TDJGalkyOTFiblJ6TG1kdmIyZHNaUzVqYjIwdmJ5OXZZWFYwYURJdmRHOXJaVzRpTEFvZ0lDSmhkWFJvWDNCeWIzWnBaR1Z5WDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2YjJGMWRHZ3lMM1l4TDJObGNuUnpJaXdLSUNBaVkyeHBaVzUwWDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2Y205aWIzUXZkakV2YldWMFlXUmhkR0V2ZURVd09TOXdkV3hzTFhObFkzSmxkQzEwWlhOMGFXNW5KVFF3WW5WcGJHUXRZM0prTFhSbGMzUnBibWN1YVdGdExtZHpaWEoyYVdObFlXTmpiM1Z1ZEM1amIyMGlDbjA9In19