<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2018 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"bytes"
	"fmt"
	"net/url"
	"testing"

	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/guregu/null.v3"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/testutils"
	"github.com/loadimpact/k6/loader"
)

type runtimeOptionsTestCase struct {
	useSysEnv     bool // Whether to include the system env vars by default (run) or not (cloud/archive/inspect)
	expErr        bool
	cliFlags      []string
	systemEnv     map[string]string
	expEnv        map[string]string
	expCompatMode null.String
}

//nolint:gochecknoglobals
var (
	defaultCompatMode  = null.NewString("extended", false)
	baseCompatMode     = null.NewString("base", true)
	extendedCompatMode = null.NewString("extended", true)
)

var runtimeOptionsTestCases = map[string]runtimeOptionsTestCase{ //nolint:gochecknoglobals
	"empty env": {
		useSysEnv: true,
		// everything else is empty
		expCompatMode: defaultCompatMode,
	},
	"disabled sys env by default": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1"},
		expEnv:        map[string]string{},
		expCompatMode: defaultCompatMode,
	},
	"disabled sys env by default with ext compat mode": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1", "K6_COMPATIBILITY_MODE": "extended"},
		expEnv:        map[string]string{},
		expCompatMode: extendedCompatMode,
	},
	"disabled sys env by cli 1": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "val1", "K6_COMPATIBILITY_MODE": "base"},
		cliFlags:      []string{"--include-system-env-vars=false"},
		expEnv:        map[string]string{},
		expCompatMode: baseCompatMode,
	},
	"disabled sys env by cli 2": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		cliFlags:      []string{"--include-system-env-vars=0", "--compatibility-mode=base"},
		expEnv:        map[string]string{},
		expCompatMode: baseCompatMode,
	},
	"disabled sys env by env": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "false", "K6_COMPATIBILITY_MODE": "extended"},
		expEnv:        map[string]string{},
		expCompatMode: extendedCompatMode,
	},
	"enabled sys env by env": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		expEnv:        map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		expCompatMode: extendedCompatMode,
	},
	"enabled sys env by default": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "val1"},
		cliFlags:      []string{},
		expEnv:        map[string]string{"test1": "val1"},
		expCompatMode: defaultCompatMode,
	},
	"enabled sys env by cli 1": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1"},
		cliFlags:      []string{"--include-system-env-vars"},
		expEnv:        map[string]string{"test1": "val1"},
		expCompatMode: defaultCompatMode,
	},
	"enabled sys env by cli 2": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1"},
		cliFlags:      []string{"--include-system-env-vars=true"},
		expEnv:        map[string]string{"test1": "val1"},
		expCompatMode: defaultCompatMode,
	},
	"run only system env": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "val1"},
		cliFlags:      []string{},
		expEnv:        map[string]string{"test1": "val1"},
		expCompatMode: defaultCompatMode,
	},
	"mixed system and cli env": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "val1", "test2": ""},
		cliFlags:      []string{"--env", "test3=val3", "-e", "test4", "-e", "test5="},
		expEnv:        map[string]string{"test1": "val1", "test2": "", "test3": "val3", "test4": "", "test5": ""},
		expCompatMode: defaultCompatMode,
	},
	"mixed system and cli env 2": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1", "test2": ""},
		cliFlags:      []string{"--env", "test3=val3", "-e", "test4", "-e", "test5=", "--include-system-env-vars=1"},
		expEnv:        map[string]string{"test1": "val1", "test2": "", "test3": "val3", "test4": "", "test5": ""},
		expCompatMode: defaultCompatMode,
	},
	"disabled system env with cli params": {
		useSysEnv:     false,
		systemEnv:     map[string]string{"test1": "val1"},
		cliFlags:      []string{"-e", "test2=overwriten", "-e", "test2=val2"},
		expEnv:        map[string]string{"test2": "val2"},
		expCompatMode: defaultCompatMode,
	},
	"overwriting system env with cli param": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "val1sys"},
		cliFlags:      []string{"--env", "test1=val1cli"},
		expEnv:        map[string]string{"test1": "val1cli"},
		expCompatMode: defaultCompatMode,
	},
	"error wrong compat mode env var value": {
		systemEnv: map[string]string{"K6_COMPATIBILITY_MODE": "asdf"},
		expErr:    true,
	},
	"error wrong compat mode cli flag value": {
		cliFlags: []string{"--compatibility-mode", "whatever"},
		expErr:   true,
	},
	"error invalid cli var name 1": {
		useSysEnv:     true,
		systemEnv:     map[string]string{},
		cliFlags:      []string{"--env", "test a=error"},
		expErr:        true,
		expEnv:        map[string]string{},
		expCompatMode: defaultCompatMode,
	},
	"error invalid cli var name 2": {
		useSysEnv:     true,
		systemEnv:     map[string]string{},
		cliFlags:      []string{"--env", "1var=error"},
		expErr:        true,
		expEnv:        map[string]string{},
		expCompatMode: defaultCompatMode,
	},
	"error invalid cli var name 3": {
		useSysEnv:     true,
		systemEnv:     map[string]string{},
		cliFlags:      []string{"--env", "уникод=unicode-disabled"},
		expErr:        true,
		expEnv:        map[string]string{},
		expCompatMode: defaultCompatMode,
	},
	"valid env vars with spaces": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "value 1"},
		cliFlags:      []string{"--env", "test2=value 2"},
		expEnv:        map[string]string{"test1": "value 1", "test2": "value 2"},
		expCompatMode: defaultCompatMode,
	},
	"valid env vars with special chars": {
		useSysEnv:     true,
		systemEnv:     map[string]string{"test1": "value 1"},
		cliFlags:      []string{"--env", "test2=value,2", "-e", `test3= ,  ,,, value, ,, 2!'@#,"`},
		expEnv:        map[string]string{"test1": "value 1", "test2": "value,2", "test3": ` ,  ,,, value, ,, 2!'@#,"`},
		expCompatMode: defaultCompatMode,
	},
}

func testRuntimeOptionsCase(t *testing.T, tc runtimeOptionsTestCase) {
	flags := runtimeOptionFlagSet(tc.useSysEnv)
	require.NoError(t, flags.Parse(tc.cliFlags))

	rtOpts, err := getRuntimeOptions(flags, tc.systemEnv)
	if tc.expErr {
		require.Error(t, err)
		return
	}
	require.NoError(t, err)
	require.EqualValues(t, tc.expEnv, rtOpts.Env)
	assert.Equal(t, tc.expCompatMode, rtOpts.CompatibilityMode)

	compatMode, err := lib.ValidateCompatibilityMode(rtOpts.CompatibilityMode.String)
	require.NoError(t, err)

	jsCode := new(bytes.Buffer)
	if compatMode == lib.CompatibilityModeExtended {
		fmt.Fprint(jsCode, "export default function() {")
	} else {
		fmt.Fprint(jsCode, "module.exports.default = function() {")
	}

	for key, val := range tc.expEnv {
		fmt.Fprintf(jsCode,
			"if (__ENV.%s !== `%s`) { throw new Error('Invalid %s: ' + __ENV.%s); }",
			key, val, key, key,
		)
	}
	fmt.Fprint(jsCode, "}")

	fs := afero.NewMemMapFs()
	require.NoError(t, afero.WriteFile(fs, "/script.js", jsCode.Bytes(), 0o644))
	runner, err := newRunner(
		testutils.NewLogger(t),
		&loader.SourceData{Data: jsCode.Bytes(), URL: &url.URL{Path: "/script.js", Scheme: "file"}},
		typeJS,
		map[string]afero.Fs{"file": fs},
		rtOpts,
	)
	require.NoError(t, err)

	archive := runner.MakeArchive()
	archiveBuf := &bytes.Buffer{}
	require.NoError(t, archive.Write(archiveBuf))

	getRunnerErr := func(rtOpts lib.RuntimeOptions) (lib.Runner, error) {
		return newRunner(
			testutils.NewLogger(t),
			&loader.SourceData{
				Data: archiveBuf.Bytes(),
				URL:  &url.URL{Path: "/script.js"},
			},
			typeArchive,
			nil,
			rtOpts,
		)
	}

	_, err = getRunnerErr(lib.RuntimeOptions{})
	require.NoError(t, err)
	for key, val := range tc.expEnv {
		r, err := getRunnerErr(lib.RuntimeOptions{Env: map[string]string{key: "almost " + val}})
		assert.NoError(t, err)
		assert.Equal(t, r.MakeArchive().Env[key], "almost "+val)
	}
}

func TestRuntimeOptions(t *testing.T) {
	for name, tc := range runtimeOptionsTestCases {
		tc := tc
		t.Run(fmt.Sprintf("RuntimeOptions test '%s'", name), func(t *testing.T) {
			t.Parallel()
			testRuntimeOptionsCase(t, tc)
		})
	}
}
<commit_msg>Improve and expand the RuntimeOptions test<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2018 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"bytes"
	"fmt"
	"net/url"
	"testing"

	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/guregu/null.v3"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/testutils"
	"github.com/loadimpact/k6/loader"
)

type runtimeOptionsTestCase struct {
	useSysEnv bool // Whether to include the system env vars by default (run) or not (cloud/archive/inspect)
	expErr    bool
	cliFlags  []string
	systemEnv map[string]string
	expRTOpts lib.RuntimeOptions
}

//nolint:gochecknoglobals
var (
	defaultCompatMode  = null.NewString("extended", false)
	baseCompatMode     = null.NewString("base", true)
	extendedCompatMode = null.NewString("extended", true)
)

