<commit_before>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"gopkg.in/yaml.v2"

	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/ids"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/internal/pipe"
	"github.com/goreleaser/goreleaser/internal/semerrgroup"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/config"
	"github.com/goreleaser/goreleaser/pkg/context"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH.
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided.
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided.
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// Metadata to generate the snap package.
type Metadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Base          string `yaml:",omitempty"`
	License       string `yaml:",omitempty"`
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
	Plugs         map[string]interface{} `yaml:",omitempty"`
}

// AppMetadata for the binaries that will be in the snap package.
type AppMetadata struct {
	Command          string
	Plugs            []string `yaml:",omitempty"`
	Daemon           string   `yaml:",omitempty"`
	Completer        string   `yaml:",omitempty"`
	RestartCondition string   `yaml:"restart-condition,omitempty"`
}

const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"

// Pipe for snapcraft packaging.
type Pipe struct{}

func (Pipe) String() string {
	return "snapcraft packages"
}

// Default sets the pipe defaults.
func (Pipe) Default(ctx *context.Context) error {
	var ids = ids.New("snapcrafts")
	for i := range ctx.Config.Snapcrafts {
		var snap = &ctx.Config.Snapcrafts[i]
		if snap.NameTemplate == "" {
			snap.NameTemplate = defaultNameTemplate
		}
		if len(snap.Builds) == 0 {
			for _, b := range ctx.Config.Builds {
				snap.Builds = append(snap.Builds, b.ID)
			}
		}
		ids.Inc(snap.ID)
	}
	return ids.Validate()
}

// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
	for _, snap := range ctx.Config.Snapcrafts {
		// TODO: deal with pipe.skip?
		if err := doRun(ctx, snap); err != nil {
			return err
		}
	}
	return nil
}

func doRun(ctx *context.Context, snap config.Snapcraft) error {
	if snap.Summary == "" && snap.Description == "" {
		return pipe.Skip("no summary nor description were provided")
	}
	if snap.Summary == "" {
		return ErrNoSummary
	}
	if snap.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g = semerrgroup.New(ctx.Parallelism)
	for platform, binaries := range ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("linux"),
			artifact.ByType(artifact.Binary),
			artifact.ByIDs(snap.Builds...),
		),
	).GroupByPlatform() {
		arch := linux.Arch(platform)
		if !isValidArch(arch) {
			log.WithField("arch", arch).Warn("ignored unsupported arch")
			continue
		}
		binaries := binaries
		g.Go(func() error {
			return create(ctx, snap, arch, binaries)
		})
	}
	return g.Wait()
}

func isValidArch(arch string) bool {
	// https://snapcraft.io/docs/architectures
	for _, a := range []string{"s390x", "ppc64el", "arm64", "armhf", "amd64", "i386"} {
		if arch == a {
			return true
		}
	}
	return false
}

// Publish packages.
func (Pipe) Publish(ctx *context.Context) error {
	if ctx.SkipPublish {
		return pipe.ErrSkipPublishEnabled
	}
	snaps := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List()
	var g = semerrgroup.New(ctx.Parallelism)
	for _, snap := range snaps {
		snap := snap
		g.Go(func() error {
			return push(ctx, snap)
		})
	}
	return g.Wait()
}

func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries []*artifact.Artifact) error {
	var log = log.WithField("arch", arch)
	folder, err := tmpl.New(ctx).
		WithArtifact(binaries[0], snap.Replacements).
		Apply(snap.NameTemplate)
	if err != nil {
		return err
	}

	// prime is the directory that then will be compressed to make the .snap package.
	var folderDir = filepath.Join(ctx.Config.Dist, folder)
	var primeDir = filepath.Join(folderDir, "prime")
	var metaDir = filepath.Join(primeDir, "meta")
	// #nosec
	if err = os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	for _, file := range snap.Files {
		if file.Destination == "" {
			file.Destination = file.Source
		}
		if file.Mode == 0 {
			file.Mode = 0644
		}
		if err := os.MkdirAll(filepath.Join(primeDir, filepath.Dir(file.Destination)), 0755); err != nil {
			return fmt.Errorf("failed to link extra file '%s': %w", file.Source, err)
		}
		if err := link(file.Source, filepath.Join(primeDir, file.Destination), os.FileMode(file.Mode)); err != nil {
			return fmt.Errorf("failed to link extra file '%s': %w", file.Source, err)
		}
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &Metadata{
		Version:       ctx.Version,
		Summary:       snap.Summary,
		Description:   snap.Description,
		Grade:         snap.Grade,
		Confinement:   snap.Confinement,
		Architectures: []string{arch},
		Apps:          map[string]AppMetadata{},
	}

	if snap.Base != "" {
		metadata.Base = snap.Base
	}

	if snap.License != "" {
		metadata.License = snap.License
	}

	metadata.Name = ctx.Config.ProjectName
	if snap.Name != "" {
		metadata.Name = snap.Name
	}

	// if the user didn't specify any apps then
	// default to the main binary being the command:
	if len(snap.Apps) == 0 {
		var name = snap.Name
		if name == "" {
			name = binaries[0].Name
		}
		metadata.Apps[name] = AppMetadata{
			Command: filepath.Base(binaries[0].Name),
		}
	}

	for _, binary := range binaries {
		// build the binaries and link resources
		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		log.WithField("src", binary.Path).
			WithField("dst", destBinaryPath).
			Debug("linking")

		if err = os.Link(binary.Path, destBinaryPath); err != nil {
			return fmt.Errorf("failed to link binary: %w", err)
		}
		if err := os.Chmod(destBinaryPath, 0555); err != nil {
			return fmt.Errorf("failed to change binary permissions: %w", err)
		}
	}

	// setup the apps: directive for each binary
	for name, config := range snap.Apps {
		command := name
		if config.Command != "" {
			command = config.Command
		}

		// TODO: test that the correct binary is used in Command
		// See https://github.com/goreleaser/goreleaser/pull/1449
		appMetadata := AppMetadata{
			Command: strings.TrimSpace(strings.Join([]string{
				command,
				config.Args,
			}, " ")),
			Plugs:            config.Plugs,
			Daemon:           config.Daemon,
			RestartCondition: config.RestartCondition,
		}

		if config.Completer != "" {
			destCompleterPath := filepath.Join(primeDir, config.Completer)
			if err := os.MkdirAll(filepath.Dir(destCompleterPath), 0755); err != nil {
				return fmt.Errorf("failed to create folder: %w", err)
			}
			log.WithField("src", config.Completer).
				WithField("dst", destCompleterPath).
				Debug("linking")
			if err := os.Link(config.Completer, destCompleterPath); err != nil {
				return fmt.Errorf("failed to link completer: %w", err)
			}
			if err := os.Chmod(destCompleterPath, 0644); err != nil {
				return fmt.Errorf("failed to change completer permissions: %w", err)
			}
			appMetadata.Completer = config.Completer
		}

		metadata.Apps[name] = appMetadata
		metadata.Plugs = snap.Plugs
	}

	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	log.WithField("file", file).Debugf("writing metadata file")
	if err = ioutil.WriteFile(file, out, 0644); err != nil { //nolint: gosec
		return err
	}

	var snapFile = filepath.Join(ctx.Config.Dist, folder+".snap")
	log.WithField("snap", snapFile).Info("creating")
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snapFile)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	if !snap.Publish {
		return nil
	}
	ctx.Artifacts.Add(&artifact.Artifact{
		Type:   artifact.PublishableSnapcraft,
		Name:   folder + ".snap",
		Path:   snapFile,
		Goos:   binaries[0].Goos,
		Goarch: binaries[0].Goarch,
		Goarm:  binaries[0].Goarm,
	})
	return nil
}

const reviewWaitMsg = `Waiting for previous upload(s) to complete their review process.`

func push(ctx *context.Context, snap *artifact.Artifact) error {
	var log = log.WithField("snap", snap.Name)
	log.Info("pushing snap")
	// TODO: customize --release based on snap.Grade?
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "push", "--release=stable", snap.Path)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), reviewWaitMsg) {
			log.Warn(reviewWaitMsg)
		} else {
			return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out))
		}
	}
	snap.Type = artifact.Snapcraft
	ctx.Artifacts.Add(snap)
	return nil
}

