<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build dragonfly freebsd linux netbsd openbsd solaris
// +build !js
// +build !android

package devicescale

import (
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

type desktop int

const (
	desktopUnknown desktop = iota
	desktopGnome
	desktopCinnamon
)

func currentDesktop() desktop {
	tokens := strings.Split(os.Getenv("XDG_CURRENT_DESKTOP"), ":")
	switch tokens[len(tokens)-1] {
	case "GNOME":
		return desktopGnome
	case "X-Cinnamon":
		return desktopCinnamon
	default:
		return desktopUnknown
	}
}

var gsettingsRe = regexp.MustCompile(`\Auint32 (\d+)\s*\z`)

func gnomeScale() float64 {
	out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "scaling-factor").Output()
	if err != nil {
		if err == exec.ErrNotFound {
			return 0
		}
		if _, ok := err.(*exec.ExitError); ok {
			return 0
		}
		panic(err)
	}
	m := gsettingsRe.FindStringSubmatch(string(out))
	s, err := strconv.Atoi(m[1])
	if err != nil {
		return 0
	}
	return float64(s)
}

func cinnamonScale() float64 {
	out, err := exec.Command("gsettings", "get", "org.cinnamon.desktop.interface", "scaling-factor").Output()
	if err != nil {
		if err == exec.ErrNotFound {
			return 0
		}
		if _, ok := err.(*exec.ExitError); ok {
			return 0
		}
		panic(err)
	}
	m := gsettingsRe.FindStringSubmatch(string(out))
	s, err := strconv.Atoi(m[1])
	if err != nil {
		return 0
	}
	return float64(s)
}

func impl() float64 {
	switch currentDesktop() {
	case desktopGnome:
		s := gnomeScale()
		if s <= 0 {
			return 1
		}
		return s
	case desktopCinnamon:
		s := cinnamonScale()
		if s <= 0 {
			return 1
		}
		return s
	}
	return 1
}
<commit_msg>devicescale: Add other desktops<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build dragonfly freebsd linux netbsd openbsd solaris
// +build !js
// +build !android

package devicescale

import (
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

type desktop int

const (
	desktopUnknown desktop = iota
	desktopGnome
	desktopCinnamon
	desktopUnity
	desktopKDE
	desktopXFCE
)

func currentDesktop() desktop {
	tokens := strings.Split(os.Getenv("XDG_CURRENT_DESKTOP"), ":")
	switch tokens[len(tokens)-1] {
	case "GNOME":
		return desktopGnome
	case "X-Cinnamon":
		return desktopCinnamon
	case "Unity":
		return desktopUnity
	case "KDE":
		return desktopKDE
	case "XFCE":
		return desktopXFCE
	default:
		return desktopUnknown
	}
}

var gsettingsRe = regexp.MustCompile(`\Auint32 (\d+)\s*\z`)

func gnomeScale() float64 {
	out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "scaling-factor").Output()
	if err != nil {
		if err == exec.ErrNotFound {
			return 0
		}
		if _, ok := err.(*exec.ExitError); ok {
			return 0
		}
		panic(err)
	}
	m := gsettingsRe.FindStringSubmatch(string(out))
	s, err := strconv.Atoi(m[1])
	if err != nil {
		return 0
	}
	return float64(s)
}

func cinnamonScale() float64 {
	out, err := exec.Command("gsettings", "get", "org.cinnamon.desktop.interface", "scaling-factor").Output()
	if err != nil {
		if err == exec.ErrNotFound {
			return 0
		}
		if _, ok := err.(*exec.ExitError); ok {
			return 0
		}
		panic(err)
	}
	m := gsettingsRe.FindStringSubmatch(string(out))
	s, err := strconv.Atoi(m[1])
	if err != nil {
		return 0
	}
	return float64(s)
}

func impl() float64 {
	switch currentDesktop() {
	case desktopGnome:
		s := gnomeScale()
		if s <= 0 {
			return 1
		}
		return s
	case desktopCinnamon:
		s := cinnamonScale()
		if s <= 0 {
			return 1
		}
		return s
	case desktopUnity:
		// TODO: Implement
		return 1
	case desktopKDE:
		// TODO: Implement
		return 1
	case desktopXFCE:
		// TODO: Implement
		return 1
	}
	return 1
}
<|endoftext|>


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

package idna

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

	"golang.org/x/text/internal/gen"
	"golang.org/x/text/internal/testtext"
	"golang.org/x/text/internal/ucd"
)

func TestAllocToUnicode(t *testing.T) {
	avg := testtext.AllocsPerRun(1000, func() {
		ToUnicode("www.golang.org")
	})
	if avg > 0 {
		t.Errorf("got %f; want 0", avg)
	}
}

func TestAllocToASCII(t *testing.T) {
	avg := testtext.AllocsPerRun(1000, func() {
		ToASCII("www.golang.org")
	})
	if avg > 0 {
		t.Errorf("got %f; want 0", avg)
	}
}

func TestProfiles(t *testing.T) {
	testCases := []struct {
		name      string
		want, got *Profile
	}{
		{"Punycode", punycode, New()},
		{"Registration", registration, New(ValidateForRegistration())},
		{"Registration", registration, New(
			ValidateForRegistration(),
			VerifyDNSLength(true),
			BidiRule(),
		)},
		{"Lookup", lookup, New(MapForLookup(), BidiRule(), Transitional(true))},
		{"Display", display, New(MapForLookup(), BidiRule())},
	}
	for _, tc := range testCases {
		// Functions are not comparable, but the printed version will include
		// their pointers.
		got := fmt.Sprintf("%#v", tc.got)
		want := fmt.Sprintf("%#v", tc.want)
		if got != want {
			t.Errorf("%s: \ngot  %#v,\nwant %#v", tc.name, got, want)
		}
	}
}

// doTest performs a single test f(input) and verifies that the output matches
// out and that the returned error is expected. The errors string contains
// all allowed error codes as categorized in
// http://www.unicode.org/Public/idna/9.0.0/IdnaTest.txt:
// P: Processing
// V: Validity
// A: to ASCII
// B: Bidi
// C: Context J
func doTest(t *testing.T, f func(string) (string, error), name, input, want, errors string) {
	errors = strings.Trim(errors, "[]")
	test := "ok"
	if errors != "" {
		test = "err:" + errors
	}
	// Replace some of the escape sequences to make it easier to single out
	// tests on the command name.
	in := strings.Trim(strconv.QuoteToASCII(input), `"`)
	in = strings.Replace(in, `\u`, "#", -1)
	in = strings.Replace(in, `\U`, "#", -1)
	name = fmt.Sprintf("%s/%s/%s", name, in, test)

	testtext.Run(t, name, func(t *testing.T) {
		got, err := f(input)

		if err != nil {
			code := err.(interface {
				code() string
			}).code()
			if strings.Index(errors, code) == -1 {
				t.Errorf("error %q not in set of expected errors {%v}", code, errors)
			}
		} else if errors != "" {
			t.Errorf("no errors; want error in {%v}", errors)
		}

		if want != "" && got != want {
			t.Errorf(`string: got %+q; want %+q`, got, want)
		}
	})
}

// TestLabelErrors tests strings returned in case of error. All results should
// be identical to the reference implementation and can be verified at
// http://unicode.org/cldr/utility/idna.jsp. The reference implementation,
// however, seems to not display Bidi and ContextJ errors.
//
// In some cases the behavior of browsers is added as a comment. In all cases,
// whenever a resolve search returns an error here, Chrome will treat the input
// string as a search string (including those for Bidi and Context J errors),
// unless noted otherwise.
func TestLabelErrors(t *testing.T) {
	encode := func(s string) string { s, _ = encode(acePrefix, s); return s }
	type kind struct {
		name string
		f    func(string) (string, error)
	}
	punyA := kind{"PunycodeA", punycode.ToASCII}
	resolve := kind{"ResolveA", Lookup.ToASCII}
	display := kind{"ToUnicode", Display.ToUnicode}
	p := New(VerifyDNSLength(true), MapForLookup(), BidiRule())
	lengthU := kind{"CheckLengthU", p.ToUnicode}
	lengthA := kind{"CheckLengthA", p.ToASCII}
	p = New(MapForLookup(), StrictDomainName(false))
	std3 := kind{"STD3", p.ToASCII}

	testCases := []struct {
		kind
		input   string
		want    string
		wantErr string
	}{
		{lengthU, "", "", "A4"}, // From UTS 46 conformance test.
		{lengthA, "", "", "A4"},

		{lengthU, "xn--", "", "A4"},
		{lengthU, "foo.xn--", "foo.", "A4"}, // TODO: is dropping xn-- correct?
		{lengthU, "xn--.foo", ".foo", "A4"},
		{lengthU, "foo.xn--.bar", "foo..bar", "A4"},

		{display, "xn--", "", ""},
		{display, "foo.xn--", "foo.", ""}, // TODO: is dropping xn-- correct?
		{display, "xn--.foo", ".foo", ""},
		{display, "foo.xn--.bar", "foo..bar", ""},

		{lengthA, "a..b", "a..b", "A4"},
		{punyA, ".b", ".b", ""},
		// For backwards compatibility, the Punycode profile does not map runes.
		{punyA, "\u3002b", "xn--b-83t", ""},
		{punyA, "..b", "..b", ""},

		{lengthA, ".b", ".b", "A4"},
		{lengthA, "\u3002b", ".b", "A4"},
		{lengthA, "..b", "..b", "A4"},
		{lengthA, "b..", "b..", ""},

		// Sharpened Bidi rules for Unicode 10.0.0. Apply for ALL labels in ANY
		// of the labels is RTL.
		{lengthA, "\ufe05\u3002\u3002\U0002603e\u1ce0", "..xn--t6f5138v", "A4"},
		{lengthA, "FAX\u2a77\U0001d186\u3002\U0001e942\U000e0181\u180c", "", "B6"},

		{resolve, "a..b", "a..b", ""},
		// Note that leading dots are not stripped. This is to be consistent
		// with the Punycode profile as well as the conformance test.
		{resolve, ".b", ".b", ""},
		{resolve, "\u3002b", ".b", ""},
		{resolve, "..b", "..b", ""},
		{resolve, "b..", "b..", ""},

		// Raw punycode
		{punyA, "", "", ""},
		{punyA, "*.foo.com", "*.foo.com", ""},
		{punyA, "Foo.com", "Foo.com", ""},

		// STD3 rules
		{display, "*.foo.com", "*.foo.com", "P1"},
		{std3, "*.foo.com", "*.foo.com", ""},

		// Don't map U+2490 (DIGIT NINE FULL STOP). This is the behavior of
		// Chrome, Safari, and IE. Firefox will first map ⒐ to 9. and return
		// lab9.be.
		{resolve, "lab⒐be", "xn--labbe-zh9b", "P1"}, // encode("lab⒐be")
		{display, "lab⒐be", "lab⒐be", "P1"},

		{resolve, "plan⒐faß.de", "xn--planfass-c31e.de", "P1"}, // encode("plan⒐fass") + ".de"
		{display, "Plan⒐faß.de", "plan⒐faß.de", "P1"},

		// Chrome 54.0 recognizes the error and treats this input verbatim as a
		// search string.
		// Safari 10.0 (non-conform spec) decomposes "⒈" and computes the
		// punycode on the result using transitional mapping.
		// Firefox 49.0.1 goes haywire on this string and prints a bunch of what
		// seems to be nested punycode encodings.
		{resolve, "日本⒈co.ßßß.de", "xn--co-wuw5954azlb.ssssss.de", "P1"},
		{display, "日本⒈co.ßßß.de", "日本⒈co.ßßß.de", "P1"},

		{resolve, "a\u200Cb", "ab", ""},
		{display, "a\u200Cb", "a\u200Cb", "C"},

		{resolve, encode("a\u200Cb"), encode("a\u200Cb"), "C"},
		{display, "a\u200Cb", "a\u200Cb", "C"},

		{resolve, "grﻋﺮﺑﻲ.de", "xn--gr-gtd9a1b0g.de", "B"},
		{
			// Notice how the string gets transformed, even with an error.
			// Chrome will use the original string if it finds an error, so not
			// the transformed one.
			display,
			"gr\ufecb\ufeae\ufe91\ufef2.de",
			"gr\u0639\u0631\u0628\u064a.de",
			"B",
		},

		{resolve, "\u0671.\u03c3\u07dc", "xn--qib.xn--4xa21s", "B"}, // ٱ.σߜ
		{display, "\u0671.\u03c3\u07dc", "\u0671.\u03c3\u07dc", "B"},

		// normalize input
		{resolve, "a\u0323\u0322", "xn--jta191l", ""}, // ạ̢
		{display, "a\u0323\u0322", "\u1ea1\u0322", ""},

		// Non-normalized strings are not normalized when they originate from
		// punycode. Despite the error, Chrome, Safari and Firefox will attempt
		// to look up the input punycode.
		{resolve, encode("a\u0323\u0322") + ".com", "xn--a-tdbc.com", "V1"},
		{display, encode("a\u0323\u0322") + ".com", "a\u0323\u0322.com", "V1"},
	}

	for _, tc := range testCases {
		doTest(t, tc.f, tc.name, tc.input, tc.want, tc.wantErr)
	}
}

