<commit_before>// +build windows

package cli

import (
	"os/exec"
	"path/filepath"

	"golang.org/x/sys/windows/registry"

	"github.com/gopasspw/gopass/pkg/fsutil"
)

func detectBinaryCandidates(bin string) ([]string, error) {
	// gpg.exe for GPG4Win 3.0.0; would be gpg2.exe for 2.x
	bins := make([]string, 0, 4)

	bins, err := searchRegistry(bin, bins)
	if err != nil {
		return bins, err
	}

	bins, err = searchPath(bin, bins)
	if err != nil {
		return bins, err
	}

	return bins, nil
}

func searchRegistry(bin string, bins []string) ([]string, error) {
	// try to detect location of installed GPG4Win
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\GnuPG`, registry.QUERY_VALUE|registry.WOW64_32KEY)
	if err != nil {
		return bins, nil
	}

	if v, _, err := k.GetStringValue("Install Directory"); err == nil && v != "" {
		for _, b := range []string{bin, "gpg2.exe", "gpg.exe"} {
			gpgPath := filepath.Join(v, "bin", b)
			if fsutil.IsFile(gpgPath) {
				bins = append(bins, gpgPath)
			}
		}
	}

	return bins, nil
}

func searchPath(bin string, bins []string) ([]string, error) {
	// try to detect location for GPG installed somewhere on the PATH
	for _, b := range []string{bin, "gpg2.exe", "gpg.exe"} {
		gpgPath, err := exec.LookPath(b)
		if err != nil {
			continue
		}
		if fsutil.IsFile(gpgPath) {
			bins = append(bins, gpgPath)
		}
	}

	return bins, nil
}
<commit_msg>Skip empty binary name to be searched on Windows (#993) (#1018)<commit_after>// +build windows

package cli

import (
	"os/exec"
	"path/filepath"

	"golang.org/x/sys/windows/registry"

	"github.com/gopasspw/gopass/pkg/fsutil"
)

func detectBinaryCandidates(bin string) ([]string, error) {
	// gpg.exe for GPG4Win 3.0.0; would be gpg2.exe for 2.x
	bins := make([]string, 0, 4)

	bins, err := searchRegistry(bin, bins)
	if err != nil {
		return bins, err
	}

	bins, err = searchPath(bin, bins)
	if err != nil {
		return bins, err
	}

	return bins, nil
}

func searchRegistry(bin string, bins []string) ([]string, error) {
	// try to detect location of installed GPG4Win
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\GnuPG`, registry.QUERY_VALUE|registry.WOW64_32KEY)
	if err != nil {
		return bins, nil
	}

	if v, _, err := k.GetStringValue("Install Directory"); err == nil && v != "" {
		for _, b := range []string{bin, "gpg2.exe", "gpg.exe"} {
			if b == "" {
				continue
			}
			gpgPath := filepath.Join(v, "bin", b)
			if fsutil.IsFile(gpgPath) {
				bins = append(bins, gpgPath)
			}
		}
	}

	return bins, nil
}

func searchPath(bin string, bins []string) ([]string, error) {
	// try to detect location for GPG installed somewhere on the PATH
	for _, b := range []string{bin, "gpg2.exe", "gpg.exe"} {
		if b == "" {
			continue
		}
		gpgPath, err := exec.LookPath(b)
		if err != nil {
			continue
		}
		if fsutil.IsFile(gpgPath) {
			bins = append(bins, gpgPath)
		}
	}

	return bins, nil
}
<|endoftext|>


<commit_before><commit_msg>remove the health chekcer, because it's the same as command_volume_fix_replication<commit_after><|endoftext|>


<commit_before>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package krusty_test

import (
	"fmt"
	"net/http"
	"path/filepath"
	"testing"

	"sigs.k8s.io/kustomize/api/resmap"

	"github.com/stretchr/testify/assert"
	"sigs.k8s.io/kustomize/api/internal/utils"
	"sigs.k8s.io/kustomize/api/krusty"
	"sigs.k8s.io/kustomize/api/loader"
	"sigs.k8s.io/kustomize/kyaml/filesys"
)

const multibaseDevExampleBuild = `apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
  name: dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`

type remoteResourceCase struct {
	skip          bool
	kustomization string
}

func createKusDir(content string, assert *assert.Assertions) (filesys.FileSystem, filesys.ConfirmedDir) {
	fSys := filesys.MakeFsOnDisk()
	tmpDir, err := filesys.NewTmpConfirmedDir()
	assert.NoError(err)
	assert.NoError(fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(content)))
	return fSys, tmpDir
}

func checkYaml(actual resmap.ResMap, expected string, assert *assert.Assertions) {
	yml, err := actual.AsYaml()
	assert.NoError(err)
	assert.Equal(expected, string(yml))
}

func testRemoteResource(assert *assert.Assertions, kustomization string, expected string) {
	fSys, tmpDir := createKusDir(kustomization, assert)

	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
	m, err := b.Run(
		fSys,
		tmpDir.String())

	assert.NoError(err)
	checkYaml(m, expected, assert)

	assert.NoError(fSys.RemoveAll(tmpDir.String()))
}

func TestRemoteLoad(t *testing.T) {
	assert := assert.New(t)

	fSys := filesys.MakeFsOnDisk()
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())

	m, err := b.Run(
		fSys,
		"github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6")
	assert.NoError(err)
	checkYaml(m, multibaseDevExampleBuild, assert)
}