var runtimeOptionsTestCases = map[string]runtimeOptionsTestCase{ //nolint:gochecknoglobals
	"empty env": {
		useSysEnv: true,
		// everything else is empty
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  nil,
		},
	},
	"disabled sys env by default": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{},
		},
	},
	"disabled sys env by default with ext compat mode": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1", "K6_COMPATIBILITY_MODE": "extended"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, false),
			CompatibilityMode:    extendedCompatMode,
			Env:                  map[string]string{},
		},
	},
	"disabled sys env by cli 1": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "val1", "K6_COMPATIBILITY_MODE": "base"},
		cliFlags:  []string{"--include-system-env-vars=false"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, true),
			CompatibilityMode:    baseCompatMode,
			Env:                  map[string]string{},
		},
	},
	"disabled sys env by cli 2": {
		useSysEnv: true,
		systemEnv: map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		cliFlags:  []string{"--include-system-env-vars=0", "--compatibility-mode=base"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, true),
			CompatibilityMode:    baseCompatMode,
			Env:                  map[string]string{},
		},
	},
	"disabled sys env by env": {
		useSysEnv: true,
		systemEnv: map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "false", "K6_COMPATIBILITY_MODE": "extended"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, true),
			CompatibilityMode:    extendedCompatMode,
			Env:                  map[string]string{},
		},
	},
	"enabled sys env by env": {
		useSysEnv: false,
		systemEnv: map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, true),
			CompatibilityMode:    extendedCompatMode,
			Env:                  map[string]string{"K6_INCLUDE_SYSTEM_ENV_VARS": "true", "K6_COMPATIBILITY_MODE": "extended"},
		},
	},
	"enabled sys env by default": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "val1"},
		cliFlags:  []string{},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1"},
		},
	},
	"enabled sys env by cli 1": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1"},
		cliFlags:  []string{"--include-system-env-vars"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, true),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1"},
		},
	},
	"enabled sys env by cli 2": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1"},
		cliFlags:  []string{"--include-system-env-vars=true"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, true),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1"},
		},
	},
	"run only system env": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "val1"},
		cliFlags:  []string{},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1"},
		},
	},
	"mixed system and cli env": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "val1", "test2": ""},
		cliFlags:  []string{"--env", "test3=val3", "-e", "test4", "-e", "test5="},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1", "test2": "", "test3": "val3", "test4": "", "test5": ""},
		},
	},
	"mixed system and cli env 2": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1", "test2": ""},
		cliFlags:  []string{"--env", "test3=val3", "-e", "test4", "-e", "test5=", "--include-system-env-vars=1"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, true),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1", "test2": "", "test3": "val3", "test4": "", "test5": ""},
		},
	},
	"disabled system env with cli params": {
		useSysEnv: false,
		systemEnv: map[string]string{"test1": "val1"},
		cliFlags:  []string{"-e", "test2=overwriten", "-e", "test2=val2"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test2": "val2"},
		},
	},
	"overwriting system env with cli param": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "val1sys"},
		cliFlags:  []string{"--env", "test1=val1cli"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "val1cli"},
		},
	},
	"error wrong compat mode env var value": {
		systemEnv: map[string]string{"K6_COMPATIBILITY_MODE": "asdf"},
		expErr:    true,
	},
	"error wrong compat mode env var value even with CLI flag": {
		systemEnv: map[string]string{"K6_COMPATIBILITY_MODE": "asdf"},
		cliFlags:  []string{"--compatibility-mode", "true"},
		expErr:    true,
	},
	"error wrong compat mode cli flag value": {
		cliFlags: []string{"--compatibility-mode", "whatever"},
		expErr:   true,
	},
	"error invalid cli var name 1": {
		useSysEnv: true,
		systemEnv: map[string]string{},
		cliFlags:  []string{"--env", "test a=error"},
		expErr:    true,
	},
	"error invalid cli var name 2": {
		useSysEnv: true,
		systemEnv: map[string]string{},
		cliFlags:  []string{"--env", "1var=error"},
		expErr:    true,
	},
	"error invalid cli var name 3": {
		useSysEnv: true,
		systemEnv: map[string]string{},
		cliFlags:  []string{"--env", "уникод=unicode-disabled"},
		expErr:    true,
	},
	"valid env vars with spaces": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "value 1"},
		cliFlags:  []string{"--env", "test2=value 2"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "value 1", "test2": "value 2"},
		},
	},
	"valid env vars with special chars": {
		useSysEnv: true,
		systemEnv: map[string]string{"test1": "value 1"},
		cliFlags:  []string{"--env", "test2=value,2", "-e", `test3= ,  ,,, value, ,, 2!'@#,"`},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(true, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{"test1": "value 1", "test2": "value,2", "test3": ` ,  ,,, value, ,, 2!'@#,"`},
		},
	},
	"summary and thresholds from env": {
		useSysEnv: false,
		systemEnv: map[string]string{"K6_NO_THRESHOLDS": "false", "K6_NO_SUMMARY": "0", "K6_SUMMARY_EXPORT": "foo"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{},
			NoThresholds:         null.NewBool(false, true),
			NoSummary:            null.NewBool(false, true),
			SummaryExport:        null.NewString("foo", true),
		},
	},
	"summary and thresholds from env overwritten by CLI": {
		useSysEnv: false,
		systemEnv: map[string]string{"K6_NO_THRESHOLDS": "FALSE", "K6_NO_SUMMARY": "0", "K6_SUMMARY_EXPORT": "foo"},
		cliFlags:  []string{"--no-thresholds", "true", "--no-summary", "true", "--summary-export", "bar"},
		expRTOpts: lib.RuntimeOptions{
			IncludeSystemEnvVars: null.NewBool(false, false),
			CompatibilityMode:    defaultCompatMode,
			Env:                  map[string]string{},
			NoThresholds:         null.NewBool(true, true),
			NoSummary:            null.NewBool(true, true),
			SummaryExport:        null.NewString("bar", true),
		},
	},
	"env var error detected even when CLI flags overwrite 1": {
		useSysEnv: false,
		systemEnv: map[string]string{"K6_NO_THRESHOLDS": "boo"},
		cliFlags:  []string{"--no-thresholds", "true"},
		expErr:    true,
	},
	"env var error detected even when CLI flags overwrite 2": {
		useSysEnv: false,
		systemEnv: map[string]string{"K6_NO_SUMMARY": "hoo"},
		cliFlags:  []string{"--no-summary", "true"},
		expErr:    true,
	},
}

func testRuntimeOptionsCase(t *testing.T, tc runtimeOptionsTestCase) {
	flags := runtimeOptionFlagSet(tc.useSysEnv)
	require.NoError(t, flags.Parse(tc.cliFlags))

	rtOpts, err := getRuntimeOptions(flags, tc.systemEnv)
	if tc.expErr {
		require.Error(t, err)
		return
	}
	require.NoError(t, err)
	require.Equal(t, tc.expRTOpts, rtOpts)

	compatMode, err := lib.ValidateCompatibilityMode(rtOpts.CompatibilityMode.String)
	require.NoError(t, err)

	jsCode := new(bytes.Buffer)
	if compatMode == lib.CompatibilityModeExtended {
		fmt.Fprint(jsCode, "export default function() {")
	} else {
		fmt.Fprint(jsCode, "module.exports.default = function() {")
	}

	for key, val := range tc.expRTOpts.Env {
		fmt.Fprintf(jsCode,
			"if (__ENV.%s !== `%s`) { throw new Error('Invalid %s: ' + __ENV.%s); }",
			key, val, key, key,
		)
	}
	fmt.Fprint(jsCode, "}")

	fs := afero.NewMemMapFs()
	require.NoError(t, afero.WriteFile(fs, "/script.js", jsCode.Bytes(), 0o644))
	runner, err := newRunner(
		testutils.NewLogger(t),
		&loader.SourceData{Data: jsCode.Bytes(), URL: &url.URL{Path: "/script.js", Scheme: "file"}},
		typeJS,
		map[string]afero.Fs{"file": fs},
		rtOpts,
	)
	require.NoError(t, err)

	archive := runner.MakeArchive()
	archiveBuf := &bytes.Buffer{}
	require.NoError(t, archive.Write(archiveBuf))

	getRunnerErr := func(rtOpts lib.RuntimeOptions) (lib.Runner, error) {
		return newRunner(
			testutils.NewLogger(t),
			&loader.SourceData{
				Data: archiveBuf.Bytes(),
				URL:  &url.URL{Path: "/script.js"},
			},
			typeArchive,
			nil,
			rtOpts,
		)
	}

	_, err = getRunnerErr(lib.RuntimeOptions{})
	require.NoError(t, err)
	for key, val := range tc.expRTOpts.Env {
		r, err := getRunnerErr(lib.RuntimeOptions{Env: map[string]string{key: "almost " + val}})
		assert.NoError(t, err)
		assert.Equal(t, r.MakeArchive().Env[key], "almost "+val)
	}
}

func TestRuntimeOptions(t *testing.T) {
	for name, tc := range runtimeOptionsTestCases {
		tc := tc
		t.Run(fmt.Sprintf("RuntimeOptions test '%s'", name), func(t *testing.T) {
			t.Parallel()
			testRuntimeOptionsCase(t, tc)
		})
	}
}
<|endoftext|>


<commit_before>package expr

import (
	"sort"

	"github.com/grafana/metrictank/consolidation"

	"github.com/grafana/metrictank/api/models"
)

type FuncHighestLowest struct {
	in      GraphiteFunc
	n       int64
	fn      string
	highest bool
}

func NewHighestLowestConstructor(fn string, highest bool) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncHighestLowest{fn: fn, highest: highest}
	}
}

func (s *FuncHighestLowest) Signature() ([]Arg, []Arg) {
	if s.fn != "" {
		return []Arg{
			ArgSeriesList{val: &s.in},
			ArgInt{key: "n", val: &s.n},
		}, []Arg{ArgSeriesList{}}
	}
	return []Arg{
		ArgSeriesList{val: &s.in},
		ArgInt{key: "n", val: &s.n},
		ArgString{key: "func", val: &s.fn, validator: []Validator{IsConsolFunc}},
	}, []Arg{ArgSeriesList{}}
}

func (s *FuncHighestLowest) Context(context Context) Context {
	return context
}

func (s *FuncHighestLowest) Exec(cache map[Req][]models.Series) ([]models.Series, error) {
	series, err := s.in.Exec(cache)
	if err != nil {
		return nil, err
	}

	if len(series) == 0 {
		return series, nil
	}

	consolidationFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))

	seriesLess := func(i, j int) bool {
		si := consolidationFunc(series[i].Datapoints)
		sj := consolidationFunc(series[j].Datapoints)
		if s.highest {
			return si > sj
		}
		return si < sj
	}
	sort.Slice(series, seriesLess)

	if s.n > int64(len(series)) {
		s.n = int64(len(series))
	}

	return series[:s.n], nil
}
<commit_msg>added slice for consolidated values<commit_after>package expr

import (
	"sort"

	"github.com/grafana/metrictank/consolidation"

	"github.com/grafana/metrictank/api/models"
)

type FuncHighestLowest struct {
	in      GraphiteFunc
	n       int64
	fn      string
	highest bool
}

func NewHighestLowestConstructor(fn string, highest bool) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncHighestLowest{fn: fn, highest: highest}
	}
}

func (s *FuncHighestLowest) Signature() ([]Arg, []Arg) {
	if s.fn != "" {
		return []Arg{
			ArgSeriesList{val: &s.in},
			ArgInt{key: "n", val: &s.n},
		}, []Arg{ArgSeriesList{}}
	}
	return []Arg{
		ArgSeriesList{val: &s.in},
		ArgInt{key: "n", val: &s.n},
		ArgString{key: "func", val: &s.fn, validator: []Validator{IsConsolFunc}},
	}, []Arg{ArgSeriesList{}}
}

func (s *FuncHighestLowest) Context(context Context) Context {
	return context
}

func (s *FuncHighestLowest) Exec(cache map[Req][]models.Series) ([]models.Series, error) {
	series, err := s.in.Exec(cache)
	if err != nil {
		return nil, err
	}

	if len(series) == 0 {
		return series, nil
	}

	consolidationFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))

	consolidationVals := make([]float64, len(series))

	for i, serie := range series {
		consolidationVals[i] = consolidationFunc(serie.Datapoints)
	}

	seriesLess := func(i, j int) bool {
		if s.highest {
			return consolidationVals[i] > consolidationVals[j]
		}
		return consolidationVals[i] < consolidationVals[j]
	}
	sort.Slice(series, seriesLess)

	if s.n > int64(len(series)) {
		s.n = int64(len(series))
	}

	return series[:s.n], nil
}
<|endoftext|>


<commit_before>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// A parity test can be run:
//     go test
//     EXECPATH="wget -O -" go test
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/testutil"
)

const content = "Very simple web server"