func TestConformance(t *testing.T) {
	testtext.SkipIfNotLong(t)

	r := gen.OpenUnicodeFile("idna", "", "IdnaTest.txt")
	defer r.Close()

	section := "main"
	started := false
	p := ucd.New(r, ucd.CommentHandler(func(s string) {
		if started {
			section = strings.ToLower(strings.Split(s, " ")[0])
		}
	}))
	transitional := New(Transitional(true), VerifyDNSLength(true), BidiRule(), MapForLookup())
	nonTransitional := New(VerifyDNSLength(true), BidiRule(), MapForLookup())
	for p.Next() {
		started = true

		// What to test
		profiles := []*Profile{}
		switch p.String(0) {
		case "T":
			profiles = append(profiles, transitional)
		case "N":
			profiles = append(profiles, nonTransitional)
		case "B":
			profiles = append(profiles, transitional)
			profiles = append(profiles, nonTransitional)
		}

		src := unescape(p.String(1))

		wantToUnicode := unescape(p.String(2))
		if wantToUnicode == "" {
			wantToUnicode = src
		}
		wantToASCII := unescape(p.String(3))
		if wantToASCII == "" {
			wantToASCII = wantToUnicode
		}
		wantErrToUnicode := ""
		if strings.HasPrefix(wantToUnicode, "[") {
			wantErrToUnicode = wantToUnicode
			wantToUnicode = ""
		}
		wantErrToASCII := ""
		if strings.HasPrefix(wantToASCII, "[") {
			wantErrToASCII = wantToASCII
			wantToASCII = ""
		}

		// TODO: also do IDNA tests.
		// invalidInIDNA2008 := p.String(4) == "NV8"

		for _, p := range profiles {
			name := fmt.Sprintf("%s:%s", section, p)
			doTest(t, p.ToUnicode, name+":ToUnicode", src, wantToUnicode, wantErrToUnicode)
			doTest(t, p.ToASCII, name+":ToASCII", src, wantToASCII, wantErrToASCII)
		}
	}
}

func unescape(s string) string {
	s, err := strconv.Unquote(`"` + s + `"`)
	if err != nil {
		panic(err)
	}
	return s
}
<commit_msg>internal/export/idna: added benchmark<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package idna

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

	"golang.org/x/text/internal/gen"
	"golang.org/x/text/internal/testtext"
	"golang.org/x/text/internal/ucd"
)

func TestAllocToUnicode(t *testing.T) {
	avg := testtext.AllocsPerRun(1000, func() {
		ToUnicode("www.golang.org")
	})
	if avg > 0 {
		t.Errorf("got %f; want 0", avg)
	}
}

func TestAllocToASCII(t *testing.T) {
	avg := testtext.AllocsPerRun(1000, func() {
		ToASCII("www.golang.org")
	})
	if avg > 0 {
		t.Errorf("got %f; want 0", avg)
	}
}

func TestProfiles(t *testing.T) {
	testCases := []struct {
		name      string
		want, got *Profile
	}{
		{"Punycode", punycode, New()},
		{"Registration", registration, New(ValidateForRegistration())},
		{"Registration", registration, New(
			ValidateForRegistration(),
			VerifyDNSLength(true),
			BidiRule(),
		)},
		{"Lookup", lookup, New(MapForLookup(), BidiRule(), Transitional(true))},
		{"Display", display, New(MapForLookup(), BidiRule())},
	}
	for _, tc := range testCases {
		// Functions are not comparable, but the printed version will include
		// their pointers.
		got := fmt.Sprintf("%#v", tc.got)
		want := fmt.Sprintf("%#v", tc.want)
		if got != want {
			t.Errorf("%s: \ngot  %#v,\nwant %#v", tc.name, got, want)
		}
	}
}

// doTest performs a single test f(input) and verifies that the output matches
// out and that the returned error is expected. The errors string contains
// all allowed error codes as categorized in
// http://www.unicode.org/Public/idna/9.0.0/IdnaTest.txt:
// P: Processing
// V: Validity
// A: to ASCII
// B: Bidi
// C: Context J
func doTest(t *testing.T, f func(string) (string, error), name, input, want, errors string) {
	errors = strings.Trim(errors, "[]")
	test := "ok"
	if errors != "" {
		test = "err:" + errors
	}
	// Replace some of the escape sequences to make it easier to single out
	// tests on the command name.
	in := strings.Trim(strconv.QuoteToASCII(input), `"`)
	in = strings.Replace(in, `\u`, "#", -1)
	in = strings.Replace(in, `\U`, "#", -1)
	name = fmt.Sprintf("%s/%s/%s", name, in, test)

	testtext.Run(t, name, func(t *testing.T) {
		got, err := f(input)

		if err != nil {
			code := err.(interface {
				code() string
			}).code()
			if strings.Index(errors, code) == -1 {
				t.Errorf("error %q not in set of expected errors {%v}", code, errors)
			}
		} else if errors != "" {
			t.Errorf("no errors; want error in {%v}", errors)
		}

		if want != "" && got != want {
			t.Errorf(`string: got %+q; want %+q`, got, want)
		}
	})
}

// TestLabelErrors tests strings returned in case of error. All results should
// be identical to the reference implementation and can be verified at
// http://unicode.org/cldr/utility/idna.jsp. The reference implementation,
// however, seems to not display Bidi and ContextJ errors.
//
// In some cases the behavior of browsers is added as a comment. In all cases,
// whenever a resolve search returns an error here, Chrome will treat the input
// string as a search string (including those for Bidi and Context J errors),
// unless noted otherwise.
func TestLabelErrors(t *testing.T) {
	encode := func(s string) string { s, _ = encode(acePrefix, s); return s }
	type kind struct {
		name string
		f    func(string) (string, error)
	}
	punyA := kind{"PunycodeA", punycode.ToASCII}
	resolve := kind{"ResolveA", Lookup.ToASCII}
	display := kind{"ToUnicode", Display.ToUnicode}
	p := New(VerifyDNSLength(true), MapForLookup(), BidiRule())
	lengthU := kind{"CheckLengthU", p.ToUnicode}
	lengthA := kind{"CheckLengthA", p.ToASCII}
	p = New(MapForLookup(), StrictDomainName(false))
	std3 := kind{"STD3", p.ToASCII}

	testCases := []struct {
		kind
		input   string
		want    string
		wantErr string
	}{
		{lengthU, "", "", "A4"}, // From UTS 46 conformance test.
		{lengthA, "", "", "A4"},

		{lengthU, "xn--", "", "A4"},
		{lengthU, "foo.xn--", "foo.", "A4"}, // TODO: is dropping xn-- correct?
		{lengthU, "xn--.foo", ".foo", "A4"},
		{lengthU, "foo.xn--.bar", "foo..bar", "A4"},

		{display, "xn--", "", ""},
		{display, "foo.xn--", "foo.", ""}, // TODO: is dropping xn-- correct?
		{display, "xn--.foo", ".foo", ""},
		{display, "foo.xn--.bar", "foo..bar", ""},

		{lengthA, "a..b", "a..b", "A4"},
		{punyA, ".b", ".b", ""},
		// For backwards compatibility, the Punycode profile does not map runes.
		{punyA, "\u3002b", "xn--b-83t", ""},
		{punyA, "..b", "..b", ""},

		{lengthA, ".b", ".b", "A4"},
		{lengthA, "\u3002b", ".b", "A4"},
		{lengthA, "..b", "..b", "A4"},
		{lengthA, "b..", "b..", ""},

		// Sharpened Bidi rules for Unicode 10.0.0. Apply for ALL labels in ANY
		// of the labels is RTL.
		{lengthA, "\ufe05\u3002\u3002\U0002603e\u1ce0", "..xn--t6f5138v", "A4"},
		{lengthA, "FAX\u2a77\U0001d186\u3002\U0001e942\U000e0181\u180c", "", "B6"},

		{resolve, "a..b", "a..b", ""},
		// Note that leading dots are not stripped. This is to be consistent
		// with the Punycode profile as well as the conformance test.
		{resolve, ".b", ".b", ""},
		{resolve, "\u3002b", ".b", ""},
		{resolve, "..b", "..b", ""},
		{resolve, "b..", "b..", ""},

		// Raw punycode
		{punyA, "", "", ""},
		{punyA, "*.foo.com", "*.foo.com", ""},
		{punyA, "Foo.com", "Foo.com", ""},

		// STD3 rules
		{display, "*.foo.com", "*.foo.com", "P1"},
		{std3, "*.foo.com", "*.foo.com", ""},

		// Don't map U+2490 (DIGIT NINE FULL STOP). This is the behavior of
		// Chrome, Safari, and IE. Firefox will first map ⒐ to 9. and return
		// lab9.be.
		{resolve, "lab⒐be", "xn--labbe-zh9b", "P1"}, // encode("lab⒐be")
		{display, "lab⒐be", "lab⒐be", "P1"},

		{resolve, "plan⒐faß.de", "xn--planfass-c31e.de", "P1"}, // encode("plan⒐fass") + ".de"
		{display, "Plan⒐faß.de", "plan⒐faß.de", "P1"},

		// Chrome 54.0 recognizes the error and treats this input verbatim as a
		// search string.
		// Safari 10.0 (non-conform spec) decomposes "⒈" and computes the
		// punycode on the result using transitional mapping.
		// Firefox 49.0.1 goes haywire on this string and prints a bunch of what
		// seems to be nested punycode encodings.
		{resolve, "日本⒈co.ßßß.de", "xn--co-wuw5954azlb.ssssss.de", "P1"},
		{display, "日本⒈co.ßßß.de", "日本⒈co.ßßß.de", "P1"},

		{resolve, "a\u200Cb", "ab", ""},
		{display, "a\u200Cb", "a\u200Cb", "C"},

		{resolve, encode("a\u200Cb"), encode("a\u200Cb"), "C"},
		{display, "a\u200Cb", "a\u200Cb", "C"},

		{resolve, "grﻋﺮﺑﻲ.de", "xn--gr-gtd9a1b0g.de", "B"},
		{
			// Notice how the string gets transformed, even with an error.
			// Chrome will use the original string if it finds an error, so not
			// the transformed one.
			display,
			"gr\ufecb\ufeae\ufe91\ufef2.de",
			"gr\u0639\u0631\u0628\u064a.de",
			"B",
		},

		{resolve, "\u0671.\u03c3\u07dc", "xn--qib.xn--4xa21s", "B"}, // ٱ.σߜ
		{display, "\u0671.\u03c3\u07dc", "\u0671.\u03c3\u07dc", "B"},

		// normalize input
		{resolve, "a\u0323\u0322", "xn--jta191l", ""}, // ạ̢
		{display, "a\u0323\u0322", "\u1ea1\u0322", ""},

		// Non-normalized strings are not normalized when they originate from
		// punycode. Despite the error, Chrome, Safari and Firefox will attempt
		// to look up the input punycode.
		{resolve, encode("a\u0323\u0322") + ".com", "xn--a-tdbc.com", "V1"},
		{display, encode("a\u0323\u0322") + ".com", "a\u0323\u0322.com", "V1"},
	}

	for _, tc := range testCases {
		doTest(t, tc.f, tc.name, tc.input, tc.want, tc.wantErr)
	}
}