func TestRemoteResourceHttps(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"basic": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
		},
		".git repo suffix, no slash suffix": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize.git//examples/multibases/dev?ref=v1.0.6`,
		},
		"repo": {
			kustomization: `
resources:
- https://github.com/annasong20/kustomize-test.git?ref=main`,
		},
		"raw remote file": {
			kustomization: `
resources:
- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/v3.1.0/examples/multibases/base/pod.yaml
namePrefix: dev-`,
		},
	}

	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourceSsh(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"scp shorthand": {
			kustomization: `
resources:
- git@github.com:kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
		},
		"full ssh, no ending slash": {
			kustomization: `
resources:
- ssh://git@github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=v1.0.6`,
		},
		"repo": {
			kustomization: `
resources:
- ssh://git@github.com/annasong20/kustomize-test.git?ref=main`,
		},
	}

	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourcePort(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"https": {
			skip: true, // timeout
			kustomization: `
resources:
- https://github.com:443/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
		},
		"ssh": {
			skip: true, // error
			kustomization: `
resources:
- ssh://git@github.com:22/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
		},
	}
	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourceRepo(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"https, no ref": {
			kustomization: `
resources:
- https://github.com/annasong20/kustomize-test.git`,
		},
		"ssh, no ref": {
			kustomization: `
resources:
- git@github.com:annasong20/kustomize-test.git`,
		},
	}

	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourceParameters(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"https no params": {
			skip: true, // timeout
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/`,
		},
		"https master": {
			skip: true, // timeout
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=master`,
		},
		"https master and no submodules": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=master&submodules=false`,
		},
		"https all params": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=v1.0.6&timeout=10&submodules=true`,
		},
		"ssh no params": {
			skip: true, // timeout
			kustomization: `
resources:
- git@github.com:kubernetes-sigs/kustomize//examples/multibases/dev`,
		},
		"ssh all params": {
			kustomization: `
resources:
- ssh://git@github.com/annasong20/kustomize-test.git?ref=main&timeout=10&submodules=true`,
		},
	}

	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourceGoGetter(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"git detector with / subdirectory separator": {
			kustomization: `
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
		},
		"git detector for repo": {
			kustomization: `
resources:
- github.com/annasong20/kustomize-test`,
		},
		"https with / subdirectory separator": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
		},
		"git forced protocol": {
			kustomization: `
resources:
- git::https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
		},
		"git forced protocol with / subdirectory separator": {
			kustomization: `
resources:
- git::https://github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
		},
	}

	for name, test := range tests {
		test := test
		if !test.skip {
			t.Run(name, func(t *testing.T) {
				testRemoteResource(assert.New(t), test.kustomization, multibaseDevExampleBuild)
			})
		}
	}
}

func TestRemoteResourceWithHttpError(t *testing.T) {
	fSys := filesys.MakeFsOnDisk()
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
	tmpDir, err := filesys.NewTmpConfirmedDir()
	assert.NoError(t, err)

	url404 := "https://github.com/thisisa404.yaml"
	kusto := filepath.Join(tmpDir.String(), "kustomization.yaml")
	assert.NoError(t, fSys.WriteFile(kusto, []byte(fmt.Sprintf(`
resources:
- %s
`, url404))))

	_, err = b.Run(fSys, tmpDir.String())
	if utils.IsErrTimeout(err) {
		// Don't fail on timeouts.
		t.SkipNow()
	}

	httpErr := fmt.Errorf("%w: status code %d (%s)", loader.ErrorHTTP, 404, http.StatusText(404))
	accuFromErr := fmt.Errorf("accumulating resources from '%s': %w", url404, httpErr)
	expectedErr := fmt.Errorf("accumulating resources: %w", accuFromErr)
	assert.EqualErrorf(t, err, expectedErr.Error(), url404)
}

func TestRemoteResourceAnnoOrigin(t *testing.T) {
	kustomization := `
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6
buildMetadata: [originAnnotations]
`
	expected := `apiVersion: v1
kind: Pod
metadata:
  annotations:
    config.kubernetes.io/origin: |
      path: examples/multibases/base/pod.yaml
      repo: https://github.com/kubernetes-sigs/kustomize
      ref: v1.0.6
  labels:
    app: myapp
  name: dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`
	testRemoteResource(assert.New(t), kustomization, expected)
}

func TestRemoteResourceAsBaseWithAnnoOrigin(t *testing.T) {
	assert := assert.New(t)

	fSys := filesys.MakeFsOnDisk()
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
	tmpDir, err := filesys.NewTmpConfirmedDir()
	assert.NoError(err)
	base := filepath.Join(tmpDir.String(), "base")
	prod := filepath.Join(tmpDir.String(), "prod")
	assert.NoError(fSys.Mkdir(base))
	assert.NoError(fSys.Mkdir(prod))
	assert.NoError(fSys.WriteFile(filepath.Join(base, "kustomization.yaml"), []byte(`
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6
`)))
	assert.NoError(fSys.WriteFile(filepath.Join(prod, "kustomization.yaml"), []byte(`
resources:
- ../base
namePrefix: prefix-
buildMetadata: [originAnnotations]
`)))

	m, err := b.Run(
		fSys,
		prod)
	assert.NoError(err)

	expected := `apiVersion: v1
kind: Pod
metadata:
  annotations:
    config.kubernetes.io/origin: |
      path: examples/multibases/base/pod.yaml
      repo: https://github.com/kubernetes-sigs/kustomize
      ref: v1.0.6
  labels:
    app: myapp
  name: prefix-dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`
	checkYaml(m, expected, assert)

	assert.NoError(fSys.RemoveAll(tmpDir.String()))
}
<commit_msg>Address feedback<commit_after>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package krusty_test

import (
	"fmt"
	"net/http"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"sigs.k8s.io/kustomize/api/krusty"
	"sigs.k8s.io/kustomize/api/loader"
	"sigs.k8s.io/kustomize/api/resmap"
	"sigs.k8s.io/kustomize/kyaml/filesys"
)

const resourcesField = `resources:
- %s`
const resourceErrorFormat = "accumulating resources: accumulation err='accumulating resources from '%s': "
const fileError = "evalsymlink failure"
const repoFindError = "URL is a git repository': hit 27s timeout running"

const multibaseDevExampleBuild = `apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
  name: dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`

type remoteResourceCase struct {
	skip          bool
	kustomization string
	error         bool
	expected      string // if error, expected is error string
}

func createKustDir(content string, require *require.Assertions) (filesys.FileSystem, filesys.ConfirmedDir) {
	fSys := filesys.MakeFsOnDisk()
	tmpDir, err := filesys.NewTmpConfirmedDir()
	require.NoError(err)
	require.NoError(fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(content)))
	return fSys, tmpDir
}

func checkYaml(actual resmap.ResMap, expected string, require *require.Assertions) {
	yml, err := actual.AsYaml()
	require.NoError(err)
	require.Equal(expected, string(yml))
}

func testRemoteResource(require *require.Assertions, test *remoteResourceCase) {
	fSys, tmpDir := createKustDir(test.kustomization, require)

	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
	m, err := b.Run(
		fSys,
		tmpDir.String())

	if test.error {
		require.Error(err)
		require.Contains(err.Error(), test.expected)
	} else {
		require.NoError(err)
		checkYaml(m, test.expected, require)
	}

	require.NoError(fSys.RemoveAll(tmpDir.String()))
}

func TestRemoteLoad(t *testing.T) {
	require := require.New(t)

	fSys := filesys.MakeFsOnDisk()
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())

	m, err := b.Run(
		fSys,
		"github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6")
	require.NoError(err)
	checkYaml(m, multibaseDevExampleBuild, require)
}

func TestRemoteResourceHttps(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"basic": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		".git repo suffix, no slash suffix": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize.git//examples/multibases/dev?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"repo": {
			kustomization: `
resources:
- https://github.com/annasong20/kustomize-test.git?ref=main`,
			expected: multibaseDevExampleBuild,
		},
		"raw remote file": {
			kustomization: `
resources:
- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/v3.1.0/examples/multibases/base/pod.yaml
namePrefix: dev-`,
			expected: multibaseDevExampleBuild,
		},
	}

	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourceSsh(t *testing.T) {
	// skip all tests until server has ssh keys
	tests := map[string]remoteResourceCase{
		"scp shorthand": {
			kustomization: `
resources:
- git@github.com:kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"full ssh, no ending slash": {
			kustomization: `
resources:
- ssh://git@github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"repo": {
			kustomization: `
resources:
- ssh://git@github.com/annasong20/kustomize-test.git?ref=main`,
			expected: multibaseDevExampleBuild,
		},
	}

	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourcePort(t *testing.T) {
	sshURL := "ssh://git@github.com:22/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6"
	httpsURL := "https://github.com:443/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6"

	// ports not currently supported; should implement in future
	tests := map[string]remoteResourceCase{
		"ssh": {
			kustomization: fmt.Sprintf(resourcesField, sshURL),
			error:         true,
			expected:      fmt.Sprintf(resourceErrorFormat+fileError, sshURL),
		},
		"https": {
			kustomization: fmt.Sprintf(resourcesField, httpsURL),
			error:         true,
			expected:      fmt.Sprintf(resourceErrorFormat+repoFindError, httpsURL),
		},
	}
	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourceRepo(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"https, no ref": {
			kustomization: `
resources:
- https://github.com/annasong20/kustomize-test.git`,
			expected: multibaseDevExampleBuild,
		},
		"ssh, no ref": {
			kustomization: `
resources:
- git@github.com:annasong20/kustomize-test.git`,
			expected: multibaseDevExampleBuild,
		},
	}

	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourceParameters(t *testing.T) {
	httpsNoParam := "https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/"
	httpsMasterBranch := "https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=master"
	sshNoParams := "git@github.com:kubernetes-sigs/kustomize//examples/multibases/dev"

	// some query parameter combinations not currently supported; should implement in future
	tests := map[string]remoteResourceCase{
		"https no params": {
			kustomization: fmt.Sprintf(resourcesField, httpsNoParam),
			error:         true,
			expected:      fmt.Sprintf(resourceErrorFormat+repoFindError, httpsNoParam),
		},
		"https master": {
			kustomization: fmt.Sprintf(resourcesField, httpsMasterBranch),
			error:         true,
			expected:      fmt.Sprintf(resourceErrorFormat+repoFindError, httpsMasterBranch),
		},
		"https master and no submodules": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=master&submodules=false`,
			expected: multibaseDevExampleBuild,
		},
		"https all params": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=v1.0.6&timeout=10&submodules=true`,
			expected: multibaseDevExampleBuild,
		},
		"ssh no params": {
			kustomization: fmt.Sprintf(resourcesField, sshNoParams),
			error:         true,
			expected:      fmt.Sprintf(resourceErrorFormat+fileError, sshNoParams),
		},
		"ssh all params": {
			kustomization: `
resources:
- ssh://git@github.com/annasong20/kustomize-test.git?ref=main&timeout=10&submodules=true`,
			expected: multibaseDevExampleBuild,
		},
	}

	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourceGoGetter(t *testing.T) {
	tests := map[string]remoteResourceCase{
		"git detector with / subdirectory separator": {
			kustomization: `
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"git detector for repo": {
			kustomization: `
resources:
- github.com/annasong20/kustomize-test`,
			expected: multibaseDevExampleBuild,
		},
		"https with / subdirectory separator": {
			kustomization: `
resources:
- https://github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"git forced protocol": {
			kustomization: `
resources:
- git::https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
		"git forced protocol with / subdirectory separator": {
			kustomization: `
resources:
- git::https://github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6`,
			expected: multibaseDevExampleBuild,
		},
	}

	for name, test := range tests {
		test := test
		t.Run(name, func(t *testing.T) {
			if test.skip {
				t.SkipNow()
			}
			testRemoteResource(require.New(t), &test)
		})
	}
}

func TestRemoteResourceWithHttpError(t *testing.T) {
	require := require.New(t)

	url404 := "https://github.com/thisisa404.yaml"
	fSys, tmpDir := createKustDir(fmt.Sprintf(resourcesField, url404), require)
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())

	_, err := b.Run(fSys, tmpDir.String())

	httpErr := fmt.Errorf("%w: status code %d (%s)", loader.ErrorHTTP, 404, http.StatusText(404))
	accuFromErr := fmt.Errorf("accumulating resources from '%s': %w", url404, httpErr)
	expectedErr := fmt.Errorf("accumulating resources: %w", accuFromErr)
	require.EqualErrorf(err, expectedErr.Error(), url404)

	require.NoError(fSys.RemoveAll(tmpDir.String()))
}

func TestRemoteResourceAnnoOrigin(t *testing.T) {
	test := remoteResourceCase{
		kustomization: `
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6
buildMetadata: [originAnnotations]
`,
		expected: `apiVersion: v1
kind: Pod
metadata:
  annotations:
    config.kubernetes.io/origin: |
      path: examples/multibases/base/pod.yaml
      repo: https://github.com/kubernetes-sigs/kustomize
      ref: v1.0.6
  labels:
    app: myapp
  name: dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`,
	}

	testRemoteResource(require.New(t), &test)
}

func TestRemoteResourceAsBaseWithAnnoOrigin(t *testing.T) {
	require := require.New(t)

	fSys := filesys.MakeFsOnDisk()
	b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
	tmpDir, err := filesys.NewTmpConfirmedDir()
	require.NoError(err)
	base := filepath.Join(tmpDir.String(), "base")
	prod := filepath.Join(tmpDir.String(), "prod")
	require.NoError(fSys.Mkdir(base))
	require.NoError(fSys.Mkdir(prod))
	require.NoError(fSys.WriteFile(filepath.Join(base, "kustomization.yaml"), []byte(`
resources:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6
`)))
	require.NoError(fSys.WriteFile(filepath.Join(prod, "kustomization.yaml"), []byte(`
resources:
- ../base
namePrefix: prefix-
buildMetadata: [originAnnotations]
`)))

	m, err := b.Run(
		fSys,
		prod)
	require.NoError(err)

	expected := `apiVersion: v1
kind: Pod
metadata:
  annotations:
    config.kubernetes.io/origin: |
      path: examples/multibases/base/pod.yaml
      repo: https://github.com/kubernetes-sigs/kustomize
      ref: v1.0.6
  labels:
    app: myapp
  name: prefix-dev-myapp-pod
spec:
  containers:
  - image: nginx:1.7.9
    name: nginx
`
	checkYaml(m, expected, require)

	require.NoError(fSys.RemoveAll(tmpDir.String()))
}
<|endoftext|>


<commit_before>package activeingress

import (
	"fmt"
	"strings"

	"github.com/containerum/chkit/pkg/model/ingress"
	"github.com/containerum/chkit/pkg/model/service"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/host2dnslabel"
)

type Config struct {
	Services service.ServiceList
	Ingress  *ingress.Ingress
}

func Wizard(config Config) (ingress.Ingress, error) {
	var ingr ingress.Ingress
	if config.Ingress != nil {
		ingr = (*config.Ingress).Copy()
	}
	var rule ingress.Rule
	if len(config.Ingress.Rules) > 0 {
		rule = config.Ingress.Rules[0].Copy()
	}
	for exit := false; !exit; {
		_, err := (&activekit.Menu{
			Items: []*activekit.MenuItem{
				{
					Label: fmt.Sprintf("Set host       : %s",
						activekit.OrString(ingr.Host(), "undefined (required)")),
					Action: func() error {
						host := strings.TrimSpace(activekit.Promt(fmt.Sprintf("type host name (hit enter to leave %s): ",
							activekit.OrString(ingr.Host(), ingr.Host()))))
						if host != "" {
							ingr.Name = host2dnslabel.Host2DNSLabel(host)
							rule.Host = host
							ingr.Rules = []ingress.Rule{rule}
						}
						return nil
					},
				},
				{
					Label: fmt.Sprintf("Set TLS secret : %s", func() string {
						if rule.TLSSecret == "" {
							return "none"
						}
						return rule.TLSSecret
					}()),
					Action: func() error {
						rule.TLSSecret = tlsSecretMenu(rule.TLSSecret)
						return nil
					},
				},
				{
					Label: "Edit paths",
					Action: func() error {
						rule.Paths = pathsMenu(config.Services, rule.Paths)
						return nil
					},
				},
				{
					Label: "Confirm",
					Action: func() error {
						ingr.Rules = []ingress.Rule{rule}
						if err := ValidateIngress(ingr); err != nil {
							activekit.Attention(err.Error())
							return nil
						}
						exit = true
						return nil
					},
				},
			},
		}).Run()
		if err != nil {
			return ingr, err
		}
	}
	return ingr, nil
}
<commit_msg>Print/save to file ingresses<commit_after>package activeingress

import (
	"fmt"
	"strings"

	"io/ioutil"
	"os"

	"github.com/containerum/chkit/pkg/model/ingress"
	"github.com/containerum/chkit/pkg/model/service"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/host2dnslabel"
	"github.com/containerum/chkit/pkg/util/text"
	"github.com/sirupsen/logrus"
)

type Config struct {
	Services service.ServiceList
	Ingress  *ingress.Ingress
}

func Wizard(config Config) (ingress.Ingress, error) {
	var ingr ingress.Ingress
	if config.Ingress != nil {
		ingr = (*config.Ingress).Copy()
	}
	var rule ingress.Rule
	if len(config.Ingress.Rules) > 0 {
		rule = config.Ingress.Rules[0].Copy()
	}
	for exit := false; !exit; {
		_, err := (&activekit.Menu{
			Items: []*activekit.MenuItem{
				{
					Label: fmt.Sprintf("Set host       : %s",
						activekit.OrString(ingr.Host(), "undefined (required)")),
					Action: func() error {
						host := strings.TrimSpace(activekit.Promt(fmt.Sprintf("type host name (hit enter to leave %s): ",
							activekit.OrString(ingr.Host(), ingr.Host()))))
						if host != "" {
							ingr.Name = host2dnslabel.Host2DNSLabel(host)
							rule.Host = host
							ingr.Rules = []ingress.Rule{rule}
						}
						return nil
					},
				},
				{
					Label: fmt.Sprintf("Set TLS secret : %s", func() string {
						if rule.TLSSecret == "" {
							return "none"
						}
						return rule.TLSSecret
					}()),
					Action: func() error {
						rule.TLSSecret = tlsSecretMenu(rule.TLSSecret)
						return nil
					},
				},
				{
					Label: "Edit paths",
					Action: func() error {
						rule.Paths = pathsMenu(config.Services, rule.Paths)
						return nil
					},
				},
				{
					Label: "Print to terminal",
					Action: func() error {
						ingr.Rules = ingress.RuleList{rule}
						data, err := ingr.RenderYAML()
						if err != nil {
							logrus.WithError(err).Errorf("unable to render ingress to yaml")
							activekit.Attention(err.Error())
						}
						border := strings.Repeat("_", text.Width(data))
						fmt.Printf("%s\n%s\n%s\n", border, data, border)
						return nil
					},
				}, {
					Label: "Save to file",
					Action: func() error {
						logrus.Debugf("saving soltion to file")
						ingr.Rules = ingress.RuleList{rule}
						data, err := ingr.RenderJSON()
						if err != nil {
							logrus.WithError(err).Errorf("unable to render ingress to json")
							activekit.Attention(err.Error())
							return nil
						}
						fname := activekit.Promt("Print filename: ")
						if err := ioutil.WriteFile(fname, []byte(data), os.ModePerm); err != nil {
							logrus.WithError(err).Errorf("unable to write ingress data to file")
							activekit.Attention(err.Error())
							return nil
						}
						fmt.Println("OK")
						return nil
					},
				},
				{
					Label: "Confirm",
					Action: func() error {
						ingr.Rules = []ingress.Rule{rule}
						if err := ValidateIngress(ingr); err != nil {
							activekit.Attention(err.Error())
							return nil
						}
						exit = true
						return nil
					},
				},
			},
		}).Run()
		if err != nil {
			return ingr, err
		}
	}
	return ingr, nil
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2014 Ashley Jeffs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// Package tunny implements a simple pool for maintaining independant worker goroutines.
package tunny

import (
	"reflect"
	"errors"
	"time"
	"sync"
)

type TunnyWorker interface {
	Job(interface{}) (interface{})
	Ready() bool
}

type TunnyExtendedWorker interface {
	Initialize()
	Terminate()
}

type TunnyInteruptable interface {
	TunnyInterupt()
}

// Default implementation of worker

type tunnyDefaultWorker struct {
	job *func(interface{}) (interface{})
}

func (worker *tunnyDefaultWorker) Job(data interface{}) interface{} {
	return (*worker.job)(data)
}

func (worker *tunnyDefaultWorker) Ready() bool {
	return true
}

/*
WorkPool allows you to contain and send work to your worker pool.
You must first indicate that the pool should run by calling Open(), then send work to the workers
through SendWork.
*/
type WorkPool struct {
	workers []*workerWrapper
	selects []reflect.SelectCase
	mutex   sync.RWMutex
	running bool
}

/*
SendWorkTimed - Send a job to a worker and return the result, this is a blocking call with a timeout.
SendWorkTimed - Args:    milliTimeout time.Duration, jobData interface{}
SendWorkTimed - Summary: the timeout period in milliseconds, the input data for the worker to process
*/
func (pool *WorkPool) SendWorkTimed(milliTimeout time.Duration, jobData interface{}) (interface{}, error) {
	pool.mutex.RLock()
	defer pool.mutex.RUnlock()

	if pool.running {
		before := time.Now()

		// Create new selectcase[] and add time out case
		selectCases := append(pool.selects[:], reflect.SelectCase {
			Dir: reflect.SelectRecv,
			Chan: reflect.ValueOf(time.After(milliTimeout * time.Millisecond)),
		})

		// Wait for workers, or time out
		if chosen, _, ok := reflect.Select(selectCases); ok {
			if chosen < ( len(selectCases) - 1 ) {
				pool.workers[chosen].jobChan <- jobData

				// Wait for response, or time out
				select {
				case data, open := <-pool.workers[chosen].outputChan:
					if !open {
						return nil, errors.New("Worker was closed before reaching a result")
					}
					return data, nil
				case <- time.After((milliTimeout * time.Millisecond) - time.Since(before)):
					/* If we time out here we also need to ensure that the output is still
					 * collected and that the worker can move on. Therefore, we fork the
					 * waiting process into a new goroutine.
					 */
					go func() {
						pool.workers[chosen].Interupt()
						<-pool.workers[chosen].outputChan
					}()
					return nil, errors.New("Request timed out whilst waiting for job to complete")
				}
			} else {
				return nil, errors.New("Request timed out whilst waiting for a worker")
			}
		} else {
			return nil, errors.New("Failed to find a worker")
		}
	} else {
		return nil, errors.New("Pool is not running! Call Open() before sending work")
	}
}

/*
SendWorkTimedAsync - Send a timed job to a worker without blocking, and send the result to a receiving closure.
SendWorkTimedAsync - Args:    milliTimeout time.Duration, jobData interface{}, after func(interface{}, error)
SendWorkTimedAsync - Summary: the timeout period in milliseconds
SendWorkTimedAsync - Summary: the input data for the worker to process
SendWorkTimedAsync - Summary: the closure to hand the result to
*/
func (pool *WorkPool) SendWorkTimedAsync(
	milliTimeout time.Duration,
	jobData interface{},
	after func(interface{}, error),
) {
	go func() {
		result, err := pool.SendWorkTimed(milliTimeout, jobData)
		if after != nil {
			after(result, err)
		}
	}()
}

/*
SendWork - Send a job to a worker and return the result, this is a blocking call.
SendWork - Args:    jobData interface{}
SendWork - Summary: the input data for the worker to process
*/
func (pool *WorkPool) SendWork(jobData interface{}) (interface{}, error) {
	pool.mutex.RLock()
	defer pool.mutex.RUnlock()

	if pool.running {

		if chosen, _, ok := reflect.Select(pool.selects); ok && chosen >= 0 {
			pool.workers[chosen].jobChan <- jobData
			result, open := <-pool.workers[chosen].outputChan

			if !open {
				return nil, errors.New("Worker was closed before reaching a result")
			}
			return result, nil
		}

		return nil, errors.New("Failed to find or wait for a worker")

	} else {
		return nil, errors.New("Pool is not running! Call Open() before sending work")
	}
}

/*
SendWorkAsync - Send a job to a worker without blocking, and send the result to a receiving closure.
SendWorkAsync - Args:    jobData interface{}, after func(interface{}, error)
SendWorkAsync - Summary: the input data for the worker to process, the closure to hand the result to
*/
func (pool *WorkPool) SendWorkAsync(jobData interface{}, after func(interface{}, error)) {
	go func() {
		result, err := pool.SendWork(jobData)
		if after != nil {
			after(result, err)
		}
	}()
}

/*
Open - Open all channels and launch the background goroutines managed by the pool.
*/
func (pool *WorkPool) Open() (*WorkPool, error) {
	pool.mutex.Lock()
	defer pool.mutex.Unlock()

	if !pool.running {

		pool.selects = make( []reflect.SelectCase, len(pool.workers) )

		for i, workerWrapper := range pool.workers {
			workerWrapper.Open()

			pool.selects[i] = reflect.SelectCase {
				Dir: reflect.SelectRecv,
				Chan: reflect.ValueOf(workerWrapper.readyChan),
			}
		}

		pool.running = true
		return pool, nil

	} else {
		return nil, errors.New("Pool is already running!")
	}
}

/*
Close - Close all channels and goroutines managed by the pool.
*/
func (pool *WorkPool) Close() error {
	pool.mutex.Lock()
	defer pool.mutex.Unlock()

	if pool.running {

		for _, workerWrapper := range pool.workers {
			workerWrapper.Close()
		}

		for _, workerWrapper := range pool.workers {
			workerWrapper.Join()
		}

		pool.running = false
		return nil

	} else {
		return errors.New("Cannot close when the pool is not running!")
	}
}

/*
CreatePool - Creates a pool of workers.
CreatePool - Args:    numWorkers int,    job func(interface{}) (interface{})
CreatePool - Summary: number of workers, the closure to run for each job
*/
func CreatePool(numWorkers int, job func(interface{}) interface{}) *WorkPool {
	pool := WorkPool { running: false }

	pool.workers = make ([]*workerWrapper, numWorkers)
	for i, _ := range pool.workers {
		newWorker := workerWrapper {
			worker: &(tunnyDefaultWorker { &job }),
		}
		pool.workers[i] = &newWorker
	}

	return &pool
}

/*
CreatePoolGeneric - Creates a pool of generic workers, they take a func as their only argument and execute it.
CreatePoolGeneric - Args:    numWorkers int
CreatePoolGeneric - Summary: number of workers
*/
func CreatePoolGeneric(numWorkers int) *WorkPool {

	return CreatePool(numWorkers, func (jobCall interface{}) interface{} {
		if method, ok := jobCall.(func()); ok {
			method()
			return nil
		}
		return errors.New("Generic worker not given a func()")
	})

}

/*
CreateCustomPool - Creates a pool for an array of custom workers.
CreateCustomPool - Args:    customWorkers []TunnyWorker
CreateCustomPool - Summary: An array of workers to use in the pool, each worker gets its own goroutine
*/
func CreateCustomPool(customWorkers []TunnyWorker) *WorkPool {
	pool := WorkPool { running: false }

	pool.workers = make ([]*workerWrapper, len(customWorkers))
	for i, _ := range pool.workers {
		newWorker := workerWrapper {
			worker: customWorkers[i],
		}
		pool.workers[i] = &newWorker
	}

	return &pool
}
<commit_msg>Improved comments for godoc<commit_after>/*
Copyright (c) 2014 Ashley Jeffs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// Package tunny implements a simple pool for maintaining independant worker goroutines.
package tunny

import (
	"reflect"
	"errors"
	"time"
	"sync"
)

/*
The basic interface of a tunny worker.
*/
type TunnyWorker interface {

	// Called for each job, expects the result to be returned synchronously
	Job(interface{}) (interface{})

	// Called after each job, this indicates whether the worker is ready for the next job.
	// The default implementation is to return true always. If false is returned then the
	// method is called every five milliseconds until either true is returned or the pool
	// is closed.
	Ready() bool

}

/*
An optional interface that can be implemented if the worker needs more control over its state.
*/
type TunnyExtendedWorker interface {

	// Called when the pool is opened, this will be called before any jobs are sent.
	Initialize()

	// Called when the pool is closed, this will be called after all jobs are completed.
	Terminate()

}

/*
An optional interface that can be implemented in order to allow the worker to drop jobs when
they are abandoned.
*/
type TunnyInteruptable interface {

	// Called when the current job has been abandoned by the client.
	TunnyInterupt()

}

/*
Default and very basic implementation of a tunny worker. This worker holds a closure which
is assigned at construction, and this closure is called on each job.
*/
type tunnyDefaultWorker struct {
	job *func(interface{}) (interface{})
}

func (worker *tunnyDefaultWorker) Job(data interface{}) interface{} {
	return (*worker.job)(data)
}

func (worker *tunnyDefaultWorker) Ready() bool {
	return true
}

/*
WorkPool contains the structures and methods required to communicate with your pool, it must
be opened before sending work and closed when all jobs are completed.

You may open and close a pool as many times as you wish, calling close is a blocking call that
guarantees all goroutines are stopped.
*/
type WorkPool struct {
	workers []*workerWrapper
	selects []reflect.SelectCase
	mutex   sync.RWMutex
	running bool
}

/*
Send a job to a worker and return the result, this is a synchronous call with a timeout.
*/
func (pool *WorkPool) SendWorkTimed(milliTimeout time.Duration, jobData interface{}) (interface{}, error) {
	pool.mutex.RLock()
	defer pool.mutex.RUnlock()

	if pool.running {
		before := time.Now()

		// Create new selectcase[] and add time out case
		selectCases := append(pool.selects[:], reflect.SelectCase {
			Dir: reflect.SelectRecv,
			Chan: reflect.ValueOf(time.After(milliTimeout * time.Millisecond)),
		})

		// Wait for workers, or time out
		if chosen, _, ok := reflect.Select(selectCases); ok {
			if chosen < ( len(selectCases) - 1 ) {
				pool.workers[chosen].jobChan <- jobData

				// Wait for response, or time out
				select {
				case data, open := <-pool.workers[chosen].outputChan:
					if !open {
						return nil, errors.New("Worker was closed before reaching a result")
					}
					return data, nil
				case <- time.After((milliTimeout * time.Millisecond) - time.Since(before)):
					/* If we time out here we also need to ensure that the output is still
					 * collected and that the worker can move on. Therefore, we fork the
					 * waiting process into a new goroutine.
					 */
					go func() {
						pool.workers[chosen].Interupt()
						<-pool.workers[chosen].outputChan
					}()
					return nil, errors.New("Request timed out whilst waiting for job to complete")
				}
			} else {
				return nil, errors.New("Request timed out whilst waiting for a worker")
			}
		} else {
			return nil, errors.New("Failed to find a worker")
		}
	} else {
		return nil, errors.New("Pool is not running! Call Open() before sending work")
	}
}

/*
Send a timed job to a worker without blocking, and optionally send the result to a
receiving closure. You may set the closure to nil if no further actions are required.
*/
func (pool *WorkPool) SendWorkTimedAsync(
	milliTimeout time.Duration,
	jobData interface{},
	after func(interface{}, error),
) {
	go func() {
		result, err := pool.SendWorkTimed(milliTimeout, jobData)
		if after != nil {
			after(result, err)
		}
	}()
}

/*
Send a job to a worker and return the result, this is a synchronous call.
*/
func (pool *WorkPool) SendWork(jobData interface{}) (interface{}, error) {
	pool.mutex.RLock()
	defer pool.mutex.RUnlock()

	if pool.running {

		if chosen, _, ok := reflect.Select(pool.selects); ok && chosen >= 0 {
			pool.workers[chosen].jobChan <- jobData
			result, open := <-pool.workers[chosen].outputChan

			if !open {
				return nil, errors.New("Worker was closed before reaching a result")
			}
			return result, nil
		}

		return nil, errors.New("Failed to find or wait for a worker")

	} else {
		return nil, errors.New("Pool is not running! Call Open() before sending work")
	}
}

/*
Send a job to a worker without blocking, and optionally send the result to a receiving
closure. You may set the closure to nil if no further actions are required.
*/
func (pool *WorkPool) SendWorkAsync(jobData interface{}, after func(interface{}, error)) {
	go func() {
		result, err := pool.SendWork(jobData)
		if after != nil {
			after(result, err)
		}
	}()
}

/*
Open all channels and launch the background goroutines managed by the pool.
*/
func (pool *WorkPool) Open() (*WorkPool, error) {
	pool.mutex.Lock()
	defer pool.mutex.Unlock()

	if !pool.running {

		pool.selects = make( []reflect.SelectCase, len(pool.workers) )

		for i, workerWrapper := range pool.workers {
			workerWrapper.Open()

			pool.selects[i] = reflect.SelectCase {
				Dir: reflect.SelectRecv,
				Chan: reflect.ValueOf(workerWrapper.readyChan),
			}
		}

		pool.running = true
		return pool, nil

	} else {
		return nil, errors.New("Pool is already running!")
	}
}

/*
Close all channels and goroutines managed by the pool.
*/
func (pool *WorkPool) Close() error {
	pool.mutex.Lock()
	defer pool.mutex.Unlock()

	if pool.running {

		for _, workerWrapper := range pool.workers {
			workerWrapper.Close()
		}

		for _, workerWrapper := range pool.workers {
			workerWrapper.Join()
		}

		pool.running = false
		return nil

	} else {
		return errors.New("Cannot close when the pool is not running!")
	}
}

/*
Creates a pool of workers, and takes a closure argument which is the action to perform
for each job.
*/
func CreatePool(numWorkers int, job func(interface{}) interface{}) *WorkPool {
	pool := WorkPool { running: false }

	pool.workers = make ([]*workerWrapper, numWorkers)
	for i, _ := range pool.workers {
		newWorker := workerWrapper {
			worker: &(tunnyDefaultWorker { &job }),
		}
		pool.workers[i] = &newWorker
	}

	return &pool
}

/*
Creates a pool of generic workers. When sending work to a pool of generic workers you
send a closure (func()) which is the job to perform.
*/
func CreatePoolGeneric(numWorkers int) *WorkPool {

	return CreatePool(numWorkers, func (jobCall interface{}) interface{} {
		if method, ok := jobCall.(func()); ok {
			method()
			return nil
		}
		return errors.New("Generic worker not given a func()")
	})

}

/*
Creates a pool for an array of custom workers. The custom workers must implement TunnyWorker,
and may also optionally implement TunnyExtendedWorker and TunnyInteruptable.
*/
func CreateCustomPool(customWorkers []TunnyWorker) *WorkPool {
	pool := WorkPool { running: false }

	pool.workers = make ([]*workerWrapper, len(customWorkers))
	for i, _ := range pool.workers {
		newWorker := workerWrapper {
			worker: customWorkers[i],
		}
		pool.workers[i] = &newWorker
	}

	return &pool
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//
// Note:
// 1. It needs manually import: _ "github.com/mattn/go-sqlite3"
// 2. It does not support Save/Replace features.

package gdb

import (
	"database/sql"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/text/gstr"
)

// DriverSqlite is the driver for sqlite database.
type DriverSqlite struct {
	*Core
}

// New creates and returns a database object for sqlite.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
	return &DriverSqlite{
		Core: core,
	}, nil
}

// Open creates and returns a underlying sql.DB object for sqlite.
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
	var source string
	if config.LinkInfo != "" {
		source = config.LinkInfo
	} else {
		source = config.Name
	}
	intlog.Printf("Open: %s", source)
	if db, err := sql.Open("sqlite3", source); err == nil {
		return db, nil
	} else {
		return nil, err
	}
}

// GetChars returns the security char for this type of database.
func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
	return "`", "`"
}

// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
// @todo 需要增加对Save方法的支持，可使用正则来实现替换，
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
func (d *DriverSqlite) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
	return sql, args
}

// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
// TODO
func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
	return
}

// TableFields retrieves and returns the fields information of specified table of current schema.
// TODO
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
	table = gstr.Trim(table)
	if gstr.Contains(table, " ") {
		panic("function TableFields supports only single table operations")
	}
	return
}
<commit_msg>add Tables and TableFields method<commit_after>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//
// Note:
// 1. It needs manually import: _ "github.com/mattn/go-sqlite3"
// 2. It does not support Save/Replace features.

package gdb

import (
	"database/sql"
	"fmt"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/text/gstr"
	"strings"
)

// DriverSqlite is the driver for sqlite database.
type DriverSqlite struct {
	*Core
}

// New creates and returns a database object for sqlite.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
	return &DriverSqlite{
		Core: core,
	}, nil
}

// Open creates and returns a underlying sql.DB object for sqlite.
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
	var source string
	if config.LinkInfo != "" {
		source = config.LinkInfo
	} else {
		source = config.Name
	}
	intlog.Printf("Open: %s", source)
	if db, err := sql.Open("sqlite3", source); err == nil {
		return db, nil
	} else {
		return nil, err
	}
}

// GetChars returns the security char for this type of database.
func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
	return "`", "`"
}

// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
// @todo 需要增加对Save方法的支持，可使用正则来实现替换，
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
func (d *DriverSqlite) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
	return sql, args
}

// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
	var result Result

	result, err = d.DB.DoGetAll(nil, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
	if err != nil {
		return
	}
	for _, m := range result {
		for _, v := range m {
			tables = append(tables, v.String())
		}
	}
	return
}

// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
	table = gstr.Trim(table)
	if gstr.Contains(table, " ") {
		panic("function TableFields supports only single table operations")
	}

	checkSchema := d.DB.GetSchema()
	if len(schema) > 0 && schema[0] != "" {
		checkSchema = schema[0]
	}
	v := d.DB.GetCache().GetOrSetFunc(
		fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
			var result Result

			result, err = d.DB.DoGetAll(nil, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
			if err != nil {
				return nil
			}
			fields = make(map[string]*TableField)
			for i, m := range result {
				fields[strings.ToLower(m["name"].String())] = &TableField{
					Index: i,
					Name:  strings.ToLower(m["name"].String()),
					Type:  strings.ToLower(m["type"].String()),
				}
			}
			return fields
		}, 0)
	if err == nil {
		fields = v.(map[string]*TableField)
	}
	return
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package admitters

import (
	"fmt"
	"net/http"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	admissionv1 "k8s.io/api/admission/v1"
	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/testing"

	virtv1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/kubevirt/pkg/testutils"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

var _ = Describe("Pod eviction admitter", func() {

	testns := "kubevirt-test-ns"
	var ctrl *gomock.Controller

	var kubeClient *fake.Clientset
	var virtClient *kubecli.MockKubevirtClient
	var vmiClient *kubecli.MockVirtualMachineInstanceInterface

	newClusterConfigWithFeatureGate := func(featureGate string) *virtconfig.ClusterConfig {
		kv := kubecli.NewMinimalKubeVirt(testns)
		kv.Namespace = "kubevirt"
		if kv.Spec.Configuration.DeveloperConfiguration == nil {
			kv.Spec.Configuration.DeveloperConfiguration = &virtv1.DeveloperConfiguration{}
		}
		kv.Spec.Configuration.DeveloperConfiguration.FeatureGates = []string{featureGate}
		clusterConfig, _, _ := testutils.NewFakeClusterConfigUsingKV(kv)

		return clusterConfig
	}

	BeforeEach(func() {
		ctrl = gomock.NewController(GinkgoT())
		virtClient = kubecli.NewMockKubevirtClient(ctrl)
		vmiClient = kubecli.NewMockVirtualMachineInstanceInterface(ctrl)
		kubeClient = fake.NewSimpleClientset()
		virtClient.EXPECT().CoreV1().Return(kubeClient.CoreV1()).AnyTimes()
		virtClient.EXPECT().VirtualMachineInstance(testns).Return(vmiClient).AnyTimes()

		// Make sure that any unexpected call to the client will fail
		kubeClient.Fake.PrependReactor("*", "*", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
			Expect(action).To(BeNil())
			return true, nil, nil
		})
	})

	Context("Live migration enabled", func() {

		var podEvictionAdmitter PodEvictionAdmitter

		BeforeEach(func() {
			clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)
			podEvictionAdmitter = PodEvictionAdmitter{
				ClusterConfig: clusterConfig,
				VirtClient:    virtClient,
			}
		})

		Context("Migratable and evictable VMI", func() {

			var vmi *virtv1.VirtualMachineInstance
			liveMigrateStrategy := virtv1.EvictionStrategyLiveMigrate

			BeforeEach(func() {
				vmi = &virtv1.VirtualMachineInstance{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: testns,
						Name:      "testvmi",
					},
					Status: virtv1.VirtualMachineInstanceStatus{
						Conditions: []virtv1.VirtualMachineInstanceCondition{
							{
								Type:   virtv1.VirtualMachineInstanceIsMigratable,
								Status: k8sv1.ConditionTrue,
							},
						},
					},
					Spec: virtv1.VirtualMachineInstanceSpec{
						EvictionStrategy: &liveMigrateStrategy,
					},
				}
			})

			It("Should deny review requests when updating the VMI fails", func() {

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec:   k8sv1.PodSpec{},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				vmiClient.EXPECT().Update(gomock.Any()).Return(nil, fmt.Errorf("err"))

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeFalse())
				Expect(resp.Result.Code).To(Equal(int32(http.StatusTooManyRequests)))
				Expect(kubeClient.Fake.Actions()).To(HaveLen(1))
			})

			It("Should allow review requests when eviction strategy is not configured", func() {

				By("Removing eviction strategy from the VMI")
				vmi.Spec.EvictionStrategy = nil

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec:   k8sv1.PodSpec{},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				vmiClient.EXPECT().Update(gomock.Any()).Return(nil, fmt.Errorf("err"))

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
				Expect(kubeClient.Fake.Actions()).To(HaveLen(1))
			})

			table.DescribeTable("Should allow  review requests that are on a virt-launcher pod", func(dryRun bool) {
				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec:   k8sv1.PodSpec{},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
						DryRun:    &dryRun,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				if !dryRun {
					vmiClient.EXPECT().Update(gomock.Any()).Return(nil, nil)
				}
				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
				actions := kubeClient.Fake.Actions()
				Expect(actions).To(HaveLen(1))
			},
				table.Entry("and should mark the VMI when not in dry-run mode", false),
				table.Entry("and should not mark the VMI when in dry-run mode", true),
			)

		})

		Context("Not a virt launcher pod", func() {

			It("Should allow any review requests", func() {

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: testns,
						Name:      "foo",
					},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
			})
		})

	})

	Context("Live migration disabled", func() {
		kv := kubecli.NewMinimalKubeVirt(testns)
		clusterConfig, _, _ := testutils.NewFakeClusterConfigUsingKV(kv)
		podEvictionAdmitter := PodEvictionAdmitter{
			ClusterConfig: clusterConfig,
		}

		It("Should allow any review request", func() {
			resp := podEvictionAdmitter.Admit(&admissionv1.AdmissionReview{})
			Expect(resp.Allowed).To(BeTrue())
		})

	})

})
<commit_msg>virt-api: adjust unit tests for the pod evitction patch method<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package admitters