type handler struct{}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/200":
		w.WriteHeader(200)
		w.Write([]byte(content))
	case "/302":
		http.Redirect(w, r, "/200", http.StatusFound /* 302 */)
	case "/500":
		w.WriteHeader(500)
		w.Write([]byte(content))
	default:
		w.WriteHeader(404)
		w.Write([]byte(content))
	}
}

var tests = []struct {
	flags   []string // in, %[1]d is the server's port, %[2] is an unopen port
	url     string   // in
	content string   // out
	retCode int      // out
}{
	{
		// basic
		flags:   []string{},
		url:     "http://localhost:%[1]d/200",
		content: content,
		retCode: 0,
	}, {
		// ipv4
		flags:   []string{},
		url:     "http://127.0.0.1:%[1]d/200",
		content: content,
		retCode: 0,
	}, /*{ TODO: travis does not support ipv6
		// ipv6
		flags:   []string{},
		url:     "http://[::1]:%[1]d/200",
		content:  content,
		retCode: 0,
	},*/{
		// redirect
		flags:   []string{},
		url:     "http://localhost:%[1]d/302",
		content: "",
		retCode: 0,
	}, {
		// 4xx error
		flags:   []string{},
		url:     "http://localhost:%[1]d/404",
		content: "",
		retCode: 1,
	}, {
		// 5xx error
		flags:   []string{},
		url:     "http://localhost:%[1]d/500",
		content: "",
		retCode: 1,
	}, {
		// no server
		flags:   []string{},
		url:     "http://localhost:%[2]d/200",
		content: "",
		retCode: 1,
	}, {
		// output file
		flags:   []string{"-O", "/dev/null"},
		url:     "http://localhost:%[1]d/200",
		content: "",
		retCode: 0,
	},
}

func getFreePort(t *testing.T) int {
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatalf("Cannot create free port: %v", err)
	}
	l.Close()
	return l.Addr().(*net.TCPAddr).Port
}

// TestWget implements a table-driven test.
func TestWget(t *testing.T) {
	// Start a webserver on a free port.
	unusedPort := getFreePort(t)

	l, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatalf("Cannot create free port: %v", err)
	}
	port := l.Addr().(*net.TCPAddr).Port

	h := handler{}
	go func() {
		log.Fatal(http.Serve(l, h))
	}()

	for i, tt := range tests {
		args := append(tt.flags, fmt.Sprintf(tt.url, port, unusedPort))
		_, err := testutil.Command(t, args...).Output()

		// Check return code.
		if err := testutil.IsExitCode(err, tt.retCode); err != nil {
			t.Error(err)
		}

		if tt.content != "" {
			fileName := filepath.Base(tt.url)
			content, err := ioutil.ReadFile(fileName)
			if err != nil {
				t.Errorf("%d. File %s was not created: %v", i, fileName, err)
			}

			// Check content.
			if string(content) != tt.content {
				t.Errorf("%d. Want:\n%#v\nGot:\n%#v", i, tt.content, string(content))
			}
		}
	}
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<commit_msg>wget: improve test error message<commit_after>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// A parity test can be run:
//     go test
//     EXECPATH="wget -O -" go test
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/testutil"
)

const content = "Very simple web server"

type handler struct{}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/200":
		w.WriteHeader(200)
		w.Write([]byte(content))
	case "/302":
		http.Redirect(w, r, "/200", http.StatusFound /* 302 */)
	case "/500":
		w.WriteHeader(500)
		w.Write([]byte(content))
	default:
		w.WriteHeader(404)
		w.Write([]byte(content))
	}
}

var tests = []struct {
	flags   []string // in, %[1]d is the server's port, %[2] is an unopen port
	url     string   // in
	content string   // out
	retCode int      // out
}{
	{
		// basic
		flags:   []string{},
		url:     "http://localhost:%[1]d/200",
		content: content,
		retCode: 0,
	}, {
		// ipv4
		flags:   []string{},
		url:     "http://127.0.0.1:%[1]d/200",
		content: content,
		retCode: 0,
	}, /*{ TODO: travis does not support ipv6
		// ipv6
		flags:   []string{},
		url:     "http://[::1]:%[1]d/200",
		content:  content,
		retCode: 0,
	},*/{
		// redirect
		flags:   []string{},
		url:     "http://localhost:%[1]d/302",
		content: "",
		retCode: 0,
	}, {
		// 4xx error
		flags:   []string{},
		url:     "http://localhost:%[1]d/404",
		content: "",
		retCode: 1,
	}, {
		// 5xx error
		flags:   []string{},
		url:     "http://localhost:%[1]d/500",
		content: "",
		retCode: 1,
	}, {
		// no server
		flags:   []string{},
		url:     "http://localhost:%[2]d/200",
		content: "",
		retCode: 1,
	}, {
		// output file
		flags:   []string{"-O", "/dev/null"},
		url:     "http://localhost:%[1]d/200",
		content: "",
		retCode: 0,
	},
}

func getFreePort(t *testing.T) int {
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatalf("Cannot create free port: %v", err)
	}
	l.Close()
	return l.Addr().(*net.TCPAddr).Port
}

// TestWget implements a table-driven test.
func TestWget(t *testing.T) {
	// Start a webserver on a free port.
	unusedPort := getFreePort(t)

	l, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatalf("Cannot create free port: %v", err)
	}
	port := l.Addr().(*net.TCPAddr).Port

	h := handler{}
	go func() {
		log.Fatal(http.Serve(l, h))
	}()

	for i, tt := range tests {
		args := append(tt.flags, fmt.Sprintf(tt.url, port, unusedPort))
		output, err := testutil.Command(t, args...).CombinedOutput()

		// Check return code.
		if err := testutil.IsExitCode(err, tt.retCode); err != nil {
			t.Errorf("exit code: %v, output: %s", err, string(output))
		}

		if tt.content != "" {
			fileName := filepath.Base(tt.url)
			content, err := ioutil.ReadFile(fileName)
			if err != nil {
				t.Errorf("%d. File %s was not created: %v", i, fileName, err)
			}

			// Check content.
			if string(content) != tt.content {
				t.Errorf("%d. Want:\n%#v\nGot:\n%#v", i, tt.content, string(content))
			}
		}
	}
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<|endoftext|>


<commit_before>// Package pool implements a pool of net.Conn interfaces to manage and reuse them.
package pool

import (
	"errors"
	"net"
)

var (
	// ErrClosed is the error resulting if the pool is closed via pool.Close().
	ErrClosed = errors.New("pool is closed")

	// ErrFull is the error resulting if the pool is full.
	ErrFull = errors.New("pool is full")
)

// Pool interface describes a pool implementation. A pool should have maximum
// capacity. An ideal pool is threadsafe and easy to use.
type Pool interface {
	// Get returns a new connection from the pool. Closing the connections puts
	// it back to the Pool. Closing it when the pool is destroyed or full will
	// be counted as an error.

	// If there is no new connection available in the pool, a new connection will
	// be created via the Factory() method.
	Get() (net.Conn, error)

	// Close closes the pool and all its connections. After Close() the pool is
	// no longer usable.
	Close()

	// Len returns the current number of connections of the pool.
	Len() int
}
<commit_msg>Improve docs<commit_after>// Package pool implements a pool of net.Conn interfaces to manage and reuse them.
package pool

import (
	"errors"
	"net"
)

var (
	// ErrClosed is the error resulting if the pool is closed via pool.Close().
	ErrClosed = errors.New("pool is closed")

	// ErrFull is the error resulting if the pool is full.
	ErrFull = errors.New("pool is full")
)

// Pool interface describes a pool implementation. A pool should have maximum
// capacity. An ideal pool is threadsafe and easy to use.
type Pool interface {
	// Get returns a new connection from the pool. Closing the connections puts
	// it back to the Pool. Closing it when the pool is destroyed or full will
	// be counted as an error.
	Get() (net.Conn, error)

	// Close closes the pool and all its connections. After Close() the pool is
	// no longer usable.
	Close()

	// Len returns the current number of connections of the pool.
	Len() int
}
<|endoftext|>


<commit_before>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"fmt"
	"flag"
	"net/http"
	"net/url"
	"encoding/json"
	"strings"
	"github.com/fzzy/radix/redis"
	"github.com/glacjay/goini"
)

// some default global variable
var (
	PORT string = "8765"
	API_PATH string = "/api"
	REDIS string = "localhost:6379"
	CLASS string = "SlackOPS"
	QUEUE string = "slackops"
	TOKENS map[string]bool = make(map[string]bool)
	VERBOSE bool = false
	FLAGS map[string]bool = make(map[string]bool)
	err error
)

func check_flag(f *flag.Flag) {
	FLAGS[f.Name] = true
}

func main() {
	// config parameter
	p_port := flag.String("p", "8765", "listen port")
	p_redis := flag.String("r", "localhost:6379", "redis host")
	p_class := flag.String("c", "SlackOPS", "resque class")
	p_queue := flag.String("q", "slackops", "resque queue")
	p_tokens := flag.String("t", "", "slack tokens, split by a comma (,)")
	p_verbose := flag.Bool("v", false, "verbose")
	p_config := flag.String("C", "", "configuration file")
	flag.Parse()

	// mark parsed flags
	flag.Visit(check_flag)
	fmt.Printf("flags: %s\n", FLAGS)

	// read config file
	if len(*p_config) > 0 {
		config, err := ini.Load(*p_config)
		if err != nil {
			fmt.Println("error:", err)
			return
		}
		c_port, exist := config.GetString("general", "port")
		if exist {
			PORT = ":" + c_port
		}
		// TODO: other config
	}

	// override configuration with flags
	if FLAGS["p"] {
		PORT = ":" + *p_port
	}
	if FLAGS["r"] {
		REDIS = *p_redis
	}
	if FLAGS["c"] {
		CLASS = *p_class
	}
	if FLAGS["q"] {
		QUEUE = *p_queue
	}
	if FLAGS["t"] {
		for _, t := range strings.Split(*p_tokens, ",") {
			TOKENS[t] = true
		}
	}
	if FLAGS["v"] {
		VERBOSE = *p_verbose
	}

	if VERBOSE {
		fmt.Printf("Listening on port %s\n", PORT)
	}

	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	http.ListenAndServe(PORT, nil)
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string `json:"class"`
		Args map[string]string `json:"args"`
	}

	// accept only POST
	if r.Method != "POST" {
		return
	}

	// read and parse request body content
	body := make([]byte, r.ContentLength)
	r.Body.Read(body)
	data, err := url.ParseQuery(string(body))
	if err != nil {
		fmt.Println("error:", err)
	}

	// check token
	if ! TOKENS[data.Get("token")] {
		return
	}

	// make connection to redis
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
	}

	// create job
	job := Job{
		Class: CLASS,
		Args: map[string]string {
		"request": strings.TrimPrefix(data.Get("text"), data.Get("trigger_word")),
		"user": data.Get("user_name"),
		"channel": data.Get("channel_name"),
		"timestamp": data.Get("timestamp"),
		},
	}
	jjob, err := json.Marshal(job)
	if err != nil {
		fmt.Println("error:", err)
	}
	// push job to resque
	queue := "resque:queue:" + QUEUE
	rcon.Cmd("RPUSH", queue, string(jjob))
	rcon.Close()

	// response to slack
	res := Response{
		Text: "@" + data.Get("user_name") + ": " + strings.TrimPrefix(data.Get("text"), "slackops: "),
	}
	jres, err := json.Marshal(res)
	if err != nil {
		fmt.Println("error:", err)
	}
	w.Header().Set("Content-type", "application/json")
	fmt.Fprintf(w, string(jres))
}
<commit_msg>load general config<commit_after>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"fmt"
	"flag"
	"net/http"
	"net/url"
	"encoding/json"
	"strings"
	"github.com/fzzy/radix/redis"
	"github.com/glacjay/goini"
)