func TestConformance(t *testing.T) {
	testtext.SkipIfNotLong(t)

	r := gen.OpenUnicodeFile("idna", "", "IdnaTest.txt")
	defer r.Close()

	section := "main"
	started := false
	p := ucd.New(r, ucd.CommentHandler(func(s string) {
		if started {
			section = strings.ToLower(strings.Split(s, " ")[0])
		}
	}))
	transitional := New(Transitional(true), VerifyDNSLength(true), BidiRule(), MapForLookup())
	nonTransitional := New(VerifyDNSLength(true), BidiRule(), MapForLookup())
	for p.Next() {
		started = true

		// What to test
		profiles := []*Profile{}
		switch p.String(0) {
		case "T":
			profiles = append(profiles, transitional)
		case "N":
			profiles = append(profiles, nonTransitional)
		case "B":
			profiles = append(profiles, transitional)
			profiles = append(profiles, nonTransitional)
		}

		src := unescape(p.String(1))

		wantToUnicode := unescape(p.String(2))
		if wantToUnicode == "" {
			wantToUnicode = src
		}
		wantToASCII := unescape(p.String(3))
		if wantToASCII == "" {
			wantToASCII = wantToUnicode
		}
		wantErrToUnicode := ""
		if strings.HasPrefix(wantToUnicode, "[") {
			wantErrToUnicode = wantToUnicode
			wantToUnicode = ""
		}
		wantErrToASCII := ""
		if strings.HasPrefix(wantToASCII, "[") {
			wantErrToASCII = wantToASCII
			wantToASCII = ""
		}

		// TODO: also do IDNA tests.
		// invalidInIDNA2008 := p.String(4) == "NV8"

		for _, p := range profiles {
			name := fmt.Sprintf("%s:%s", section, p)
			doTest(t, p.ToUnicode, name+":ToUnicode", src, wantToUnicode, wantErrToUnicode)
			doTest(t, p.ToASCII, name+":ToASCII", src, wantToASCII, wantErrToASCII)
		}
	}
}

func unescape(s string) string {
	s, err := strconv.Unquote(`"` + s + `"`)
	if err != nil {
		panic(err)
	}
	return s
}

func BenchmarkProfile(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Lookup.ToASCII("www.yahoogle.com")
	}
}
<|endoftext|>


<commit_before>// Copyright Istio Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validate

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/gogo/protobuf/proto"
	"github.com/hashicorp/go-multierror"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"

	operator_istio "istio.io/istio/operator/pkg/apis/istio"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/util"
	operator_validate "istio.io/istio/operator/pkg/validate"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/config/schema/collection"
	"istio.io/istio/pkg/config/schema/collections"
	"istio.io/istio/pkg/config/schema/resource"
	"istio.io/istio/pkg/util/gogoprotomarshal"
	"istio.io/pkg/log"
)

var (
	errMissingFilename = errors.New(`error: you must specify resources by --filename.
Example resource specifications include:
   '-f rsrc.yaml'
   '--filename=rsrc.json'`)

	validFields = map[string]struct{}{
		"apiVersion": {},
		"kind":       {},
		"metadata":   {},
		"spec":       {},
		"status":     {},
	}

	istioDeploymentLabel = []string{
		"app",
		"version",
	}
	serviceProtocolUDP = "UDP"
)

const (
	// RequirementsURL specifies deployment requirements for pod and services
	RequirementsURL = "https://istio.io/latest/docs/ops/deployment/requirements/"
)

type validator struct {
}

func checkFields(un *unstructured.Unstructured) error {
	var errs error
	for key := range un.Object {
		if _, ok := validFields[key]; !ok {
			errs = multierror.Append(errs, fmt.Errorf("unknown field %q", key))
		}
	}
	return errs
}

func (v *validator) validateResource(istioNamespace string, un *unstructured.Unstructured) error {
	gvk := resource.GroupVersionKind{
		Group:   un.GroupVersionKind().Group,
		Version: un.GroupVersionKind().Version,
		Kind:    un.GroupVersionKind().Kind,
	}
	// TODO(jasonwzm) remove this when multi-version is supported. v1beta1 shares the same
	// schema as v1lalpha3. Fake conversion and validate against v1alpha3.
	if gvk.Group == name.NetworkingAPIGroupName && gvk.Version == "v1beta1" {
		gvk.Version = "v1alpha3"
	}
	schema, exists := collections.Pilot.FindByGroupVersionKind(gvk)
	if exists {
		obj, err := convertObjectFromUnstructured(schema, un, "")
		if err != nil {
			return fmt.Errorf("cannot parse proto message: %v", err)
		}
		if err = checkFields(un); err != nil {
			return err
		}
		return schema.Resource().ValidateProto(obj.Name, obj.Namespace, obj.Spec)
	}

	var errs error
	if un.IsList() {
		_ = un.EachListItem(func(item runtime.Object) error {
			castItem := item.(*unstructured.Unstructured)
			if castItem.GetKind() == name.ServiceStr {
				err := v.validateServicePortPrefix(istioNamespace, castItem)
				if err != nil {
					errs = multierror.Append(errs, err)
				}
			}
			if castItem.GetKind() == name.DeploymentStr {
				v.validateDeploymentLabel(istioNamespace, castItem)
			}
			return nil
		})
	}

	if errs != nil {
		return errs
	}
	if un.GetKind() == name.ServiceStr {
		return v.validateServicePortPrefix(istioNamespace, un)
	}

	if un.GetKind() == name.DeploymentStr {
		v.validateDeploymentLabel(istioNamespace, un)
		return nil
	}

	if un.GetAPIVersion() == "install.istio.io/v1alpha1" {
		if un.GetKind() == "IstioOperator" {
			if err := checkFields(un); err != nil {
				return err
			}
			// IstioOperator isn't part of pkg/config/schema/collections,
			// usual conversion not available.  Convert unstructured to string
			// and ask operator code to check.
			un.SetCreationTimestamp(metav1.Time{}) // UnmarshalIstioOperator chokes on these
			by := util.ToYAML(un)
			iop, err := operator_istio.UnmarshalIstioOperator(by, false)
			if err != nil {
				return err
			}
			return operator_validate.CheckIstioOperator(iop, true)
		}
	}

	// Didn't really validate.  This is OK, as we often get non-Istio Kubernetes YAML
	// we can't complain about.

	return nil
}

func (v *validator) validateServicePortPrefix(istioNamespace string, un *unstructured.Unstructured) error {
	var errs error
	if un.GetNamespace() == handleNamespace(istioNamespace) {
		return nil
	}
	spec := un.Object["spec"].(map[string]interface{})
	if _, ok := spec["ports"]; ok {
		ports := spec["ports"].([]interface{})
		for _, port := range ports {
			p := port.(map[string]interface{})
			if p["protocol"] != nil && strings.EqualFold(p["protocol"].(string), serviceProtocolUDP) {
				continue
			}
			if p["name"] == nil {
				errs = multierror.Append(errs, fmt.Errorf("service %q has an unnamed port. This is not recommended,"+
					" See "+RequirementsURL, fmt.Sprintf("%s/%s/:", un.GetName(), un.GetNamespace())))
				continue
			}
			if servicePortPrefixed(p["name"].(string)) {
				errs = multierror.Append(errs, fmt.Errorf("service %q port %q does not follow the Istio naming convention."+
					" See "+RequirementsURL, fmt.Sprintf("%s/%s/:", un.GetName(), un.GetNamespace()), p["name"].(string)))
			}
		}
	}
	if errs != nil {
		return errs
	}
	return nil
}

func (v *validator) validateDeploymentLabel(istioNamespace string, un *unstructured.Unstructured) {
	if un.GetNamespace() == handleNamespace(istioNamespace) {
		return
	}
	labels := un.GetLabels()
	for _, l := range istioDeploymentLabel {
		if _, ok := labels[l]; !ok {
			log.Warnf("deployment %q may not provide Istio metrics and telemetry without label %q."+
				" See "+RequirementsURL, fmt.Sprintf("%s/%s:", un.GetName(), un.GetNamespace()), l)
		}
	}
}

func (v *validator) validateFile(istioNamespace *string, reader io.Reader) error {
	decoder := yaml.NewDecoder(reader)
	decoder.SetStrict(true)
	var errs error
	for {
		// YAML allows non-string keys and the produces generic keys for nested fields
		raw := make(map[interface{}]interface{})
		err := decoder.Decode(&raw)
		if err == io.EOF {
			return errs
		}
		if err != nil {
			errs = multierror.Append(errs, err)
			return errs
		}
		if len(raw) == 0 {
			continue
		}
		out := transformInterfaceMap(raw)
		un := unstructured.Unstructured{Object: out}
		err = v.validateResource(*istioNamespace, &un)
		if err != nil {
			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("%s/%s/%s:",
				un.GetKind(), un.GetNamespace(), un.GetName())))
		}
	}
}

func validateFiles(istioNamespace *string, filenames []string, writer io.Writer) error {
	if len(filenames) == 0 {
		return errMissingFilename
	}

	v := &validator{}

	var errs, err error
	var reader io.Reader
	for _, filename := range filenames {
		if filename == "-" {
			reader = os.Stdin
		} else {
			reader, err = os.Open(filename)
		}
		if err != nil {
			errs = multierror.Append(errs, fmt.Errorf("cannot read file %q: %v", filename, err))
			continue
		}
		err = v.validateFile(istioNamespace, reader)
		if err != nil {
			errs = multierror.Append(errs, err)
		}
	}
	if errs != nil {
		return errs
	}
	for _, fname := range filenames {
		if fname == "-" {
			_, _ = fmt.Fprintf(writer, "validation succeed\n")
			break
		} else {
			_, _ = fmt.Fprintf(writer, "%q is valid\n", fname)
		}
	}

	return nil
}

// NewValidateCommand creates a new command for validating Istio k8s resources.
func NewValidateCommand(istioNamespace *string) *cobra.Command {
	var filenames []string
	var referential bool

	c := &cobra.Command{
		Use:   "validate -f FILENAME [options]",
		Short: "Validate Istio policy and rules (NOTE: validate is deprecated and will be removed in 1.6. Use 'istioctl analyze' to validate configuration.)",
		Example: `
		# Validate bookinfo-gateway.yaml
		istioctl validate -f bookinfo-gateway.yaml

		# Validate current deployments under 'default' namespace within the cluster
		kubectl get deployments -o yaml |istioctl validate -f -

		# Validate current services under 'default' namespace within the cluster
		kubectl get services -o yaml |istioctl validate -f -

		# Also see the related command 'istioctl analyze'
		istioctl analyze samples/bookinfo/networking/bookinfo-gateway.yaml
`,
		Args: cobra.NoArgs,
		RunE: func(c *cobra.Command, _ []string) error {
			return validateFiles(istioNamespace, filenames, c.OutOrStderr())
		},
	}

	flags := c.PersistentFlags()
	flags.StringSliceVarP(&filenames, "filename", "f", nil, "Names of files to validate")
	flags.BoolVarP(&referential, "referential", "x", true, "Enable structural validation for policy and telemetry")

	return c
}

func transformInterfaceArray(in []interface{}) []interface{} {
	out := make([]interface{}, len(in))
	for i, v := range in {
		out[i] = transformMapValue(v)
	}
	return out
}

func transformInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
	out := make(map[string]interface{}, len(in))
	for k, v := range in {
		out[fmt.Sprintf("%v", k)] = transformMapValue(v)
	}
	return out
}

func transformMapValue(in interface{}) interface{} {
	switch v := in.(type) {
	case []interface{}:
		return transformInterfaceArray(v)
	case map[interface{}]interface{}:
		return transformInterfaceMap(v)
	default:
		return v
	}
}

func servicePortPrefixed(n string) bool {
	i := strings.IndexByte(n, '-')
	if i >= 0 {
		n = n[:i]
	}
	p := protocol.Parse(n)
	return p == protocol.Unsupported
}

func handleNamespace(istioNamespace string) string {
	if istioNamespace == "" {
		istioNamespace = controller.IstioNamespace
	}
	return istioNamespace
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func convertObjectFromUnstructured(schema collection.Schema, un *unstructured.Unstructured, domain string) (*model.Config, error) {
	data, err := fromSchemaAndJSONMap(schema, un.Object["spec"])
	if err != nil {
		return nil, err
	}

	return &model.Config{
		ConfigMeta: model.ConfigMeta{
			GroupVersionKind:  schema.Resource().GroupVersionKind(),
			Name:              un.GetName(),
			Namespace:         un.GetNamespace(),
			Domain:            domain,
			Labels:            un.GetLabels(),
			Annotations:       un.GetAnnotations(),
			ResourceVersion:   un.GetResourceVersion(),
			CreationTimestamp: un.GetCreationTimestamp().Time,
		},
		Spec: data,
	}, nil
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func fromSchemaAndYAML(schema collection.Schema, yml string) (proto.Message, error) {
	pb, err := schema.Resource().NewProtoInstance()
	if err != nil {
		return nil, err
	}
	if err = gogoprotomarshal.ApplyYAMLStrict(yml, pb); err != nil {
		return nil, err
	}
	return pb, nil
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func fromSchemaAndJSONMap(schema collection.Schema, data interface{}) (proto.Message, error) {
	// Marshal to YAML bytes
	str, err := yaml.Marshal(data)
	if err != nil {
		return nil, err
	}
	out, err := fromSchemaAndYAML(schema, string(str))
	if err != nil {
		return nil, multierror.Prefix(err, fmt.Sprintf("YAML decoding error: %v", string(str)))
	}
	return out, nil
}
<commit_msg>Don't claim 'istioctl validate' is deprecated; we can't yet (#26082)<commit_after>// Copyright Istio Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validate

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/gogo/protobuf/proto"
	"github.com/hashicorp/go-multierror"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"

	operator_istio "istio.io/istio/operator/pkg/apis/istio"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/util"
	operator_validate "istio.io/istio/operator/pkg/validate"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/config/schema/collection"
	"istio.io/istio/pkg/config/schema/collections"
	"istio.io/istio/pkg/config/schema/resource"
	"istio.io/istio/pkg/util/gogoprotomarshal"
	"istio.io/pkg/log"
)

var (
	errMissingFilename = errors.New(`error: you must specify resources by --filename.
Example resource specifications include:
   '-f rsrc.yaml'
   '--filename=rsrc.json'`)

	validFields = map[string]struct{}{
		"apiVersion": {},
		"kind":       {},
		"metadata":   {},
		"spec":       {},
		"status":     {},
	}

	istioDeploymentLabel = []string{
		"app",
		"version",
	}
	serviceProtocolUDP = "UDP"
)

const (
	// RequirementsURL specifies deployment requirements for pod and services
	RequirementsURL = "https://istio.io/latest/docs/ops/deployment/requirements/"
)

type validator struct {
}

func checkFields(un *unstructured.Unstructured) error {
	var errs error
	for key := range un.Object {
		if _, ok := validFields[key]; !ok {
			errs = multierror.Append(errs, fmt.Errorf("unknown field %q", key))
		}
	}
	return errs
}

func (v *validator) validateResource(istioNamespace string, un *unstructured.Unstructured) error {
	gvk := resource.GroupVersionKind{
		Group:   un.GroupVersionKind().Group,
		Version: un.GroupVersionKind().Version,
		Kind:    un.GroupVersionKind().Kind,
	}
	// TODO(jasonwzm) remove this when multi-version is supported. v1beta1 shares the same
	// schema as v1lalpha3. Fake conversion and validate against v1alpha3.
	if gvk.Group == name.NetworkingAPIGroupName && gvk.Version == "v1beta1" {
		gvk.Version = "v1alpha3"
	}
	schema, exists := collections.Pilot.FindByGroupVersionKind(gvk)
	if exists {
		obj, err := convertObjectFromUnstructured(schema, un, "")
		if err != nil {
			return fmt.Errorf("cannot parse proto message: %v", err)
		}
		if err = checkFields(un); err != nil {
			return err
		}
		return schema.Resource().ValidateProto(obj.Name, obj.Namespace, obj.Spec)
	}

	var errs error
	if un.IsList() {
		_ = un.EachListItem(func(item runtime.Object) error {
			castItem := item.(*unstructured.Unstructured)
			if castItem.GetKind() == name.ServiceStr {
				err := v.validateServicePortPrefix(istioNamespace, castItem)
				if err != nil {
					errs = multierror.Append(errs, err)
				}
			}
			if castItem.GetKind() == name.DeploymentStr {
				v.validateDeploymentLabel(istioNamespace, castItem)
			}
			return nil
		})
	}

	if errs != nil {
		return errs
	}
	if un.GetKind() == name.ServiceStr {
		return v.validateServicePortPrefix(istioNamespace, un)
	}

	if un.GetKind() == name.DeploymentStr {
		v.validateDeploymentLabel(istioNamespace, un)
		return nil
	}

	if un.GetAPIVersion() == "install.istio.io/v1alpha1" {
		if un.GetKind() == "IstioOperator" {
			if err := checkFields(un); err != nil {
				return err
			}
			// IstioOperator isn't part of pkg/config/schema/collections,
			// usual conversion not available.  Convert unstructured to string
			// and ask operator code to check.
			un.SetCreationTimestamp(metav1.Time{}) // UnmarshalIstioOperator chokes on these
			by := util.ToYAML(un)
			iop, err := operator_istio.UnmarshalIstioOperator(by, false)
			if err != nil {
				return err
			}
			return operator_validate.CheckIstioOperator(iop, true)
		}
	}

	// Didn't really validate.  This is OK, as we often get non-Istio Kubernetes YAML
	// we can't complain about.

	return nil
}

func (v *validator) validateServicePortPrefix(istioNamespace string, un *unstructured.Unstructured) error {
	var errs error
	if un.GetNamespace() == handleNamespace(istioNamespace) {
		return nil
	}
	spec := un.Object["spec"].(map[string]interface{})
	if _, ok := spec["ports"]; ok {
		ports := spec["ports"].([]interface{})
		for _, port := range ports {
			p := port.(map[string]interface{})
			if p["protocol"] != nil && strings.EqualFold(p["protocol"].(string), serviceProtocolUDP) {
				continue
			}
			if p["name"] == nil {
				errs = multierror.Append(errs, fmt.Errorf("service %q has an unnamed port. This is not recommended,"+
					" See "+RequirementsURL, fmt.Sprintf("%s/%s/:", un.GetName(), un.GetNamespace())))
				continue
			}
			if servicePortPrefixed(p["name"].(string)) {
				errs = multierror.Append(errs, fmt.Errorf("service %q port %q does not follow the Istio naming convention."+
					" See "+RequirementsURL, fmt.Sprintf("%s/%s/:", un.GetName(), un.GetNamespace()), p["name"].(string)))
			}
		}
	}
	if errs != nil {
		return errs
	}
	return nil
}

func (v *validator) validateDeploymentLabel(istioNamespace string, un *unstructured.Unstructured) {
	if un.GetNamespace() == handleNamespace(istioNamespace) {
		return
	}
	labels := un.GetLabels()
	for _, l := range istioDeploymentLabel {
		if _, ok := labels[l]; !ok {
			log.Warnf("deployment %q may not provide Istio metrics and telemetry without label %q."+
				" See "+RequirementsURL, fmt.Sprintf("%s/%s:", un.GetName(), un.GetNamespace()), l)
		}
	}
}

func (v *validator) validateFile(istioNamespace *string, reader io.Reader) error {
	decoder := yaml.NewDecoder(reader)
	decoder.SetStrict(true)
	var errs error
	for {
		// YAML allows non-string keys and the produces generic keys for nested fields
		raw := make(map[interface{}]interface{})
		err := decoder.Decode(&raw)
		if err == io.EOF {
			return errs
		}
		if err != nil {
			errs = multierror.Append(errs, err)
			return errs
		}
		if len(raw) == 0 {
			continue
		}
		out := transformInterfaceMap(raw)
		un := unstructured.Unstructured{Object: out}
		err = v.validateResource(*istioNamespace, &un)
		if err != nil {
			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("%s/%s/%s:",
				un.GetKind(), un.GetNamespace(), un.GetName())))
		}
	}
}

func validateFiles(istioNamespace *string, filenames []string, writer io.Writer) error {
	if len(filenames) == 0 {
		return errMissingFilename
	}

	v := &validator{}

	var errs, err error
	var reader io.Reader
	for _, filename := range filenames {
		if filename == "-" {
			reader = os.Stdin
		} else {
			reader, err = os.Open(filename)
		}
		if err != nil {
			errs = multierror.Append(errs, fmt.Errorf("cannot read file %q: %v", filename, err))
			continue
		}
		err = v.validateFile(istioNamespace, reader)
		if err != nil {
			errs = multierror.Append(errs, err)
		}
	}
	if errs != nil {
		return errs
	}
	for _, fname := range filenames {
		if fname == "-" {
			_, _ = fmt.Fprintf(writer, "validation succeed\n")
			break
		} else {
			_, _ = fmt.Fprintf(writer, "%q is valid\n", fname)
		}
	}

	return nil
}

// NewValidateCommand creates a new command for validating Istio k8s resources.
func NewValidateCommand(istioNamespace *string) *cobra.Command {
	var filenames []string
	var referential bool

	c := &cobra.Command{
		Use:   "validate -f FILENAME [options]",
		Short: "Validate Istio policy and rules files",
		Example: `
		# Validate bookinfo-gateway.yaml
		istioctl validate -f bookinfo-gateway.yaml

		# Validate current deployments under 'default' namespace within the cluster
		kubectl get deployments -o yaml |istioctl validate -f -

		# Validate current services under 'default' namespace within the cluster
		kubectl get services -o yaml |istioctl validate -f -

		# Also see the related command 'istioctl analyze'
		istioctl analyze samples/bookinfo/networking/bookinfo-gateway.yaml
`,
		Args: cobra.NoArgs,
		RunE: func(c *cobra.Command, _ []string) error {
			return validateFiles(istioNamespace, filenames, c.OutOrStderr())
		},
	}

	flags := c.PersistentFlags()
	flags.StringSliceVarP(&filenames, "filename", "f", nil, "Names of files to validate")
	flags.BoolVarP(&referential, "referential", "x", true, "Enable structural validation for policy and telemetry")

	return c
}

func transformInterfaceArray(in []interface{}) []interface{} {
	out := make([]interface{}, len(in))
	for i, v := range in {
		out[i] = transformMapValue(v)
	}
	return out
}

func transformInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
	out := make(map[string]interface{}, len(in))
	for k, v := range in {
		out[fmt.Sprintf("%v", k)] = transformMapValue(v)
	}
	return out
}

func transformMapValue(in interface{}) interface{} {
	switch v := in.(type) {
	case []interface{}:
		return transformInterfaceArray(v)
	case map[interface{}]interface{}:
		return transformInterfaceMap(v)
	default:
		return v
	}
}

func servicePortPrefixed(n string) bool {
	i := strings.IndexByte(n, '-')
	if i >= 0 {
		n = n[:i]
	}
	p := protocol.Parse(n)
	return p == protocol.Unsupported
}