// walks the src, recreating dirs and hard-linking files.
func link(src, dest string, mode os.FileMode) error {
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// We have the following:
		// - src = "a/b"
		// - dest = "dist/linuxamd64/b"
		// - path = "a/b/c.txt"
		// So we join "a/b" with "c.txt" and use it as the destination.
		var dst = filepath.Join(dest, strings.Replace(path, src, "", 1))
		log.WithFields(log.Fields{
			"src": path,
			"dst": dst,
		}).Debug("extra file")
		if info.IsDir() {
			return os.MkdirAll(dst, info.Mode())
		}
		if err := os.Link(path, dst); err != nil {
			return err
		}
		return os.Chmod(dst, mode)
	})
}
<commit_msg>fix: update snapcraft command from 'push' to 'upload' (#2051)<commit_after>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"gopkg.in/yaml.v2"

	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/ids"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/internal/pipe"
	"github.com/goreleaser/goreleaser/internal/semerrgroup"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/config"
	"github.com/goreleaser/goreleaser/pkg/context"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH.
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided.
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided.
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// Metadata to generate the snap package.
type Metadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Base          string `yaml:",omitempty"`
	License       string `yaml:",omitempty"`
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
	Plugs         map[string]interface{} `yaml:",omitempty"`
}

// AppMetadata for the binaries that will be in the snap package.
type AppMetadata struct {
	Command          string
	Plugs            []string `yaml:",omitempty"`
	Daemon           string   `yaml:",omitempty"`
	Completer        string   `yaml:",omitempty"`
	RestartCondition string   `yaml:"restart-condition,omitempty"`
}

const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"

// Pipe for snapcraft packaging.
type Pipe struct{}

func (Pipe) String() string {
	return "snapcraft packages"
}

// Default sets the pipe defaults.
func (Pipe) Default(ctx *context.Context) error {
	var ids = ids.New("snapcrafts")
	for i := range ctx.Config.Snapcrafts {
		var snap = &ctx.Config.Snapcrafts[i]
		if snap.NameTemplate == "" {
			snap.NameTemplate = defaultNameTemplate
		}
		if len(snap.Builds) == 0 {
			for _, b := range ctx.Config.Builds {
				snap.Builds = append(snap.Builds, b.ID)
			}
		}
		ids.Inc(snap.ID)
	}
	return ids.Validate()
}

// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
	for _, snap := range ctx.Config.Snapcrafts {
		// TODO: deal with pipe.skip?
		if err := doRun(ctx, snap); err != nil {
			return err
		}
	}
	return nil
}

func doRun(ctx *context.Context, snap config.Snapcraft) error {
	if snap.Summary == "" && snap.Description == "" {
		return pipe.Skip("no summary nor description were provided")
	}
	if snap.Summary == "" {
		return ErrNoSummary
	}
	if snap.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g = semerrgroup.New(ctx.Parallelism)
	for platform, binaries := range ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("linux"),
			artifact.ByType(artifact.Binary),
			artifact.ByIDs(snap.Builds...),
		),
	).GroupByPlatform() {
		arch := linux.Arch(platform)
		if !isValidArch(arch) {
			log.WithField("arch", arch).Warn("ignored unsupported arch")
			continue
		}
		binaries := binaries
		g.Go(func() error {
			return create(ctx, snap, arch, binaries)
		})
	}
	return g.Wait()
}

func isValidArch(arch string) bool {
	// https://snapcraft.io/docs/architectures
	for _, a := range []string{"s390x", "ppc64el", "arm64", "armhf", "amd64", "i386"} {
		if arch == a {
			return true
		}
	}
	return false
}

// Publish packages.
func (Pipe) Publish(ctx *context.Context) error {
	if ctx.SkipPublish {
		return pipe.ErrSkipPublishEnabled
	}
	snaps := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List()
	var g = semerrgroup.New(ctx.Parallelism)
	for _, snap := range snaps {
		snap := snap
		g.Go(func() error {
			return push(ctx, snap)
		})
	}
	return g.Wait()
}

func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries []*artifact.Artifact) error {
	var log = log.WithField("arch", arch)
	folder, err := tmpl.New(ctx).
		WithArtifact(binaries[0], snap.Replacements).
		Apply(snap.NameTemplate)
	if err != nil {
		return err
	}

	// prime is the directory that then will be compressed to make the .snap package.
	var folderDir = filepath.Join(ctx.Config.Dist, folder)
	var primeDir = filepath.Join(folderDir, "prime")
	var metaDir = filepath.Join(primeDir, "meta")
	// #nosec
	if err = os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	for _, file := range snap.Files {
		if file.Destination == "" {
			file.Destination = file.Source
		}
		if file.Mode == 0 {
			file.Mode = 0644
		}
		if err := os.MkdirAll(filepath.Join(primeDir, filepath.Dir(file.Destination)), 0755); err != nil {
			return fmt.Errorf("failed to link extra file '%s': %w", file.Source, err)
		}
		if err := link(file.Source, filepath.Join(primeDir, file.Destination), os.FileMode(file.Mode)); err != nil {
			return fmt.Errorf("failed to link extra file '%s': %w", file.Source, err)
		}
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &Metadata{
		Version:       ctx.Version,
		Summary:       snap.Summary,
		Description:   snap.Description,
		Grade:         snap.Grade,
		Confinement:   snap.Confinement,
		Architectures: []string{arch},
		Apps:          map[string]AppMetadata{},
	}

	if snap.Base != "" {
		metadata.Base = snap.Base
	}

	if snap.License != "" {
		metadata.License = snap.License
	}

	metadata.Name = ctx.Config.ProjectName
	if snap.Name != "" {
		metadata.Name = snap.Name
	}

	// if the user didn't specify any apps then
	// default to the main binary being the command:
	if len(snap.Apps) == 0 {
		var name = snap.Name
		if name == "" {
			name = binaries[0].Name
		}
		metadata.Apps[name] = AppMetadata{
			Command: filepath.Base(binaries[0].Name),
		}
	}

	for _, binary := range binaries {
		// build the binaries and link resources
		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		log.WithField("src", binary.Path).
			WithField("dst", destBinaryPath).
			Debug("linking")

		if err = os.Link(binary.Path, destBinaryPath); err != nil {
			return fmt.Errorf("failed to link binary: %w", err)
		}
		if err := os.Chmod(destBinaryPath, 0555); err != nil {
			return fmt.Errorf("failed to change binary permissions: %w", err)
		}
	}

	// setup the apps: directive for each binary
	for name, config := range snap.Apps {
		command := name
		if config.Command != "" {
			command = config.Command
		}

		// TODO: test that the correct binary is used in Command
		// See https://github.com/goreleaser/goreleaser/pull/1449
		appMetadata := AppMetadata{
			Command: strings.TrimSpace(strings.Join([]string{
				command,
				config.Args,
			}, " ")),
			Plugs:            config.Plugs,
			Daemon:           config.Daemon,
			RestartCondition: config.RestartCondition,
		}

		if config.Completer != "" {
			destCompleterPath := filepath.Join(primeDir, config.Completer)
			if err := os.MkdirAll(filepath.Dir(destCompleterPath), 0755); err != nil {
				return fmt.Errorf("failed to create folder: %w", err)
			}
			log.WithField("src", config.Completer).
				WithField("dst", destCompleterPath).
				Debug("linking")
			if err := os.Link(config.Completer, destCompleterPath); err != nil {
				return fmt.Errorf("failed to link completer: %w", err)
			}
			if err := os.Chmod(destCompleterPath, 0644); err != nil {
				return fmt.Errorf("failed to change completer permissions: %w", err)
			}
			appMetadata.Completer = config.Completer
		}

		metadata.Apps[name] = appMetadata
		metadata.Plugs = snap.Plugs
	}

	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	log.WithField("file", file).Debugf("writing metadata file")
	if err = ioutil.WriteFile(file, out, 0644); err != nil { //nolint: gosec
		return err
	}

	var snapFile = filepath.Join(ctx.Config.Dist, folder+".snap")
	log.WithField("snap", snapFile).Info("creating")
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snapFile)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	if !snap.Publish {
		return nil
	}
	ctx.Artifacts.Add(&artifact.Artifact{
		Type:   artifact.PublishableSnapcraft,
		Name:   folder + ".snap",
		Path:   snapFile,
		Goos:   binaries[0].Goos,
		Goarch: binaries[0].Goarch,
		Goarm:  binaries[0].Goarm,
	})
	return nil
}

const reviewWaitMsg = `Waiting for previous upload(s) to complete their review process.`