// some default global variable
var (
	PORT string = ":8765"
	API_PATH string = "/api"
	REDIS string = "localhost:6379"
	CLASS string = "SlackOPS"
	QUEUE string = "slackops"
	TOKENS map[string]bool = make(map[string]bool)
	VERBOSE bool = false
	FLAGS map[string]bool = make(map[string]bool)
	err error
)

func check_flag(f *flag.Flag) {
	FLAGS[f.Name] = true
}

func main() {
	// config parameter
	p_port := flag.String("p", "8765", "listen port")
	p_redis := flag.String("r", "localhost:6379", "redis host")
	p_class := flag.String("c", "SlackOPS", "resque class")
	p_queue := flag.String("q", "slackops", "resque queue")
	p_tokens := flag.String("t", "", "slack tokens, split by a comma (,)")
	p_verbose := flag.Bool("v", false, "verbose")
	p_config := flag.String("C", "", "configuration file")
	flag.Parse()

	// mark parsed flags
	flag.Visit(check_flag)

	// read config file
	if FLAGS["C"] {
		config, err := ini.Load(*p_config)
		if err != nil {
			fmt.Println("error:", err)
			return
		}
		// load general config
		c_port, exist := config.GetString("general", "port")
		if exist {
			PORT = ":" + c_port
		}
		c_redis, exist := config.GetString("general", "redis")
		if exist {
			REDIS = c_redis
		}
		c_class, exist := config.GetString("general", "class")
		if exist {
			CLASS = c_class
		}
		c_queue, exist := config.GetString("general", "queue")
		if exist {
			QUEUE = c_queue
		}
		c_tokens, exist := config.GetString("general", "tokens")
		if exist {
			for _, t := range strings.Split(c_tokens, ",") {
				TOKENS[t] = true
			}
		}
		c_verbose, exist := config.GetBool("general", "verbose")
		if exist {
			VERBOSE = c_verbose
		}
	}

	// override configuration with flags
	if FLAGS["p"] {
		PORT = ":" + *p_port
	}
	if FLAGS["r"] {
		REDIS = *p_redis
	}
	if FLAGS["c"] {
		CLASS = *p_class
	}
	if FLAGS["q"] {
		QUEUE = *p_queue
	}
	if FLAGS["t"] {
		for _, t := range strings.Split(*p_tokens, ",") {
			TOKENS[t] = true
		}
	}
	if FLAGS["v"] {
		VERBOSE = *p_verbose
	}

	if VERBOSE {
		fmt.Printf("Listening on port %s using resque at %s\n", PORT, REDIS)
		fmt.Printf("Jobs will be pushed to queue %s, class %s\n", QUEUE, CLASS)
		fmt.Printf("Accepting tokens:\n")
		for t, _ := range TOKENS {
			fmt.Printf("+ %s\n", t)
		}
	}

	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	err = http.ListenAndServe(PORT, nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string `json:"class"`
		Args map[string]string `json:"args"`
	}

	// accept only POST
	if r.Method != "POST" {
		return
	}

	// read and parse request body content
	body := make([]byte, r.ContentLength)
	r.Body.Read(body)
	data, err := url.ParseQuery(string(body))
	if err != nil {
		fmt.Println("error:", err)
	}

	// check token
	if ! TOKENS[data.Get("token")] {
		return
	}

	// make connection to redis
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
	}

	// create job
	job := Job{
		Class: CLASS,
		Args: map[string]string {
		"request": strings.TrimPrefix(data.Get("text"), data.Get("trigger_word")),
		"user": data.Get("user_name"),
		"channel": data.Get("channel_name"),
		"timestamp": data.Get("timestamp"),
		},
	}
	jjob, err := json.Marshal(job)
	if err != nil {
		fmt.Println("error:", err)
	}
	// push job to resque
	queue := "resque:queue:" + QUEUE
	rcon.Cmd("RPUSH", queue, string(jjob))
	rcon.Close()

	// response to slack
	res := Response{
		Text: "@" + data.Get("user_name") + ": " + strings.TrimPrefix(data.Get("text"), "slackops: "),
	}
	jres, err := json.Marshal(res)
	if err != nil {
		fmt.Println("error:", err)
	}
	w.Header().Set("Content-type", "application/json")
	fmt.Fprintf(w, string(jres))
}
<|endoftext|>


<commit_before>package jsoniter

import (
	"io"
)

// IteratorPool a thread safe pool of iterators with same configuration
type IteratorPool interface {
	BorrowIterator(data []byte) *Iterator
	ReturnIterator(iter *Iterator)
}

// StreamPool a thread safe pool of streams with same configuration
type StreamPool interface {
	BorrowStream(writer io.Writer) *Stream
	ReturnStream(stream *Stream)
}

func (cfg *frozenConfig) BorrowStream(writer io.Writer) *Stream {
	stream := cfg.streamPool.Get().(*Stream)
	stream.Reset(writer)
	return stream
}

func (cfg *frozenConfig) ReturnStream(stream *Stream) {
	stream.Error = nil
	stream.Attachment = nil
	cfg.streamPool.Put(stream)
}

func (cfg *frozenConfig) BorrowIterator(data []byte) *Iterator {
	iter := cfg.iteratorPool.Get().(*Iterator)
	iter.ResetBytes(data)
	return iter
}

func (cfg *frozenConfig) ReturnIterator(iter *Iterator) {
	iter.Error = nil
	iter.Attachment = nil
	cfg.iteratorPool.Put(iter)
}
<commit_msg>Release writer to enable GC<commit_after>package jsoniter

import (
	"io"
)

// IteratorPool a thread safe pool of iterators with same configuration
type IteratorPool interface {
	BorrowIterator(data []byte) *Iterator
	ReturnIterator(iter *Iterator)
}

// StreamPool a thread safe pool of streams with same configuration
type StreamPool interface {
	BorrowStream(writer io.Writer) *Stream
	ReturnStream(stream *Stream)
}

func (cfg *frozenConfig) BorrowStream(writer io.Writer) *Stream {
	stream := cfg.streamPool.Get().(*Stream)
	stream.Reset(writer)
	return stream
}

func (cfg *frozenConfig) ReturnStream(stream *Stream) {
	stream.out = nil
	stream.Error = nil
	stream.Attachment = nil
	cfg.streamPool.Put(stream)
}

func (cfg *frozenConfig) BorrowIterator(data []byte) *Iterator {
	iter := cfg.iteratorPool.Get().(*Iterator)
	iter.ResetBytes(data)
	return iter
}

func (cfg *frozenConfig) ReturnIterator(iter *Iterator) {
	iter.Error = nil
	iter.Attachment = nil
	cfg.iteratorPool.Put(iter)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
)

type Post struct {
	sourcePath string
	timestamp  time.Time
	title      string
	tags       []string
	content    string
}

func NewPost(filePath string, readContent bool) (*Post, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}

}
<commit_msg>Actually fill in code.<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strings"
	"time"
)

type Post struct {
	SourcePath string
	Title      string
	Timestamp  time.Time
	Tags       []string
	Content    []byte
}

// NewPost reads a post from disk and returns a Post containing its data.
// If readContent is false, only the header of the post is read.
func NewPost(filePath string, readContent bool) (p *Post, err error) {
	f, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	p = new(Post)
	p.SourcePath = filePath

	reader := bufio.NewReader(f)

	line, err := reader.ReadString('\n')
	if err != nil {
		return
	}
	p.Title = string(line[0 : len(line)-1])

	line, err = reader.ReadString('\n')
	if err != nil {
		return
	}

	p.Timestamp, err = time.Parse(time.RFC822, line[0:len(line)-1])
	if err != nil {
		return
	}

	line, err = reader.ReadString('\n')
	if err != nil {
		return
	}
	p.Tags = strings.Split(line, ",")
	for i := range p.Tags {
		p.Tags[i] = strings.Title(strings.TrimSpace(p.Tags[i]))
	}

	line, err = reader.ReadString('\n')
	if err != nil {
		return
	}
	if len(line) != 1 {
		return p, fmt.Errorf("Unexpected input %s", string(line))
	}

	if readContent {
		buf := &bytes.Buffer{}
		_, err = buf.ReadFrom(reader)
		if err != nil {
			return
		}
		p.Content = buf.Bytes()
	}

	return
}
<|endoftext|>


<commit_before>package procspy

// /proc based implementation

import (
	"bufio"
	"encoding/hex"
	"errors"
	"io"
	"net"
	"os"
	"regexp"
	"strconv"
	"strings"
	"syscall"
)

const procRoot = "/proc"

// SpyProc uses /proc directly to make the connection list.
func SpyProc() ([]ConnProc, error) {
	// A map of inode -> pid
	inodes, err := walkProcPid()
	if err != nil {
		// fmt.Printf("walkProcPid err: %v\n", err)
		return nil, err
	}

	res := []ConnProc{}
	for _, procFile := range []string{
		procRoot + "/net/tcp",
		procRoot + "/net/tcp6",
	} {
		fh, err := os.Open(procFile)
		if err != nil {
			// File might not be there if IPv{4,6} is not supported.
			continue
		}
		defer fh.Close()
		for _, tp := range parseTransport(fh) {
			if pid, ok := inodes[tp.inode]; ok {
				name, err := procName(pid)
				if err != nil {
					// Process might be gone by now
					continue
				}
				if tp.remoteAddress.IsUnspecified() {
					// remote address is zero. This is a listen entry.
					continue
				}
				res = append(res, ConnProc{
					Transport:  "tcp",
					LocalAddr:  tp.localAddress.String(),
					LocalPort:  tp.localPort,
					RemoteAddr: tp.remoteAddress.String(),
					RemotePort: tp.remotePort,
					PID:        pid,
					Name:       name,
				})
			}
		}
	}
	return res, nil
}

func walkProcPid() (map[uint64]uint, error) {
	// Walk over all /proc entries (numerical ones, those are PIDs), and see if
	// their ./fd/* files are symlink to sockets.
	// Returns a map from socket id ('inode`) to PID.
	// Will return an error if /proc/ isn't there.
	fh, err := os.Open(procRoot)
	if err != nil {
		return nil, err
	}
	defer fh.Close()
	dirNames, err := fh.Readdirnames(-1)
	if err != nil {
		return nil, err
	}
	procmap := map[uint64]uint{}
	for _, dirName := range dirNames {
		pid, err := strconv.ParseUint(dirName, 10, 0)
		if err != nil {
			// Not a number, so not a PID subdir.
			continue
		}

		fdBase := procRoot + "/" + dirName + "/fd/"
		dfh, err := os.Open(fdBase)
		if err != nil {
			// Process is be gone by now, or we don't have access.
			continue
		}
		fdNames, err := dfh.Readdirnames(-1)
		dfh.Close()
		if err != nil {
			continue
		}
		for _, fdName := range fdNames {
			// We want sockets only
			stat, err := os.Stat(fdBase + fdName)
			if err != nil {
				continue
			}
			if stat.Mode()&os.ModeSocket == 0 {
				continue
			}
			sys, ok := stat.Sys().(*syscall.Stat_t)
			if !ok {
				panic("Weird result from stat.Sys()")
			}
			procmap[sys.Ino] = uint(pid)
		}
	}
	return procmap, nil
}