func handleNamespace(istioNamespace string) string {
	if istioNamespace == "" {
		istioNamespace = controller.IstioNamespace
	}
	return istioNamespace
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func convertObjectFromUnstructured(schema collection.Schema, un *unstructured.Unstructured, domain string) (*model.Config, error) {
	data, err := fromSchemaAndJSONMap(schema, un.Object["spec"])
	if err != nil {
		return nil, err
	}

	return &model.Config{
		ConfigMeta: model.ConfigMeta{
			GroupVersionKind:  schema.Resource().GroupVersionKind(),
			Name:              un.GetName(),
			Namespace:         un.GetNamespace(),
			Domain:            domain,
			Labels:            un.GetLabels(),
			Annotations:       un.GetAnnotations(),
			ResourceVersion:   un.GetResourceVersion(),
			CreationTimestamp: un.GetCreationTimestamp().Time,
		},
		Spec: data,
	}, nil
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func fromSchemaAndYAML(schema collection.Schema, yml string) (proto.Message, error) {
	pb, err := schema.Resource().NewProtoInstance()
	if err != nil {
		return nil, err
	}
	if err = gogoprotomarshal.ApplyYAMLStrict(yml, pb); err != nil {
		return nil, err
	}
	return pb, nil
}

// TODO(nmittler): Remove this once Pilot migrates to galley schema.
func fromSchemaAndJSONMap(schema collection.Schema, data interface{}) (proto.Message, error) {
	// Marshal to YAML bytes
	str, err := yaml.Marshal(data)
	if err != nil {
		return nil, err
	}
	out, err := fromSchemaAndYAML(schema, string(str))
	if err != nil {
		return nil, multierror.Prefix(err, fmt.Sprintf("YAML decoding error: %v", string(str)))
	}
	return out, nil
}
<|endoftext|>


<commit_before>package azurerm

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/jen20/riviera/azure"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceArmAvailabilitySet() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmAvailabilitySetCreate,
		Read:   resourceArmAvailabilitySetRead,
		Update: resourceArmAvailabilitySetCreate,
		Delete: resourceArmAvailabilitySetDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"location": locationSchema(),

			"platform_update_domain_count": {
				Type:     schema.TypeInt,
				Optional: true,
				Default:  5,
				ForceNew: true,
				ValidateFunc: validation.IntBetween(1, 20),
			},

			"platform_fault_domain_count": {
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
				ForceNew: true,
				ValidateFunc: validation.IntBetween(1, 3),
			},

			"managed": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
				ForceNew: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmAvailabilitySetCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	log.Printf("[INFO] preparing arguments for AzureRM Availability Set creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	updateDomainCount := d.Get("platform_update_domain_count").(int)
	faultDomainCount := d.Get("platform_fault_domain_count").(int)
	managed := d.Get("managed").(bool)
	tags := d.Get("tags").(map[string]interface{})

	availSet := compute.AvailabilitySet{
		Name:     &name,
		Location: &location,
		AvailabilitySetProperties: &compute.AvailabilitySetProperties{
			PlatformFaultDomainCount:  azure.Int32(int32(faultDomainCount)),
			PlatformUpdateDomainCount: azure.Int32(int32(updateDomainCount)),
		},
		Tags: expandTags(tags),
	}

	if managed == true {
		n := "Aligned"
		availSet.Sku = &compute.Sku{
			Name: &n,
		}
	}

	resp, err := client.CreateOrUpdate(resGroup, name, availSet)
	if err != nil {
		return err
	}

	d.SetId(*resp.ID)

	return resourceArmAvailabilitySetRead(d, meta)
}

func resourceArmAvailabilitySetRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["availabilitySets"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Azure Availability Set %s: %s", name, err)
	}

	availSet := *resp.AvailabilitySetProperties
	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("platform_update_domain_count", availSet.PlatformUpdateDomainCount)
	d.Set("platform_fault_domain_count", availSet.PlatformFaultDomainCount)

	if resp.Sku != nil && resp.Sku.Name != nil {
		d.Set("managed", strings.EqualFold(*resp.Sku.Name, "Aligned"))
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmAvailabilitySetDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["availabilitySets"]

	_, err = client.Delete(resGroup, name)

	return err
}
<commit_msg>Linting<commit_after>package azurerm

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/jen20/riviera/azure"
)

func resourceArmAvailabilitySet() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmAvailabilitySetCreate,
		Read:   resourceArmAvailabilitySetRead,
		Update: resourceArmAvailabilitySetCreate,
		Delete: resourceArmAvailabilitySetDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"location": locationSchema(),

			"platform_update_domain_count": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      5,
				ForceNew:     true,
				ValidateFunc: validation.IntBetween(1, 20),
			},

			"platform_fault_domain_count": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      3,
				ForceNew:     true,
				ValidateFunc: validation.IntBetween(1, 3),
			},

			"managed": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
				ForceNew: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmAvailabilitySetCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	log.Printf("[INFO] preparing arguments for AzureRM Availability Set creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	updateDomainCount := d.Get("platform_update_domain_count").(int)
	faultDomainCount := d.Get("platform_fault_domain_count").(int)
	managed := d.Get("managed").(bool)
	tags := d.Get("tags").(map[string]interface{})

	availSet := compute.AvailabilitySet{
		Name:     &name,
		Location: &location,
		AvailabilitySetProperties: &compute.AvailabilitySetProperties{
			PlatformFaultDomainCount:  azure.Int32(int32(faultDomainCount)),
			PlatformUpdateDomainCount: azure.Int32(int32(updateDomainCount)),
		},
		Tags: expandTags(tags),
	}

	if managed == true {
		n := "Aligned"
		availSet.Sku = &compute.Sku{
			Name: &n,
		}
	}

	resp, err := client.CreateOrUpdate(resGroup, name, availSet)
	if err != nil {
		return err
	}

	d.SetId(*resp.ID)

	return resourceArmAvailabilitySetRead(d, meta)
}

func resourceArmAvailabilitySetRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["availabilitySets"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Azure Availability Set %s: %s", name, err)
	}

	availSet := *resp.AvailabilitySetProperties
	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("platform_update_domain_count", availSet.PlatformUpdateDomainCount)
	d.Set("platform_fault_domain_count", availSet.PlatformFaultDomainCount)

	if resp.Sku != nil && resp.Sku.Name != nil {
		d.Set("managed", strings.EqualFold(*resp.Sku.Name, "Aligned"))
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmAvailabilitySetDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).availSetClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["availabilitySets"]

	_, err = client.Delete(resGroup, name)

	return err
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package services

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"
)

const (
	// DefaultAPIGroup is a default group of permissions API,
	// lets us to add different permission types
	DefaultAPIGroup = "gravitational.io/teleport"

	// ActionRead grants read access (get, list)
	ActionRead = "read"

	// ActionWrite allows to write (create, update, delete)
	ActionWrite = "write"

	// Wildcard is a special wildcard character matching everything
	Wildcard = "*"

	// DefaultNamespace is a default namespace of all resources
	DefaultNamespace = "default"

	// KindRole is a resource of kind role
	KindRole = "role"

	// V1 is our current version
	V1 = "v1"
)

// Role contains a set of permissions or settings
type Role interface {
	// GetMetadata returns role metadata
	GetMetadata() Metadata
	// GetMaxSessionTTL is a maximum SSH or Web session TTL
	GetMaxSessionTTL() Duration
	// GetLogins returns a list of linux logins allowed for this role
	GetLogins() []string
	// GetNodeLabels returns a list of matchign nodes this role has access to
	GetNodeLabels() map[string]string
	// GetNamespaces returns a list of namespaces this role has access to
	GetNamespaces() []string
	// GetResources returns access to resources
	GetResources() map[string][]string
}

// Metadata is resource metadata
type Metadata struct {
	// Name is an object name
	Name string `json:"name"`
	// Namespace is object namespace
	Namespace string `json:"namespace"`
	// Description is object description
	Description string `json:"description"`
	// Labels is a set of labels
	Labels map[string]string `json:"labels,omitempty"`
}

// RoleResource represents role resource specification
type RoleResource struct {
	// Kind is a resource kind - always resource
	Kind string `json:"kind"`
	// Version is a resource version
	Version string `json:"version"`
	// Metadata is Role metadata
	Metadata Metadata `json:"metadata"`
	// Spec contains role specification
	Spec RoleSpec `json:"spec"`
}

// GetMetadata returns role metadata
func (r *RoleResource) GetMetadata() Metadata {
	return r.Metadata
}

// GetMaxSessionTTL is a maximum SSH or Web session TTL
func (r *RoleResource) GetMaxSessionTTL() Duration {
	return r.Spec.MaxSessionTTL
}

// GetLogins returns a list of linux logins allowed for this role
func (r *RoleResource) GetLogins() []string {
	return r.Spec.Logins
}

// GetNodeLabels returns a list of matchign nodes this role has access to
func (r *RoleResource) GetNodeLabels() map[string]string {
	return r.Spec.NodeLabels
}

// GetNamespaces returns a list of namespaces this role has access to
func (r *RoleResource) GetNamespaces() []string {
	return r.Spec.Namespaces
}

// GetResources returns access to resources
func (r *RoleResource) GetResources() map[string][]string {
	return r.Spec.Resources
}

// RoleSpec is role specification
type RoleSpec struct {
	// MaxSessionTTL is a maximum SSH or Web session TTL
	MaxSessionTTL Duration `json:"max_session_ttl"`
	// Logins is a list of linux logins allowed for this role
	Logins []string `json:"logins,omitempty"`
	// NodeLabels is a set of matching labels that users of this role
	// will be allowed to access
	NodeLabels map[string]string `json:"node_labels,omitempty"`
	// Namespaces is a list of namespaces, guarding accesss to resources
	Namespaces []string `json:"namespaces,omitempty"`
	// Resources limits access to resources
	Resources map[string][]string `json:"resources,omitempty"`
}

// Duration is a wrapper around duration to set up custom marshal/unmarshal
type Duration struct {
	time.Duration
}

// MarshalJSON marshals Duration to string
func (d Duration) MarshalJSON() ([]byte, error) {
	return json.Marshal(fmt.Sprintf("%v", d.Duration))
}

// UnmarshalJSON marshals Duration to string
func (d *Duration) UnmarshalJSON(data []byte) error {
	if len(data) == 0 {
		return nil
	}
	var stringVar string
	if err := json.Unmarshal(data, &stringVar); err != nil {
		return trace.Wrap(err)
	}
	out, err := time.ParseDuration(stringVar)
	if err != nil {
		return trace.BadParameter(err.Error())
	}
	d.Duration = out
	return nil
}

const MetadataSchema = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "required": ["name"],
  "properties": {
    "name": {"type": "string"},
    "namespace": {"type": "string", "default": "default"},
    "description": {"type": "string"},
    "labels": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "string" }
      }
    }
  }
}`

const RoleSpecSchema = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "properties": {
    "max_session_ttl": {"type": "string"},
    "node_labels": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "string" }
      }
    },
    "namespaces": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "resources": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "array", "items": {"type": "string"} }
       }
    }
  }
}`

var RoleSchema = fmt.Sprintf(`{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "required": ["kind", "spec", "metadata"],
  "properties": {
    "kind": {"type": "string"},
    "version": {"type": "string", "default": "v1"},
    "metadata": %v,
    "spec": %v
  }
}`, MetadataSchema, RoleSpecSchema)

