Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run command #33

Merged
merged 7 commits into from
Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
12 changes: 6 additions & 6 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions executor/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ limitations under the License.
package cmd

import (
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/commands"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/constants"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/dockerfile"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/image"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/snapshot"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"io/ioutil"
Expand Down Expand Up @@ -89,7 +91,44 @@ func execute() error {
}

// Execute commands here
if err := image.SetEnvVariables(sourceImage); err != nil {
return err
}

// Currently only supports single stage builds
for _, stage := range stages {
for _, cmd := range stage.Commands {
dockerCommand := commands.GetCommand(cmd)
if dockerCommand == nil {
return errors.Errorf("Invalid or unsupported docker command: %v", cmd)
}
if err := dockerCommand.ExecuteCommand(); err != nil {
return err
}
// Now, we get the files to snapshot from this command
// If this is nil, snapshot the entire filesystem
// Else take a snapshot of the specific files
snapshotFiles := dockerCommand.FilesToSnapshot()
var contents []byte
if snapshotFiles == nil {
contents, err = snapshotter.TakeSnapshot()
} else {
contents, err = snapshotter.TakeSnapshotOfFiles(snapshotFiles)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fairly common practice to have a single function for things like this that takes a nil parameter to determine whether to check all files or no files or a set of files.

}
if err != nil {
return err
}
if contents == nil {
logrus.Info("No files were changed, appending empty layer to config.")
sourceImage.AppendConfigHistory(dockerCommand.Author(), true)
continue
}
// Append the layer to the image
if err := sourceImage.AppendLayer(contents, dockerCommand.Author()); err != nil {
return err
}
}
}
// Push the image
return image.PushImage(sourceImage, destination)
}
19 changes: 19 additions & 0 deletions integration_tests/dockerfiles/Dockerfile_test_run
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018 Google, Inc. All rights reserved.
#
# 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.

FROM gcr.io/google-appengine/debian9
RUN echo "hey" > /etc/foo
RUN apt-get update && apt-get install -y \
bzr \
cvs \
19 changes: 19 additions & 0 deletions integration_tests/dockerfiles/Dockerfile_test_run_2
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018 Google, Inc. All rights reserved.
#
# 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.

# Test to make sure the executor builds an image correctly
# when no files are changed

FROM gcr.io/google-appengine/debian9
RUN echo "hey"
48 changes: 48 additions & 0 deletions integration_tests/dockerfiles/config_test_run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[
{
"Image1": "gcr.io/kbuild-test/docker-test-run:latest",
"Image2": "gcr.io/kbuild-test/kbuild-test-run:latest",
"DiffType": "File",
"Diff": {
"Adds": null,
"Dels": null,
"Mods": [
{
"Name": "/var/log/dpkg.log",
"Size1": 57425,
"Size2": 57425
},
{
"Name": "/var/log/apt/term.log",
"Size1": 24400,
"Size2": 24400
},
{
"Name": "/var/cache/ldconfig/aux-cache",
"Size1": 8057,
"Size2": 8057
},
{
"Name": "/var/log/apt/history.log",
"Size1": 5089,
"Size2": 5089
},
{
"Name": "/var/log/alternatives.log",
"Size1": 2579,
"Size2": 2579
},
{
"Name": "/usr/lib/python2.7/dist-packages/keyrings/__init__.pyc",
"Size1": 140,
"Size2": 140
},
{
"Name": "/usr/lib/python2.7/dist-packages/lazr/__init__.pyc",
"Size1": 136,
"Size2": 136
}
]
}
}
]
12 changes: 12 additions & 0 deletions integration_tests/dockerfiles/config_test_run_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"Image1": "gcr.io/kbuild-test/docker-test-run-2:latest",
"Image2": "gcr.io/kbuild-test/kbuild-test-run-2:latest",
"DiffType": "File",
"Diff": {
"Adds": null,
"Dels": null,
"Mods": null
}
}
]
14 changes: 14 additions & 0 deletions integration_tests/integration_test_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ var tests = []struct {
context: "integration_tests/dockerfiles/",
repo: "extract-filesystem",
},
{
description: "test run",
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_run",
configPath: "/workspace/integration_tests/dockerfiles/config_test_run.json",
context: "integration_tests/dockerfiles/",
repo: "test-run",
},
{
description: "test run no files changed",
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_run_2",
configPath: "/workspace/integration_tests/dockerfiles/config_test_run_2.json",
context: "integration_tests/dockerfiles/",
repo: "test-run-2",
},
}