func push(ctx *context.Context, snap *artifact.Artifact) error {
	var log = log.WithField("snap", snap.Name)
	log.Info("pushing snap")
	// TODO: customize --release based on snap.Grade?
	/* #nosec */
	var cmd = exec.CommandContext(ctx, "snapcraft", "upload", "--release=stable", snap.Path)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), reviewWaitMsg) {
			log.Warn(reviewWaitMsg)
		} else {
			return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out))
		}
	}
	snap.Type = artifact.Snapcraft
	ctx.Artifacts.Add(snap)
	return nil
}

// walks the src, recreating dirs and hard-linking files.
func link(src, dest string, mode os.FileMode) error {
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// We have the following:
		// - src = "a/b"
		// - dest = "dist/linuxamd64/b"
		// - path = "a/b/c.txt"
		// So we join "a/b" with "c.txt" and use it as the destination.
		var dst = filepath.Join(dest, strings.Replace(path, src, "", 1))
		log.WithFields(log.Fields{
			"src": path,
			"dst": dst,
		}).Debug("extra file")
		if info.IsDir() {
			return os.MkdirAll(dst, info.Mode())
		}
		if err := os.Link(path, dst); err != nil {
			return err
		}
		return os.Chmod(dst, mode)
	})
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package watcher

import (
	"context"
	"errors"
	"expvar"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/golang/glog"
	"github.com/google/mtail/internal/testutil"
)

// This test requires disk access, and cannot be injected without internal
// knowledge of the fsnotify code. Make the wait deadlines long.
const deadline = 5 * time.Second

func TestLogWatcher(t *testing.T) {
	if testing.Short() {
		// This test is slow due to disk access.
		t.Skip("skipping log watcher test in short mode")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		if err = os.RemoveAll(workdir); err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()
	handle, eventsChannel := w.Events()

	if err = w.Add(workdir, handle); err != nil {
		t.Fatal(err)
	}
	f, err := os.Create(filepath.Join(workdir, "logfile"))
	if err != nil {
		t.Fatalf("couldn't make a logfile in temp dir: %s\n", err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Create:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("create doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive create message before timeout")
	}
	if n, err := f.WriteString("hi"); err != nil {
		t.Fatal(err)
		if n != 2 {
			t.Fatalf("wrote %d instead of 2", n)
		}
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Update:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("update doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive update message before timeout")
	}
	if err := os.Rename(filepath.Join(workdir, "logfile"), filepath.Join(workdir, "logfile2")); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Delete:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("delete doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive delete before timeout")
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Create:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("create doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive create message before timeout")
	}
	if err := os.Chmod(filepath.Join(workdir, "logfile2"), os.ModePerm); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Update:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("update doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive update message before timeout")
	}
	if err := os.Remove(filepath.Join(workdir, "logfile2")); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Delete:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("delete doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive delete message before timeout")
	}
}

// This test may be OS specific; possibly break it out to a file with build tags.
func TestFsnotifyErrorFallbackToPoll(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	// The Warning log isn't created until the first write.  Create it before
	// setting the rlimit on open files or the test will fail trying to open
	// the log file instead of where it should.
	glog.Warning("pre-creating log to avoid too many open file")

	var rLimit syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		t.Fatalf("couldn't get rlimit: %s", err)
	}
	var zero = rLimit
	zero.Cur = 0
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &zero); err != nil {
		t.Fatalf("couldn't set rlimit: %s", err)
	}
	_, err := NewLogWatcher(0, true)
	if err != nil {
		t.Error(err)
	}
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		t.Fatalf("couldn't reset rlimit: %s", err)
	}
}

func TestLogWatcherAddError(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		err = os.RemoveAll(workdir)
		if err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()
	handle, _ := w.Events()
	filename := filepath.Join(workdir, "test")
	err = w.Add(filename, handle)
	if err == nil {
		t.Errorf("did not receive an error for nonexistent file")
	}
}

func TestLogWatcherAddWhilePermissionDenied(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	u, err := user.Current()
	if err != nil {
		t.Skip(fmt.Sprintf("Couldn't determine current user id: %s", err))
	}
	if u.Uid == "0" {
		t.Skip("Skipping test when run as root")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		err = os.RemoveAll(workdir)
		if err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()

	filename := filepath.Join(workdir, "test")
	if _, err = os.Create(filename); err != nil {
		t.Fatalf("couldn't create file: %s", err)
	}
	if err = os.Chmod(filename, 0); err != nil {
		t.Fatalf("couldn't chmod file: %s", err)
	}
	handle, _ := w.Events()
	err = w.Add(filename, handle)
	if err != nil {
		t.Errorf("failed to add watch on permission denied")
	}
	if err := os.Chmod(filename, 0777); err != nil {
		t.Fatalf("couldn't reset file perms: %s", err)
	}
}

func TestWatcherErrors(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	orig, err := strconv.ParseInt(expvar.Get("log_watcher_error_count").String(), 10, 64)
	if err != nil {
		t.Fatalf("couldn't convert expvar %q", expvar.Get("log_watcher_error_count").String())
	}
	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher")
	}
	w.watcher.Errors <- errors.New("Injected error for test")
	if err := w.Close(); err != nil {
		t.Fatalf("watcher close failed: %q", err)
	}
	expected := strconv.FormatInt(orig+1, 10)
	if diff := testutil.Diff(expected, expvar.Get("log_watcher_error_count").String()); diff != "" {
		t.Errorf("log watcher error count not increased:\n%s", diff)
	}
}

func TestWatcherNewFile(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	tests := []struct {
		d time.Duration
		b bool
	}{
		{0, true},
		{10 * time.Millisecond, false},
	}
	for _, test := range tests {
		t.Run(fmt.Sprintf("%s %v", test.d, test.b), func(t *testing.T) {
			w, err := NewLogWatcher(test.d, test.b)
			testutil.FatalIfErr(t, err)
			tmpDir, rmTmpDir := testutil.TestTempDir(t)
			defer rmTmpDir()
			handle, eventsChan := w.Events()
			testutil.FatalIfErr(t, w.Add(tmpDir, handle))
			result := []Event{}
			done := make(chan struct{})
			wg := sync.WaitGroup{}
			go func() {
				for event := range eventsChan {
					glog.Infof("Event: %v", event)
					result = append(result, event)
					if event.Op == Update && event.Pathname == tmpDir {
						testutil.FatalIfErr(t, w.Add(path.Join(tmpDir, "log"), handle))
					}
					wg.Done()
				}
				close(done)
			}()
			wg.Add(2)
			testutil.TestOpenFile(t, path.Join(tmpDir, "log"))
			time.Sleep(250 * time.Millisecond)
			w.Close()
			<-done
			expected := []Event{{Op: Create, Pathname: path.Join(tmpDir, "log")}}
			if diff := testutil.Diff(expected, result); diff != "" {
				t.Errorf("event unexpected: diff:\n%s", diff)
				t.Logf("received:\n%v", result)
			}
		})
	}
}

type testStubProcessor struct {
	Events []Event
}

func (t *testStubProcessor) ProcessFileEvent(ctx context.Context, e Event) {
	t.Events = append(t.Events, e)
}

func TestLogWatcherObserve(t *testing.T) {
	p := &testStubProcessor{}
	w, err := NewLogWatcher(0, false)
	testing.FatalIfErr(err)
	tmpDir, rmTmpDir := testutil.TestTempDir(t)
	defer rmTmpDir()
	testing.FatalIfErr(t, w.Observe(path.Join(tmpDir, "f"), p))
}
<commit_msg>Fix test compile bug.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package watcher

import (
	"context"
	"errors"
	"expvar"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/golang/glog"
	"github.com/google/mtail/internal/testutil"
)

// This test requires disk access, and cannot be injected without internal
// knowledge of the fsnotify code. Make the wait deadlines long.
const deadline = 5 * time.Second