const NodeSelectorSchema = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "properties": {
    "match_labels": {
      "type": "object",
      "default": {},
      "additionalProperties": false,
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "string" }
      }
    },
    "namespaces": {
      "type": "object",
      "default": {},
      "additionalProperties": false,
      "patternProperties": {
         ".*":  { "type": "boolean" }
      }
    }
  }
}`

// UnmarshalRoleResource unmarshals role from JSON or YAML,
// sets defaults and checks the schema
func UnmarshalRoleResource(data []byte) (*RoleResource, error) {
	if len(data) == 0 {
		return nil, trace.BadParameter("empty input")
	}
	var role RoleResource
	if err := utils.UnmarshalWithSchema(RoleSchema, &role, data); err != nil {
		return nil, trace.BadParameter(err.Error())
	}
	return &role, nil
}

var roleMarshaler RoleMarshaler = &TeleportRoleMarshaler{}

func SetRoleMarshaler(u RoleMarshaler) {
	mtx.Lock()
	defer mtx.Unlock()
	roleMarshaler = u
}

func GetRoleMarshaler() RoleMarshaler {
	mtx.Lock()
	defer mtx.Unlock()
	return roleMarshaler
}

// RoleMarshaler implements marshal/unmarshal of Role implementations
// mostly adds support for extended versions
type RoleMarshaler interface {
	// UnmarshalRole from binary representation
	UnmarshalRole(bytes []byte) (Role, error)
	// MarshalRole to binary representation
	MarshalRole(u Role) ([]byte, error)
}

type TeleportRoleMarshaler struct{}

// UnmarshalRole unmarshals role from JSON
func (*TeleportRoleMarshaler) UnmarshalRole(bytes []byte) (Role, error) {
	return UnmarshalRoleResource(bytes)
}

// MarshalRole marshalls role into JSON
func (*TeleportRoleMarshaler) MarshalRole(u Role) ([]byte, error) {
	return json.Marshal(u)
}
<commit_msg>add support for role schema extensions<commit_after>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package services

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"
)

const (
	// DefaultAPIGroup is a default group of permissions API,
	// lets us to add different permission types
	DefaultAPIGroup = "gravitational.io/teleport"

	// ActionRead grants read access (get, list)
	ActionRead = "read"

	// ActionWrite allows to write (create, update, delete)
	ActionWrite = "write"

	// Wildcard is a special wildcard character matching everything
	Wildcard = "*"

	// DefaultNamespace is a default namespace of all resources
	DefaultNamespace = "default"

	// KindRole is a resource of kind role
	KindRole = "role"

	// V1 is our current version
	V1 = "v1"
)

// Role contains a set of permissions or settings
type Role interface {
	// GetMetadata returns role metadata
	GetMetadata() Metadata
	// GetMaxSessionTTL is a maximum SSH or Web session TTL
	GetMaxSessionTTL() Duration
	// GetLogins returns a list of linux logins allowed for this role
	GetLogins() []string
	// GetNodeLabels returns a list of matchign nodes this role has access to
	GetNodeLabels() map[string]string
	// GetNamespaces returns a list of namespaces this role has access to
	GetNamespaces() []string
	// GetResources returns access to resources
	GetResources() map[string][]string
}

// Metadata is resource metadata
type Metadata struct {
	// Name is an object name
	Name string `json:"name"`
	// Namespace is object namespace
	Namespace string `json:"namespace"`
	// Description is object description
	Description string `json:"description"`
	// Labels is a set of labels
	Labels map[string]string `json:"labels,omitempty"`
}

// RoleResource represents role resource specification
type RoleResource struct {
	// Kind is a resource kind - always resource
	Kind string `json:"kind"`
	// Version is a resource version
	Version string `json:"version"`
	// Metadata is Role metadata
	Metadata Metadata `json:"metadata"`
	// Spec contains role specification
	Spec RoleSpec `json:"spec"`
}

// GetMetadata returns role metadata
func (r *RoleResource) GetMetadata() Metadata {
	return r.Metadata
}

// GetMaxSessionTTL is a maximum SSH or Web session TTL
func (r *RoleResource) GetMaxSessionTTL() Duration {
	return r.Spec.MaxSessionTTL
}

// GetLogins returns a list of linux logins allowed for this role
func (r *RoleResource) GetLogins() []string {
	return r.Spec.Logins
}

// GetNodeLabels returns a list of matchign nodes this role has access to
func (r *RoleResource) GetNodeLabels() map[string]string {
	return r.Spec.NodeLabels
}

// GetNamespaces returns a list of namespaces this role has access to
func (r *RoleResource) GetNamespaces() []string {
	return r.Spec.Namespaces
}

// GetResources returns access to resources
func (r *RoleResource) GetResources() map[string][]string {
	return r.Spec.Resources
}

// RoleSpec is role specification
type RoleSpec struct {
	// MaxSessionTTL is a maximum SSH or Web session TTL
	MaxSessionTTL Duration `json:"max_session_ttl"`
	// Logins is a list of linux logins allowed for this role
	Logins []string `json:"logins,omitempty"`
	// NodeLabels is a set of matching labels that users of this role
	// will be allowed to access
	NodeLabels map[string]string `json:"node_labels,omitempty"`
	// Namespaces is a list of namespaces, guarding accesss to resources
	Namespaces []string `json:"namespaces,omitempty"`
	// Resources limits access to resources
	Resources map[string][]string `json:"resources,omitempty"`
}

// Duration is a wrapper around duration to set up custom marshal/unmarshal
type Duration struct {
	time.Duration
}

// MarshalJSON marshals Duration to string
func (d Duration) MarshalJSON() ([]byte, error) {
	return json.Marshal(fmt.Sprintf("%v", d.Duration))
}

// UnmarshalJSON marshals Duration to string
func (d *Duration) UnmarshalJSON(data []byte) error {
	if len(data) == 0 {
		return nil
	}
	var stringVar string
	if err := json.Unmarshal(data, &stringVar); err != nil {
		return trace.Wrap(err)
	}
	out, err := time.ParseDuration(stringVar)
	if err != nil {
		return trace.BadParameter(err.Error())
	}
	d.Duration = out
	return nil
}

const MetadataSchema = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "required": ["name"],
  "properties": {
    "name": {"type": "string"},
    "namespace": {"type": "string", "default": "default"},
    "description": {"type": "string"},
    "labels": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "string" }
      }
    }
  }
}`

const RoleSpecSchemaTemplate = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "properties": {
    "max_session_ttl": {"type": "string"},
    "node_labels": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "string" }
      }
    },
    "namespaces": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "resources": {
      "type": "object",
      "patternProperties": {
         "^[a-zA-Z/.0-9_]$":  { "type": "array", "items": {"type": "string"} }
       }
    },
    "extensions": %v
  }
}`

const RoleSchemaTemplate = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "required": ["kind", "spec", "metadata"],
  "properties": {
    "kind": {"type": "string"},
    "version": {"type": "string", "default": "v1"},
    "metadata": %v,
    "spec": %v
  }
}`

// GetRoleSchema returns role schema with optionally injected
// schema for extensions
func GetRoleSchema(extensionSchema string) string {
	var roleSchema string
	if extensionSchema == "" {
		roleSchema = fmt.Sprintf(RoleSpecSchemaTemplate, `{"type": "object"}`)
	} else {
		roleSchema = fmt.Sprintf(RoleSpecSchemaTemplate, extensionSchema)
	}
	return fmt.Sprintf(RoleSchemaTemplate, MetadataSchema, roleSchema)
}

// UnmarshalRoleResource unmarshals role from JSON or YAML,
// sets defaults and checks the schema
func UnmarshalRoleResource(data []byte) (*RoleResource, error) {
	if len(data) == 0 {
		return nil, trace.BadParameter("empty input")
	}
	var role RoleResource
	if err := utils.UnmarshalWithSchema(GetRoleSchema(""), &role, data); err != nil {
		return nil, trace.BadParameter(err.Error())
	}
	return &role, nil
}

var roleMarshaler RoleMarshaler = &TeleportRoleMarshaler{}

func SetRoleMarshaler(u RoleMarshaler) {
	mtx.Lock()
	defer mtx.Unlock()
	roleMarshaler = u
}

func GetRoleMarshaler() RoleMarshaler {
	mtx.Lock()
	defer mtx.Unlock()
	return roleMarshaler
}

// RoleMarshaler implements marshal/unmarshal of Role implementations
// mostly adds support for extended versions
type RoleMarshaler interface {
	// UnmarshalRole from binary representation
	UnmarshalRole(bytes []byte) (Role, error)
	// MarshalRole to binary representation
	MarshalRole(u Role) ([]byte, error)
}

type TeleportRoleMarshaler struct{}

// UnmarshalRole unmarshals role from JSON
func (*TeleportRoleMarshaler) UnmarshalRole(bytes []byte) (Role, error) {
	return UnmarshalRoleResource(bytes)
}

// MarshalRole marshalls role into JSON
func (*TeleportRoleMarshaler) MarshalRole(u Role) ([]byte, error) {
	return json.Marshal(u)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"math/rand"
)

// Tree data structure and methods taken from https://golang.org/doc/play/tree.go
func main() {
	// Tree data structure where attempts are stored
	guessingTree := &tree{}

	// s1 := rand.NewSource(time.Now().UnixNano())
	// randomNumber := rand.New(s1)

	// Debug
	randomNumber := uint16(rand.Intn(9999) + 1)
	var triedNumber uint16
	var found bool

	// Title
	fmt.Println("Guessing Game")
	fmt.Println("-------------\n")

	// Instructions
	fmt.Println("Guess a number positive integer between 1 and 10000 inclusive.")
	fmt.Println("Press '0' to exit.\n")

	// Debug
	fmt.Printf("Debug: randomNumber: %d\n\n", randomNumber)

	// Capture the first try
	fmt.Print("Guess a number: ")
	fmt.Scan(&triedNumber)

	// Main game loop
	for triedNumber != 0 {
		// Debug
		fmt.Printf("Debug: triedNumber: %d\n\n", triedNumber)

		guessingTree.insert(randomNumber)
		// Debug
		fmt.Println("Debug: tree: traverse:")
		guessingTree.traverse(guessingTree.Root, func(n *node) { fmt.Printf("Value: %d | ", n.Value) })
		fmt.Println()

		found = guessingTree.find(triedNumber)
		// Debug
		fmt.Println("Debug: tree: find: ", found, "\n")

		if found {
			// Force loop exit
			triedNumber = 0
		} else {
			guessingTree.insert(triedNumber)
			// Debug
			fmt.Println("Debug: tree: traverse:")
			guessingTree.traverse(guessingTree.Root, func(n *node) { fmt.Printf("Value: %d | ", n.Value) })
			fmt.Println()
		}

		// Subsequent capture
		fmt.Print("Guess a number: ")
		fmt.Scan(&triedNumber)

	} // for - main game loop

} // main

// I searched for a Go implementation of a binary tree in Go's API and didn't find one.
// Tree data structure taken and adapted from https://appliedgo.net/bintree/
// Node
type node struct {
	Value uint16
	Left  *node
	Right *node
} // node

func (n *node) insert(value uint16) error {
	if n == nil {
		return errors.New("Cannot insert a value into a nil tree")
	}

	switch {
	case value == n.Value:
		return nil
	case value < n.Value:
		if n.Left == nil {
			n.Left = &node{Value: value}
			return nil
		}
		return n.Left.insert(value)
	case value > n.Value:
		if n.Right == nil {
			n.Right = &node{Value: value}
			return nil
		}
		return n.Right.insert(value)
	}
	return nil
} // insert

func (n *node) find(s uint16) bool {

	if n == nil {
		// Return value of '0' means 'not found'
		return false
	}

	switch {
	case s == n.Value:
		// Debug
		fmt.Println("Debug: node: find: s: ", s)
		return true
	case s < n.Value:
		return n.Left.find(s)
	default:
		return n.Right.find(s)
	}
} // find

func (n *node) findMax(parent *node) (*node, *node) {
	if n.Right == nil {
		return n, parent
	}
	return n.Right.findMax(n)
} // findMax

// replaceNode replaces the parent’s child pointer to n with a pointer to the replacement node. parent must not be nil.
func (n *node) replaceNode(parent, replacement *node) error {
	if n == nil {
		return errors.New("replaceNode() not allowed on a nil node")
	}

	if n == parent.Left {
		parent.Left = replacement
		return nil
	}
	parent.Right = replacement
	return nil
} // replaceNode