import (
	"fmt"
	"net/http"

	"k8s.io/apimachinery/pkg/types"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	admissionv1 "k8s.io/api/admission/v1"
	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/testing"

	virtv1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/kubevirt/pkg/testutils"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

var _ = Describe("Pod eviction admitter", func() {

	testns := "kubevirt-test-ns"
	var ctrl *gomock.Controller

	var kubeClient *fake.Clientset
	var virtClient *kubecli.MockKubevirtClient
	var vmiClient *kubecli.MockVirtualMachineInstanceInterface

	newClusterConfigWithFeatureGate := func(featureGate string) *virtconfig.ClusterConfig {
		kv := kubecli.NewMinimalKubeVirt(testns)
		kv.Namespace = "kubevirt"
		if kv.Spec.Configuration.DeveloperConfiguration == nil {
			kv.Spec.Configuration.DeveloperConfiguration = &virtv1.DeveloperConfiguration{}
		}
		kv.Spec.Configuration.DeveloperConfiguration.FeatureGates = []string{featureGate}
		clusterConfig, _, _ := testutils.NewFakeClusterConfigUsingKV(kv)

		return clusterConfig
	}

	BeforeEach(func() {
		ctrl = gomock.NewController(GinkgoT())
		virtClient = kubecli.NewMockKubevirtClient(ctrl)
		vmiClient = kubecli.NewMockVirtualMachineInstanceInterface(ctrl)
		kubeClient = fake.NewSimpleClientset()
		virtClient.EXPECT().CoreV1().Return(kubeClient.CoreV1()).AnyTimes()
		virtClient.EXPECT().VirtualMachineInstance(testns).Return(vmiClient).AnyTimes()

		// Make sure that any unexpected call to the client will fail
		kubeClient.Fake.PrependReactor("*", "*", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
			Expect(action).To(BeNil())
			return true, nil, nil
		})
	})

	Context("Live migration enabled", func() {

		var podEvictionAdmitter PodEvictionAdmitter

		BeforeEach(func() {
			clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)
			podEvictionAdmitter = PodEvictionAdmitter{
				ClusterConfig: clusterConfig,
				VirtClient:    virtClient,
			}
		})

		Context("Migratable and evictable VMI", func() {

			var vmi *virtv1.VirtualMachineInstance
			liveMigrateStrategy := virtv1.EvictionStrategyLiveMigrate
			nodeName := "node01"

			BeforeEach(func() {
				vmi = &virtv1.VirtualMachineInstance{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: testns,
						Name:      "testvmi",
					},
					Status: virtv1.VirtualMachineInstanceStatus{
						Conditions: []virtv1.VirtualMachineInstanceCondition{
							{
								Type:   virtv1.VirtualMachineInstanceIsMigratable,
								Status: k8sv1.ConditionTrue,
							},
						},
						NodeName: nodeName,
					},
					Spec: virtv1.VirtualMachineInstanceSpec{
						EvictionStrategy: &liveMigrateStrategy,
					},
				}
			})

			It("Should deny review requests when updating the VMI fails", func() {

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec: k8sv1.PodSpec{
						NodeName: nodeName,
					},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				data := fmt.Sprintf(`[{ "op": "add", "path": "/status/evacuationNodeName", "value": "%s" }]`, nodeName)
				vmiClient.
					EXPECT().
					Patch(vmi.Name,
						types.JSONPatchType,
						[]byte(data),
						&metav1.PatchOptions{}).
					Return(nil, fmt.Errorf("err"))

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeFalse())
				Expect(resp.Result.Code).To(Equal(int32(http.StatusTooManyRequests)))
				Expect(kubeClient.Fake.Actions()).To(HaveLen(1))
			})

			It("Should allow review requests when eviction strategy is not configured", func() {

				By("Removing eviction strategy from the VMI")
				vmi.Spec.EvictionStrategy = nil

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec: k8sv1.PodSpec{
						NodeName: nodeName,
					},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				data := fmt.Sprintf(`[{ "op": "add", "path": "/status/evacuationNodeName", "value": "%s" }]`, nodeName)
				vmiClient.
					EXPECT().
					Patch(vmi.Name,
						types.JSONPatchType,
						[]byte(data),
						&metav1.PatchOptions{}).
					Return(nil, fmt.Errorf("err"))

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
				Expect(kubeClient.Fake.Actions()).To(HaveLen(1))
			})

			table.DescribeTable("Should allow  review requests that are on a virt-launcher pod", func(dryRun bool) {
				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "testpod",
						Namespace: testns,
						Annotations: map[string]string{
							virtv1.DomainAnnotation: vmi.Name,
						},
						Labels: map[string]string{
							virtv1.AppLabel: "virt-launcher",
						},
					},
					Spec: k8sv1.PodSpec{
						NodeName: nodeName,
					},
					Status: k8sv1.PodStatus{},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
						DryRun:    &dryRun,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				clusterConfig := newClusterConfigWithFeatureGate(virtconfig.LiveMigrationGate)

				if !dryRun {
					data := fmt.Sprintf(`[{ "op": "add", "path": "/status/evacuationNodeName", "value": "%s" }]`, nodeName)
					vmiClient.
						EXPECT().
						Patch(vmi.Name,
							types.JSONPatchType,
							[]byte(data),
							&metav1.PatchOptions{}).
						Return(nil, nil)
				}
				vmiClient.EXPECT().Get(vmi.Name, &metav1.GetOptions{}).Return(vmi, nil)

				podEvictionAdmitter := PodEvictionAdmitter{
					ClusterConfig: clusterConfig,
					VirtClient:    virtClient,
				}
				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
				actions := kubeClient.Fake.Actions()
				Expect(actions).To(HaveLen(1))
			},
				table.Entry("and should mark the VMI when not in dry-run mode", false),
				table.Entry("and should not mark the VMI when in dry-run mode", true),
			)

		})

		Context("Not a virt launcher pod", func() {

			It("Should allow any review requests", func() {

				By("Composing a dummy admission request on a virt-launcher pod")
				pod := &k8sv1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: testns,
						Name:      "foo",
					},
				}

				ar := &admissionv1.AdmissionReview{
					Request: &admissionv1.AdmissionRequest{
						Name:      pod.Name,
						Namespace: pod.Namespace,
					},
				}

				kubeClient.Fake.PrependReactor("get", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
					get, ok := action.(testing.GetAction)
					Expect(ok).To(BeTrue())
					Expect(pod.Namespace).To(Equal(get.GetNamespace()))
					Expect(pod.Name).To(Equal(get.GetName()))
					return true, pod, nil
				})

				resp := podEvictionAdmitter.Admit(ar)
				Expect(resp.Allowed).To(BeTrue())
			})
		})

	})

	Context("Live migration disabled", func() {
		kv := kubecli.NewMinimalKubeVirt(testns)
		clusterConfig, _, _ := testutils.NewFakeClusterConfigUsingKV(kv)
		podEvictionAdmitter := PodEvictionAdmitter{
			ClusterConfig: clusterConfig,
		}

		It("Should allow any review request", func() {
			resp := podEvictionAdmitter.Admit(&admissionv1.AdmissionReview{})
			Expect(resp.Allowed).To(BeTrue())
		})

	})

})
<|endoftext|>