func TestLogWatcher(t *testing.T) {
	if testing.Short() {
		// This test is slow due to disk access.
		t.Skip("skipping log watcher test in short mode")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		if err = os.RemoveAll(workdir); err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()
	handle, eventsChannel := w.Events()

	if err = w.Add(workdir, handle); err != nil {
		t.Fatal(err)
	}
	f, err := os.Create(filepath.Join(workdir, "logfile"))
	if err != nil {
		t.Fatalf("couldn't make a logfile in temp dir: %s\n", err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Create:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("create doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive create message before timeout")
	}
	if n, err := f.WriteString("hi"); err != nil {
		t.Fatal(err)
		if n != 2 {
			t.Fatalf("wrote %d instead of 2", n)
		}
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Update:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("update doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive update message before timeout")
	}
	if err := os.Rename(filepath.Join(workdir, "logfile"), filepath.Join(workdir, "logfile2")); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Delete:
			if e.Pathname != filepath.Join(workdir, "logfile") {
				t.Errorf("delete doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive delete before timeout")
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Create:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("create doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive create message before timeout")
	}
	if err := os.Chmod(filepath.Join(workdir, "logfile2"), os.ModePerm); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Update:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("update doesn't match")
			}
		default:

			t.Errorf("wrong event type: %v", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive update message before timeout")
	}
	if err := os.Remove(filepath.Join(workdir, "logfile2")); err != nil {
		t.Fatal(err)
	}
	select {
	case e := <-eventsChannel:
		switch e.Op {
		case Delete:
			if e.Pathname != filepath.Join(workdir, "logfile2") {
				t.Errorf("delete doesn't match")
			}
		default:
			t.Errorf("Wrong event type: %q", e)
		}
	case <-time.After(deadline):
		t.Errorf("didn't receive delete message before timeout")
	}
}

// This test may be OS specific; possibly break it out to a file with build tags.
func TestFsnotifyErrorFallbackToPoll(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	// The Warning log isn't created until the first write.  Create it before
	// setting the rlimit on open files or the test will fail trying to open
	// the log file instead of where it should.
	glog.Warning("pre-creating log to avoid too many open file")

	var rLimit syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		t.Fatalf("couldn't get rlimit: %s", err)
	}
	var zero = rLimit
	zero.Cur = 0
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &zero); err != nil {
		t.Fatalf("couldn't set rlimit: %s", err)
	}
	_, err := NewLogWatcher(0, true)
	if err != nil {
		t.Error(err)
	}
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		t.Fatalf("couldn't reset rlimit: %s", err)
	}
}

func TestLogWatcherAddError(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		err = os.RemoveAll(workdir)
		if err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()
	handle, _ := w.Events()
	filename := filepath.Join(workdir, "test")
	err = w.Add(filename, handle)
	if err == nil {
		t.Errorf("did not receive an error for nonexistent file")
	}
}

func TestLogWatcherAddWhilePermissionDenied(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	u, err := user.Current()
	if err != nil {
		t.Skip(fmt.Sprintf("Couldn't determine current user id: %s", err))
	}
	if u.Uid == "0" {
		t.Skip("Skipping test when run as root")
	}

	workdir, err := ioutil.TempDir("", "log_watcher_test")
	if err != nil {
		t.Fatalf("could not create temporary working directory: %s", err)
	}

	defer func() {
		err = os.RemoveAll(workdir)
		if err != nil {
			t.Fatalf("could not remove temp dir %s: %s:", workdir, err)
		}
	}()

	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher: %s\n", err)
	}
	defer func() {
		if err = w.Close(); err != nil {
			t.Fatal(err)
		}
	}()

	filename := filepath.Join(workdir, "test")
	if _, err = os.Create(filename); err != nil {
		t.Fatalf("couldn't create file: %s", err)
	}
	if err = os.Chmod(filename, 0); err != nil {
		t.Fatalf("couldn't chmod file: %s", err)
	}
	handle, _ := w.Events()
	err = w.Add(filename, handle)
	if err != nil {
		t.Errorf("failed to add watch on permission denied")
	}
	if err := os.Chmod(filename, 0777); err != nil {
		t.Fatalf("couldn't reset file perms: %s", err)
	}
}

func TestWatcherErrors(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	orig, err := strconv.ParseInt(expvar.Get("log_watcher_error_count").String(), 10, 64)
	if err != nil {
		t.Fatalf("couldn't convert expvar %q", expvar.Get("log_watcher_error_count").String())
	}
	w, err := NewLogWatcher(0, true)
	if err != nil {
		t.Fatalf("couldn't create a watcher")
	}
	w.watcher.Errors <- errors.New("Injected error for test")
	if err := w.Close(); err != nil {
		t.Fatalf("watcher close failed: %q", err)
	}
	expected := strconv.FormatInt(orig+1, 10)
	if diff := testutil.Diff(expected, expvar.Get("log_watcher_error_count").String()); diff != "" {
		t.Errorf("log watcher error count not increased:\n%s", diff)
	}
}

func TestWatcherNewFile(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping log watcher test in short mode")
	}
	tests := []struct {
		d time.Duration
		b bool
	}{
		{0, true},
		{10 * time.Millisecond, false},
	}
	for _, test := range tests {
		t.Run(fmt.Sprintf("%s %v", test.d, test.b), func(t *testing.T) {
			w, err := NewLogWatcher(test.d, test.b)
			testutil.FatalIfErr(t, err)
			tmpDir, rmTmpDir := testutil.TestTempDir(t)
			defer rmTmpDir()
			handle, eventsChan := w.Events()
			testutil.FatalIfErr(t, w.Add(tmpDir, handle))
			result := []Event{}
			done := make(chan struct{})
			wg := sync.WaitGroup{}
			go func() {
				for event := range eventsChan {
					glog.Infof("Event: %v", event)
					result = append(result, event)
					if event.Op == Update && event.Pathname == tmpDir {
						testutil.FatalIfErr(t, w.Add(path.Join(tmpDir, "log"), handle))
					}
					wg.Done()
				}
				close(done)
			}()
			wg.Add(2)
			testutil.TestOpenFile(t, path.Join(tmpDir, "log"))
			time.Sleep(250 * time.Millisecond)
			w.Close()
			<-done
			expected := []Event{{Op: Create, Pathname: path.Join(tmpDir, "log")}}
			if diff := testutil.Diff(expected, result); diff != "" {
				t.Errorf("event unexpected: diff:\n%s", diff)
				t.Logf("received:\n%v", result)
			}
		})
	}
}

type testStubProcessor struct {
	Events []Event
}

func (t *testStubProcessor) ProcessFileEvent(ctx context.Context, e Event) {
	t.Events = append(t.Events, e)
}

func TestLogWatcherObserve(t *testing.T) {
	p := &testStubProcessor{}
	w, err := NewLogWatcher(0, false)
	testutil.FatalIfErr(t, err)
	tmpDir, rmTmpDir := testutil.TestTempDir(t)
	defer rmTmpDir()
	testutil.FatalIfErr(t, w.Observe(path.Join(tmpDir, "f"), p))
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/terraform-providers/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/terraform-providers/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if Monitoring operation returns a 429 with a specific message for
// concurrent operations.
func isMonitoringRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 429 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}
<commit_msg>Retry internal errors from cloud SQL. (#3469) (#432)<commit_after>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/terraform-providers/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/terraform-providers/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlInternalError(err error) (bool, string) {
	if gerr, ok := err.(*SqlAdminOperationError); ok {
		// SqlAdminOperationError is a non-interface type so we need to cast it through
		// a layer of interface{}.  :)
		var ierr interface{}
		ierr = gerr
		if serr, ok := ierr.(*sqladmin.OperationErrors); ok && serr.Errors[0].Code == "INTERNAL_ERROR" {
			return true, "Received an internal error, which is sometimes retryable for some SQL resources.  Optimistically retrying."
		}

	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if Monitoring operation returns a 429 with a specific message for
// concurrent operations.
func isMonitoringRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 429 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"strings"
)

func main() {
	dec := xml.NewDecoder(os.Stdin)
	var stack []xml.StartElement
	for {
		tok, err := dec.Token()
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
			os.Exit(1)
		}
		switch tok := tok.(type) {
		case xml.StartElement:
			stack = append(stack, tok)
		case xml.EndElement:
			stack = stack[:len(stack)-1]
		case xml.CharData:
			if containsAll(stack, os.Args[1:]) {
				printStack(stack, tok)
			}
		}
	}
}

func printStack(st []xml.StartElement, tok interface{}) {
	names := make([]string, 0, len(st))
	for _, e := range st {
		names = append(names, e.Name.Local)
	}
	fmt.Printf("%s: %s\n", strings.Join(names, " "), tok)
}