// Delete removes an element from the tree. It is an error to try deleting an element that does not exist. In order to remove an element properly, Delete needs to know the node’s parent node. parent must not be nil.
func (n *node) delete(s uint16, parent *node) error {
	if n == nil {
		return errors.New("Value to be deleted does not exist in the tree")
	}
	// Search the node to be deleted.
	switch {
	case s < n.Value:
		return n.Left.delete(s, n)
	case s > n.Value:
		return n.Right.delete(s, n)
	default:
		// We found the node to be deleted. If the node has no children, simply remove it from its parent.
		if n.Left == nil && n.Right == nil {
			n.replaceNode(parent, nil)
			return nil
		}
		// If the node has one child: Replace the node with its child.
		if n.Left == nil {
			n.replaceNode(parent, n.Right)
			return nil
		}
		if n.Right == nil {
			n.replaceNode(parent, n.Left)
			return nil
		}
		// If the node has two children: Find the maximum element in the left subtree…
		replacement, replParent := n.Left.findMax(n)
		// …and replace the node’s value and data with the replacement’s value and data.
		n.Value = replacement.Value
		// Then remove the replacement node.
		return replacement.delete(replacement.Value, replParent)
	}
}

// Tree
type tree struct {
	Root *node
}

func (t *tree) insert(value uint16) error {
	// If the tree is empty, create a new node,…
	if t.Root == nil {
		t.Root = &node{Value: value}
		return nil
	}
	// …else call Node.Insert.
	return t.Root.insert(value)
}

// Find calls Node.Find unless the root node is nil
func (t *tree) find(s uint16) bool {
	if t.Root == nil {
		return false
	}
	return t.Root.find(s)
}

// Delete has one special case: the empty tree. (And deleting from an empty tree is an error.) In all other cases, it calls Node.Delete.
func (t *tree) delete(s uint16) error {

	if t.Root == nil {
		return errors.New("Cannot delete from an empty tree")
	}
	// CallNode.Delete. Passing a “fake” parent node here almost avoids having to treat the root node as a special case, with one exception.
	fakeParent := &node{Right: t.Root}
	err := t.Root.delete(s, fakeParent)
	if err != nil {
		return err
	}
	// If the root node is the only node in the tree, and if it is deleted, then it only got removed from fakeParent. t.Root still points to the old node. We rectify this by setting t.Root to nil.
	if fakeParent.Right == nil {
		t.Root = nil
	}
	return nil
}

// Traverse is a simple method that traverses the tree in left-to-right order (which, by pure incidence ;-), is the same as traversing from smallest to largest value) and calls a custom function on each node.
func (t *tree) traverse(n *node, f func(*node)) {
	if n == nil {
		return
	}
	t.traverse(n.Left, f)
	f(n)
	t.traverse(n.Right, f)
}
<commit_msg>Binary Search Tree implemented<commit_after>package main

import (
	"errors"
	"fmt"
	"math/rand"
)

func main() {
	// Tree data structure where attempts are stored
	guessingTree := &tree{}

	// s1 := rand.NewSource(time.Now().UnixNano())
	// randomNumber := rand.New(s1)

	// Debug
	randomNumber := uint16(rand.Intn(9999) + 1)
	var triedNumber uint16
	var found bool
	var tryCounter, previousTriedNumber, foundValue uint16

	// Title
	fmt.Println("Guessing Game")
	fmt.Println("-------------\n")

	// Instructions
	fmt.Println("Guess a number positive integer between 1 and 10000 inclusive.")
	fmt.Println("Press '0' to exit.\n")

	// Debug
	fmt.Printf("Debug: randomNumber: %d\n\n", randomNumber)

	// Let's make the 'randomNumber' the root of the 'guessingTree'
	guessingTree.insert(randomNumber)

	// Debug
	// fmt.Println("Debug: tree: traverse:")
	// guessingTree.traverse(guessingTree.Root, func(n *node) { fmt.Printf("Value: %d | ", n.Value) })
	// fmt.Println()

	// Capture the first try
	fmt.Print("Guess a number: ")
	fmt.Scan(&triedNumber)

	// Main game loop
	for triedNumber != 0 {
		// Debug
		fmt.Printf("Debug: triedNumber: %d\n\n", triedNumber)

		foundValue, found = guessingTree.find(triedNumber)
		// Debug
		fmt.Println("Debug: tree: find: ", found, "\n")

		// 'randomNumber' was guessed
		if foundValue == randomNumber {
			// Count this try
			tryCounter++
			// Debug
			fmt.Println("Debug: for loop: tryCounter: ", tryCounter, "\n")

			// Force loop exit
			// triedNumber = 0
			break;

		// 'triedNumber' is found in 'guessingTree'
		} else if foundValue != randomNumber && found {
			// Debug
			fmt.Println("Debug: for loop: previousTriedNumber: ", previousTriedNumber, "\n")

			// Avoid counting the same number multiple times consecutively
			if previousTriedNumber != triedNumber {
				tryCounter++
				// Debug
				fmt.Println("Debug: for loop: tryCounter: ", tryCounter, "\n")
			}

			// Save 'triedNumber' value for next iteration
			previousTriedNumber = triedNumber
			// Debug
			fmt.Println("Debug: for loop: previousTriedNumber: ", previousTriedNumber)
			fmt.Println("Debug: for loop: triedNumber: ", triedNumber)
			fmt.Println("Debug: for loop: tryCounter: ", tryCounter, "\n")

		// 'triedNumber' is not in 'guessingTree'
		} else {
			// Add the new 'triedNumber' into the 'guessingTree'
			guessingTree.insert(triedNumber)
			// Debug
			fmt.Println("Debug: tree: traverse:")
			guessingTree.traverse(guessingTree.Root, func(n *node) { fmt.Printf("Value: %d | ", n.Value) })
			fmt.Println()

			// Save 'triedNumber' value for next iteration
			previousTriedNumber = triedNumber
			tryCounter++
			// Debug
			fmt.Println("Debug: for loop: previousTriedNumber: ", previousTriedNumber)
			fmt.Println("Debug: for loop: triedNumber: ", triedNumber)
			fmt.Println("Debug: for loop: tryCounter: ", tryCounter, "\n")

		} // if - else if - else

		// Subsequent capture
		fmt.Print("Guess a number: ")
		fmt.Scan(&triedNumber)

	} // for - main game loop

} // main

// I searched for a Go implementation of a binary tree in Go's API and didn't find one.
// Tree data structure taken and adapted from https://appliedgo.net/bintree/

// Node
type node struct {
	Value uint16
	Left  *node
	Right *node
} // node

func (n *node) insert(value uint16) error {
	if n == nil {
		return errors.New("Cannot insert a value into a nil tree")
	}

	switch {
	case value == n.Value:
		return nil
	case value < n.Value:
		if n.Left == nil {
			n.Left = &node{Value: value}
			return nil
		}
		return n.Left.insert(value)
	case value > n.Value:
		if n.Right == nil {
			n.Right = &node{Value: value}
			return nil
		}
		return n.Right.insert(value)
	}
	return nil
} // node::insert

func (n *node) find(s uint16) (uint16, bool) {

	if n == nil {
		// Return value of '0' means 'not found'
		return 0, false
	}

	switch {
	case s == n.Value:
		// Debug
		fmt.Println("Debug: node: find: s: ", s)
		return n.Value, true
	case s < n.Value:
		return n.Left.find(s)
	default:
		return n.Right.find(s)
	}
} // node::find

func (n *node) findMax(parent *node) (*node, *node) {
	if n.Right == nil {
		return n, parent
	}
	return n.Right.findMax(n)
} // node::findMax

// replaceNode replaces the parent’s child pointer to n with a pointer to the replacement node. parent must not be nil.
func (n *node) replaceNode(parent, replacement *node) error {
	if n == nil {
		return errors.New("replaceNode() not allowed on a nil node")
	}

	if n == parent.Left {
		parent.Left = replacement
		return nil
	}
	parent.Right = replacement
	return nil
} // node::replaceNode

// Delete removes an element from the tree. It is an error to try deleting an element that does not exist. In order to remove an element properly, Delete needs to know the node’s parent node. parent must not be nil.
func (n *node) delete(s uint16, parent *node) error {
	if n == nil {
		return errors.New("Value to be deleted does not exist in the tree")
	}
	// Search the node to be deleted.
	switch {
	case s < n.Value:
		return n.Left.delete(s, n)
	case s > n.Value:
		return n.Right.delete(s, n)
	default:
		// We found the node to be deleted. If the node has no children, simply remove it from its parent.
		if n.Left == nil && n.Right == nil {
			n.replaceNode(parent, nil)
			return nil
		}
		// If the node has one child: Replace the node with its child.
		if n.Left == nil {
			n.replaceNode(parent, n.Right)
			return nil
		}
		if n.Right == nil {
			n.replaceNode(parent, n.Left)
			return nil
		}
		// If the node has two children: Find the maximum element in the left subtree…
		replacement, replParent := n.Left.findMax(n)
		// …and replace the node’s value and data with the replacement’s value and data.
		n.Value = replacement.Value
		// Then remove the replacement node.
		return replacement.delete(replacement.Value, replParent)
	}
} // node::delete

// Tree
type tree struct {
	Root *node
}

func (t *tree) insert(value uint16) error {
	// If the tree is empty, create a new node,…
	if t.Root == nil {
		t.Root = &node{Value: value}
		return nil
	}
	// …else call Node.Insert.
	return t.Root.insert(value)
} // tree::insert

// Find calls Node.Find unless the root node is nil
func (t *tree) find(s uint16) (uint16, bool) {
	if t.Root == nil {
		// Return value of '0' means 'not found'
		return 0, false
	}
	return t.Root.find(s)
} // tree::find

// Delete has one special case: the empty tree. (And deleting from an empty tree is an error.) In all other cases, it calls Node.Delete.
func (t *tree) delete(s uint16) error {

	if t.Root == nil {
		return errors.New("Cannot delete from an empty tree")
	}
	// CallNode.Delete. Passing a “fake” parent node here almost avoids having to treat the root node as a special case, with one exception.
	fakeParent := &node{Right: t.Root}
	err := t.Root.delete(s, fakeParent)
	if err != nil {
		return err
	}
	// If the root node is the only node in the tree, and if it is deleted, then it only got removed from fakeParent. t.Root still points to the old node. We rectify this by setting t.Root to nil.
	if fakeParent.Right == nil {
		t.Root = nil
	}
	return nil
} // tree::delete

// Traverse is a simple method that traverses the tree in left-to-right order (which, by pure incidence ;-), is the same as traversing from smallest to largest value) and calls a custom function on each node.
func (t *tree) traverse(n *node, f func(*node)) {
	if n == nil {
		return
	}
	t.traverse(n.Left, f)
	f(n)
	t.traverse(n.Right, f)
} // tree::traverse
<|endoftext|>


<commit_before>package ssh

import (
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"golang.org/x/crypto/ssh/terminal"

	"bufio"
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"sync"
)

type SSHCommander struct {
	ssh_config  *ssh.ClientConfig
	sshAuthSock net.Conn
	addr        string
	sudo        bool
}

func (sshCmd *SSHCommander) connect() (*ssh.Session, error) {
	cli, err := ssh.Dial("tcp", sshCmd.addr, sshCmd.ssh_config)
	if err != nil {
		return nil, err
	} else {
		return cli.NewSession()
	}
}

func (sshCmd *SSHCommander) Host() (host, port string) {
	host, port, _ = net.SplitHostPort(sshCmd.addr)
	return
}

func (sshCmd *SSHCommander) Sudo() SudoSession {
	sshCmd.sudo = true
	return sshCmd
}

func (sshCmd *SSHCommander) StepDown() {
	sshCmd.sudo = false
}

func (sshCmd *SSHCommander) Load(target string, here io.Writer) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	r, _ := session.StdoutPipe()
	var ret = make(chan error)
	go func() {
		defer session.Close()
		var cmd = fmt.Sprint("cat ", target)
		if sshCmd.sudo {
			cmd = fmt.Sprintf("sudo -s %s", cmd)
		}
		ret <- session.Run(cmd)
	}()
	io.Copy(here, r)
	return <-ret
}