// transport are found in /proc/net/{tcp,udp}{,6} files
type transport struct {
	localAddress  net.IP
	localPort     uint16
	remoteAddress net.IP
	remotePort    uint16
	uid           int
	inode         uint64
}

// parseTransport parses /proc/net/{tcp,udp}{,6} files
var fieldRe = regexp.MustCompile(`\s+`)

func parseTransport(r io.Reader) []transport {
	res := []transport{}
	scanner := bufio.NewScanner(r)
	for i := 0; scanner.Scan(); i++ {
		if i == 0 {
			continue
		}
		// Fields are:
		//  'sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode <more>'
		line := strings.TrimSpace(scanner.Text())
		fields := fieldRe.Split(line, -1)
		if len(fields) < 10 {
			// fmt.Printf("Invalid format")
			continue
		}

		localAddress, localPort, err := scanAddress(fields[1])
		if err != nil {
			// fmt.Printf("err: %s\n", err)
			continue
		}

		remoteAddress, remotePort, err := scanAddress(fields[2])
		if err != nil {
			// fmt.Printf("err: %s\n", err)
			continue
		}

		uid, err := strconv.Atoi(fields[7])
		if err != nil {
			// fmt.Printf("err: %s\n", err)
			continue
		}

		inode, err := strconv.ParseUint(fields[9], 10, 64)
		if err != nil {
			// fmt.Printf("err: %s\n", err)
			continue
		}
		t := transport{
			localAddress:  localAddress,
			localPort:     localPort,
			remoteAddress: remoteAddress,
			remotePort:    remotePort,
			uid:           uid,
			inode:         inode,
		}
		res = append(res, t)

	}
	return res
}

// scanAddress parses things like 'A12CF62E:E4D7' to their address/port.
// Deals with IPv4 and IPv6 addresses.
// The address part are big endian 32 bit ints, hex encoded. Since net.IP is a
// byte slice we just decode the hex and flip the bytes in every group of 4.
func scanAddress(in string) (net.IP, uint16, error) {
	parts := strings.Split(in, ":")
	if len(parts) != 2 {
		return nil, 0, errors.New("invalid address:port")
	}

	// network (big) endian. Can be either ipv4 or ipv6
	address, err := hex.DecodeString(parts[0])
	if err != nil {
		return nil, 0, err
	}
	// reverse every 4 byte-sequence.
	for i := 0; i < len(address); i += 4 {
		address[i], address[i+3] = address[i+3], address[i]
		address[i+1], address[i+2] = address[i+2], address[i+1]
	}

	// var port uint16
	// _, err = fmt.Sscanf(parts[1], "%X", &port)
	port, err := strconv.ParseUint(parts[1], 16, 16)
	if err != nil {
		return nil, 0, err
	}

	return net.IP(address), uint16(port), err
}

// procName does a pid->name lookup
func procName(pid uint) (string, error) {
	fh, err := os.Open(procRoot + "/" + strconv.FormatUint(uint64(pid), 10) + "/comm")
	if err != nil {
		return "err1", err
	}
	name := make([]byte, 1024)
	l, err := fh.Read(name)
	if err != nil {
		return "err", err
	}
	if l < 2 {
		return "", nil
	}
	// drop trailing "\n"
	return string(name[:l-1]), nil
}
<commit_msg>minor cleanups<commit_after>package procspy

// /proc based implementation

import (
	"bufio"
	"encoding/hex"
	"errors"
	"io"
	"net"
	"os"
	"regexp"
	"strconv"
	"strings"
	"syscall"
)

const procRoot = "/proc"

// SpyProc uses /proc directly to make the connection list.
func SpyProc() ([]ConnProc, error) {
	// A map of inode -> pid
	inodes, err := walkProcPid()
	if err != nil {
		return nil, err
	}

	res := []ConnProc{}
	for _, procFile := range []string{
		procRoot + "/net/tcp",
		procRoot + "/net/tcp6",
	} {
		fh, err := os.Open(procFile)
		if err != nil {
			// File might not be there if IPv{4,6} is not supported.
			continue
		}
		defer fh.Close()
		for _, tp := range parseTransport(fh) {
			if pid, ok := inodes[tp.inode]; ok {
				name, err := procName(pid)
				if err != nil {
					// Process might be gone by now
					continue
				}
				if tp.remoteAddress.IsUnspecified() {
					// Remote address is zero. This is a listen entry.
					continue
				}
				res = append(res, ConnProc{
					Transport:  "tcp",
					LocalAddr:  tp.localAddress.String(),
					LocalPort:  tp.localPort,
					RemoteAddr: tp.remoteAddress.String(),
					RemotePort: tp.remotePort,
					PID:        pid,
					Name:       name,
				})
			}
		}
	}
	return res, nil
}

func walkProcPid() (map[uint64]uint, error) {
	// Walk over all /proc entries (numerical ones, those are PIDs), and see if
	// their ./fd/* files are symlink to sockets.
	// Returns a map from socket id ('inode`) to PID.
	// Will return an error if /proc/ isn't there.
	fh, err := os.Open(procRoot)
	if err != nil {
		return nil, err
	}
	defer fh.Close()
	dirNames, err := fh.Readdirnames(-1)
	if err != nil {
		return nil, err
	}
	procmap := map[uint64]uint{}
	for _, dirName := range dirNames {
		pid, err := strconv.ParseUint(dirName, 10, 0)
		if err != nil {
			// Not a number, so not a PID subdir.
			continue
		}

		fdBase := procRoot + "/" + dirName + "/fd/"
		dfh, err := os.Open(fdBase)
		if err != nil {
			// Process is be gone by now, or we don't have access.
			continue
		}
		fdNames, err := dfh.Readdirnames(-1)
		dfh.Close()
		if err != nil {
			continue
		}
		for _, fdName := range fdNames {
			// We want sockets only
			stat, err := os.Stat(fdBase + fdName)
			if err != nil {
				continue
			}
			if stat.Mode()&os.ModeSocket == 0 {
				continue
			}
			sys, ok := stat.Sys().(*syscall.Stat_t)
			if !ok {
				panic("Weird result from stat.Sys()")
			}
			procmap[sys.Ino] = uint(pid)
		}
	}
	return procmap, nil
}

// transport are found in /proc/net/{tcp,udp}{,6} files
type transport struct {
	localAddress  net.IP
	localPort     uint16
	remoteAddress net.IP
	remotePort    uint16
	uid           int
	inode         uint64
}

// parseTransport parses /proc/net/{tcp,udp}{,6} files
var fieldRe = regexp.MustCompile(`\s+`)

func parseTransport(r io.Reader) []transport {
	res := []transport{}
	scanner := bufio.NewScanner(r)
	for i := 0; scanner.Scan(); i++ {
		if i == 0 {
			// Skip header
			continue
		}
		// Fields are:
		//  'sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode <more>'
		// The first field might or might not have leading whitespace.
		line := strings.TrimSpace(scanner.Text())
		fields := fieldRe.Split(line, -1)
		if len(fields) < 10 {
			continue
		}

		localAddress, localPort, err := scanAddress(fields[1])
		if err != nil {
			continue
		}

		remoteAddress, remotePort, err := scanAddress(fields[2])
		if err != nil {
			continue
		}

		uid, err := strconv.Atoi(fields[7])
		if err != nil {
			continue
		}

		inode, err := strconv.ParseUint(fields[9], 10, 64)
		if err != nil {
			continue
		}
		t := transport{
			localAddress:  localAddress,
			localPort:     localPort,
			remoteAddress: remoteAddress,
			remotePort:    remotePort,
			uid:           uid,
			inode:         inode,
		}
		res = append(res, t)

	}
	return res
}

// scanAddress parses 'A12CF62E:E4D7' to the address and port.
// Handles IPv4 and IPv6 addresses.
// The address part are big endian 32 bit ints, hex encoded. Since net.IP is a
// byte slice we just decode the hex and flip the bytes in every group of 4.
func scanAddress(in string) (net.IP, uint16, error) {
	parts := strings.Split(in, ":")
	if len(parts) != 2 {
		return nil, 0, errors.New("invalid address:port")
	}

	// Network address is big endian. Can be either ipv4 or ipv6.
	address, err := hex.DecodeString(parts[0])
	if err != nil {
		return nil, 0, err
	}
	// reverse every 4 byte-sequence.
	for i := 0; i < len(address); i += 4 {
		address[i], address[i+3] = address[i+3], address[i]
		address[i+1], address[i+2] = address[i+2], address[i+1]
	}

	// Port number
	port, err := strconv.ParseUint(parts[1], 16, 16)
	if err != nil {
		return nil, 0, err
	}

	return net.IP(address), uint16(port), err
}

// procName does a pid->name lookup
func procName(pid uint) (string, error) {
	fh, err := os.Open(procRoot + "/" + strconv.FormatUint(uint64(pid), 10) + "/comm")
	if err != nil {
		return "", err
	}
	name := make([]byte, 1024)
	l, err := fh.Read(name)
	if err != nil {
		return "", err
	}
	if l < 2 {
		return "", nil
	}
	// drop trailing "\n"
	return string(name[:l-1]), nil
}
<|endoftext|>