func containsAll(st []xml.StartElement, ss []string) bool {

	for len(ss) <= len(st) {
		if len(ss) == 0 {
			return true
		}
		if st[0].Name.Local == ss[0] {
			ss = ss[1:]
		}
		st = st[1:]
	}
	return false
}
<commit_msg>[7.17] Parsing input.<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"strings"
)

type Element struct {
	Name  string
	Attrs []Attr
}

func NewElement(name string) *Element {
	e := new(Element)
	e.Name = name
	e.Attrs = make([]Attr, 0)
	return e
}

type Attr struct {
	Name, Value string
}

func main() {
	input, err := parseInput(os.Args[1:])
	if err != nil {
		fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
		os.Exit(1)
	}
	dec := xml.NewDecoder(os.Stdin)
	var stack []xml.StartElement
	for {
		tok, err := dec.Token()
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
			os.Exit(1)
		}
		switch tok := tok.(type) {
		case xml.StartElement:
			stack = append(stack, tok)
		case xml.EndElement:
			stack = stack[:len(stack)-1]
		case xml.CharData:
			if containsAll(stack, input) {
				printStack(stack, tok)
			}
		}
	}
}

func parseInput(input []string) (elements []*Element, err error) {
	var e *Element
	for i, s := range input {
		if strings.Contains(s, "=") {
			if i > 0 {
				fmt.Printf("%d, %s\n", i, s)
				a := strings.SplitN(s, "=", 2)
				e.Attrs = append(e.Attrs, Attr{a[0], a[1]})
			} else {
				return nil, fmt.Errorf("attribute before tag name: %s", s)
			}
		}
		e = NewElement(s)
		elements = append(elements, e)
	}
	return
}

func printStack(st []xml.StartElement, tok interface{}) {
	names := make([]string, 0, len(st))
	for _, e := range st {
		names = append(names, e.Name.Local)
	}
	fmt.Printf("%s: %s\n", strings.Join(names, " "), tok)
}

func containsAll(stack []xml.StartElement, search []*Element) bool {

	for len(search) <= len(stack) {
		if len(search) == 0 {
			return true
		}
		if stack[0].Name.Local == search[0].Name {
			search = search[1:]
		}
		stack = stack[1:]
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"strings"

	"github.com/docker/docker/pkg/integration/checker"
	"github.com/go-check/check"
)

func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) {
	testRequires(c, DaemonIsLinux)
	out, _ := dockerCmd(c, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "wait", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", cleanedContainerID)

	cleanedImageID := strings.TrimSpace(out)

	dockerCmd(c, "inspect", cleanedImageID)
}

func (s *DockerSuite) TestCommitWithoutPause(c *check.C) {
	testRequires(c, DaemonIsLinux)
	out, _ := dockerCmd(c, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "wait", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", "-p=false", cleanedContainerID)

	cleanedImageID := strings.TrimSpace(out)

	dockerCmd(c, "inspect", cleanedImageID)
}

//test commit a paused container should not unpause it after commit
func (s *DockerSuite) TestCommitPausedContainer(c *check.C) {
	testRequires(c, DaemonIsLinux)
	defer unpauseAllContainers()
	out, _ := dockerCmd(c, "run", "-i", "-d", "busybox")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "pause", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", cleanedContainerID)

	out = inspectField(c, cleanedContainerID, "State.Paused")
	// commit should not unpause a paused container
	c.Assert(out, checker.Contains, "true")
}

func (s *DockerSuite) TestCommitNewFile(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo")

	imageID, _ := dockerCmd(c, "commit", "commiter")
	imageID = strings.TrimSpace(imageID)

	out, _ := dockerCmd(c, "run", imageID, "cat", "/foo")
	actual := strings.TrimSpace(out)
	c.Assert(actual, checker.Equals, "koye")
}

func (s *DockerSuite) TestCommitHardlink(c *check.C) {
	testRequires(c, DaemonIsLinux)
	firstOutput, _ := dockerCmd(c, "run", "-t", "--name", "hardlinks", "busybox", "sh", "-c", "touch file1 && ln file1 file2 && ls -di file1 file2")

	chunks := strings.Split(strings.TrimSpace(firstOutput), " ")
	inode := chunks[0]
	chunks = strings.SplitAfterN(strings.TrimSpace(firstOutput), " ", 2)
	c.Assert(chunks[1], checker.Contains, chunks[0], check.Commentf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]))

	imageID, _ := dockerCmd(c, "commit", "hardlinks", "hardlinks")
	imageID = strings.TrimSpace(imageID)

	secondOutput, _ := dockerCmd(c, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2")

	chunks = strings.Split(strings.TrimSpace(secondOutput), " ")
	inode = chunks[0]
	chunks = strings.SplitAfterN(strings.TrimSpace(secondOutput), " ", 2)
	c.Assert(chunks[1], checker.Contains, chunks[0], check.Commentf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]))
}

func (s *DockerSuite) TestCommitTTY(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "-t", "--name", "tty", "busybox", "/bin/ls")

	imageID, _ := dockerCmd(c, "commit", "tty", "ttytest")
	imageID = strings.TrimSpace(imageID)

	dockerCmd(c, "run", "ttytest", "/bin/ls")
}

func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true")

	imageID, _ := dockerCmd(c, "commit", "bind-commit", "bindtest")
	imageID = strings.TrimSpace(imageID)

	dockerCmd(c, "run", "bindtest", "true")
}

func (s *DockerSuite) TestCommitChange(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "--name", "test", "busybox", "true")

	imageID, _ := dockerCmd(c, "commit",
		"--change", "EXPOSE 8080",
		"--change", "ENV DEBUG true",
		"--change", "ENV test 1",
		"--change", "ENV PATH /foo",
		"--change", "LABEL foo bar",
		"--change", "CMD [\"/bin/sh\"]",
		"--change", "WORKDIR /opt",
		"--change", "ENTRYPOINT [\"/bin/sh\"]",
		"--change", "USER testuser",
		"--change", "VOLUME /var/lib/docker",
		"--change", "ONBUILD /usr/local/bin/python-build --dir /app/src",
		"test", "test-commit")
	imageID = strings.TrimSpace(imageID)

	expected := map[string]string{
		"Config.ExposedPorts": "map[8080/tcp:{}]",
		"Config.Env":          "[DEBUG=true test=1 PATH=/foo]",
		"Config.Labels":       "map[foo:bar]",
		"Config.Cmd":          "[/bin/sh]",
		"Config.WorkingDir":   "/opt",
		"Config.Entrypoint":   "[/bin/sh]",
		"Config.User":         "testuser",
		"Config.Volumes":      "map[/var/lib/docker:{}]",
		"Config.OnBuild":      "[/usr/local/bin/python-build --dir /app/src]",
	}

	for conf, value := range expected {
		res := inspectField(c, imageID, conf)
		if res != value {
			c.Errorf("%s('%s'), expected %s", conf, res, value)
		}
	}
}
<commit_msg>Windows: Enable some commit tests<commit_after>package main

import (
	"strings"

	"github.com/docker/docker/pkg/integration/checker"
	"github.com/go-check/check"
)

func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) {
	out, _ := dockerCmd(c, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "wait", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", cleanedContainerID)

	cleanedImageID := strings.TrimSpace(out)

	dockerCmd(c, "inspect", cleanedImageID)
}

func (s *DockerSuite) TestCommitWithoutPause(c *check.C) {
	testRequires(c, DaemonIsLinux)
	out, _ := dockerCmd(c, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "wait", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", "-p=false", cleanedContainerID)

	cleanedImageID := strings.TrimSpace(out)

	dockerCmd(c, "inspect", cleanedImageID)
}

//test commit a paused container should not unpause it after commit
func (s *DockerSuite) TestCommitPausedContainer(c *check.C) {
	testRequires(c, DaemonIsLinux)
	defer unpauseAllContainers()
	out, _ := dockerCmd(c, "run", "-i", "-d", "busybox")

	cleanedContainerID := strings.TrimSpace(out)

	dockerCmd(c, "pause", cleanedContainerID)

	out, _ = dockerCmd(c, "commit", cleanedContainerID)

	out = inspectField(c, cleanedContainerID, "State.Paused")
	// commit should not unpause a paused container
	c.Assert(out, checker.Contains, "true")
}

func (s *DockerSuite) TestCommitNewFile(c *check.C) {
	dockerCmd(c, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo")

	imageID, _ := dockerCmd(c, "commit", "commiter")
	imageID = strings.TrimSpace(imageID)

	out, _ := dockerCmd(c, "run", imageID, "cat", "/foo")
	actual := strings.TrimSpace(out)
	c.Assert(actual, checker.Equals, "koye")
}

func (s *DockerSuite) TestCommitHardlink(c *check.C) {
	testRequires(c, DaemonIsLinux)
	firstOutput, _ := dockerCmd(c, "run", "-t", "--name", "hardlinks", "busybox", "sh", "-c", "touch file1 && ln file1 file2 && ls -di file1 file2")

	chunks := strings.Split(strings.TrimSpace(firstOutput), " ")
	inode := chunks[0]
	chunks = strings.SplitAfterN(strings.TrimSpace(firstOutput), " ", 2)
	c.Assert(chunks[1], checker.Contains, chunks[0], check.Commentf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]))

	imageID, _ := dockerCmd(c, "commit", "hardlinks", "hardlinks")
	imageID = strings.TrimSpace(imageID)

	secondOutput, _ := dockerCmd(c, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2")

	chunks = strings.Split(strings.TrimSpace(secondOutput), " ")
	inode = chunks[0]
	chunks = strings.SplitAfterN(strings.TrimSpace(secondOutput), " ", 2)
	c.Assert(chunks[1], checker.Contains, chunks[0], check.Commentf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]))
}

