예제 #1
0
func TestReplaceLastFrom(t *testing.T) {
	tests := []struct {
		original string
		image    string
		want     string
	}{
		{
			original: `# no FROM instruction`,
			image:    "centos",
			want:     ``,
		},
		{
			original: `FROM scratch
# FROM busybox
RUN echo "hello world"
`,
			image: "centos",
			want: `FROM centos
RUN echo "hello world"
`,
		},
		{
			original: `FROM scratch
FROM busybox
RUN echo "hello world"
`,
			image: "centos",
			want: `FROM scratch
FROM centos
RUN echo "hello world"
`,
		},
	}
	for i, test := range tests {
		got, err := parser.Parse(strings.NewReader(test.original))
		if err != nil {
			t.Errorf("test[%d]: %v", i, err)
			continue
		}
		want, err := parser.Parse(strings.NewReader(test.want))
		if err != nil {
			t.Errorf("test[%d]: %v", i, err)
			continue
		}
		replaceLastFrom(got, test.image)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("test[%d]: replaceLastFrom(node, %+v) = %+v; want %+v", i, test.image, got, want)
			t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got))
		}
	}
}
예제 #2
0
// Parse parses an input Dockerfile
func (_ *parser) Parse(input io.Reader) (Dockerfile, error) {
	buf := bufio.NewReader(input)
	bts, err := buf.Peek(buf.Buffered())
	if err != nil {
		return nil, err
	}
	parsedByDocker := bytes.NewBuffer(bts)
	// Add one more level of validation by using the Docker parser
	if _, err := dparser.Parse(parsedByDocker); err != nil {
		return nil, fmt.Errorf("cannot parse Dockerfile: %v", err)
	}

	d := dockerfile{}
	scanner := bufio.NewScanner(input)
	for {
		line, ok := nextLine(scanner, true)
		if !ok {
			break
		}
		parts, err := parseLine(line)
		if err != nil {
			return nil, err
		}
		d = append(d, parts)
	}
	return d, nil
}
예제 #3
0
파일: parser.go 프로젝트: ncantor/origin
// Parse parses an input Dockerfile
func (_ *parser) Parse(input io.Reader) (Dockerfile, error) {
	b, err := ioutil.ReadAll(input)
	if err != nil {
		return nil, err
	}
	r := bytes.NewReader(b)

	// Add one more level of validation by using the Docker parser
	if _, err := dparser.Parse(r); err != nil {
		return nil, fmt.Errorf("cannot parse Dockerfile: %v", err)
	}

	if _, err = r.Seek(0, 0); err != nil {
		return nil, err
	}

	d := dockerfile{}
	scanner := bufio.NewScanner(r)
	for {
		line, ok := nextLine(scanner, true)
		if !ok {
			break
		}
		parts, err := parseLine(line)
		if err != nil {
			return nil, err
		}
		d = append(d, parts)
	}
	return d, nil
}
예제 #4
0
// FromImage updates the builder to use the provided image (resetting RunConfig
// and recording the image environment), and updates the node with any ONBUILD
// statements extracted from the parent image.
func (b *Builder) FromImage(image *docker.Image, node *parser.Node) error {
	SplitChildren(node, command.From)

	b.RunConfig = *image.Config
	b.Env = b.RunConfig.Env
	b.RunConfig.Env = nil

	// Check to see if we have a default PATH, note that windows won't
	// have one as its set by HCS
	if runtime.GOOS != "windows" && !hasEnvName(b.Env, "PATH") {
		b.RunConfig.Env = append(b.RunConfig.Env, "PATH="+defaultPathEnv)
	}

	// Join the image onbuild statements into node
	if image.Config == nil || len(image.Config.OnBuild) == 0 {
		return nil
	}
	extra, err := parser.Parse(bytes.NewBufferString(strings.Join(image.Config.OnBuild, "\n")))
	if err != nil {
		return err
	}
	for _, child := range extra.Children {
		switch strings.ToUpper(child.Value) {
		case "ONBUILD":
			return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
		case "MAINTAINER", "FROM":
			return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", child.Value)
		}
	}
	node.Children = append(extra.Children, node.Children...)
	// Since we've processed the OnBuild statements, clear them from the runconfig state.
	b.RunConfig.OnBuild = nil
	return nil
}
예제 #5
0
// TestNextValuesOnbuild tests calling nextValues with ONBUILD instructions as
// input.
func TestNextValuesOnbuild(t *testing.T) {
	testCases := map[string][]string{
		`ONBUILD ADD . /app/src`:             {".", "/app/src"},
		`ONBUILD RUN echo "Hello universe!"`: {`echo "Hello universe!"`},
	}
	for original, want := range testCases {
		node, err := parser.Parse(strings.NewReader(original))
		if err != nil {
			t.Fatalf("parse error: %s: %v", original, err)
		}
		if len(node.Children) != 1 {
			t.Fatalf("unexpected number of children in test case: %s", original)
		}
		// The Docker parser always wrap instructions in a root node.
		// Look at the node representing the instruction following
		// ONBUILD, the one and only one in each test case.
		node = node.Children[0].Next
		if node == nil || len(node.Children) != 1 {
			t.Fatalf("unexpected number of children in ONBUILD instruction of test case: %s", original)
		}
		node = node.Children[0]
		if got := nextValues(node); !reflect.DeepEqual(got, want) {
			t.Errorf("nextValues(%+v) = %#v; want %#v", node, got, want)
		}
	}
}
예제 #6
0
// TestNextValues tests calling nextValues with multiple valid combinations of
// input.
func TestNextValues(t *testing.T) {
	testCases := map[string][]string{
		`FROM busybox:latest`:           {"busybox:latest"},
		`MAINTAINER [email protected]`: {"*****@*****.**"},
		`LABEL version=1.0`:             {"version", "1.0"},
		`EXPOSE 8080`:                   {"8080"},
		`VOLUME /var/run/www`:           {"/var/run/www"},
		`ENV PATH=/bin`:                 {"PATH", "/bin"},
		`ADD file /home/`:               {"file", "/home/"},
		`COPY dir/ /tmp/`:               {"dir/", "/tmp/"},
		`RUN echo "Hello world!"`:       {`echo "Hello world!"`},
		`ENTRYPOINT /bin/sh`:            {"/bin/sh"},
		`CMD ["-c", "env"]`:             {"-c", "env"},
		`USER 1001`:                     {"1001"},
		`WORKDIR /home`:                 {"/home"},
	}
	for original, want := range testCases {
		node, err := parser.Parse(strings.NewReader(original))
		if err != nil {
			t.Fatalf("parse error: %s: %v", original, err)
		}
		if len(node.Children) != 1 {
			t.Fatalf("unexpected number of children in test case: %s", original)
		}
		// The Docker parser always wrap instructions in a root node.
		// Look at the node representing the first instruction, the one
		// and only one in each test case.
		node = node.Children[0]
		if got := nextValues(node); !reflect.DeepEqual(got, want) {
			t.Errorf("nextValues(%+v) = %#v; want %#v", node, got, want)
		}
	}
}
예제 #7
0
파일: job.go 프로젝트: j-stew/git_sandbox
// BuildFromConfig will do build directly from parameter 'changes', which comes
// from Dockerfile entries, it will:
//
// - call parse.Parse() to get AST root from Dockerfile entries
// - do build by calling builder.dispatch() to call all entries' handling routines
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
	if err != nil {
		return nil, err
	}

	// ensure that the commands are valid
	for _, n := range ast.Children {
		if !validCommitCommands[n.Value] {
			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
		}
	}

	builder := &builder{
		Daemon:        d,
		Config:        c,
		OutStream:     ioutil.Discard,
		ErrStream:     ioutil.Discard,
		disableCommit: true,
	}

	for i, n := range ast.Children {
		if err := builder.dispatch(i, n); err != nil {
			return nil, err
		}
	}

	return builder.Config, nil
}
예제 #8
0
func TestRun(t *testing.T) {
	f, err := os.Open("../../../../../images/dockerregistry/Dockerfile")
	if err != nil {
		t.Fatal(err)
	}
	node, err := parser.Parse(f)
	if err != nil {
		t.Fatal(err)
	}
	b := NewBuilder()
	from, err := b.From(node)
	if err != nil {
		t.Fatal(err)
	}
	if from != "openshift/origin-base" {
		t.Fatalf("unexpected from: %s", from)
	}
	for _, child := range node.Children {
		step := b.Step()
		if err := step.Resolve(child); err != nil {
			t.Fatal(err)
		}
		if err := b.Run(step, LogExecutor); err != nil {
			t.Fatal(err)
		}
	}
	t.Logf("config: %#v", b.Config())
	t.Logf(node.Dump())
}
예제 #9
0
// Run the builder with the context. This is the lynchpin of this package. This
// will (barring errors):
//
// * call readContext() which will set up the temporary directory and unpack
//   the context into it.
// * read the dockerfile
// * parse the dockerfile
// * walk the parse tree and execute it by dispatching to handlers. If Remove
//   or ForceRemove is set, additional cleanup around containers happens after
//   processing.
// * Print a happy message and return the image ID.
//
func (b *Builder) Run(context io.Reader) (string, error) {
	if err := b.readContext(context); err != nil {
		return "", err
	}

	defer func() {
		if err := os.RemoveAll(b.contextPath); err != nil {
			log.Debugf("[BUILDER] failed to remove temporary context: %s", err)
		}
	}()

	filename := path.Join(b.contextPath, "Dockerfile")

	fi, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return "", fmt.Errorf("Cannot build a directory without a Dockerfile")
	}
	if fi.Size() == 0 {
		return "", ErrDockerfileEmpty
	}

	f, err := os.Open(filename)
	if err != nil {
		return "", err
	}

	defer f.Close()

	ast, err := parser.Parse(f)
	if err != nil {
		return "", err
	}

	b.dockerfile = ast

	// some initializations that would not have been supplied by the caller.
	b.Config = &runconfig.Config{}
	b.TmpContainers = map[string]struct{}{}

	for i, n := range b.dockerfile.Children {
		if err := b.dispatch(i, n); err != nil {
			if b.ForceRemove {
				b.clearTmp()
			}
			return "", err
		}
		fmt.Fprintf(b.OutStream, " ---> %s\n", utils.TruncateID(b.image))
		if b.Remove {
			b.clearTmp()
		}
	}

	if b.image == "" {
		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
	}

	fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
	return b.image, nil
}
예제 #10
0
파일: docker.go 프로젝트: Waxolunist/origin
// addBuildParameters checks if a Image is set to replace the default base image.
// If that's the case then change the Dockerfile to make the build with the given image.
// Also append the environment variables and labels in the Dockerfile.
func (d *DockerBuilder) addBuildParameters(dir string) error {
	dockerfilePath := filepath.Join(dir, "Dockerfile")
	if d.build.Spec.Strategy.DockerStrategy != nil && len(d.build.Spec.Source.ContextDir) > 0 {
		dockerfilePath = filepath.Join(dir, d.build.Spec.Source.ContextDir, "Dockerfile")
	}

	f, err := os.Open(dockerfilePath)
	if err != nil {
		return err
	}

	// Parse the Dockerfile.
	node, err := parser.Parse(f)
	if err != nil {
		return err
	}

	// Update base image if build strategy specifies the From field.
	if d.build.Spec.Strategy.DockerStrategy.From != nil && d.build.Spec.Strategy.DockerStrategy.From.Kind == "DockerImage" {
		// Reduce the name to a minimal canonical form for the daemon
		name := d.build.Spec.Strategy.DockerStrategy.From.Name
		if ref, err := imageapi.ParseDockerImageReference(name); err == nil {
			name = ref.DaemonMinimal().String()
		}
		err := replaceLastFrom(node, name)
		if err != nil {
			return err
		}
	}

	// Append build info as environment variables.
	err = appendEnv(node, d.buildInfo())
	if err != nil {
		return err
	}

	// Append build labels.
	err = appendLabel(node, d.buildLabels(dir))
	if err != nil {
		return err
	}

	// Insert environment variables defined in the build strategy.
	err = insertEnvAfterFrom(node, d.build.Spec.Strategy.DockerStrategy.Env)
	if err != nil {
		return err
	}

	instructions := dockerfile.ParseTreeToDockerfile(node)

	// Overwrite the Dockerfile.
	fi, err := f.Stat()
	if err != nil {
		return err
	}
	return ioutil.WriteFile(dockerfilePath, instructions, fi.Mode())
}
예제 #11
0
func NewDockerfile(contents string) (Dockerfile, error) {
	if len(contents) == 0 {
		return nil, fmt.Errorf("Dockerfile is empty")
	}
	node, err := parser.Parse(strings.NewReader(contents))
	if err != nil {
		return nil, err
	}
	return dockerfileContents{node, contents}, nil
}
예제 #12
0
func TestTraverseAST(t *testing.T) {
	tests := []struct {
		name     string
		cmd      string
		fileData []byte
		expected int
	}{
		{
			name:     "dockerFile",
			cmd:      dockercmd.Entrypoint,
			fileData: []byte(dockerFile),
			expected: 1,
		},
		{
			name:     "dockerFile no newline",
			cmd:      dockercmd.Entrypoint,
			fileData: []byte(dockerFileNoNewline),
			expected: 1,
		},
		{
			name:     "expectedFROM",
			cmd:      dockercmd.From,
			fileData: []byte(expectedFROM),
			expected: 2,
		},
		{
			name:     "trSlashFile",
			cmd:      dockercmd.Entrypoint,
			fileData: []byte(trSlashFile),
			expected: 0,
		},
		{
			name:     "expectedtrSlashFile",
			cmd:      dockercmd.Cmd,
			fileData: []byte(expectedtrSlashFile),
			expected: 1,
		},
	}

	var buf *bytes.Buffer
	for _, test := range tests {
		buf = bytes.NewBuffer([]byte(test.fileData))
		node, err := parser.Parse(buf)
		if err != nil {
			log.Println(err)
		}

		howMany := traverseAST(test.cmd, node)
		if howMany != test.expected {
			t.Errorf("Wrong result, expected %d, got %d", test.expected, howMany)
		}
	}
}
예제 #13
0
// TestExposedPorts tests calling exposedPorts with multiple valid combinations
// of input.
func TestExposedPorts(t *testing.T) {
	testCases := map[string]struct {
		in   string
		want [][]string
	}{
		"empty Dockerfile": {
			in:   ``,
			want: nil,
		},
		"EXPOSE missing argument": {
			in:   `EXPOSE`,
			want: nil,
		},
		"EXPOSE no FROM": {
			in:   `EXPOSE 8080`,
			want: nil,
		},
		"single EXPOSE after FROM": {
			in: `FROM centos:7
		EXPOSE 8080`,
			want: [][]string{{"8080"}},
		},
		"multiple EXPOSE and FROM": {
			in: `# EXPOSE before FROM should be ignore
EXPOSE 777
FROM busybox
EXPOSE 8080
COPY . /boot
FROM rhel
# no EXPOSE instruction
FROM centos:7
EXPOSE 8000
EXPOSE 9090 9091
`,
			want: [][]string{{"8080"}, nil, {"8000", "9090", "9091"}},
		},
	}
	for name, tc := range testCases {
		node, err := parser.Parse(strings.NewReader(tc.in))
		if err != nil {
			t.Errorf("%s: parse error: %v", name, err)
			continue
		}
		got := exposedPorts(node)
		if !reflect.DeepEqual(got, tc.want) {
			t.Errorf("exposedPorts: %s: got %#v; want %#v", name, got, tc.want)
		}
	}
}
예제 #14
0
// TestInsertInstructionsPosOutOfRange tests calling InsertInstructions with
// invalid values for the pos argument.
func TestInsertInstructionsPosOutOfRange(t *testing.T) {
	original := `FROM busybox
ENV PATH=/bin
`
	node, err := parser.Parse(strings.NewReader(original))
	if err != nil {
		t.Fatalf("parse error: %v", err)
	}
	for _, pos := range []int{-1, 3, 4} {
		err := InsertInstructions(node, pos, "")
		if err == nil {
			t.Errorf("InsertInstructions(node, %d, \"\"): got nil; want error", pos)
		}
	}
}
예제 #15
0
// InsertInstructions inserts instructions starting from the pos-th child of
// node, moving other children as necessary. The instructions should be valid
// Dockerfile instructions. InsertInstructions mutates node in-place, and the
// final state of node is equivalent to what parser.Parse would return if the
// original Dockerfile represented by node contained the instructions at the
// specified position pos. If the returned error is non-nil, node is guaranteed
// to be unchanged.
func InsertInstructions(node *parser.Node, pos int, instructions string) error {
	if node == nil {
		return fmt.Errorf("cannot insert instructions in a nil node")
	}
	if pos < 0 || pos > len(node.Children) {
		return fmt.Errorf("pos %d out of range [0, %d]", pos, len(node.Children)-1)
	}
	newChild, err := parser.Parse(strings.NewReader(instructions))
	if err != nil {
		return err
	}
	// InsertVector pattern (https://github.com/golang/go/wiki/SliceTricks)
	node.Children = append(node.Children[:pos], append(newChild.Children, node.Children[pos:]...)...)
	return nil
}
예제 #16
0
// FromDockerfile generates an ImageRef from a given name, directory, and context path.
// The directory and context path will be joined and the resulting path should be a
// Dockerfile from where the image's ports will be extracted.
func (g *imageRefGenerator) FromDockerfile(name string, dir string, context string) (*ImageRef, error) {
	// Look for Dockerfile in repository
	file, err := os.Open(filepath.Join(dir, context, "Dockerfile"))
	if err != nil {
		return nil, err
	}

	node, err := parser.Parse(file)
	if err != nil {
		return nil, err
	}
	ports := dockerfile.LastExposedPorts(node)

	return g.FromNameAndPorts(name, ports)
}
예제 #17
0
파일: internals.go 프로젝트: rajurs/docker
func (b *builder) processImageFrom(img *image.Image) error {
	b.image = img.ID

	if img.Config != nil {
		b.Config = img.Config
	}

	// The default path will be blank on Windows (set by HCS)
	if len(b.Config.Env) == 0 && daemon.DefaultPathEnv != "" {
		b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
	}

	// Process ONBUILD triggers if they exist
	if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
		word := "trigger"
		if nTriggers > 1 {
			word = "triggers"
		}
		fmt.Fprintf(b.ErrStream, "# Executing %d build %s...\n", nTriggers, word)
	}

	// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
	onBuildTriggers := b.Config.OnBuild
	b.Config.OnBuild = []string{}

	// parse the ONBUILD triggers by invoking the parser
	for _, step := range onBuildTriggers {
		ast, err := parser.Parse(strings.NewReader(step))
		if err != nil {
			return err
		}

		for i, n := range ast.Children {
			switch strings.ToUpper(n.Value) {
			case "ONBUILD":
				return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
			case "MAINTAINER", "FROM":
				return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
			}

			if err := b.dispatch(i, n); err != nil {
				return err
			}
		}
	}

	return nil
}
예제 #18
0
// Reads a Dockerfile from the current context. It assumes that the
// 'filename' is a relative path from the root of the context
func (b *Builder) readDockerfile(origFile string) error {
	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
	if err != nil {
		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
	}

	fi, err := os.Lstat(filename)
	if os.IsNotExist(err) {
		return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
	}
	if fi.Size() == 0 {
		return ErrDockerfileEmpty
	}

	f, err := os.Open(filename)
	if err != nil {
		return err
	}

	b.dockerfile, err = parser.Parse(f)
	f.Close()

	if err != nil {
		return err
	}

	// After the Dockerfile has been parsed, we need to check the .dockerignore
	// file for either "Dockerfile" or ".dockerignore", and if either are
	// present then erase them from the build context. These files should never
	// have been sent from the client but we did send them to make sure that
	// we had the Dockerfile to actually parse, and then we also need the
	// .dockerignore file to know whether either file should be removed.
	// Note that this assumes the Dockerfile has been read into memory and
	// is now safe to be removed.

	excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
	if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
		os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
		b.context.(tarsum.BuilderContext).Remove(".dockerignore")
	}
	if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
		os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
		b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
	}

	return nil
}
예제 #19
0
func checkDockerfile(fs *test.FakeFileSystem, t *testing.T) {
	if fs.WriteFileError != nil {
		t.Errorf("%v", fs.WriteFileError)
	}
	if fs.WriteFileName != "upload/src/Dockerfile" {
		t.Errorf("Expected Dockerfile in 'upload/src/Dockerfile', got %v", fs.WriteFileName)
	}
	if !strings.Contains(fs.WriteFileContent, `ENTRYPOINT ["./run"]`) {
		t.Errorf("The Dockerfile does not set correct entrypoint:\n %s\n", fs.WriteFileContent)
	}

	buf := bytes.NewBuffer([]byte(fs.WriteFileContent))
	if _, err := parser.Parse(buf); err != nil {
		t.Errorf("cannot parse new Dockerfile: " + err.Error())
	}

}
예제 #20
0
파일: job.go 프로젝트: viirya/docker
func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status {
	if len(job.Args) != 0 {
		return job.Errorf("Usage: %s\n", job.Name)
	}

	var (
		changes   = job.GetenvList("changes")
		newConfig runconfig.Config
	)

	if err := job.GetenvJson("config", &newConfig); err != nil {
		return job.Error(err)
	}

	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
	if err != nil {
		return job.Error(err)
	}

	// ensure that the commands are valid
	for _, n := range ast.Children {
		if !validCommitCommands[n.Value] {
			return job.Errorf("%s is not a valid change command", n.Value)
		}
	}

	builder := &Builder{
		Daemon:        b.Daemon,
		Engine:        b.Engine,
		Config:        &newConfig,
		OutStream:     ioutil.Discard,
		ErrStream:     ioutil.Discard,
		disableCommit: true,
	}

	for i, n := range ast.Children {
		if err := builder.dispatch(i, n); err != nil {
			return job.Error(err)
		}
	}

	if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil {
		return job.Error(err)
	}
	return engine.StatusOK
}
예제 #21
0
// TestInsertInstructionsUnparseable tests calling InsertInstructions with
// instructions that the Docker parser cannot handle.
func TestInsertInstructionsUnparseable(t *testing.T) {
	original := `FROM busybox
ENV PATH=/bin
`
	node, err := parser.Parse(strings.NewReader(original))
	if err != nil {
		t.Fatalf("parse error: %v", err)
	}
	for name, instructions := range map[string]string{
		"env without value": `ENV PATH`,
		"nested json":       `CMD [ "echo", [ "nested json" ] ]`,
	} {
		err = InsertInstructions(node, 1, instructions)
		if err == nil {
			t.Errorf("InsertInstructions: %s: got nil; want error", name)
		}
	}
}
예제 #22
0
// replaceLastFrom changes the last FROM instruction of node to point to the
// base image image.
func replaceLastFrom(node *parser.Node, image string) error {
	if node == nil {
		return nil
	}
	for i := len(node.Children) - 1; i >= 0; i-- {
		child := node.Children[i]
		if child != nil && child.Value == dockercmd.From {
			from, err := dockerfile.From(image)
			if err != nil {
				return err
			}
			fromTree, err := parser.Parse(strings.NewReader(from))
			if err != nil {
				return err
			}
			node.Children[i] = fromTree.Children[0]
			return nil
		}
	}
	return nil
}
예제 #23
0
파일: main.go 프로젝트: NERSC/docker
func main() {
	var f *os.File
	var err error

	if len(os.Args) < 2 {
		fmt.Println("please supply filename(s)")
		os.Exit(1)
	}

	for _, fn := range os.Args[1:] {
		f, err = os.Open(fn)
		if err != nil {
			panic(err)
		}

		ast, err := parser.Parse(f)
		if err != nil {
			panic(err)
		} else {
			fmt.Println(ast.Dump())
		}
	}
}
예제 #24
0
// TestFindAll tests calling FindAll with multiple values of cmd.
func TestFindAll(t *testing.T) {
	instructions := `FROM scratch
LABEL version=1.0
FROM busybox
ENV PATH=/bin
`
	node, err := parser.Parse(strings.NewReader(instructions))
	if err != nil {
		t.Fatalf("parse error: %v", err)
	}
	for cmd, want := range map[string][]int{
		command.From:       {0, 2},
		command.Label:      {1},
		command.Env:        {3},
		command.Maintainer: nil,
		"UnknownCommand":   nil,
	} {
		got := FindAll(node, cmd)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("FindAll(node, %q) = %#v; want %#v", cmd, got, want)
		}
	}
}
예제 #25
0
// TestBaseImages tests calling baseImages with multiple valid combinations of
// input.
func TestBaseImages(t *testing.T) {
	testCases := map[string]struct {
		in   string
		want []string
	}{
		"empty Dockerfile": {
			in:   ``,
			want: nil,
		},
		"FROM missing argument": {
			in:   `FROM`,
			want: nil,
		},
		"single FROM": {
			in:   `FROM centos:7`,
			want: []string{"centos:7"},
		},
		"multiple FROM": {
			in: `FROM scratch
COPY . /boot
FROM centos:7`,
			want: []string{"scratch", "centos:7"},
		},
	}
	for name, tc := range testCases {
		node, err := parser.Parse(strings.NewReader(tc.in))
		if err != nil {
			t.Errorf("%s: parse error: %v", name, err)
			continue
		}
		got := baseImages(node)
		if !reflect.DeepEqual(got, tc.want) {
			t.Errorf("baseImages: %s: got %#v; want %#v", name, got, tc.want)
		}
	}
}
예제 #26
0
func TestBuilder(t *testing.T) {
	testCases := []struct {
		Dockerfile string
		From       string
		Copies     []Copy
		Runs       []Run
		Config     docker.Config
		ErrFn      func(err error) bool
	}{
		{
			Dockerfile: "testdata/dir/Dockerfile",
			From:       "busybox",
			Copies: []Copy{
				{Src: ".", Dest: []string{"/"}, Download: false},
				{Src: ".", Dest: []string{"/dir"}},
				{Src: "subdir/", Dest: []string{"/test/"}, Download: false},
			},
			Config: docker.Config{
				Image: "busybox",
			},
		},
		{
			Dockerfile: "testdata/ignore/Dockerfile",
			From:       "busybox",
			Copies: []Copy{
				{Src: ".", Dest: []string{"/"}},
			},
			Config: docker.Config{
				Image: "busybox",
			},
		},
		{
			Dockerfile: "testdata/Dockerfile.env",
			From:       "busybox",
			Config: docker.Config{
				Env:   []string{"name=value", "name2=value2a            value2b", "name1=value1", "name3=value3a\\n\"value3b\"", "name4=value4a\\\\nvalue4b"},
				Image: "busybox",
			},
		},
		{
			Dockerfile: "testdata/Dockerfile.edgecases",
			From:       "busybox",
			Copies: []Copy{
				{Src: ".", Dest: []string{"/"}, Download: true},
				{Src: ".", Dest: []string{"/test/copy"}},
			},
			Runs: []Run{
				{Shell: false, Args: []string{"ls", "-la"}},
				{Shell: false, Args: []string{"echo", "'1234'"}},
				{Shell: true, Args: []string{"echo \"1234\""}},
				{Shell: true, Args: []string{"echo 1234"}},
				{Shell: true, Args: []string{"echo '1234' &&     echo \"456\" &&     echo 789"}},
				{Shell: true, Args: []string{"sh -c 'echo root:testpass         > /tmp/passwd'"}},
				{Shell: true, Args: []string{"mkdir -p /test /test2 /test3/test"}},
			},
			Config: docker.Config{
				User:         "******",
				ExposedPorts: map[docker.Port]struct{}{"6000/tcp": {}, "3000/tcp": {}, "9000/tcp": {}, "5000/tcp": {}},
				Env:          []string{"SCUBA=1 DUBA 3"},
				Cmd:          []string{"/bin/sh", "-c", "echo 'test' | wc -"},
				Image:        "busybox",
				Volumes:      map[string]struct{}{"/test2": {}, "/test3": {}, "/test": {}},
				WorkingDir:   "/test",
				OnBuild:      []string{"RUN [\"echo\", \"test\"]", "RUN echo test", "COPY . /"},
			},
		},
		{
			Dockerfile: "testdata/Dockerfile.exposedefault",
			From:       "busybox",
			Config: docker.Config{
				ExposedPorts: map[docker.Port]struct{}{"3469/tcp": {}},
				Image:        "busybox",
			},
		},
		{
			Dockerfile: "testdata/Dockerfile.add",
			From:       "busybox",
			Copies: []Copy{
				{Src: "https://github.com/openshift/origin/raw/master/README.md", Dest: []string{"/README.md"}, Download: true},
				{Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/"}, Download: true},
				{Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/A"}, Download: true},
				{Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/a"}, Download: true},
				{Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/b/a"}, Download: true},
				{Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/b/"}, Download: true},
				{Src: "https://github.com/openshift/ruby-hello-world/archive/master.zip", Dest: []string{"/tmp/"}, Download: true},
			},
			Runs: []Run{
				{Shell: true, Args: []string{"mkdir ./b"}},
			},
			Config: docker.Config{
				Image: "busybox",
				User:  "******",
			},
		},
	}
	for i, test := range testCases {
		data, err := ioutil.ReadFile(test.Dockerfile)
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		node, err := parser.Parse(bytes.NewBuffer(data))
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		b := NewBuilder()
		from, err := b.From(node)
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		if from != test.From {
			t.Errorf("%d: unexpected FROM: %s", i, from)
		}
		e := &testExecutor{}
		var lastErr error
		for j, child := range node.Children {
			step := b.Step()
			if err := step.Resolve(child); err != nil {
				lastErr = fmt.Errorf("%d: %d: %s: resolve: %v", i, j, step.Original, err)
				break
			}
			if err := b.Run(step, e); err != nil {
				lastErr = fmt.Errorf("%d: %d: %s: run: %v", i, j, step.Original, err)
				break
			}
		}
		if lastErr != nil {
			if test.ErrFn == nil || !test.ErrFn(lastErr) {
				t.Errorf("%d: unexpected error: %v", i, lastErr)
			}
			continue
		}
		if !reflect.DeepEqual(test.Copies, e.Copies) {
			t.Errorf("%d: unexpected copies: %#v", i, e.Copies)
		}
		if !reflect.DeepEqual(test.Runs, e.Runs) {
			t.Errorf("%d: unexpected runs: %#v", i, e.Runs)
		}
		lastConfig := b.RunConfig
		if !reflect.DeepEqual(test.Config, lastConfig) {
			t.Errorf("%d: unexpected config: %#v", i, lastConfig)
		}
	}
}
예제 #27
0
func TestReplaceValidCmd(t *testing.T) {
	tests := []struct {
		name           string
		cmd            string
		replaceArgs    string
		fileData       []byte
		expectedOutput string
		expectedDiffs  int
		expectedErr    error
	}{
		{
			name:           "from-replacement",
			cmd:            dockercmd.From,
			replaceArgs:    "other/image",
			fileData:       []byte(dockerFile),
			expectedOutput: expectedFROM,
			expectedDiffs:  1,
			expectedErr:    nil,
		},
		{
			name:           "run-replacement",
			cmd:            dockercmd.Run,
			replaceArgs:    "This test kind-of-fails before string replacement so this string won't be used",
			fileData:       []byte(dockerFile),
			expectedOutput: "",
			expectedErr:    replaceCmdErr,
		},
		{
			name:           "invalid-dockerfile-cmd",
			cmd:            "blabla",
			replaceArgs:    "This test fails at start so this string won't be used",
			fileData:       []byte(dockerFile),
			expectedOutput: "",
			expectedErr:    invalidCmdErr,
		},
		{
			name:           "no-cmd-in-dockerfile",
			cmd:            dockercmd.Cmd,
			replaceArgs:    "runme.sh",
			fileData:       []byte(dockerFile),
			expectedOutput: "",
			expectedErr:    replaceCmdErr,
		},
		{
			name:           "trailing-slash",
			cmd:            dockercmd.From,
			replaceArgs:    "rhel",
			fileData:       []byte(trSlashFile),
			expectedOutput: expectedtrSlashFile,
			expectedDiffs:  1,
			expectedErr:    nil,
		},
		{
			name:           "multiple trailing slashes plus plus",
			cmd:            dockercmd.From,
			replaceArgs:    "scratch",
			fileData:       []byte(trickierFile),
			expectedOutput: expectedTrickierFile,
			expectedDiffs:  1,
			expectedErr:    nil,
		},
	}

	for _, test := range tests {
		out, err := replaceValidCmd(test.cmd, test.replaceArgs, test.fileData)
		if err != test.expectedErr {
			t.Errorf("%s: Unexpected error: Expected %v, got %v", test.name, test.expectedErr, err)
		}
		if out != test.expectedOutput {
			t.Errorf("%s: Unexpected output:\n\nExpected:\n%s\n(length: %d)\n\ngot:\n%s\n(length: %d)",
				test.name, test.expectedOutput, len(test.expectedOutput), out, len(out))
		}
	}

	// Re-use the tests above
	var buf *bytes.Buffer
	for _, test := range tests {
		buf = bytes.NewBuffer([]byte(test.fileData))
		original, err := parser.Parse(buf)
		if err != nil {
			log.Println(err)
		}
		repl, err := replaceValidCmd(test.cmd, test.replaceArgs, test.fileData)
		if err != nil {
			log.Println(err)
		}
		buf = bytes.NewBuffer([]byte(repl))
		edited, err := parser.Parse(buf)
		if err != nil {
			log.Println(err)
		}

		diff := cmpASTs(original, edited)

		if diff != test.expectedDiffs {
			t.Errorf("%s: Edit mismatch, expected %d edit(s), got %d", test.name, test.expectedDiffs, diff)
		}
	}
}
예제 #28
0
// Reads a Dockerfile from the current context. It assumes that the
// 'filename' is a relative path from the root of the context
func (b *builder) readDockerfile() error {
	// If no -f was specified then look for 'Dockerfile'. If we can't find
	// that then look for 'dockerfile'.  If neither are found then default
	// back to 'Dockerfile' and use that in the error message.
	if b.dockerfileName == "" {
		b.dockerfileName = api.DefaultDockerfileName
		tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName)
		if _, err := os.Lstat(tmpFN); err != nil {
			tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName))
			if _, err := os.Lstat(tmpFN); err == nil {
				b.dockerfileName = strings.ToLower(api.DefaultDockerfileName)
			}
		}
	}

	origFile := b.dockerfileName

	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
	if err != nil {
		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
	}

	fi, err := os.Lstat(filename)
	if os.IsNotExist(err) {
		return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
	}
	if fi.Size() == 0 {
		return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile)
	}

	f, err := os.Open(filename)
	if err != nil {
		return err
	}

	b.dockerfile, err = parser.Parse(f)
	f.Close()

	if err != nil {
		return err
	}

	// After the Dockerfile has been parsed, we need to check the .dockerignore
	// file for either "Dockerfile" or ".dockerignore", and if either are
	// present then erase them from the build context. These files should never
	// have been sent from the client but we did send them to make sure that
	// we had the Dockerfile to actually parse, and then we also need the
	// .dockerignore file to know whether either file should be removed.
	// Note that this assumes the Dockerfile has been read into memory and
	// is now safe to be removed.

	excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
	if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
		os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
		b.context.(tarsum.BuilderContext).Remove(".dockerignore")
	}
	if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
		os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
		b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
	}

	return nil
}
예제 #29
0
파일: docker.go 프로젝트: nikkomega/origin
// replaceValidCmd replaces the valid occurrence of a command
// in a Dockerfile with the given replaceArgs
func replaceValidCmd(cmd, replaceArgs string, fileData []byte) (string, error) {
	if _, ok := dockercmd.Commands[cmd]; !ok {
		return "", invalidCmdErr
	}
	buf := bytes.NewBuffer(fileData)
	// Parse with Docker parser
	node, err := parser.Parse(buf)
	if err != nil {
		return "", errors.New("cannot parse Dockerfile: " + err.Error())
	}

	pos := traverseAST(cmd, node)
	if pos == 0 {
		return "", replaceCmdErr
	}

	// Re-initialize the buffer
	buf = bytes.NewBuffer(fileData)
	var newFileData string
	var index int
	var replaceNextLn bool
	for {
		line, err := buf.ReadString('\n')
		if err != nil && err != io.EOF {
			return "", err
		}
		line = strings.TrimSpace(line)

		// The current line starts with the specified command (cmd)
		if strings.HasPrefix(strings.ToUpper(line), strings.ToUpper(cmd)) {
			index++

			// The current line finishes on a backslash.
			// All we need to do is replace the next line
			// with our specified replaceArgs
			if line[len(line)-1:] == "\\" && index == pos {
				replaceNextLn = true

				args := strings.Split(line, " ")
				if len(args) > 2 {
					// Keep just our Dockerfile command and the backslash
					newFileData += args[0] + " \\" + "\n"
				} else {
					newFileData += line + "\n"
				}
				continue
			}

			// Normal ending line
			if index == pos {
				line = fmt.Sprintf("%s %s", strings.ToUpper(cmd), replaceArgs)
			}
		}

		// Previous line ended on a backslash
		// This line contains command arguments
		if replaceNextLn {
			if line[len(line)-1:] == "\\" {
				// Ignore all successive lines terminating on a backslash
				// since they all are going to be replaced by replaceArgs
				continue
			}
			replaceNextLn = false
			line = replaceArgs
		}

		if err == io.EOF {
			// Otherwise, the new Dockerfile will have one newline
			// more in the end
			newFileData += line
			break
		}
		newFileData += line + "\n"
	}

	// Parse output for validation
	buf = bytes.NewBuffer([]byte(newFileData))
	if _, err := parser.Parse(buf); err != nil {
		return "", errors.New("cannot parse new Dockerfile: " + err.Error())
	}

	return newFileData, nil
}
예제 #30
0
func TestInsertEnvAfterFrom(t *testing.T) {
	tests := map[string]struct {
		original string
		env      []kapi.EnvVar
		want     string
	}{
		"no FROM instruction": {
			original: `RUN echo "invalid Dockerfile"
`,
			env: []kapi.EnvVar{
				{Name: "PATH", Value: "/bin"},
			},
			want: `RUN echo "invalid Dockerfile"
`},
		"empty env": {
			original: `FROM busybox
`,
			env: []kapi.EnvVar{},
			want: `FROM busybox
`},
		"single FROM instruction": {
			original: `FROM busybox
RUN echo "hello world"
`,
			env: []kapi.EnvVar{
				{Name: "PATH", Value: "/bin"},
			},
			want: `FROM busybox
ENV "PATH"="/bin"
RUN echo "hello world"
`},
		"multiple FROM instructions": {
			original: `FROM scratch
FROM busybox
RUN echo "hello world"
`,
			env: []kapi.EnvVar{
				{Name: "PATH", Value: "/bin"},
				{Name: "GOPATH", Value: "/go"},
				{Name: "PATH", Value: "/go/bin:$PATH"},
			},
			want: `FROM scratch
ENV "PATH"="/bin" "GOPATH"="/go" "PATH"="/go/bin:$PATH"
FROM busybox
ENV "PATH"="/bin" "GOPATH"="/go" "PATH"="/go/bin:$PATH"
RUN echo "hello world"
`},
	}
	for name, test := range tests {
		got, err := parser.Parse(strings.NewReader(test.original))
		if err != nil {
			t.Errorf("%s: %v", name, err)
			continue
		}
		want, err := parser.Parse(strings.NewReader(test.want))
		if err != nil {
			t.Errorf("%s: %v", name, err)
			continue
		}
		insertEnvAfterFrom(got, test.env)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("%s: insertEnvAfterFrom(node, %+v) = %+v; want %+v", name, test.env, got, want)
			t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got))
		}
	}
}