func (sshCmd *SSHCommander) LoadFile(target, here string, mode os.FileMode) error {
	buf := new(bytes.Buffer)
	err := sshCmd.Load(target, buf)
	if err != nil {
		return err
	}
	file, err := os.OpenFile(here, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = io.Copy(file, buf)
	return err
}

func (sshCmd *SSHCommander) Copy(src io.Reader, size int64, dst string, mode os.FileMode) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	defer session.Close()

	// setup remote path structure
	if err = sshCmd.Mkdir(filepath.Dir(dst)); err != nil {
		return err
	}

	perm, err := getFileMode(mode)
	if err != nil {
		return err
	}

	w, _ := session.StdinPipe()
	go func() {
		defer w.Close()
		// stream file content
		fmt.Fprintln(w, perm, size, filepath.Base(dst))
		io.Copy(w, src)
		fmt.Fprint(w, "\x00")
	}()

	// initiate scp on remote
	var cmd = fmt.Sprint("scp -t ", dst)
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	return session.Run(cmd)
}

func (sshCmd *SSHCommander) CopyFile(src, dst string, mode os.FileMode) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()
	info, err := file.Stat()
	if err != nil {
		return err
	}
	return sshCmd.Copy(file, info.Size(), dst, mode)
}

func (sshCmd *SSHCommander) Mkdir(path string) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	defer session.Close()
	// initiate mkdir on remote
	var cmd = fmt.Sprint("mkdir -p ", path)
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	return session.Run(cmd)
}

// buffer is a utility object for combined output
type buffer struct {
	sync.Mutex
	buf bytes.Buffer
}

func (b *buffer) Write(p []byte) (int, error) {
	b.Lock()
	defer b.Unlock()
	return b.buf.Write(p)
}

func (sshCmd *SSHCommander) Run(cmd string) (output string, err error) {
	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	defer session.Close()
	var b buffer
	session.Stdout = &b
	session.Stderr = &b
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	err = session.Run(cmd)
	output = b.buf.String()
	return
}

func (sshCmd *SSHCommander) RunQuiet(cmd string) (err error) {
	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	session.Stdout = nil
	session.Stderr = nil
	defer session.Close()
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	err = session.Run(cmd)
	return
}

func (sshCmd *SSHCommander) Shell() (err error) {
	var (
		termWidth, termHeight int
	)

	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	defer session.Close()

	// Attach host input, output
	session.Stdin = os.Stdin
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr

	modes := ssh.TerminalModes{
		ssh.ECHO: 1,
	}
	fd := os.Stdin.Fd()

	if terminal.IsTerminal(int(fd)) {
		oldState, err := terminal.MakeRaw(int(fd))
		if err != nil {
			return err
		}
		defer terminal.Restore(int(fd), oldState)

		termWidth, termHeight, err = terminal.GetSize(int(fd))
		if err != nil {
			termWidth = 80
			termHeight = 24
		}
	}

	err = session.RequestPty("xterm", termHeight, termWidth, modes)
	if err != nil {
		return
	}

	err = session.Shell()
	if err != nil {
		return
	}

	return session.Wait()
}

func (sshCmd *SSHCommander) Stream(cmd string) (<-chan Response, error) {
	session, err := sshCmd.connect()
	if err != nil {
		return nil, err
	}
	stdout, _ := session.StdoutPipe()
	stderr, _ := session.StderrPipe()
	output := make(chan Response)
	go func() {
		var reader = func(r io.Reader) <-chan string {
			var ch = make(chan string)
			go func() {
				defer close(ch)
				lnr := bufio.NewScanner(r)
				for lnr.Scan() {
					ch <- lnr.Text()
				}
			}()
			return ch
		}
		var ln string
		defer session.Close()
		defer close(output)
		stdOut, stdErr := reader(stdout), reader(stderr)
		for outOk, errOk := true, true; outOk || errOk; {
			select {
			case ln, outOk = <-stdOut:
				if outOk {
					output <- Response{text: ln}
				}
			case ln, errOk = <-stdErr:
				if errOk {
					output <- Response{text: ln}
				}
			}
		}
		output <- Response{err: session.Wait()}
	}()
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	if err := session.Start(cmd); err != nil {
		return nil, err
	} else {
		return output, nil
	}
}

func (sshCmd *SSHCommander) Close() error {
	if sshCmd.sshAuthSock != nil {
		return sshCmd.sshAuthSock.Close()
	} else {
		return nil
	}
}

func New(cfg Config) Commander {
	var (
		auths = []ssh.AuthMethod{}

		sshAuthSock net.Conn
	)
	if cfg.Password != "" {
		auths = append(auths, ssh.Password(cfg.Password))
	}
	if conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(conn).Signers))
		sshAuthSock = conn
	}
	if pubkey, err := cfg.GetKeyFile(); err == nil {
		auths = append(auths, ssh.PublicKeys(pubkey))
	}
	return &SSHCommander{
		ssh_config:  &ssh.ClientConfig{User: cfg.User, Auth: auths},
		sshAuthSock: sshAuthSock,
		addr:        cfg.Server + ":" + cfg.Port,
	}
}
<commit_msg>UPDATE: shell logout error is ignored<commit_after>package ssh

import (
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"golang.org/x/crypto/ssh/terminal"

	"bufio"
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"sync"
)

type SSHCommander struct {
	ssh_config  *ssh.ClientConfig
	sshAuthSock net.Conn
	addr        string
	sudo        bool
}

func (sshCmd *SSHCommander) connect() (*ssh.Session, error) {
	cli, err := ssh.Dial("tcp", sshCmd.addr, sshCmd.ssh_config)
	if err != nil {
		return nil, err
	} else {
		return cli.NewSession()
	}
}

func (sshCmd *SSHCommander) Host() (host, port string) {
	host, port, _ = net.SplitHostPort(sshCmd.addr)
	return
}

func (sshCmd *SSHCommander) Sudo() SudoSession {
	sshCmd.sudo = true
	return sshCmd
}

func (sshCmd *SSHCommander) StepDown() {
	sshCmd.sudo = false
}

func (sshCmd *SSHCommander) Load(target string, here io.Writer) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	r, _ := session.StdoutPipe()
	var ret = make(chan error)
	go func() {
		defer session.Close()
		var cmd = fmt.Sprint("cat ", target)
		if sshCmd.sudo {
			cmd = fmt.Sprintf("sudo -s %s", cmd)
		}
		ret <- session.Run(cmd)
	}()
	io.Copy(here, r)
	return <-ret
}

func (sshCmd *SSHCommander) LoadFile(target, here string, mode os.FileMode) error {
	buf := new(bytes.Buffer)
	err := sshCmd.Load(target, buf)
	if err != nil {
		return err
	}
	file, err := os.OpenFile(here, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = io.Copy(file, buf)
	return err
}

func (sshCmd *SSHCommander) Copy(src io.Reader, size int64, dst string, mode os.FileMode) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	defer session.Close()

	// setup remote path structure
	if err = sshCmd.Mkdir(filepath.Dir(dst)); err != nil {
		return err
	}

	perm, err := getFileMode(mode)
	if err != nil {
		return err
	}

	w, _ := session.StdinPipe()
	go func() {
		defer w.Close()
		// stream file content
		fmt.Fprintln(w, perm, size, filepath.Base(dst))
		io.Copy(w, src)
		fmt.Fprint(w, "\x00")
	}()

	// initiate scp on remote
	var cmd = fmt.Sprint("scp -t ", dst)
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	return session.Run(cmd)
}

func (sshCmd *SSHCommander) CopyFile(src, dst string, mode os.FileMode) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()
	info, err := file.Stat()
	if err != nil {
		return err
	}
	return sshCmd.Copy(file, info.Size(), dst, mode)
}

func (sshCmd *SSHCommander) Mkdir(path string) error {
	session, err := sshCmd.connect()
	if err != nil {
		return err
	}
	defer session.Close()
	// initiate mkdir on remote
	var cmd = fmt.Sprint("mkdir -p ", path)
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	return session.Run(cmd)
}

// buffer is a utility object for combined output
type buffer struct {
	sync.Mutex
	buf bytes.Buffer
}

func (b *buffer) Write(p []byte) (int, error) {
	b.Lock()
	defer b.Unlock()
	return b.buf.Write(p)
}

func (sshCmd *SSHCommander) Run(cmd string) (output string, err error) {
	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	defer session.Close()
	var b buffer
	session.Stdout = &b
	session.Stderr = &b
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	err = session.Run(cmd)
	output = b.buf.String()
	return
}

func (sshCmd *SSHCommander) RunQuiet(cmd string) (err error) {
	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	session.Stdout = nil
	session.Stderr = nil
	defer session.Close()
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	err = session.Run(cmd)
	return
}

func (sshCmd *SSHCommander) Shell() (err error) {
	var (
		termWidth, termHeight int
	)

	session, err := sshCmd.connect()
	if err != nil {
		return
	}
	defer session.Close()

	// Attach host input, output
	session.Stdin = os.Stdin
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr

	modes := ssh.TerminalModes{
		ssh.ECHO: 1,
	}
	fd := os.Stdin.Fd()

	if terminal.IsTerminal(int(fd)) {
		oldState, err := terminal.MakeRaw(int(fd))
		if err != nil {
			return err
		}
		defer terminal.Restore(int(fd), oldState)

		termWidth, termHeight, err = terminal.GetSize(int(fd))
		if err != nil {
			termWidth = 80
			termHeight = 24
		}
	}

	err = session.RequestPty("xterm", termHeight, termWidth, modes)
	if err != nil {
		return
	}

	err = session.Shell()
	if err != nil {
		return
	}

	session.Wait()
	return nil
}

func (sshCmd *SSHCommander) Stream(cmd string) (<-chan Response, error) {
	session, err := sshCmd.connect()
	if err != nil {
		return nil, err
	}
	stdout, _ := session.StdoutPipe()
	stderr, _ := session.StderrPipe()
	output := make(chan Response)
	go func() {
		var reader = func(r io.Reader) <-chan string {
			var ch = make(chan string)
			go func() {
				defer close(ch)
				lnr := bufio.NewScanner(r)
				for lnr.Scan() {
					ch <- lnr.Text()
				}
			}()
			return ch
		}
		var ln string
		defer session.Close()
		defer close(output)
		stdOut, stdErr := reader(stdout), reader(stderr)
		for outOk, errOk := true, true; outOk || errOk; {
			select {
			case ln, outOk = <-stdOut:
				if outOk {
					output <- Response{text: ln}
				}
			case ln, errOk = <-stdErr:
				if errOk {
					output <- Response{text: ln}
				}
			}
		}
		output <- Response{err: session.Wait()}
	}()
	if sshCmd.sudo {
		cmd = fmt.Sprintf("sudo -s %s", cmd)
	}
	if err := session.Start(cmd); err != nil {
		return nil, err
	} else {
		return output, nil
	}
}

func (sshCmd *SSHCommander) Close() error {
	if sshCmd.sshAuthSock != nil {
		return sshCmd.sshAuthSock.Close()
	} else {
		return nil
	}
}

func New(cfg Config) Commander {
	var (
		auths = []ssh.AuthMethod{}

		sshAuthSock net.Conn
	)
	if cfg.Password != "" {
		auths = append(auths, ssh.Password(cfg.Password))
	}
	if conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(conn).Signers))
		sshAuthSock = conn
	}
	if pubkey, err := cfg.GetKeyFile(); err == nil {
		auths = append(auths, ssh.PublicKeys(pubkey))
	}
	return &SSHCommander{
		ssh_config:  &ssh.ClientConfig{User: cfg.User, Auth: auths},
		sshAuthSock: sshAuthSock,
		addr:        cfg.Server + ":" + cfg.Port,
	}
}
<|endoftext|>