<commit_before>package push

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/remotes"
	"github.com/containerd/containerd/remotes/docker"
	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/cmd/buildkitd/config"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/util/flightcontrol"
	"github.com/moby/buildkit/util/imageutil"
	"github.com/moby/buildkit/util/progress"
	"github.com/moby/buildkit/util/progress/logs"
	"github.com/moby/buildkit/util/resolver"
	"github.com/moby/buildkit/util/resolver/retryhandler"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func Push(ctx context.Context, sm *session.Manager, sid string, provider content.Provider, manager content.Manager, dgst digest.Digest, ref string, insecure bool, hosts docker.RegistryHosts, byDigest bool, annotations map[digest.Digest]map[string]string) error {
	desc := ocispec.Descriptor{
		Digest: dgst,
	}
	parsed, err := reference.ParseNormalizedNamed(ref)
	if err != nil {
		return err
	}
	if byDigest && !reference.IsNameOnly(parsed) {
		return errors.Errorf("can't push tagged ref %s by digest", parsed.String())
	}

	if byDigest {
		ref = parsed.Name()
	} else {
		ref = reference.TagNameOnly(parsed).String()
	}

	scope := "push"
	if insecure {
		insecureTrue := true
		httpTrue := true
		hosts = resolver.NewRegistryConfig(map[string]config.RegistryConfig{
			reference.Domain(parsed): {
				Insecure:  &insecureTrue,
				PlainHTTP: &httpTrue,
			},
		})
		scope += ":insecure"
	}

	resolver := resolver.DefaultPool.GetResolver(hosts, ref, scope, sm, session.NewGroup(sid))

	pusher, err := resolver.Pusher(ctx, ref)
	if err != nil {
		return err
	}

	var m sync.Mutex
	manifestStack := []ocispec.Descriptor{}

	filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
			m.Lock()
			manifestStack = append(manifestStack, desc)
			m.Unlock()
			return nil, images.ErrStopHandler
		default:
			return nil, nil
		}
	})

	pushHandler := retryhandler.New(remotes.PushHandler(pusher, provider), logs.LoggerFromContext(ctx))
	pushUpdateSourceHandler, err := updateDistributionSourceHandler(manager, pushHandler, ref)
	if err != nil {
		return err
	}

	handlers := append([]images.Handler{},
		images.HandlerFunc(annotateDistributionSourceHandler(manager, annotations, childrenHandler(provider))),
		filterHandler,
		dedupeHandler(pushUpdateSourceHandler),
	)

	ra, err := provider.ReaderAt(ctx, desc)
	if err != nil {
		return err
	}

	mtype, err := imageutil.DetectManifestMediaType(ra)
	if err != nil {
		return err
	}

	layersDone := oneOffProgress(ctx, "pushing layers")
	err = images.Dispatch(ctx, images.Handlers(handlers...), nil, ocispec.Descriptor{
		Digest:    dgst,
		Size:      ra.Size(),
		MediaType: mtype,
	})
	if err := layersDone(err); err != nil {
		return err
	}

	mfstDone := oneOffProgress(ctx, fmt.Sprintf("pushing manifest for %s", ref))
	for i := len(manifestStack) - 1; i >= 0; i-- {
		if _, err := pushHandler(ctx, manifestStack[i]); err != nil {
			return mfstDone(err)
		}
	}
	return mfstDone(nil)
}

func annotateDistributionSourceHandler(manager content.Manager, annotations map[digest.Digest]map[string]string, f images.HandlerFunc) func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		children, err := f(ctx, desc)
		if err != nil {
			return nil, err
		}

		// only add distribution source for the config or blob data descriptor
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
		default:
			return children, nil
		}

		for i := range children {
			child := children[i]

			if m, ok := annotations[child.Digest]; ok {
				for k, v := range m {
					if !strings.HasPrefix(k, "containerd.io/distribution.source.") {
						continue
					}
					if child.Annotations == nil {
						child.Annotations = map[string]string{}
					}
					child.Annotations[k] = v
				}
			}
			children[i] = child

			info, err := manager.Info(ctx, child.Digest)
			if errors.Is(err, errdefs.ErrNotFound) {
				continue
			} else if err != nil {
				return nil, err
			}

			for k, v := range info.Labels {
				if !strings.HasPrefix(k, "containerd.io/distribution.source.") {
					continue
				}

				if child.Annotations == nil {
					child.Annotations = map[string]string{}
				}
				child.Annotations[k] = v
			}

			children[i] = child
		}
		return children, nil
	}
}

func oneOffProgress(ctx context.Context, id string) func(err error) error {
	pw, _, _ := progress.FromContext(ctx)
	now := time.Now()
	st := progress.Status{
		Started: &now,
	}
	pw.Write(id, st)
	return func(err error) error {
		// TODO: set error on status
		now := time.Now()
		st.Completed = &now
		pw.Write(id, st)
		pw.Close()
		return err
	}
}

func childrenHandler(provider content.Provider) images.HandlerFunc {
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var descs []ocispec.Descriptor
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
			p, err := content.ReadBlob(ctx, provider, desc)
			if err != nil {
				return nil, err
			}

			// TODO(stevvooe): We just assume oci manifest, for now. There may be
			// subtle differences from the docker version.
			var manifest ocispec.Manifest
			if err := json.Unmarshal(p, &manifest); err != nil {
				return nil, err
			}

			descs = append(descs, manifest.Config)
			descs = append(descs, manifest.Layers...)
		case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
			p, err := content.ReadBlob(ctx, provider, desc)
			if err != nil {
				return nil, err
			}

			var index ocispec.Index
			if err := json.Unmarshal(p, &index); err != nil {
				return nil, err
			}

			for _, m := range index.Manifests {
				if m.Digest != "" {
					descs = append(descs, m)
				}
			}
		case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip,
			images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig,
			ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip:
			// childless data types.
			return nil, nil
		default:
			logrus.Warnf("encountered unknown type %v; children may not be fetched", desc.MediaType)
		}

		return descs, nil
	}
}

// updateDistributionSourceHandler will update distribution source label after
// pushing layer successfully.
//
// FIXME(fuweid): There is race condition for current design of distribution
// source label if there are pull/push jobs consuming same layer.
func updateDistributionSourceHandler(manager content.Manager, pushF images.HandlerFunc, ref string) (images.HandlerFunc, error) {
	updateF, err := docker.AppendDistributionSourceLabel(manager, ref)
	if err != nil {
		return nil, err
	}

	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var islayer bool

		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip,
			ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip:
			islayer = true
		}

		children, err := pushF(ctx, desc)
		if err != nil {
			return nil, err
		}

		// update distribution source to layer
		if islayer {
			if _, err := updateF(ctx, desc); err != nil {
				logrus.Warnf("failed to update distribution source for layer %v: %v", desc.Digest, err)
			}
		}
		return children, nil
	}), nil
}

func dedupeHandler(h images.HandlerFunc) images.HandlerFunc {
	var g flightcontrol.Group
	res := map[digest.Digest][]ocispec.Descriptor{}
	var mu sync.Mutex

	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		res, err := g.Do(ctx, desc.Digest.String(), func(ctx context.Context) (interface{}, error) {
			mu.Lock()
			if r, ok := res[desc.Digest]; ok {
				mu.Unlock()
				return r, nil
			}
			mu.Unlock()

			children, err := h(ctx, desc)
			if err != nil {
				return nil, err
			}

			mu.Lock()
			res[desc.Digest] = children
			mu.Unlock()
			return children, nil
		})
		if err != nil {
			return nil, err
		}
		if res == nil {
			return nil, nil
		}
		return res.([]ocispec.Descriptor), nil
	})
}
<commit_msg>push: avoid overwriting tags on push<commit_after>package push

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/remotes"
	"github.com/containerd/containerd/remotes/docker"
	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/cmd/buildkitd/config"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/util/flightcontrol"
	"github.com/moby/buildkit/util/imageutil"
	"github.com/moby/buildkit/util/progress"
	"github.com/moby/buildkit/util/progress/logs"
	"github.com/moby/buildkit/util/resolver"
	"github.com/moby/buildkit/util/resolver/retryhandler"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func Push(ctx context.Context, sm *session.Manager, sid string, provider content.Provider, manager content.Manager, dgst digest.Digest, ref string, insecure bool, hosts docker.RegistryHosts, byDigest bool, annotations map[digest.Digest]map[string]string) error {
	desc := ocispec.Descriptor{
		Digest: dgst,
	}
	parsed, err := reference.ParseNormalizedNamed(ref)
	if err != nil {
		return err
	}
	if byDigest && !reference.IsNameOnly(parsed) {
		return errors.Errorf("can't push tagged ref %s by digest", parsed.String())
	}

	if byDigest {
		ref = parsed.Name()
	} else {
		// add digest to ref, this is what containderd uses to choose root manifest from all manifests
		r, err := reference.WithDigest(reference.TagNameOnly(parsed), dgst)
		if err != nil {
			return errors.Wrapf(err, "failed to combine ref %s with digest %s", ref, dgst)
		}
		ref = r.String()
	}

	scope := "push"
	if insecure {
		insecureTrue := true
		httpTrue := true
		hosts = resolver.NewRegistryConfig(map[string]config.RegistryConfig{
			reference.Domain(parsed): {
				Insecure:  &insecureTrue,
				PlainHTTP: &httpTrue,
			},
		})
		scope += ":insecure"
	}

	resolver := resolver.DefaultPool.GetResolver(hosts, ref, scope, sm, session.NewGroup(sid))

	pusher, err := resolver.Pusher(ctx, ref)
	if err != nil {
		return err
	}

	var m sync.Mutex
	manifestStack := []ocispec.Descriptor{}

	filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
			m.Lock()
			manifestStack = append(manifestStack, desc)
			m.Unlock()
			return nil, images.ErrStopHandler
		default:
			return nil, nil
		}
	})

	pushHandler := retryhandler.New(remotes.PushHandler(pusher, provider), logs.LoggerFromContext(ctx))
	pushUpdateSourceHandler, err := updateDistributionSourceHandler(manager, pushHandler, ref)
	if err != nil {
		return err
	}

	handlers := append([]images.Handler{},
		images.HandlerFunc(annotateDistributionSourceHandler(manager, annotations, childrenHandler(provider))),
		filterHandler,
		dedupeHandler(pushUpdateSourceHandler),
	)

	ra, err := provider.ReaderAt(ctx, desc)
	if err != nil {
		return err
	}

	mtype, err := imageutil.DetectManifestMediaType(ra)
	if err != nil {
		return err
	}

	layersDone := oneOffProgress(ctx, "pushing layers")
	err = images.Dispatch(ctx, images.Handlers(handlers...), nil, ocispec.Descriptor{
		Digest:    dgst,
		Size:      ra.Size(),
		MediaType: mtype,
	})
	if err := layersDone(err); err != nil {
		return err
	}

	mfstDone := oneOffProgress(ctx, fmt.Sprintf("pushing manifest for %s", ref))
	for i := len(manifestStack) - 1; i >= 0; i-- {
		if _, err := pushHandler(ctx, manifestStack[i]); err != nil {
			return mfstDone(err)
		}
	}
	return mfstDone(nil)
}

func annotateDistributionSourceHandler(manager content.Manager, annotations map[digest.Digest]map[string]string, f images.HandlerFunc) func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		children, err := f(ctx, desc)
		if err != nil {
			return nil, err
		}

		// only add distribution source for the config or blob data descriptor
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
		default:
			return children, nil
		}

		for i := range children {
			child := children[i]

			if m, ok := annotations[child.Digest]; ok {
				for k, v := range m {
					if !strings.HasPrefix(k, "containerd.io/distribution.source.") {
						continue
					}
					if child.Annotations == nil {
						child.Annotations = map[string]string{}
					}
					child.Annotations[k] = v
				}
			}
			children[i] = child

			info, err := manager.Info(ctx, child.Digest)
			if errors.Is(err, errdefs.ErrNotFound) {
				continue
			} else if err != nil {
				return nil, err
			}

			for k, v := range info.Labels {
				if !strings.HasPrefix(k, "containerd.io/distribution.source.") {
					continue
				}

				if child.Annotations == nil {
					child.Annotations = map[string]string{}
				}
				child.Annotations[k] = v
			}

			children[i] = child
		}
		return children, nil
	}
}

func oneOffProgress(ctx context.Context, id string) func(err error) error {
	pw, _, _ := progress.FromContext(ctx)
	now := time.Now()
	st := progress.Status{
		Started: &now,
	}
	pw.Write(id, st)
	return func(err error) error {
		// TODO: set error on status
		now := time.Now()
		st.Completed = &now
		pw.Write(id, st)
		pw.Close()
		return err
	}
}