func (s *DockerSuite) TestCommitTTY(c *check.C) {
	dockerCmd(c, "run", "-t", "--name", "tty", "busybox", "/bin/ls")

	imageID, _ := dockerCmd(c, "commit", "tty", "ttytest")
	imageID = strings.TrimSpace(imageID)

	dockerCmd(c, "run", "ttytest", "/bin/ls")
}

func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true")

	imageID, _ := dockerCmd(c, "commit", "bind-commit", "bindtest")
	imageID = strings.TrimSpace(imageID)

	dockerCmd(c, "run", "bindtest", "true")
}

func (s *DockerSuite) TestCommitChange(c *check.C) {
	testRequires(c, DaemonIsLinux)
	dockerCmd(c, "run", "--name", "test", "busybox", "true")

	imageID, _ := dockerCmd(c, "commit",
		"--change", "EXPOSE 8080",
		"--change", "ENV DEBUG true",
		"--change", "ENV test 1",
		"--change", "ENV PATH /foo",
		"--change", "LABEL foo bar",
		"--change", "CMD [\"/bin/sh\"]",
		"--change", "WORKDIR /opt",
		"--change", "ENTRYPOINT [\"/bin/sh\"]",
		"--change", "USER testuser",
		"--change", "VOLUME /var/lib/docker",
		"--change", "ONBUILD /usr/local/bin/python-build --dir /app/src",
		"test", "test-commit")
	imageID = strings.TrimSpace(imageID)

	expected := map[string]string{
		"Config.ExposedPorts": "map[8080/tcp:{}]",
		"Config.Env":          "[DEBUG=true test=1 PATH=/foo]",
		"Config.Labels":       "map[foo:bar]",
		"Config.Cmd":          "[/bin/sh]",
		"Config.WorkingDir":   "/opt",
		"Config.Entrypoint":   "[/bin/sh]",
		"Config.User":         "testuser",
		"Config.Volumes":      "map[/var/lib/docker:{}]",
		"Config.OnBuild":      "[/usr/local/bin/python-build --dir /app/src]",
	}

	for conf, value := range expected {
		res := inspectField(c, imageID, conf)
		if res != value {
			c.Errorf("%s('%s'), expected %s", conf, res, value)
		}
	}
}
<|endoftext|>


<commit_before>package push

import (
	"path/filepath"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("displaying manifest differences between pushes", func() {
	var (
		appName string
	)

	BeforeEach(func() {
		appName = helpers.PrefixedRandomName("app")
	})
	When("there is an app that has been pushed and we push it again with a different manifest", func() {
		It("displays the diff from the manifest", func() {
			helpers.WithHelloWorldApp(func(dir string) {

				pathToManifest := filepath.Join(dir, "manifest.yml")
				helpers.WriteManifest(pathToManifest, map[string]interface{}{
					"applications": []map[string]interface{}{
						{
							"name":       appName,
							"disk_quota": "1GB",
						},
					},
				})

				session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName)
				Eventually(session).Should(Exit(0))
				Expect(session).To(Say("name:\\s+%s", appName))

				helpers.WriteManifest(pathToManifest, map[string]interface{}{
					"applications": []map[string]interface{}{
						{
							"name":       appName,
							"disk_quota": "2GB",
						},
					},
				})

				session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName)
				Eventually(session).Should(Exit(0))
				Expect(session).To(Say("name:\\s+%s", appName))
				Expect(session).To(Say(`\-   disk-quota: 1024M`))
				Expect(session).To(Say(`\+   disk-quota: 2028M`))
			})
		})
	})
})
<commit_msg>Fix integration test for manifest diffs<commit_after>package push

import (
	"path/filepath"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("displaying manifest differences between pushes", func() {
	var (
		appName string
	)

	BeforeEach(func() {
		appName = helpers.PrefixedRandomName("app")
	})
	When("there is an app that has been pushed and we push it again with a different manifest", func() {
		It("displays the diff from the manifest", func() {
			helpers.WithHelloWorldApp(func(dir string) {

				pathToManifest := filepath.Join(dir, "manifest.yml")
				helpers.WriteManifest(pathToManifest, map[string]interface{}{
					"applications": []map[string]interface{}{
						{
							"name":      appName,
							"instances": 1,
						},
					},
				})

				session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName)
				Eventually(session).Should(Exit(0))
				Expect(session).To(Say("name:\\s+%s", appName))

				helpers.WriteManifest(pathToManifest, map[string]interface{}{
					"applications": []map[string]interface{}{
						{
							"name":      appName,
							"instances": 2,
						},
					},
				})

				session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName)
				Eventually(session).Should(Exit(0))
				Expect(session).To(Say("name:\\s+%s", appName))
				Expect(session).To(Say(`\-   instances: 1`))
				Expect(session).To(Say(`\+   instances: 2`))
			})
		})
	})
})
<|endoftext|>


<commit_before>package platform

// Largely based on
// https://github.com/moby/moby/blob/bc846d2e8fe5538220e0c31e9d0e8446f6fbc022/distribution/cpuinfo_unix.go
// Copyright 2012-2017 Docker, Inc.
//
// https://github.com/containerd/containerd/blob/726dcaea50883e51b2ec6db13caff0e7936b711d/platforms/cpuinfo.go
//    Copyright The containerd 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.

import (
	"bufio"
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/containers/image/v5/types"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)

// For Linux, the kernel has already detected the ABI, ISA and Features.
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
	if runtime.GOOS != "linux" {
		return "", fmt.Errorf("getCPUInfo for OS %s not implemented", runtime.GOOS)
	}

	cpuinfo, err := os.Open("/proc/cpuinfo")
	if err != nil {
		return "", err
	}
	defer cpuinfo.Close()

	// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
	// the first core is enough.
	scanner := bufio.NewScanner(cpuinfo)
	for scanner.Scan() {
		newline := scanner.Text()
		list := strings.Split(newline, ":")

		if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
			return strings.TrimSpace(list[1]), nil
		}
	}

	// Check whether the scanner encountered errors
	err = scanner.Err()
	if err != nil {
		return "", err
	}

	return "", fmt.Errorf("getCPUInfo for pattern: %s not found", pattern)
}

func getCPUVariantWindows(arch string) string {
	// Windows only supports v7 for ARM32 and v8 for ARM64 and so we can use
	// runtime.GOARCH to determine the variants
	var variant string
	switch arch {
	case "arm64":
		variant = "v8"
	case "arm":
		variant = "v7"
	default:
		variant = ""
	}

	return variant
}

func getCPUVariantArm() string {
	variant, err := getCPUInfo("Cpu architecture")
	if err != nil {
		return ""
	}
	// TODO handle RPi Zero mismatch (https://github.com/moby/moby/pull/36121#issuecomment-398328286)

	switch strings.ToLower(variant) {
	case "8", "aarch64":
		variant = "v8"
	case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
		variant = "v7"
	case "6", "6tej":
		variant = "v6"
	case "5", "5t", "5te", "5tej":
		variant = "v5"
	case "4", "4t":
		variant = "v4"
	case "3":
		variant = "v3"
	default:
		variant = ""
	}

	return variant
}