<commit_before>// types.go

// This file contains the various types used by the API

package atlas

// APIError is for errors returned by the RIPE API.
type APIError struct {
	Error struct {
		Status int    `json:"status"`
		Code   int    `json:"code"`
		Detail string `json:"detail"`
		Title  string `json:"title"`
	} `json:"error"`
}

// Key is holding the API key parameters
type Key struct {
	UUID      string `json:"uuid"`
	ValidFrom string `json:"valid_from"`
	ValidTo   string `json:"valid_to"`
	Enabled   bool
	IsActive  bool    `json:"is_active"`
	CreatedAt string  `json:"created_at"`
	Label     string  `json:"label"`
	Grants    []Grant `json:"grants"`
	Type      string  `json:"type"`
}

// Grant is the permission(s) associated with a key
type Grant struct {
	Permission string `json:"permission"`
	Target     struct {
		Type string `json:"type"`
		ID   string `json:"id"`
	} `json:"target"`
}

// Probe is holding probe's data
type Probe struct {
	AddressV4      string `json:"address_v4"`
	AddressV6      string `json:"address_v6"`
	AsnV4          int    `json:"asn_v4"`
	AsnV6          int    `json:"asn_v6"`
	CountryCode    string `json:"country_code"`
	Description    string `json:"description"`
	FirstConnected int    `json:"first_connected"`
	Geometry       struct {
		Type        string    `json:"type"`
		Coordinates []float64 `json:"coordinates"`
	} `json:"geometry"`
	ID            int    `json:"id"`
	IsAnchor      bool   `json:"is_anchor"`
	IsPublic      bool   `json:"is_public"`
	LastConnected int    `json:"last_connected"`
	PrefixV4      string `json:"prefix_v4"`
	PrefixV6      string `json:"prefix_v6"`
	Status        struct {
		Since string `json:"since"`
		ID    int    `json:"id"`
		Name  string `json:"name"`
	} `json:"status"`
	StatusSince int `json:"status_since"`
	Tags        []struct {
		Name string `json:"name"`
		Slug string `json:"slug"`
	} `json:"tags"`
	Type string `json:"type"`
}