func childrenHandler(provider content.Provider) images.HandlerFunc {
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var descs []ocispec.Descriptor
		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
			p, err := content.ReadBlob(ctx, provider, desc)
			if err != nil {
				return nil, err
			}

			// TODO(stevvooe): We just assume oci manifest, for now. There may be
			// subtle differences from the docker version.
			var manifest ocispec.Manifest
			if err := json.Unmarshal(p, &manifest); err != nil {
				return nil, err
			}

			descs = append(descs, manifest.Config)
			descs = append(descs, manifest.Layers...)
		case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
			p, err := content.ReadBlob(ctx, provider, desc)
			if err != nil {
				return nil, err
			}

			var index ocispec.Index
			if err := json.Unmarshal(p, &index); err != nil {
				return nil, err
			}

			for _, m := range index.Manifests {
				if m.Digest != "" {
					descs = append(descs, m)
				}
			}
		case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip,
			images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig,
			ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip:
			// childless data types.
			return nil, nil
		default:
			logrus.Warnf("encountered unknown type %v; children may not be fetched", desc.MediaType)
		}

		return descs, nil
	}
}

// updateDistributionSourceHandler will update distribution source label after
// pushing layer successfully.
//
// FIXME(fuweid): There is race condition for current design of distribution
// source label if there are pull/push jobs consuming same layer.
func updateDistributionSourceHandler(manager content.Manager, pushF images.HandlerFunc, ref string) (images.HandlerFunc, error) {
	updateF, err := docker.AppendDistributionSourceLabel(manager, ref)
	if err != nil {
		return nil, err
	}

	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var islayer bool

		switch desc.MediaType {
		case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip,
			ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip:
			islayer = true
		}

		children, err := pushF(ctx, desc)
		if err != nil {
			return nil, err
		}

		// update distribution source to layer
		if islayer {
			if _, err := updateF(ctx, desc); err != nil {
				logrus.Warnf("failed to update distribution source for layer %v: %v", desc.Digest, err)
			}
		}
		return children, nil
	}), nil
}

func dedupeHandler(h images.HandlerFunc) images.HandlerFunc {
	var g flightcontrol.Group
	res := map[digest.Digest][]ocispec.Descriptor{}
	var mu sync.Mutex

	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		res, err := g.Do(ctx, desc.Digest.String(), func(ctx context.Context) (interface{}, error) {
			mu.Lock()
			if r, ok := res[desc.Digest]; ok {
				mu.Unlock()
				return r, nil
			}
			mu.Unlock()

			children, err := h(ctx, desc)
			if err != nil {
				return nil, err
			}

			mu.Lock()
			res[desc.Digest] = children
			mu.Unlock()
			return children, nil
		})
		if err != nil {
			return nil, err
		}
		if res == nil {
			return nil, nil
		}
		return res.([]ocispec.Descriptor), nil
	})
}
<|endoftext|>


<commit_before>package svg // import "github.com/tdewolff/minify/svg"

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tdewolff/minify"
)

func assertSVG(t *testing.T, input, expected string) {
	m := minify.New()
	m.AddFunc("image/svg+xml", Minify)
	b := &bytes.Buffer{}
	assert.Nil(t, m.Minify("image/svg+xml", b, bytes.NewBufferString(input)), "Minify must not return error in "+input)
	assert.Equal(t, expected, b.String(), "Minify must give expected result in "+input)
}

////////////////////////////////////////////////////////////////

func TestSVG(t *testing.T) {
	assertSVG(t, "<!-- comment -->", "")
	assertSVG(t, "<!DOCTYPE foo SYSTEM \"Foo.dtd\">", "")
	assertSVG(t, "<?xml version=\"1.0\" ?>", "")
	assertSVG(t, "<style> <![CDATA[ x ]]> </style>", "<style>x</style>")
	assertSVG(t, "<svg version=\"1.0\"></svg>", "<svg/>")
	assertSVG(t, "<svg x=\" a \"/>", "<svg x=\"a\"/>")
	assertSVG(t, "<svg x=\" a \n b \"/>", "<svg x=\"a b\"/>")
	assertSVG(t, "<svg x=\"5.0px\"/>", "<svg x=\"5\"/>")
}
<commit_msg>Keep px unit<commit_after>package svg // import "github.com/tdewolff/minify/svg"

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tdewolff/minify"
)

func assertSVG(t *testing.T, input, expected string) {
	m := minify.New()
	m.AddFunc("image/svg+xml", Minify)
	b := &bytes.Buffer{}
	assert.Nil(t, m.Minify("image/svg+xml", b, bytes.NewBufferString(input)), "Minify must not return error in "+input)
	assert.Equal(t, expected, b.String(), "Minify must give expected result in "+input)
}

////////////////////////////////////////////////////////////////

func TestSVG(t *testing.T) {
	assertSVG(t, "<!-- comment -->", "")
	assertSVG(t, "<!DOCTYPE foo SYSTEM \"Foo.dtd\">", "")
	assertSVG(t, "<?xml version=\"1.0\" ?>", "")
	assertSVG(t, "<style> <![CDATA[ x ]]> </style>", "<style>x</style>")
	assertSVG(t, "<svg version=\"1.0\"></svg>", "<svg/>")
	assertSVG(t, "<svg x=\" a \"/>", "<svg x=\"a\"/>")
	assertSVG(t, "<svg x=\" a \n b \"/>", "<svg x=\"a b\"/>")
	assertSVG(t, "<svg x=\"5.0px\"/>", "<svg x=\"5px\"/>")
}
<|endoftext|>


<commit_before>package sous

import (
	"fmt"

	"github.com/pkg/errors"
)

type (
	// DeployConfig represents the configuration of a deployment's tasks,
	// in a specific cluster. i.e. their resources, environment, and the number
	// of instances.
	DeployConfig struct {
		// Resources represents the resources each instance of this software
		// will be given by the execution environment.
		Resources Resources `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// Metadata stores values about deployments for outside applications to use
		Metadata Metadata `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// Env is a list of environment variables to set for each instance of
		// of this deployment. It will be checked for conflict with the
		// definitions found in State.Defs.EnvVars, and if not in conflict
		// assumes the greatest priority.
		Args []string `yaml:",omitempty" validate:"values=nonempty"`
		Env  `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// NumInstances is a guide to the number of instances that should be
		// deployed in this cluster, note that the actual number may differ due
		// to decisions made by Sous. If set to zero, Sous will decide how many
		// instances to launch.
		NumInstances int

		// Volumes lists the volume mappings for this deploy
		Volumes Volumes
	}

	// A DeployConfigs is a map from cluster name to DeployConfig
	DeployConfigs map[string]DeployConfig

	// Env is a mapping of environment variable name to value, used to provision
	// single instances of an application.
	Env map[string]string

	// Metadata represents an opaque map of metadata - Sous is agnostic about
	// its contents, except to validate it against the top level schema.
	Metadata map[string]string

	// NilVolumeFlaw is used when DeployConfig.Volumes contains a nil.
	NilVolumeFlaw struct {
		*DeployConfig
	}
)

// Validate returns a slice of Flaws.
func (dc *DeployConfig) Validate() []Flaw {
	var flaws []Flaw

	for _, v := range dc.Volumes {
		if v == nil {
			flaws = append(flaws, &NilVolumeFlaw{DeployConfig: dc})
			break
		}
	}

	rezs := dc.Resources
	if dc.Resources == nil {
		flaws = append(flaws, NewFlaw("No Resources set for DeployConfig",
			func() error { dc.Resources = make(Resources); return nil }))
		rezs = make(Resources)
	}

	flaws = append(flaws, rezs.Validate()...)

	for _, f := range flaws {
		f.AddContext("deploy config", dc)
	}

	return flaws
}

// AddContext simply discards all context - NilVolumeFlaw doesn't need it.
func (nvf *NilVolumeFlaw) AddContext(string, interface{}) {
}

// Repair removes any nil entries in DeployConfig.Volumes.
func (nvf *NilVolumeFlaw) Repair() error {
	newVs := nvf.DeployConfig.Volumes[:0]
	for _, v := range nvf.DeployConfig.Volumes {
		if v != nil {
			newVs = append(newVs, v)
		}
	}
	nvf.DeployConfig.Volumes = newVs
	return nil
}

// Repair implements Flawed for State
func (dc *DeployConfig) Repair(fs []Flaw) error {
	return errors.Errorf("Can't do nuffin with flaws yet")
}

func (dc *DeployConfig) String() string {
	return fmt.Sprintf("#%d %+v : %+v %+v", dc.NumInstances, dc.Resources, dc.Env, dc.Volumes)
}

// Equal is used to compare DeployConfigs
func (dc *DeployConfig) Equal(o DeployConfig) bool {
	Log.Vomit.Printf("%+ v ?= %+ v", dc, o)
	diff, _ := dc.Diff(o)
	return !diff
}

// Diff returns a list of differences between this and the other DeployConfig.
func (dc *DeployConfig) Diff(o DeployConfig) (bool, []string) {
	var diffs []string
	if dc.NumInstances != o.NumInstances {
		diffs = append(diffs, fmt.Sprintf("number of instances; this: %d; other: %d", dc.NumInstances, o.NumInstances))
	}
	// Only compare contents if length of either > 0.
	// This makes nil equal to zero-length map.
	if len(dc.Env) != 0 || len(o.Env) != 0 {
		if !dc.Env.Equal(o.Env) {
			diffs = append(diffs, fmt.Sprintf("env; this: %v; other: %v", dc.Env, o.Env))
		}
	}
	// Only compare contents if length of either > 0.
	// This makes nil equal to zero-length map.
	if len(dc.Metadata) != 0 || len(o.Metadata) != 0 {
		if !dc.Metadata.Equal(o.Metadata) {
			diffs = append(diffs, fmt.Sprintf("metadata; this: %v; other: %v", dc.Metadata, o.Metadata))
		}
	}
	// Only compare contents if length of either > 0.
	if len(dc.Resources) != 0 || len(o.Resources) != 0 {
		if !dc.Resources.Equal(o.Resources) {
			diffs = append(diffs, fmt.Sprintf("resources; this: %v; other: %v", dc.Resources, o.Resources))
		}
	}
	// Only compare contents if length of either > 0.
	if len(dc.Volumes) != 0 || len(o.Volumes) != 0 {
		if !dc.Volumes.Equal(o.Volumes) {
			diffs = append(diffs, fmt.Sprintf("volumes; this: %v; other: %v", dc.Volumes, o.Volumes))
		}
	}
	// TODO: Compare Args
	return len(diffs) == 0, diffs
}

// Clone returns a deep copy of this DeployConfig.
func (dc DeployConfig) Clone() (c DeployConfig) {
	c.NumInstances = dc.NumInstances
	c.Args = make([]string, len(dc.Args))
	copy(dc.Args, c.Args)
	c.Env = make(Env)
	for k, v := range dc.Env {
		c.Env[k] = v
	}
	c.Resources = make(Resources)
	for k, v := range dc.Resources {
		c.Resources[k] = v
	}
	if dc.Metadata != nil {
		c.Metadata = make(Metadata)
		for k, v := range dc.Metadata {
			c.Metadata[k] = v
		}
	}
	c.Volumes = dc.Volumes.Clone()
	return
}

// Equal compares Envs
func (e Env) Equal(o Env) bool {
	Log.Vomit.Printf("Envs: %+ v ?= %+ v", e, o)
	if len(e) != len(o) {
		Log.Vomit.Printf("Envs: %+ v != %+ v (%d != %d)", e, o, len(e), len(o))
		return false
	}

	for name, value := range e {
		if ov, ok := o[name]; !ok || ov != value {
			Log.Vomit.Printf("Envs: %+ v != %+ v [%q] %q != %q", e, o, name, value, ov)
			return false
		}
	}
	Log.Vomit.Printf("Envs: %+ v == %+ v !", e, o)
	return true
}

// Equal compares Metadatas
func (e Metadata) Equal(o Metadata) bool {
	Log.Vomit.Printf("Metadatas: %+ v ?= %+ v", e, o)
	if len(e) != len(o) {
		Log.Vomit.Printf("Metadatas: %+ v != %+ v (%d != %d)", e, o, len(e), len(o))
		return false
	}

	for name, value := range e {
		if ov, ok := o[name]; !ok || ov != value {
			Log.Vomit.Printf("Metadatas: %+ v != %+ v [%q] %q != %q", e, o, name, value, ov)
			return false
		}
	}
	Log.Vomit.Printf("Metadatas: %+ v == %+ v !", e, o)
	return true
}

func stringSlicesEqual(left, right []string) bool {
	if len(left) != len(right) {
		return false
	}
	for idx := range left {
		if left[idx] != right[idx] {
			return false
		}
	}
	return true
}

func flattenDeployConfigs(dcs []DeployConfig) DeployConfig {
	dc := DeployConfig{
		Resources: make(Resources),
		Env:       make(Env),
		Metadata:  make(Metadata),
	}
	for _, c := range dcs {
		if c.NumInstances != 0 {
			dc.NumInstances = c.NumInstances
			break
		}
	}
	for _, c := range dcs {
		if len(c.Volumes) != 0 {
			dc.Volumes = c.Volumes
			break
		}
	}
	for _, c := range dcs {
		if len(c.Args) != 0 {
			dc.Args = c.Args
			break
		}
	}
	for _, c := range dcs {
		for n, v := range c.Resources {
			if _, set := dc.Resources[n]; !set {
				dc.Resources[n] = v
			}
		}
		for n, v := range c.Env {
			if _, set := dc.Env[n]; !set {
				dc.Env[n] = v
			}
		}
		for n, v := range c.Metadata {
			if _, set := dc.Metadata[n]; !set {
				dc.Metadata[n] = v
			}
		}
	}
	return dc
}
<commit_msg>Health member on DeployConfig{}<commit_after>package sous

import (
	"fmt"

	"github.com/pkg/errors"
)

type (
	// DeployConfig represents the configuration of a deployment's tasks,
	// in a specific cluster. i.e. their resources, environment, and the number
	// of instances.
	DeployConfig struct {
		// Resources represents the resources each instance of this software
		// will be given by the execution environment.
		Resources Resources `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// Metadata stores values about deployments for outside applications to use
		Metadata Metadata `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// Env is a list of environment variables to set for each instance of
		// of this deployment. It will be checked for conflict with the
		// definitions found in State.Defs.EnvVars, and if not in conflict
		// assumes the greatest priority.
		Args []string `yaml:",omitempty" validate:"values=nonempty"`
		Env  `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
		// NumInstances is a guide to the number of instances that should be
		// deployed in this cluster, note that the actual number may differ due
		// to decisions made by Sous. If set to zero, Sous will decide how many
		// instances to launch.
		NumInstances int
		// Volumes lists the volume mappings for this deploy
		Volumes Volumes
		// Health containts healthcheck options for this deploy.
		Health Health `yaml:",omitempty"`
	}

	// A DeployConfigs is a map from cluster name to DeployConfig
	DeployConfigs map[string]DeployConfig

	// Env is a mapping of environment variable name to value, used to provision
	// single instances of an application.
	Env map[string]string

	// Health is a struct of healthcheck options. Members are pointers so that they
	// can be ignored if nil.
	Health struct {
		HealthcheckRelativeURI *string
	}

	// Metadata represents an opaque map of metadata - Sous is agnostic about
	// its contents, except to validate it against the top level schema.
	Metadata map[string]string

	// NilVolumeFlaw is used when DeployConfig.Volumes contains a nil.
	NilVolumeFlaw struct {
		*DeployConfig
	}
)