func getCPUVariant(os string, arch string) string {
	if os == "windows" {
		return getCPUVariantWindows(arch)
	}
	if arch == "arm" || arch == "arm64" {
		return getCPUVariantArm()
	}
	return ""
}

var compatibility = map[string][]string{
	"arm":   {"v7", "v6", "v5"},
	"arm64": {"v8"},
}

// WantedPlatforms returns all compatible platforms with the platform specifics possibly overriden by user,
// the most compatible platform is first.
// If some option (arch, os, variant) is not present, a value from current platform is detected.
func WantedPlatforms(ctx *types.SystemContext) ([]imgspecv1.Platform, error) {
	wantedArch := runtime.GOARCH
	wantedVariant := ""
	if ctx != nil && ctx.ArchitectureChoice != "" {
		wantedArch = ctx.ArchitectureChoice
	} else {
		// Only auto-detect the variant if we are using the default architecture.
		// If the user has specified the ArchitectureChoice, don't autodetect, even if
		// ctx.ArchitectureChoice == runtime.GOARCH, because we have no idea whether the runtime.GOARCH
		// value is relevant to the use case, and if we do autodetect a variant,
		// ctx.VariantChoice can't be used to override it back to "".
		wantedVariant = getCPUVariant(runtime.GOOS, runtime.GOARCH)
	}
	if ctx != nil && ctx.VariantChoice != "" {
		wantedVariant = ctx.VariantChoice
	}

	wantedOS := runtime.GOOS
	if ctx != nil && ctx.OSChoice != "" {
		wantedOS = ctx.OSChoice
	}

	var variants []string = nil
	if wantedVariant != "" && compatibility[wantedArch] != nil {
		variantOrder := compatibility[wantedArch]
		for i, v := range variantOrder {
			if wantedVariant == v {
				variants = variantOrder[i:]
				break
			}
		}
		// user wants a variant which we know nothing about - not even compatibility
		if variants == nil {
			variants = []string{wantedVariant}
		}
	} else {
		variants = []string{wantedVariant}
	}

	res := make([]imgspecv1.Platform, 0, len(variants))
	for _, v := range variants {
		res = append(res, imgspecv1.Platform{
			OS:           wantedOS,
			Architecture: wantedArch,
			Variant:      v,
		})
	}
	return res, nil
}

func MatchesPlatform(image imgspecv1.Platform, wanted imgspecv1.Platform) bool {
	if image.Architecture != wanted.Architecture {
		return false
	}
	if image.OS != wanted.OS {
		return false
	}

	if wanted.Variant == "" || image.Variant == wanted.Variant {
		return true
	}

	return false
}
<commit_msg>Simplify further<commit_after>package platform

// Largely based on
// https://github.com/moby/moby/blob/bc846d2e8fe5538220e0c31e9d0e8446f6fbc022/distribution/cpuinfo_unix.go
// Copyright 2012-2017 Docker, Inc.
//
// https://github.com/containerd/containerd/blob/726dcaea50883e51b2ec6db13caff0e7936b711d/platforms/cpuinfo.go
//    Copyright The containerd 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.

import (
	"bufio"
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/containers/image/v5/types"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)

// For Linux, the kernel has already detected the ABI, ISA and Features.
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
	if runtime.GOOS != "linux" {
		return "", fmt.Errorf("getCPUInfo for OS %s not implemented", runtime.GOOS)
	}

	cpuinfo, err := os.Open("/proc/cpuinfo")
	if err != nil {
		return "", err
	}
	defer cpuinfo.Close()

	// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
	// the first core is enough.
	scanner := bufio.NewScanner(cpuinfo)
	for scanner.Scan() {
		newline := scanner.Text()
		list := strings.Split(newline, ":")

		if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
			return strings.TrimSpace(list[1]), nil
		}
	}

	// Check whether the scanner encountered errors
	err = scanner.Err()
	if err != nil {
		return "", err
	}

	return "", fmt.Errorf("getCPUInfo for pattern: %s not found", pattern)
}

func getCPUVariantWindows(arch string) string {
	// Windows only supports v7 for ARM32 and v8 for ARM64 and so we can use
	// runtime.GOARCH to determine the variants
	var variant string
	switch arch {
	case "arm64":
		variant = "v8"
	case "arm":
		variant = "v7"
	default:
		variant = ""
	}

	return variant
}

func getCPUVariantArm() string {
	variant, err := getCPUInfo("Cpu architecture")
	if err != nil {
		return ""
	}
	// TODO handle RPi Zero mismatch (https://github.com/moby/moby/pull/36121#issuecomment-398328286)

	switch strings.ToLower(variant) {
	case "8", "aarch64":
		variant = "v8"
	case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
		variant = "v7"
	case "6", "6tej":
		variant = "v6"
	case "5", "5t", "5te", "5tej":
		variant = "v5"
	case "4", "4t":
		variant = "v4"
	case "3":
		variant = "v3"
	default:
		variant = ""
	}

	return variant
}

func getCPUVariant(os string, arch string) string {
	if os == "windows" {
		return getCPUVariantWindows(arch)
	}
	if arch == "arm" || arch == "arm64" {
		return getCPUVariantArm()
	}
	return ""
}

var compatibility = map[string][]string{
	"arm":   {"v7", "v6", "v5"},
	"arm64": {"v8"},
}

// WantedPlatforms returns all compatible platforms with the platform specifics possibly overriden by user,
// the most compatible platform is first.
// If some option (arch, os, variant) is not present, a value from current platform is detected.
func WantedPlatforms(ctx *types.SystemContext) ([]imgspecv1.Platform, error) {
	wantedArch := runtime.GOARCH
	wantedVariant := ""
	if ctx != nil && ctx.ArchitectureChoice != "" {
		wantedArch = ctx.ArchitectureChoice
	} else {
		// Only auto-detect the variant if we are using the default architecture.
		// If the user has specified the ArchitectureChoice, don't autodetect, even if
		// ctx.ArchitectureChoice == runtime.GOARCH, because we have no idea whether the runtime.GOARCH
		// value is relevant to the use case, and if we do autodetect a variant,
		// ctx.VariantChoice can't be used to override it back to "".
		wantedVariant = getCPUVariant(runtime.GOOS, runtime.GOARCH)
	}
	if ctx != nil && ctx.VariantChoice != "" {
		wantedVariant = ctx.VariantChoice
	}

	wantedOS := runtime.GOOS
	if ctx != nil && ctx.OSChoice != "" {
		wantedOS = ctx.OSChoice
	}

	var variants []string = nil
	if wantedVariant != "" && compatibility[wantedArch] != nil {
		variantOrder := compatibility[wantedArch]
		for i, v := range variantOrder {
			if wantedVariant == v {
				variants = variantOrder[i:]
				break
			}
		}
	}
	if variants == nil {
		// user wants a variant which we know nothing about - not even compatibility
		variants = []string{wantedVariant}
	}

	res := make([]imgspecv1.Platform, 0, len(variants))
	for _, v := range variants {
		res = append(res, imgspecv1.Platform{
			OS:           wantedOS,
			Architecture: wantedArch,
			Variant:      v,
		})
	}
	return res, nil
}