type step struct {
Expand Down
39 changes: 39 additions & 0 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
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 commands

import (
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/sirupsen/logrus"
)

type DockerCommand interface {
ExecuteCommand() error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this will need to take a pointer to the image config eventually? for things like "ENV", etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah I've been trying to figure out the best way to update the config, WDYT about adding an UpdateConfig() function to the interface, which would take a pointer to the config?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, then I guess the main loop would look like:

for cmd in cmds:
  cmd.UpdateConfig(&cfg)
  cmd.ExecuteCommand()

How would that work for things like ENV? Would the "UpdateConfig" method update the env in the config, then ExecuteCommand would reapply that change to the command's environment?

I guess it could make sense to split it out into two methods, but it would be weird if most commands only implement one of them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah that's probably how it would work for ENV, but you're right most commands would only need one of them. Let's pass it in to ExecuteCommand() then

// The config file has an "author" field, should return information about the command
Author() string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this ever differ between commands? Or will it always be something like "kbuild"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to start each one with "kbuild" and then put in some extra info about the command -- but I'm not sure how necessary that it, they could also all just be "kbuild". WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, this is for the history section? I see a few different fields used in there:

{
      "created": "1970-01-01T00:00:00Z",
      "author": "Bazel",
      "created_by": "bazel build ..."
    },
    {
      "created": "2018-01-09T20:15:50.874240981Z",
      "created_by": "/bin/sh -c #(nop)  ARG DEBIAN_FRONTEND=noninteractive",
      "empty_layer": true
    },
    {
      "created": "2018-01-09T20:18:05.709512027Z",
      "created_by": "|1 DEBIAN_FRONTEND=noninteractive /bin/sh -c apt-get update -y &&     apt-get install -y -q --no-install-recommends         apt-utils         autoconf         build-essential         ca-certificates         cmake         curl         git         file         imagemagick         libcurl3         libcurl3-gnutls         libcurl4-openssl-dev         libffi-dev         libgdbm-dev         libgit2-dev         libgmp-dev         libicu-dev         libmagickwand-dev         libmysqlclient-dev         libncurses5-dev         libpq-dev         libqdbm-dev         libreadline6-dev         libsqlite3-dev         libssl-dev         libxml2-dev         libxslt-dev         libyaml-dev         libz-dev         systemtap"
    },

It looks like author isn't actually required, but created_by might be? The command metadata itself probably belongs in created_by, and author can be fixed as "kbuild".

WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good!

// A list of files to snapshot, empty for metadata commands or nil if we don't know
FilesToSnapshot() []string
}

func GetCommand(cmd instructions.Command) DockerCommand {
switch c := cmd.(type) {
case *instructions.RunCommand:
return &RunCommand{cmd: c}
}
logrus.Errorf("%s is not a supported command.", cmd.Name())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return an error instead of logging one.

return nil
}
67 changes: 67 additions & 0 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
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 commands

import (
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/constants"
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/sirupsen/logrus"
"os"
"os/exec"
"strings"
)

type RunCommand struct {
cmd *instructions.RunCommand
}

func (r *RunCommand) ExecuteCommand() error {
var newCommand []string
if r.cmd.PrependShell {
// This is the default shell on Linux
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a TODO to make this support the "SHELL" directive?

shell := []string{"/bin/sh", "-c"}
newCommand = append(shell, strings.Join(r.cmd.CmdLine, " "))
} else {
newCommand = r.cmd.CmdLine
}

logrus.Infof("cmd: %s", newCommand[0])
logrus.Infof("args: %s", newCommand[1:])

cmd := exec.Command(newCommand[0], newCommand[1:]...)
cmd.Stdout = os.Stdout
return cmd.Run()
}

// FilesToSnapshot returns nil for this command because we don't know which files
// have changed, so we snapshot the entire system.
func (r *RunCommand) FilesToSnapshot() []string {
return nil
}

// Author returns some information about the command for the image config
func (r *RunCommand) Author() string {
author := []string{constants.Author}
cmdLine := strings.Join(r.cmd.CmdLine, " ")
if r.cmd.PrependShell {
shell := []string{"/bin/sh", "-c"}
author = append(author, shell...)
return strings.Join(author, " ") + " " + cmdLine
}
author = append(author, cmdLine)
return strings.Join(author, " ")
}
2 changes: 2 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ const (
WorkspaceDir = "/workspace"

WhitelistPath = "/proc/self/mountinfo"

Author = "kbuild"
)
13 changes: 13 additions & 0 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/containers/image/signature"
"github.com/containers/image/transports/alltransports"
"github.com/sirupsen/logrus"
"os"
)

// sourceImage is the image that will be modified by the executor
Expand Down Expand Up @@ -55,6 +56,18 @@ func PushImage(ms *img.MutableSource, destImg string) error {
return copy.Image(policyContext, destRef, srcRef, nil)
}

// SetEnvVariables sets environment variables as specified in the image
func SetEnvVariables(ms *img.MutableSource) error {
envVars := ms.Env()
for key, val := range envVars {
if err := os.Setenv(key, val); err != nil {
return err
}
logrus.Debugf("Setting environment variable %s=%s", key, val)
}
return nil
}

func getPolicyContext() (*signature.PolicyContext, error) {
policyContext, err := signature.NewPolicyContext(&signature.Policy{
Default: signature.PolicyRequirements{signature.NewPRInsecureAcceptAnything()},
Expand Down
Loading