Merge pull request #306 from priyawadhwa/refactor

Refactor command line arguments and the executor
This commit is contained in:
priyawadhwa 2018-08-23 14:44:11 -07:00 committed by GitHub
commit 360390056c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 208 additions and 175 deletions

View File

@ -240,7 +240,7 @@ To configure credentials, you will need to do the following:
- name: aws-secret
mountPath: /root/.aws/
- name: docker-config
mountPath: /root/.docker/
mountPath: /kaniko/.docker/
restartPolicy: Never
volumes:
- name: aws-secret

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"errors"
"os"
"path/filepath"
"strings"
@ -25,44 +24,25 @@ import (
"github.com/GoogleContainerTools/kaniko/pkg/buildcontext"
"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/GoogleContainerTools/kaniko/pkg/executor"
"github.com/GoogleContainerTools/kaniko/pkg/options"
"github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/genuinetools/amicontained/container"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
dockerfilePath string
destinations multiArg
srcContext string
snapshotMode string
bucket string
dockerInsecureSkipTLSVerify bool
logLevel string
force bool
buildArgs multiArg
tarPath string
singleSnapshot bool
reproducible bool
target string
noPush bool
opts = &options.KanikoOptions{}
logLevel string
force bool
)
func init() {
RootCmd.PersistentFlags().StringVarP(&dockerfilePath, "dockerfile", "f", "Dockerfile", "Path to the dockerfile to be built.")
RootCmd.PersistentFlags().StringVarP(&srcContext, "context", "c", "/workspace/", "Path to the dockerfile build context.")
RootCmd.PersistentFlags().StringVarP(&bucket, "bucket", "b", "", "Name of the GCS bucket from which to access build context as tarball.")
RootCmd.PersistentFlags().VarP(&destinations, "destination", "d", "Registry the final image should be pushed to. Set it repeatedly for multiple destinations.")
RootCmd.PersistentFlags().StringVarP(&snapshotMode, "snapshotMode", "", "full", "Set this flag to change the file attributes inspected during snapshotting")
RootCmd.PersistentFlags().VarP(&buildArgs, "build-arg", "", "This flag allows you to pass in ARG values at build time. Set it repeatedly for multiple values.")
RootCmd.PersistentFlags().BoolVarP(&dockerInsecureSkipTLSVerify, "insecure-skip-tls-verify", "", false, "Push to insecure registry ignoring TLS verify")
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", constants.DefaultLogLevel, "Log level (debug, info, warn, error, fatal, panic")
RootCmd.PersistentFlags().BoolVarP(&force, "force", "", false, "Force building outside of a container")
RootCmd.PersistentFlags().StringVarP(&tarPath, "tarPath", "", "", "Path to save the image in as a tarball instead of pushing")
RootCmd.PersistentFlags().BoolVarP(&singleSnapshot, "single-snapshot", "", false, "Set this flag to take a single snapshot at the end of the build.")
RootCmd.PersistentFlags().BoolVarP(&reproducible, "reproducible", "", false, "Strip timestamps out of the image to make it reproducible")
RootCmd.PersistentFlags().StringVarP(&target, "target", "", "", " Set the target build stage to build")
RootCmd.PersistentFlags().BoolVarP(&noPush, "no-push", "", false, "Do not push the image to the registry")
addKanikoOptionsFlags(RootCmd)
addHiddenFlags(RootCmd)
}
var RootCmd = &cobra.Command{
@ -71,109 +51,109 @@ var RootCmd = &cobra.Command{
if err := util.SetLogLevel(logLevel); err != nil {
return err
}
if err := resolveSourceContext(); err != nil {
return err
}
if !noPush && len(destinations) == 0 {
if !opts.NoPush && len(opts.Destinations) == 0 {
return errors.New("You must provide --destination, or use --no-push")
}
return checkDockerfilePath()
if err := resolveSourceContext(); err != nil {
return errors.Wrap(err, "error resolving source context")
}
return resolveDockerfilePath()
},
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
if !checkContained() {
if !force {
logrus.Error("kaniko should only be run inside of a container, run with the --force flag if you are sure you want to continue.")
os.Exit(1)
return errors.New("kaniko should only be run inside of a container, run with the --force flag if you are sure you want to continue")
}
logrus.Warn("kaniko is being run outside of a container. This can have dangerous effects on your system")
}
if err := os.Chdir("/"); err != nil {
logrus.Error(err)
os.Exit(1)
return errors.Wrap(err, "error changing to root dir")
}
image, err := executor.DoBuild(executor.KanikoBuildArgs{
DockerfilePath: absouteDockerfilePath(),
SrcContext: srcContext,
SnapshotMode: snapshotMode,
Args: buildArgs,
SingleSnapshot: singleSnapshot,
Reproducible: reproducible,
Target: target,
})
image, err := executor.DoBuild(opts)
if err != nil {
logrus.Error(err)
os.Exit(1)
return errors.Wrap(err, "error building image")
}
if noPush {
logrus.Info("Skipping push to container registry due to --no-push flag")
os.Exit(0)
}
if err := executor.DoPush(image, destinations, tarPath, dockerInsecureSkipTLSVerify); err != nil {
logrus.Error(err)
os.Exit(1)
}
return executor.DoPush(image, opts)
},
}
// addKanikoOptionsFlags configures opts
func addKanikoOptionsFlags(cmd *cobra.Command) {
RootCmd.PersistentFlags().StringVarP(&opts.DockerfilePath, "dockerfile", "f", "Dockerfile", "Path to the dockerfile to be built.")
RootCmd.PersistentFlags().StringVarP(&opts.SrcContext, "context", "c", "/workspace/", "Path to the dockerfile build context.")
RootCmd.PersistentFlags().StringVarP(&opts.Bucket, "bucket", "b", "", "Name of the GCS bucket from which to access build context as tarball.")
RootCmd.PersistentFlags().VarP(&opts.Destinations, "destination", "d", "Registry the final image should be pushed to. Set it repeatedly for multiple destinations.")
RootCmd.PersistentFlags().StringVarP(&opts.SnapshotMode, "snapshotMode", "", "full", "Change the file attributes inspected during snapshotting")
RootCmd.PersistentFlags().VarP(&opts.BuildArgs, "build-arg", "", "This flag allows you to pass in ARG values at build time. Set it repeatedly for multiple values.")
RootCmd.PersistentFlags().BoolVarP(&opts.DockerInsecureSkipTLSVerify, "insecure-skip-tls-verify", "", false, "Push to insecure registry ignoring TLS verify")
RootCmd.PersistentFlags().StringVarP(&opts.TarPath, "tarPath", "", "", "Path to save the image in as a tarball instead of pushing")
RootCmd.PersistentFlags().BoolVarP(&opts.SingleSnapshot, "single-snapshot", "", false, "Take a single snapshot at the end of the build.")
RootCmd.PersistentFlags().BoolVarP(&opts.Reproducible, "reproducible", "", false, "Strip timestamps out of the image to make it reproducible")
RootCmd.PersistentFlags().StringVarP(&opts.Target, "target", "", "", "Set the target build stage to build")
RootCmd.PersistentFlags().BoolVarP(&opts.NoPush, "no-push", "", false, "Do not push the image to the registry")
}
// addHiddenFlags marks certain flags as hidden from the executor help text
func addHiddenFlags(cmd *cobra.Command) {
// This flag is added in a vendored directory, hide so that it doesn't come up via --help
RootCmd.PersistentFlags().MarkHidden("azure-container-registry-config")
// Hide this flag as we want to encourage people to use the --context flag instead
RootCmd.PersistentFlags().MarkHidden("bucket")
}
func checkContained() bool {
_, err := container.DetectRuntime()
return err == nil
}
func checkDockerfilePath() error {
if util.FilepathExists(dockerfilePath) {
if _, err := filepath.Abs(dockerfilePath); err != nil {
return err
// resolveDockerfilePath resolves the Dockerfile path to an absolute path
func resolveDockerfilePath() error {
if util.FilepathExists(opts.DockerfilePath) {
abs, err := filepath.Abs(opts.DockerfilePath)
if err != nil {
return errors.Wrap(err, "getting absolute path for dockerfile")
}
opts.DockerfilePath = abs
return nil
}
// Otherwise, check if the path relative to the build context exists
if util.FilepathExists(filepath.Join(srcContext, dockerfilePath)) {
if util.FilepathExists(filepath.Join(opts.SrcContext, opts.DockerfilePath)) {
abs, err := filepath.Abs(filepath.Join(opts.SrcContext, opts.DockerfilePath))
if err != nil {
return errors.Wrap(err, "getting absolute path for src context/dockerfile path")
}
opts.DockerfilePath = abs
return nil
}
return errors.New("please provide a valid path to a Dockerfile within the build context")
}
func absouteDockerfilePath() string {
if util.FilepathExists(dockerfilePath) {
// Ignore error since we already checked it in checkDockerfilePath()
abs, _ := filepath.Abs(dockerfilePath)
return abs
}
// Otherwise, return path relative to build context
return filepath.Join(srcContext, dockerfilePath)
return errors.New("please provide a valid path to a Dockerfile within the build context with --dockerfile")
}
// resolveSourceContext unpacks the source context if it is a tar in a bucket
// it resets srcContext to be the path to the unpacked build context within the image
func resolveSourceContext() error {
if srcContext == "" && bucket == "" {
if opts.SrcContext == "" && opts.Bucket == "" {
return errors.New("please specify a path to the build context with the --context flag or a bucket with the --bucket flag")
}
if srcContext != "" && !strings.Contains(srcContext, "://") {
if opts.SrcContext != "" && !strings.Contains(opts.SrcContext, "://") {
return nil
}
if bucket != "" {
if !strings.Contains(bucket, "://") {
srcContext = constants.GCSBuildContextPrefix + bucket
if opts.Bucket != "" {
if !strings.Contains(opts.Bucket, "://") {
opts.SrcContext = constants.GCSBuildContextPrefix + opts.Bucket
} else {
srcContext = bucket
opts.SrcContext = opts.Bucket
}
}
// if no prefix use Google Cloud Storage as default for backwards compability
contextExecutor, err := buildcontext.GetBuildContext(srcContext)
contextExecutor, err := buildcontext.GetBuildContext(opts.SrcContext)
if err != nil {
return err
}
logrus.Debugf("Getting source context from %s", srcContext)
srcContext, err = contextExecutor.UnpackTarFromBuildContext()
logrus.Debugf("Getting source context from %s", opts.SrcContext)
opts.SrcContext, err = contextExecutor.UnpackTarFromBuildContext()
if err != nil {
return err
}
logrus.Debugf("Build context located at %s", srcContext)
logrus.Debugf("Build context located at %s", opts.SrcContext)
return nil
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package main
import (
"fmt"
"os"
"github.com/GoogleContainerTools/kaniko/cmd/executor/cmd"
@ -25,7 +24,6 @@ import (
func main() {
if err := cmd.RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -18,21 +18,16 @@ package executor
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/sirupsen/logrus"
@ -40,37 +35,26 @@ import (
"github.com/GoogleContainerTools/kaniko/pkg/commands"
"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
"github.com/GoogleContainerTools/kaniko/pkg/options"
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
"github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/GoogleContainerTools/kaniko/pkg/version"
)
// KanikoBuildArgs contains all the args required to build the image
type KanikoBuildArgs struct {
DockerfilePath string
SrcContext string
SnapshotMode string
Args []string
SingleSnapshot bool
Reproducible bool
Target string
}
func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
func DoBuild(opts *options.KanikoOptions) (v1.Image, error) {
// Parse dockerfile and unpack base image to root
stages, err := dockerfile.Stages(k.DockerfilePath, k.Target)
stages, err := dockerfile.Stages(opts.DockerfilePath, opts.Target)
if err != nil {
return nil, err
}
hasher, err := getHasher(k.SnapshotMode)
hasher, err := getHasher(opts.SnapshotMode)
if err != nil {
return nil, err
}
for index, stage := range stages {
finalStage := finalStage(index, k.Target, stages)
finalStage := finalStage(index, opts.Target, stages)
// Unpack file system to root
sourceImage, err := util.RetrieveSourceImage(index, k.Args, stages)
sourceImage, err := util.RetrieveSourceImage(index, opts.BuildArgs, stages)
if err != nil {
return nil, err
}
@ -90,10 +74,10 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
return nil, err
}
buildArgs := dockerfile.NewBuildArgs(k.Args)
buildArgs := dockerfile.NewBuildArgs(opts.BuildArgs)
for index, cmd := range stage.Commands {
finalCmd := index == len(stage.Commands)-1
dockerCommand, err := commands.GetCommand(cmd, k.SrcContext)
dockerCommand, err := commands.GetCommand(cmd, opts.SrcContext)
if err != nil {
return nil, err
}
@ -105,7 +89,7 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
}
// Don't snapshot if it's not the final stage and not the final command
// Also don't snapshot if it's the final stage, not the final command, and single snapshot is set
if (!finalStage && !finalCmd) || (finalStage && !finalCmd && k.SingleSnapshot) {
if (!finalStage && !finalCmd) || (finalStage && !finalCmd && opts.SingleSnapshot) {
continue
}
// Now, we get the files to snapshot from this command and take the snapshot
@ -148,7 +132,7 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
return nil, err
}
if finalStage {
if k.Reproducible {
if opts.Reproducible {
sourceImage, err = mutate.Canonical(sourceImage)
if err != nil {
return nil, err
@ -172,64 +156,6 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
return nil, err
}
type withUserAgent struct {
t http.RoundTripper
}
func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Set("User-Agent", fmt.Sprintf("kaniko/%s", version.Version()))
return w.t.RoundTrip(r)
}
func DoPush(image v1.Image, destinations []string, tarPath string, dockerInsecureSkipTLSVerify bool) error {
// continue pushing unless an error occurs
for _, destination := range destinations {
// Push the image
destRef, err := name.NewTag(destination, name.WeakValidation)
if err != nil {
return err
}
if dockerInsecureSkipTLSVerify {
newReg, err := name.NewInsecureRegistry(destRef.Repository.Registry.Name(), name.WeakValidation)
if err != nil {
return err
}
destRef.Repository.Registry = newReg
}
if tarPath != "" {
return tarball.WriteToFile(tarPath, destRef, image, nil)
}
k8sc, err := k8schain.NewNoClient()
if err != nil {
return err
}
kc := authn.NewMultiKeychain(authn.DefaultKeychain, k8sc)
pushAuth, err := kc.Resolve(destRef.Context().Registry)
if err != nil {
return err
}
// Create a transport to set our user-agent.
tr := http.DefaultTransport
if dockerInsecureSkipTLSVerify {
tr.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
rt := &withUserAgent{t: tr}
if err := remote.Write(destRef, image, pushAuth, rt, remote.WriteOptions{}); err != nil {
logrus.Error(fmt.Errorf("Failed to push to destination %s", destination))
return err
}
}
return nil
}
func finalStage(index int, target string, stages []instructions.Stage) bool {
if index == len(stages)-1 {
return true

95
pkg/executor/push.go Normal file
View File

@ -0,0 +1,95 @@
/*
Copyright 2018 Google LLC
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 executor
import (
"crypto/tls"
"fmt"
"net/http"
"github.com/GoogleContainerTools/kaniko/pkg/options"
"github.com/GoogleContainerTools/kaniko/pkg/version"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type withUserAgent struct {
t http.RoundTripper
}
func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Set("User-Agent", fmt.Sprintf("kaniko/%s", version.Version()))
return w.t.RoundTrip(r)
}
// DoPush is responsible for pushing image to the destinations specified in opts
func DoPush(image v1.Image, opts *options.KanikoOptions) error {
if opts.NoPush {
logrus.Info("Skipping push to container registry due to --no-push flag")
return nil
}
// continue pushing unless an error occurs
for _, destination := range opts.Destinations {
// Push the image
destRef, err := name.NewTag(destination, name.WeakValidation)
if err != nil {
return errors.Wrap(err, "getting tag for destination")
}
if opts.DockerInsecureSkipTLSVerify {
newReg, err := name.NewInsecureRegistry(destRef.Repository.Registry.Name(), name.WeakValidation)
if err != nil {
return errors.Wrap(err, "getting new insecure registry")
}
destRef.Repository.Registry = newReg
}
if opts.TarPath != "" {
return tarball.WriteToFile(opts.TarPath, destRef, image, nil)
}
k8sc, err := k8schain.NewNoClient()
if err != nil {
return errors.Wrap(err, "getting k8schain client")
}
kc := authn.NewMultiKeychain(authn.DefaultKeychain, k8sc)
pushAuth, err := kc.Resolve(destRef.Context().Registry)
if err != nil {
return errors.Wrap(err, "resolving pushAuth")
}
// Create a transport to set our user-agent.
tr := http.DefaultTransport
if opts.DockerInsecureSkipTLSVerify {
tr.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
rt := &withUserAgent{t: tr}
if err := remote.Write(destRef, image, pushAuth, rt, remote.WriteOptions{}); err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to push to destination %s", destination))
}
}
return nil
}

View File

@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package options
import (
"github.com/sirupsen/logrus"
"strings"
"github.com/sirupsen/logrus"
)
// This type is used to supported passing in multiple flags

33
pkg/options/options.go Normal file
View File

@ -0,0 +1,33 @@
/*
Copyright 2018 Google LLC
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
// KanikoOptions are options that are set by command line arguments
type KanikoOptions struct {
DockerfilePath string
Destinations multiArg
SrcContext string
SnapshotMode string
Bucket string
DockerInsecureSkipTLSVerify bool
BuildArgs multiArg
TarPath string
SingleSnapshot bool
Reproducible bool
Target string
NoPush bool
}