func MatchesPlatform(image imgspecv1.Platform, wanted imgspecv1.Platform) bool {
	if image.Architecture != wanted.Architecture {
		return false
	}
	if image.OS != wanted.OS {
		return false
	}

	if wanted.Variant == "" || image.Variant == wanted.Variant {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>/*
 * provisioner_test.go
 *
 * Copyright 2016 Krzysztof Wilczynski
 *
 * 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 itamae

import (
	"io/ioutil"
	"os"
	"reflect"
	"testing"

	"github.com/mitchellh/packer/packer"
)

func TestProvisioner(t *testing.T) {
	p := &Provisioner{}

	func(v interface{}) {
		if _, ok := v.(packer.Provisioner); !ok {
			t.Fatalf("not a Provisioner type: %s", reflect.TypeOf(v).String())
		}
	}(p)
}

func TestProvisionerPrepare_Defaults(t *testing.T) {
	var err error
	var p Provisioner

	config := testConfig()

	recipeFile, err := ioutil.TempFile("", "recipe.rb")
	if err != nil {
		t.Fatalf("unable to create temporary file: %s", err)
	}
	defer os.Remove(recipeFile.Name())

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is missing")
	}

	config["recipes"] = []string{}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is empty")
	}

	config["recipes"] = []string{
		"fake.rb",
	}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipe file does not exist")
	}

	config["recipes"] = []string{
		recipeFile.Name(),
	}

	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("should not an error, but got: %s", err)
	}

	if reflect.ValueOf(p.config.Vars).Kind() != reflect.Slice || len(p.config.Vars) > 0 {
		t.Errorf("incorrect environment_vars, given {%v %v}, want {%v %d}",
			reflect.ValueOf(p.config.Vars).Kind(), len(p.config.Vars), reflect.Slice, 0)
	}

	if p.config.Command != DefaultCommand {
		t.Errorf("incorrect command, given \"%s\", want \"%s\"",
			p.config.Command, DefaultCommand)
	}

	if reflect.ValueOf(p.config.ExtraArguments).Kind() != reflect.Slice || len(p.config.ExtraArguments) > 0 {
		t.Errorf("incorrect extra_arguments, given {%v %v}, want {%v %d}",
			reflect.ValueOf(p.config.ExtraArguments).Kind(),
			len(p.config.Vars), reflect.Slice, 0)
	}

	if p.config.StagingDir != DefaultStagingDir {
		t.Errorf("incorrect staging_directory, given \"%s\", want \"%s\"",
			p.config.StagingDir, DefaultStagingDir)
	}
}
<commit_msg>Add more test coverage.<commit_after>/*
 * provisioner_test.go
 *
 * Copyright 2016 Krzysztof Wilczynski
 *
 * 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 itamae

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/mitchellh/packer/packer"
)

func TestProvisioner(t *testing.T) {
	p := &Provisioner{}

	func(v interface{}) {
		if _, ok := v.(packer.Provisioner); !ok {
			t.Fatalf("not a Provisioner type: %s", reflect.TypeOf(v).String())
		}
	}(p)
}

func TestProvisionerPrepare_InvalidKey(t *testing.T) {
	var p Provisioner
	config := testConfig()

	config["invalid-key"] = true
	err := p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error when key is invalid")
	}
}

func TestProvisionerPrepare_Defaults(t *testing.T) {
	var err error
	var p Provisioner
	var kind reflect.Kind

	config := testConfig()

	recipeFile, err := ioutil.TempFile("", "recipe.rb")
	if err != nil {
		t.Fatalf("unable to create temporary file: %s", err)
	}
	defer os.Remove(recipeFile.Name())

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is missing")
	}

	config["recipes"] = []string{}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is empty")
	}

	config["recipes"] = []string{
		os.TempDir(),
	}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipe file points to a directory")
	}

	config["recipes"] = []string{
		"fake.rb",
	}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipe file does not exist")
	}

	config["recipes"] = []string{
		recipeFile.Name(),
	}

	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("should not an error, but got: %s", err)
	}

	kind = reflect.ValueOf(p.config.Vars).Kind()
	if kind != reflect.Slice || len(p.config.Vars) != 0 {
		t.Errorf("incorrect environment_vars, given {%v %d}, want {%v %d}",
			kind, len(p.config.Vars), reflect.Slice, 0)
	}

	if p.config.Command != DefaultCommand {
		t.Errorf("incorrect command, given \"%s\", want \"%s\"",
			p.config.Command, DefaultCommand)
	}

	kind = reflect.ValueOf(p.config.ExecuteCommand).Kind()
	if kind != reflect.String || p.config.ExecuteCommand == "" {
		t.Errorf("incorrect execute_command, given {%v %d}, want {%v > 0}",
			kind, len(p.config.ExecuteCommand), reflect.String)
	}

	kind = reflect.ValueOf(p.config.ExtraArguments).Kind()
	if kind != reflect.Slice || len(p.config.ExtraArguments) != 0 {
		t.Errorf("incorrect extra_arguments, given {%v %d}, want {%v %d}",
			kind, len(p.config.Vars), reflect.Slice, 0)
	}

	if p.config.PreventSudo {
		t.Errorf("incorrect prevent_sudo, given: \"%v\", want \"%v\"",
			p.config.PreventSudo, false)
	}

	if p.config.StagingDir != DefaultStagingDir {
		t.Errorf("incorrect staging_directory, given \"%s\", want \"%s\"",
			p.config.StagingDir, DefaultStagingDir)
	}

	if p.config.CleanStagingDir {
		t.Errorf("incorrect clean_staging_directory, given: \"%v\", want \"%v\"",
			p.config.CleanStagingDir, false)
	}

	kind = reflect.ValueOf(p.config.SourceDir).Kind()
	if kind != reflect.String || p.config.SourceDir != "" {
		t.Errorf("incorrect source_directory, given {%v %d}, want {%v 0}",
			kind, len(p.config.SourceDir), reflect.String)
	}

	kind = reflect.ValueOf(p.config.LogLevel).Kind()
	if kind != reflect.String || p.config.LogLevel != "" {
		t.Errorf("incorrect log_level, given {%v %d}, want {%v 0}",
			kind, len(p.config.LogLevel), reflect.String)
	}

	kind = reflect.ValueOf(p.config.Shell).Kind()
	if kind != reflect.String || p.config.Shell != "" {
		t.Errorf("incorrect shell, given {%v %d}, want {%v 0}",
			kind, len(p.config.Shell), reflect.String)
	}

	kind = reflect.ValueOf(p.config.JsonPath).Kind()
	if kind != reflect.String || p.config.JsonPath != "" {
		t.Errorf("incorrect json_path, given {%v %d}, want {%v 0}",
			kind, len(p.config.JsonPath), reflect.String)
	}

	kind = reflect.ValueOf(p.config.YamlPath).Kind()
	if kind != reflect.String || p.config.YamlPath != "" {
		t.Errorf("incorrect yaml_path, given {%v %d}, want {%v 0}",
			kind, len(p.config.Shell), reflect.String)
	}

	p = Provisioner{}

	delete(config, "recipes")
	p.Prepare(config)

	kind = reflect.ValueOf(p.config.Recipes).Kind()
	if kind != reflect.Slice || len(p.config.Recipes) != 0 {
		t.Errorf("incorrect recipes, given {%v %d}, want {%v %d}",
			kind, len(p.config.Recipes), reflect.Slice, 0)
	}

	if p.config.IgnoreExitCodes {
		t.Errorf("incorrect ignore_exit_codes, given: \"%v\", want \"%v\"",
			p.config.IgnoreExitCodes, false)
	}
}

func TestProvisionerPrepare_EnvironmentVars(t *testing.T) {
}

func TestProvisionerPrepare_ExtraArguments(t *testing.T) {
}

func TestProvisionerPrepare_StagingDirectory(t *testing.T) {
}

func TestProvisionerPrepare_SourceDirectory(t *testing.T) {
	var err error
	var p Provisioner

	config := testConfig()

	recipeFile, err := ioutil.TempFile("", "recipe.rb")
	if err != nil {
		t.Fatalf("unable to create temporary file: %s", err)
	}
	defer os.Remove(recipeFile.Name())

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is missing")
	}

	config["recipes"] = []string{}

	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if recipes list is empty")
	}

	config["recipes"] = []string{
		filepath.Base(recipeFile.Name()),
	}

	config["source_directory"] = recipeFile.Name()
	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if source_directory points to a file")
	}

	config["source_directory"] = "/does/not/exist"
	err = p.Prepare(config)
	if err == nil {
		t.Fatalf("should be an error if source_directory does not exist")
	}

	config["source_directory"] = os.TempDir()
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("should not an error, but got: %s", err)
	}
}

func TestProvisionerPrepare_Recipes(t *testing.T) {
}

func TestProvisionerPrepare_JsonPath(t *testing.T) {
}

func TestProvisionerPrepare_YamlPath(t *testing.T) {
}

func TestProvisionerProvision_Command(t *testing.T) {
}

func TestProvisionerProvision_EnvironmentVars(t *testing.T) {
}

func TestProvisionerProvision_StagingDirectory(t *testing.T) {
}

func TestProvisionerProvision_LogLevel(t *testing.T) {
}

func TestProvisionerProvision_Shell(t *testing.T) {
}

func TestProvisionerProvision_JsonPath(t *testing.T) {
}

func TestProvisionerProvision_YamlPath(t *testing.T) {
}

func TestProvisionerProvision_ExtraArguments(t *testing.T) {
}

func TestProvisionerProvision_Recipes(t *testing.T) {
}

func TestProvisionerProvision_Sudo(t *testing.T) {
}
<|endoftext|>