// Measurement is what we are working with
type Measurement struct {
	Af                    int                    `json:"af"`
	CreationTime          int                    `json:"creation_time"`
	Description           string                 `json:"description"`
	DestinationOptionSize interface{}            `json:"destination_option_size"`
	DontFragment          interface{}            `json:"dont_fragment"`
	DuplicateTimeout      interface{}            `json:"duplicate_timeout"`
	FirstHop              int                    `json:"first_hop"`
	Group                 string                 `json:"group"`
	GroupID               int                    `json:"group_id"`
	HopByHopOptionSize    interface{}            `json:"hop_by_hop_option_size"`
	ID                    int                    `json:"id"`
	InWifiGroup           bool                   `json:"in_wifi_group"`
	Interval              int                    `json:"interval"`
	IsAllScheduled        bool                   `json:"is_all_scheduled"`
	IsOneoff              bool                   `json:"is_oneoff"`
	IsPublic              bool                   `json:"is_public"`
	MaxHops               int                    `json:"max_hops"`
	PacketInterval        interface{}            `json:"packet_interval"`
	Packets               int                    `json:"packets"`
	Paris                 int                    `json:"paris"`
	ParticipantCount      int                    `json:"participant_count"`
	ParticipationRequests []ParticipationRequest `json:"participation_requests"`
	Port                  interface{}            `json:"port"`
	ProbesRequested       int                    `json:"probes_requested"`
	ProbesScheduled       int                    `json:"probes_scheduled"`
	Protocol              string                 `json:"protocol"`
	ResolveOnProbe        bool                   `json:"resolve_on_probe"`
	ResolvedIPs           []string               `json:"resolved_ips"`
	ResponseTimeout       int                    `json:"response_timeout"`
	Result                string                 `json:"result"`
	Size                  int                    `json:"size"`
	Spread                interface{}            `json:"spread"`
	StartTime             int                    `json:"start_time"`
	Status                struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"status"`
	StopTime  int    `json:"stop_time"`
	Target    string `json:"target"`
	TargetASN int    `json:"target_asn"`
	TargetIP  string `json:"target_ip"`
	Type      string `json:"type"`
}

// ParticipationRequest allow you to add or remove probes from a measurement that
// was already created
type ParticipationRequest struct {
	Action        string `json:"action"`
	CreatedAt     int    `json:"created_at"`
	ID            int    `json:"id"`
	Self          string `json:"self"`
	Measurement   string `json:"measurement"`
	MeasurementID int    `json:"measurement_id"`
	Requested     int    `json:"requested"`
	Type          string `json:"type"`
	Value         string `json:"value"`
	Logs          string `json:"logs"`
}

var (
	// ProbeTypes should be obvious
	ProbeTypes = []string{"area", "country", "prefix", "asn", "probes", "msm"}
	// AreaTypes should also be obvious
	AreaTypes = []string{"WW", "West", "North-Central", "South-Central", "North-East", "South-East"}
)

// MeasurementRequest contains the different measurement to create/view
type MeasurementRequest struct {
	// see below for definition
	Definitions []Definition `json:"definitions"`

	// requested set of probes
	Probes ProbeSet `json:"probes"`
	//
	BillTo       int  `json:"bill_to,omitempty"`
	IsOneoff     bool `json:"is_oneoff,omitempty"`
	SkipDNSCheck bool `json:"skip_dns_check,omitempty"`
	Times        int  `json:"times,omitempty"`
	StartTime    int  `json:"start_time,omitempty"`
	StopTime     int  `json:"stop_time,omitempty"`
}

// ProbeSet is a set of probes obviously
type ProbeSet []struct {
	Requested int               `json:"requested"` // number of probes
	Type      string            `json:"type"`      // area, country, prefix, asn, probes, msm
	Value     string            `json:"value"`     // can be numeric or string
	Tags      map[string]string `json:"tags,omitempty"`
}

// Definition is used to create measurements
type Definition struct {
	// Required fields
	Description string `json:"description"`
	Type        string `json:"type"`
	AF          int    `json:"af"`

	// Required for all but "dns"
	Target string `json:"target,omitempty"`

	GroupID        int    `json:"group_id,omitempty"`
	Group          string `json:"group,omitempty"`
	InWifiGroup    bool   `json:"in_wifi_group,omitempty"`
	Spread         int    `json:"spread,omitempty"`
	Packets        int    `json:"packets,omitempty"`
	PacketInterval int    `json:"packet_interval,omitempty"`

	// Common parameters
	ExtraWait      int  `json:"extra_wait,omitempty"`
	IsOneoff       bool `json:"is_oneoff,omitempty"`
	IsPublic       bool `json:"is_public,omitempty"`
	ResolveOnProbe bool `json:"resolve_on_probe,omitempty"`

	// Default depends on type
	Interval int `json:"interval,omitempty"`

	// dns & traceroute parameters
	Protocol string `json:"protocol,omitempty"`

	// dns parameters
	QueryClass       string `json:"query_class,omitempty"`
	QueryType        string `json:"query_type,omitempty"`
	QueryArgument    string `json:"query_argument,omitempty"`
	Retry            int    `json:"retry,omitempty"`
	SetCDBit         bool   `json:"set_cd_bit,omitempty"`
	SetDOBit         bool   `json:"set_do_bit,omitempty"`
	SetNSIDBit       bool   `json:"set_nsid_bit,omitempty"`
	SetRDBit         bool   `json:"set_rd_bit,omitempty"`
	UDPPayloadSize   int    `json:"udp_payload_size,omitempty"`
	UseProbeResolver bool   `json:"use_probe_resolver"`

	// ping parameters
	//   none (see target)

	// traceroute parameters
	DestinationOptionSize int  `json:"destination_option_size,omitempty"`
	DontFragment          bool `json:"dont_fragment,omitempty"`
	DuplicateTimeout      int  `json:"duplicate_timeout,omitempty"`
	FirstHop              int  `json:"first_hop,omitempty"`
	HopByHopOptionSize    int  `json:"hop_by_hop_option_size,omitempty"`
	MaxHops               int  `json:"max_hops,omitempty"`
	Paris                 int  `json:"paris,omitempty"`

	// ntp parameters
	//   none (see target)

	// http parameters
	ExtendedTiming     bool   `json:"extended_timing,omitempty"`
	HeaderBytes        int    `json:"header_bytes,omitempty"`
	Method             string `json:"method,omitempty"`
	MoreExtendedTiming bool   `json:"more_extended_timing,omitempty"`
	Path               string `json:"path,omitempty"`
	QueryOptions       string `json:"query_options,omitempty"`
	UserAgent          string `json:"user_agent,omitempty"`
	Version            string `json:"version,omitempty"`

	// sslcert parameters
	//   none (see target)

	// sslcert & traceroute & http parameters
	Port int `json:"port,omitempty"`

	// ping & traceroute parameters
	Size int `json:"size,omitempty"`

	// wifi parameters
	AnonymousIdentity string `json:"anonymous_identity,omitempty"`
	Cert              string `json:"cert,omitempty"`
	EAP               string `json:"eap,omitempty"`
}
<commit_msg>Add Config and Client types.<commit_after>// types.go

// This file contains the various types used by the API

package atlas

type Client struct {
	config       Config
}

type Config struct {
	APIKey       string
	DefaultProbe int
	PoolSize     int
	WantAF       string
	ProxyAuth    string
	Verbose      bool
}

// APIError is for errors returned by the RIPE API.
type APIError struct {
	Error struct {
		Status int    `json:"status"`
		Code   int    `json:"code"`
		Detail string `json:"detail"`
		Title  string `json:"title"`
	} `json:"error"`
}

// Key is holding the API key parameters
type Key struct {
	UUID      string `json:"uuid"`
	ValidFrom string `json:"valid_from"`
	ValidTo   string `json:"valid_to"`
	Enabled   bool
	IsActive  bool    `json:"is_active"`
	CreatedAt string  `json:"created_at"`
	Label     string  `json:"label"`
	Grants    []Grant `json:"grants"`
	Type      string  `json:"type"`
}

// Grant is the permission(s) associated with a key
type Grant struct {
	Permission string `json:"permission"`
	Target     struct {
		Type string `json:"type"`
		ID   string `json:"id"`
	} `json:"target"`
}

// Probe is holding probe's data
type Probe struct {
	AddressV4      string `json:"address_v4"`
	AddressV6      string `json:"address_v6"`
	AsnV4          int    `json:"asn_v4"`
	AsnV6          int    `json:"asn_v6"`
	CountryCode    string `json:"country_code"`
	Description    string `json:"description"`
	FirstConnected int    `json:"first_connected"`
	Geometry       struct {
		Type        string    `json:"type"`
		Coordinates []float64 `json:"coordinates"`
	} `json:"geometry"`
	ID            int    `json:"id"`
	IsAnchor      bool   `json:"is_anchor"`
	IsPublic      bool   `json:"is_public"`
	LastConnected int    `json:"last_connected"`
	PrefixV4      string `json:"prefix_v4"`
	PrefixV6      string `json:"prefix_v6"`
	Status        struct {
		Since string `json:"since"`
		ID    int    `json:"id"`
		Name  string `json:"name"`
	} `json:"status"`
	StatusSince int `json:"status_since"`
	Tags        []struct {
		Name string `json:"name"`
		Slug string `json:"slug"`
	} `json:"tags"`
	Type string `json:"type"`
}

// Measurement is what we are working with
type Measurement struct {
	Af                    int                    `json:"af"`
	CreationTime          int                    `json:"creation_time"`
	Description           string                 `json:"description"`
	DestinationOptionSize interface{}            `json:"destination_option_size"`
	DontFragment          interface{}            `json:"dont_fragment"`
	DuplicateTimeout      interface{}            `json:"duplicate_timeout"`
	FirstHop              int                    `json:"first_hop"`
	Group                 string                 `json:"group"`
	GroupID               int                    `json:"group_id"`
	HopByHopOptionSize    interface{}            `json:"hop_by_hop_option_size"`
	ID                    int                    `json:"id"`
	InWifiGroup           bool                   `json:"in_wifi_group"`
	Interval              int                    `json:"interval"`
	IsAllScheduled        bool                   `json:"is_all_scheduled"`
	IsOneoff              bool                   `json:"is_oneoff"`
	IsPublic              bool                   `json:"is_public"`
	MaxHops               int                    `json:"max_hops"`
	PacketInterval        interface{}            `json:"packet_interval"`
	Packets               int                    `json:"packets"`
	Paris                 int                    `json:"paris"`
	ParticipantCount      int                    `json:"participant_count"`
	ParticipationRequests []ParticipationRequest `json:"participation_requests"`
	Port                  interface{}            `json:"port"`
	ProbesRequested       int                    `json:"probes_requested"`
	ProbesScheduled       int                    `json:"probes_scheduled"`
	Protocol              string                 `json:"protocol"`
	ResolveOnProbe        bool                   `json:"resolve_on_probe"`
	ResolvedIPs           []string               `json:"resolved_ips"`
	ResponseTimeout       int                    `json:"response_timeout"`
	Result                string                 `json:"result"`
	Size                  int                    `json:"size"`
	Spread                interface{}            `json:"spread"`
	StartTime             int                    `json:"start_time"`
	Status                struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"status"`
	StopTime  int    `json:"stop_time"`
	Target    string `json:"target"`
	TargetASN int    `json:"target_asn"`
	TargetIP  string `json:"target_ip"`
	Type      string `json:"type"`
}