// Validate returns a slice of Flaws.
func (dc *DeployConfig) Validate() []Flaw {
	var flaws []Flaw

	for _, v := range dc.Volumes {
		if v == nil {
			flaws = append(flaws, &NilVolumeFlaw{DeployConfig: dc})
			break
		}
	}

	rezs := dc.Resources
	if dc.Resources == nil {
		flaws = append(flaws, NewFlaw("No Resources set for DeployConfig",
			func() error { dc.Resources = make(Resources); return nil }))
		rezs = make(Resources)
	}

	flaws = append(flaws, rezs.Validate()...)

	for _, f := range flaws {
		f.AddContext("deploy config", dc)
	}

	return flaws
}

// AddContext simply discards all context - NilVolumeFlaw doesn't need it.
func (nvf *NilVolumeFlaw) AddContext(string, interface{}) {
}

// Repair removes any nil entries in DeployConfig.Volumes.
func (nvf *NilVolumeFlaw) Repair() error {
	newVs := nvf.DeployConfig.Volumes[:0]
	for _, v := range nvf.DeployConfig.Volumes {
		if v != nil {
			newVs = append(newVs, v)
		}
	}
	nvf.DeployConfig.Volumes = newVs
	return nil
}

// Repair implements Flawed for State
func (dc *DeployConfig) Repair(fs []Flaw) error {
	return errors.Errorf("Can't do nuffin with flaws yet")
}

func (dc *DeployConfig) String() string {
	return fmt.Sprintf("#%d %+v : %+v %+v", dc.NumInstances, dc.Resources, dc.Env, dc.Volumes)
}

// Equal is used to compare DeployConfigs
func (dc *DeployConfig) Equal(o DeployConfig) bool {
	Log.Vomit.Printf("%+ v ?= %+ v", dc, o)
	diff, _ := dc.Diff(o)
	return !diff
}

// Diff returns a list of differences between this and the other DeployConfig.
func (dc *DeployConfig) Diff(o DeployConfig) (bool, []string) {
	var diffs []string
	if dc.NumInstances != o.NumInstances {
		diffs = append(diffs, fmt.Sprintf("number of instances; this: %d; other: %d", dc.NumInstances, o.NumInstances))
	}
	// Only compare contents if length of either > 0.
	// This makes nil equal to zero-length map.
	if len(dc.Env) != 0 || len(o.Env) != 0 {
		if !dc.Env.Equal(o.Env) {
			diffs = append(diffs, fmt.Sprintf("env; this: %v; other: %v", dc.Env, o.Env))
		}
	}
	// Only compare contents if length of either > 0.
	// This makes nil equal to zero-length map.
	if len(dc.Metadata) != 0 || len(o.Metadata) != 0 {
		if !dc.Metadata.Equal(o.Metadata) {
			diffs = append(diffs, fmt.Sprintf("metadata; this: %v; other: %v", dc.Metadata, o.Metadata))
		}
	}
	// Only compare contents if length of either > 0.
	if len(dc.Resources) != 0 || len(o.Resources) != 0 {
		if !dc.Resources.Equal(o.Resources) {
			diffs = append(diffs, fmt.Sprintf("resources; this: %v; other: %v", dc.Resources, o.Resources))
		}
	}
	// Only compare contents if length of either > 0.
	if len(dc.Volumes) != 0 || len(o.Volumes) != 0 {
		if !dc.Volumes.Equal(o.Volumes) {
			diffs = append(diffs, fmt.Sprintf("volumes; this: %v; other: %v", dc.Volumes, o.Volumes))
		}
	}
	// TODO: Compare Args
	return len(diffs) == 0, diffs
}

// Clone returns a deep copy of this DeployConfig.
func (dc DeployConfig) Clone() (c DeployConfig) {
	c.NumInstances = dc.NumInstances
	c.Args = make([]string, len(dc.Args))
	copy(dc.Args, c.Args)
	c.Env = make(Env)
	for k, v := range dc.Env {
		c.Env[k] = v
	}
	c.Resources = make(Resources)
	for k, v := range dc.Resources {
		c.Resources[k] = v
	}
	if dc.Metadata != nil {
		c.Metadata = make(Metadata)
		for k, v := range dc.Metadata {
			c.Metadata[k] = v
		}
	}
	c.Volumes = dc.Volumes.Clone()
	return
}

// Equal compares Envs
func (e Env) Equal(o Env) bool {
	Log.Vomit.Printf("Envs: %+ v ?= %+ v", e, o)
	if len(e) != len(o) {
		Log.Vomit.Printf("Envs: %+ v != %+ v (%d != %d)", e, o, len(e), len(o))
		return false
	}

	for name, value := range e {
		if ov, ok := o[name]; !ok || ov != value {
			Log.Vomit.Printf("Envs: %+ v != %+ v [%q] %q != %q", e, o, name, value, ov)
			return false
		}
	}
	Log.Vomit.Printf("Envs: %+ v == %+ v !", e, o)
	return true
}

// Equal compares Metadatas
func (e Metadata) Equal(o Metadata) bool {
	Log.Vomit.Printf("Metadatas: %+ v ?= %+ v", e, o)
	if len(e) != len(o) {
		Log.Vomit.Printf("Metadatas: %+ v != %+ v (%d != %d)", e, o, len(e), len(o))
		return false
	}

	for name, value := range e {
		if ov, ok := o[name]; !ok || ov != value {
			Log.Vomit.Printf("Metadatas: %+ v != %+ v [%q] %q != %q", e, o, name, value, ov)
			return false
		}
	}
	Log.Vomit.Printf("Metadatas: %+ v == %+ v !", e, o)
	return true
}

func stringSlicesEqual(left, right []string) bool {
	if len(left) != len(right) {
		return false
	}
	for idx := range left {
		if left[idx] != right[idx] {
			return false
		}
	}
	return true
}

func flattenDeployConfigs(dcs []DeployConfig) DeployConfig {
	dc := DeployConfig{
		Resources: make(Resources),
		Env:       make(Env),
		Metadata:  make(Metadata),
	}
	for _, c := range dcs {
		if c.NumInstances != 0 {
			dc.NumInstances = c.NumInstances
			break
		}
	}
	for _, c := range dcs {
		if len(c.Volumes) != 0 {
			dc.Volumes = c.Volumes
			break
		}
	}
	for _, c := range dcs {
		if len(c.Args) != 0 {
			dc.Args = c.Args
			break
		}
	}
	for _, c := range dcs {
		for n, v := range c.Resources {
			if _, set := dc.Resources[n]; !set {
				dc.Resources[n] = v
			}
		}
		for n, v := range c.Env {
			if _, set := dc.Env[n]; !set {
				dc.Env[n] = v
			}
		}
		for n, v := range c.Metadata {
			if _, set := dc.Metadata[n]; !set {
				dc.Metadata[n] = v
			}
		}
	}
	return dc
}
<|endoftext|>