// ParticipationRequest allow you to add or remove probes from a measurement that
// was already created
type ParticipationRequest struct {
	Action        string `json:"action"`
	CreatedAt     int    `json:"created_at"`
	ID            int    `json:"id"`
	Self          string `json:"self"`
	Measurement   string `json:"measurement"`
	MeasurementID int    `json:"measurement_id"`
	Requested     int    `json:"requested"`
	Type          string `json:"type"`
	Value         string `json:"value"`
	Logs          string `json:"logs"`
}

var (
	// ProbeTypes should be obvious
	ProbeTypes = []string{"area", "country", "prefix", "asn", "probes", "msm"}
	// AreaTypes should also be obvious
	AreaTypes = []string{"WW", "West", "North-Central", "South-Central", "North-East", "South-East"}
)

// MeasurementRequest contains the different measurement to create/view
type MeasurementRequest struct {
	// see below for definition
	Definitions []Definition `json:"definitions"`

	// requested set of probes
	Probes ProbeSet `json:"probes"`
	//
	BillTo       int  `json:"bill_to,omitempty"`
	IsOneoff     bool `json:"is_oneoff,omitempty"`
	SkipDNSCheck bool `json:"skip_dns_check,omitempty"`
	Times        int  `json:"times,omitempty"`
	StartTime    int  `json:"start_time,omitempty"`
	StopTime     int  `json:"stop_time,omitempty"`
}

// ProbeSet is a set of probes obviously
type ProbeSet []struct {
	Requested int               `json:"requested"` // number of probes
	Type      string            `json:"type"`      // area, country, prefix, asn, probes, msm
	Value     string            `json:"value"`     // can be numeric or string
	Tags      map[string]string `json:"tags,omitempty"`
}

// Definition is used to create measurements
type Definition struct {
	// Required fields
	Description string `json:"description"`
	Type        string `json:"type"`
	AF          int    `json:"af"`

	// Required for all but "dns"
	Target string `json:"target,omitempty"`

	GroupID        int    `json:"group_id,omitempty"`
	Group          string `json:"group,omitempty"`
	InWifiGroup    bool   `json:"in_wifi_group,omitempty"`
	Spread         int    `json:"spread,omitempty"`
	Packets        int    `json:"packets,omitempty"`
	PacketInterval int    `json:"packet_interval,omitempty"`

	// Common parameters
	ExtraWait      int  `json:"extra_wait,omitempty"`
	IsOneoff       bool `json:"is_oneoff,omitempty"`
	IsPublic       bool `json:"is_public,omitempty"`
	ResolveOnProbe bool `json:"resolve_on_probe,omitempty"`

	// Default depends on type
	Interval int `json:"interval,omitempty"`

	// dns & traceroute parameters
	Protocol string `json:"protocol,omitempty"`

	// dns parameters
	QueryClass       string `json:"query_class,omitempty"`
	QueryType        string `json:"query_type,omitempty"`
	QueryArgument    string `json:"query_argument,omitempty"`
	Retry            int    `json:"retry,omitempty"`
	SetCDBit         bool   `json:"set_cd_bit,omitempty"`
	SetDOBit         bool   `json:"set_do_bit,omitempty"`
	SetNSIDBit       bool   `json:"set_nsid_bit,omitempty"`
	SetRDBit         bool   `json:"set_rd_bit,omitempty"`
	UDPPayloadSize   int    `json:"udp_payload_size,omitempty"`
	UseProbeResolver bool   `json:"use_probe_resolver"`

	// ping parameters
	//   none (see target)

	// traceroute parameters
	DestinationOptionSize int  `json:"destination_option_size,omitempty"`
	DontFragment          bool `json:"dont_fragment,omitempty"`
	DuplicateTimeout      int  `json:"duplicate_timeout,omitempty"`
	FirstHop              int  `json:"first_hop,omitempty"`
	HopByHopOptionSize    int  `json:"hop_by_hop_option_size,omitempty"`
	MaxHops               int  `json:"max_hops,omitempty"`
	Paris                 int  `json:"paris,omitempty"`

	// ntp parameters
	//   none (see target)

	// http parameters
	ExtendedTiming     bool   `json:"extended_timing,omitempty"`
	HeaderBytes        int    `json:"header_bytes,omitempty"`
	Method             string `json:"method,omitempty"`
	MoreExtendedTiming bool   `json:"more_extended_timing,omitempty"`
	Path               string `json:"path,omitempty"`
	QueryOptions       string `json:"query_options,omitempty"`
	UserAgent          string `json:"user_agent,omitempty"`
	Version            string `json:"version,omitempty"`

	// sslcert parameters
	//   none (see target)

	// sslcert & traceroute & http parameters
	Port int `json:"port,omitempty"`

	// ping & traceroute parameters
	Size int `json:"size,omitempty"`

	// wifi parameters
	AnonymousIdentity string `json:"anonymous_identity,omitempty"`
	Cert              string `json:"cert,omitempty"`
	EAP               string `json:"eap,omitempty"`
}
<|endoftext|>