<commit_before>package main

//go:generate go run generate.go ../protocol/input/json.json ../../protocol/jsonrpc/build_test.go
//go:generate go run generate.go ../protocol/output/json.json ../../protocol/jsonrpc/unmarshal_test.go
//go:generate go run generate.go ../protocol/input/query.json ../../protocol/query/build_test.go
//go:generate go run generate.go ../protocol/output/query.json ../../protocol/query/unmarshal_test.go
//go:generate go run generate.go ../protocol/input/ec2.json ../../protocol/ec2query/build_test.go
//go:generate go run generate.go ../protocol/output/ec2.json ../../protocol/ec2query/unmarshal_test.go
//go:generate go run generate.go ../protocol/input/rest-json.json ../../protocol/restjson/build_test.go
//go:generate go run generate.go ../protocol/input/rest-xml.json ../../protocol/restxml/build_test.go
//go:generate go run generate.go ../protocol/output/rest-xml.json ../../protocol/restxml/unmarshal_test.go

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/awslabs/aws-sdk-go/internal/fixtures/helpers"
	"github.com/awslabs/aws-sdk-go/internal/model/api"
	"github.com/awslabs/aws-sdk-go/internal/util"
	"github.com/awslabs/aws-sdk-go/internal/util/utilassert"
)

type TestSuite struct {
	*api.API
	Description string
	Cases       []TestCase
	title       string
}

type TestCase struct {
	*TestSuite
	Given      *api.Operation
	Params     interface{}     `json:",omitempty"`
	Data       interface{}     `json:"result,omitempty"`
	InputTest  TestExpectation `json:"serialized"`
	OutputTest TestExpectation `json:"response"`
}

type TestExpectation struct {
	Body       string
	URI        string
	Headers    map[string]string
	StatusCode uint `json:"status_code"`
}

const preamble = `
var _ bytes.Buffer // always import bytes
var _ http.Request
var _ json.Marshaler
var _ time.Time
var _ xmlutil.XMLNode
var _ xml.Attr
var _ = ioutil.Discard
var _ = util.Trim("")
`

var reStripSpace = regexp.MustCompile(`\s(\w)`)

var reImportRemoval = regexp.MustCompile(`(?s:import \((.+?)\))`)

func removeImports(code string) string {
	return reImportRemoval.ReplaceAllString(code, "")
}

var extraImports = []string{
	"bytes",
	"encoding/json",
	"encoding/xml",
	"io/ioutil",
	"net/http",
	"testing",
	"time",
	"github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil",
	"github.com/awslabs/aws-sdk-go/internal/util",
	"github.com/stretchr/testify/assert",
}

func addImports(code string) string {
	importNames := make([]string, len(extraImports))
	for i, n := range extraImports {
		importNames[i] = fmt.Sprintf("%q", n)
	}
	str := reImportRemoval.ReplaceAllString(code, "import (\n$1\n"+strings.Join(importNames, "\n")+")")
	return str
}

func (t *TestSuite) TestSuite() string {
	var buf bytes.Buffer

	t.title = reStripSpace.ReplaceAllStringFunc(t.Description, func(x string) string {
		return strings.ToUpper(x[1:])
	})
	t.title = regexp.MustCompile(`\W`).ReplaceAllString(t.title, "")

	for idx, c := range t.Cases {
		c.TestSuite = t
		buf.WriteString(c.TestCase(idx) + "\n")
	}
	return util.GoFmt(buf.String())
}

var tplInputTestCase = template.Must(template.New("inputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)
	svc.Endpoint = "https://test"

	input := {{ .ParamsString }}
	req := svc.{{ .Given.ExportedName }}Request(input)
	r := req.HTTPRequest

	// build request
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Build(req)
	assert.NoError(t, req.Error)

	{{ if ne .Body "" }}// assert body
	assert.NotNil(t, r.Body)
	{{ if eq .TestCase.TestSuite.API.Metadata.Protocol "rest-xml" }}body := util.SortXML(r.Body){{ else }}body, _ := ioutil.ReadAll(r.Body){{ end }}
	assert.Equal(t, util.Trim({{ .Body }}), util.Trim(string(body))){{ end }}

	{{ if ne .TestCase.InputTest.URI "" }}// assert URL
	assert.Equal(t, "https://test{{ .TestCase.InputTest.URI }}", r.URL.String()){{ end }}

	// assert headers
{{ range $k, $v := .TestCase.InputTest.Headers }}assert.Equal(t, "{{ $v }}", r.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplInputTestCaseData struct {
	*TestCase
	Body, OpName, ParamsString string
}

var tplOutputTestCase = template.Must(template.New("outputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	buf := bytes.NewReader([]byte({{ .Body }}))
	req, out := svc.{{ .Given.ExportedName }}Request()
	req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}

	// set headers
	{{ range $k, $v := .TestCase.OutputTest.Headers }}req.HTTPResponse.Header.Set("{{ $k }}", "{{ $v }}")
	{{ end }}

	// unmarshal response
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.UnmarshalMeta(req)
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Unmarshal(req)
	assert.NoError(t, req.Error)

	// assert response
	assert.NotNil(t, out) // ensure out variable is used
	{{ .Assertions }}
}
`))

type tplOutputTestCaseData struct {
	*TestCase
	Body, OpName, Assertions string
}

func (i *TestCase) TestCase(idx int) string {
	var buf bytes.Buffer

	opName := i.API.StructName() + i.TestSuite.title + "Case" + strconv.Itoa(idx+1)

	if i.Params != nil { // input test
		// query test should sort body as form encoded values
		switch i.API.Metadata.Protocol {
		case "query", "ec2":
			m, _ := url.ParseQuery(i.InputTest.Body)
			i.InputTest.Body = m.Encode()
		case "rest-xml":
			i.InputTest.Body = util.SortXML(bytes.NewReader([]byte(i.InputTest.Body)))
		case "json", "rest-json":
			i.InputTest.Body = strings.Replace(i.InputTest.Body, " ", "", -1)
		}

		input := tplInputTestCaseData{
			TestCase:     i,
			Body:         fmt.Sprintf("%q", i.InputTest.Body),
			OpName:       strings.ToUpper(opName[0:1]) + opName[1:],
			ParamsString: helpers.ParamsStructFromJSON(i.Params, i.Given.InputRef.Shape),
		}

		if err := tplInputTestCase.Execute(&buf, input); err != nil {
			panic(err)
		}
	} else {
		output := tplOutputTestCaseData{
			TestCase:   i,
			Body:       fmt.Sprintf("%q", i.OutputTest.Body),
			OpName:     strings.ToUpper(opName[0:1]) + opName[1:],
			Assertions: utilassert.GenerateAssertions(i.Data, i.Given.OutputRef.Shape, "out"),
		}

		if err := tplOutputTestCase.Execute(&buf, output); err != nil {
			panic(err)
		}
	}

	return util.GoFmt(buf.String())
}

func GenerateTestSuite(filename string) string {
	inout := "Input"
	if strings.Contains(filename, "/output/") {
		inout = "Output"
	}

	var suites []TestSuite
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	err = json.NewDecoder(f).Decode(&suites)
	if err != nil {
		panic(err)
	}

	var buf bytes.Buffer
	buf.WriteString("package " + suites[0].ProtocolPackage() + "_test\n\n")

	var innerBuf bytes.Buffer
	innerBuf.WriteString("//\n// Tests begin here\n//\n\n\n")

	for i, suite := range suites {
		svcPrefix := inout + "Service" + strconv.Itoa(i+1)
		suite.API.Metadata.ServiceAbbreviation = svcPrefix + "ProtocolTest"
		suite.API.Operations = map[string]*api.Operation{}
		for idx, c := range suite.Cases {
			c.Given.ExportedName = svcPrefix + "TestCaseOperation" + strconv.Itoa(idx+1)
			suite.API.Operations[c.Given.ExportedName] = c.Given
		}

		suite.API.NoInflections = true // don't require inflections
		suite.API.Setup()
		suite.API.Metadata.EndpointPrefix = suite.API.PackageName()

		for n, s := range suite.API.Shapes {
			s.Rename(svcPrefix + "TestShape" + n)
		}

		svcCode := addImports(suite.API.ServiceGoCode())
		if i == 0 {
			importMatch := reImportRemoval.FindStringSubmatch(svcCode)
			buf.WriteString(importMatch[0] + "\n\n")
			buf.WriteString(preamble + "\n\n")
		}
		svcCode = removeImports(svcCode)
		svcCode = strings.Replace(svcCode, "func New(", "func New"+suite.API.StructName()+"(", -1)

		buf.WriteString(svcCode + "\n\n")
		buf.WriteString(removeImports(suite.API.APIGoCode()) + "\n\n")
		innerBuf.WriteString(suite.TestSuite() + "\n")
	}

	return util.GoFmt(buf.String() + innerBuf.String())
}

func main() {
	out := GenerateTestSuite(os.Args[1])
	if len(os.Args) == 3 {
		f, err := os.Create(os.Args[2])
		defer f.Close()
		if err != nil {
			panic(err)
		}
		f.WriteString(out + "\n")
	} else {
		fmt.Println(out)
	}
}
<commit_msg>Refactor go:generate commands for protocol tests<commit_after>package main

//go:generate go run generate.go input/json.json ../../protocol/jsonrpc/build_test.go
//go:generate go run generate.go output/json.json ../../protocol/jsonrpc/unmarshal_test.go
//go:generate go run generate.go input/query.json ../../protocol/query/build_test.go
//go:generate go run generate.go output/query.json ../../protocol/query/unmarshal_test.go
//go:generate go run generate.go input/ec2.json ../../protocol/ec2query/build_test.go
//go:generate go run generate.go output/ec2.json ../../protocol/ec2query/unmarshal_test.go
//go:generate go run generate.go input/rest-json.json ../../protocol/restjson/build_test.go
//go:generate go run generate.go input/rest-xml.json ../../protocol/restxml/build_test.go
//go:generate go run generate.go output/rest-xml.json ../../protocol/restxml/unmarshal_test.go

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/awslabs/aws-sdk-go/internal/fixtures/helpers"
	"github.com/awslabs/aws-sdk-go/internal/model/api"
	"github.com/awslabs/aws-sdk-go/internal/util"
	"github.com/awslabs/aws-sdk-go/internal/util/utilassert"
)

type TestSuite struct {
	*api.API
	Description string
	Cases       []TestCase
	title       string
}

type TestCase struct {
	*TestSuite
	Given      *api.Operation
	Params     interface{}     `json:",omitempty"`
	Data       interface{}     `json:"result,omitempty"`
	InputTest  TestExpectation `json:"serialized"`
	OutputTest TestExpectation `json:"response"`
}

type TestExpectation struct {
	Body       string
	URI        string
	Headers    map[string]string
	StatusCode uint `json:"status_code"`
}

const preamble = `
var _ bytes.Buffer // always import bytes
var _ http.Request
var _ json.Marshaler
var _ time.Time
var _ xmlutil.XMLNode
var _ xml.Attr
var _ = ioutil.Discard
var _ = util.Trim("")
`

var reStripSpace = regexp.MustCompile(`\s(\w)`)

var reImportRemoval = regexp.MustCompile(`(?s:import \((.+?)\))`)

func removeImports(code string) string {
	return reImportRemoval.ReplaceAllString(code, "")
}

var extraImports = []string{
	"bytes",
	"encoding/json",
	"encoding/xml",
	"io/ioutil",
	"net/http",
	"testing",
	"time",
	"github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil",
	"github.com/awslabs/aws-sdk-go/internal/util",
	"github.com/stretchr/testify/assert",
}

func addImports(code string) string {
	importNames := make([]string, len(extraImports))
	for i, n := range extraImports {
		importNames[i] = fmt.Sprintf("%q", n)
	}
	str := reImportRemoval.ReplaceAllString(code, "import (\n$1\n"+strings.Join(importNames, "\n")+")")
	return str
}

func (t *TestSuite) TestSuite() string {
	var buf bytes.Buffer

	t.title = reStripSpace.ReplaceAllStringFunc(t.Description, func(x string) string {
		return strings.ToUpper(x[1:])
	})
	t.title = regexp.MustCompile(`\W`).ReplaceAllString(t.title, "")

	for idx, c := range t.Cases {
		c.TestSuite = t
		buf.WriteString(c.TestCase(idx) + "\n")
	}
	return util.GoFmt(buf.String())
}

var tplInputTestCase = template.Must(template.New("inputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)
	svc.Endpoint = "https://test"

	input := {{ .ParamsString }}
	req := svc.{{ .Given.ExportedName }}Request(input)
	r := req.HTTPRequest

	// build request
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Build(req)
	assert.NoError(t, req.Error)

	{{ if ne .Body "" }}// assert body
	assert.NotNil(t, r.Body)
	{{ if eq .TestCase.TestSuite.API.Metadata.Protocol "rest-xml" }}body := util.SortXML(r.Body){{ else }}body, _ := ioutil.ReadAll(r.Body){{ end }}
	assert.Equal(t, util.Trim({{ .Body }}), util.Trim(string(body))){{ end }}

	{{ if ne .TestCase.InputTest.URI "" }}// assert URL
	assert.Equal(t, "https://test{{ .TestCase.InputTest.URI }}", r.URL.String()){{ end }}

	// assert headers
{{ range $k, $v := .TestCase.InputTest.Headers }}assert.Equal(t, "{{ $v }}", r.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplInputTestCaseData struct {
	*TestCase
	Body, OpName, ParamsString string
}

var tplOutputTestCase = template.Must(template.New("outputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	buf := bytes.NewReader([]byte({{ .Body }}))
	req, out := svc.{{ .Given.ExportedName }}Request()
	req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}

	// set headers
	{{ range $k, $v := .TestCase.OutputTest.Headers }}req.HTTPResponse.Header.Set("{{ $k }}", "{{ $v }}")
	{{ end }}

	// unmarshal response
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.UnmarshalMeta(req)
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Unmarshal(req)
	assert.NoError(t, req.Error)

	// assert response
	assert.NotNil(t, out) // ensure out variable is used
	{{ .Assertions }}
}
`))

type tplOutputTestCaseData struct {
	*TestCase
	Body, OpName, Assertions string
}

func (i *TestCase) TestCase(idx int) string {
	var buf bytes.Buffer

	opName := i.API.StructName() + i.TestSuite.title + "Case" + strconv.Itoa(idx+1)

	if i.Params != nil { // input test
		// query test should sort body as form encoded values
		switch i.API.Metadata.Protocol {
		case "query", "ec2":
			m, _ := url.ParseQuery(i.InputTest.Body)
			i.InputTest.Body = m.Encode()
		case "rest-xml":
			i.InputTest.Body = util.SortXML(bytes.NewReader([]byte(i.InputTest.Body)))
		case "json", "rest-json":
			i.InputTest.Body = strings.Replace(i.InputTest.Body, " ", "", -1)
		}

		input := tplInputTestCaseData{
			TestCase:     i,
			Body:         fmt.Sprintf("%q", i.InputTest.Body),
			OpName:       strings.ToUpper(opName[0:1]) + opName[1:],
			ParamsString: helpers.ParamsStructFromJSON(i.Params, i.Given.InputRef.Shape),
		}

		if err := tplInputTestCase.Execute(&buf, input); err != nil {
			panic(err)
		}
	} else {
		output := tplOutputTestCaseData{
			TestCase:   i,
			Body:       fmt.Sprintf("%q", i.OutputTest.Body),
			OpName:     strings.ToUpper(opName[0:1]) + opName[1:],
			Assertions: utilassert.GenerateAssertions(i.Data, i.Given.OutputRef.Shape, "out"),
		}

		if err := tplOutputTestCase.Execute(&buf, output); err != nil {
			panic(err)
		}
	}

	return util.GoFmt(buf.String())
}

func GenerateTestSuite(filename string) string {
	inout := "Input"
	if strings.Contains(filename, "/output/") {
		inout = "Output"
	}

	var suites []TestSuite
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	err = json.NewDecoder(f).Decode(&suites)
	if err != nil {
		panic(err)
	}

	var buf bytes.Buffer
	buf.WriteString("package " + suites[0].ProtocolPackage() + "_test\n\n")

	var innerBuf bytes.Buffer
	innerBuf.WriteString("//\n// Tests begin here\n//\n\n\n")

	for i, suite := range suites {
		svcPrefix := inout + "Service" + strconv.Itoa(i+1)
		suite.API.Metadata.ServiceAbbreviation = svcPrefix + "ProtocolTest"
		suite.API.Operations = map[string]*api.Operation{}
		for idx, c := range suite.Cases {
			c.Given.ExportedName = svcPrefix + "TestCaseOperation" + strconv.Itoa(idx+1)
			suite.API.Operations[c.Given.ExportedName] = c.Given
		}

		suite.API.NoInflections = true // don't require inflections
		suite.API.Setup()
		suite.API.Metadata.EndpointPrefix = suite.API.PackageName()

		for n, s := range suite.API.Shapes {
			s.Rename(svcPrefix + "TestShape" + n)
		}

		svcCode := addImports(suite.API.ServiceGoCode())
		if i == 0 {
			importMatch := reImportRemoval.FindStringSubmatch(svcCode)
			buf.WriteString(importMatch[0] + "\n\n")
			buf.WriteString(preamble + "\n\n")
		}
		svcCode = removeImports(svcCode)
		svcCode = strings.Replace(svcCode, "func New(", "func New"+suite.API.StructName()+"(", -1)

		buf.WriteString(svcCode + "\n\n")
		buf.WriteString(removeImports(suite.API.APIGoCode()) + "\n\n")
		innerBuf.WriteString(suite.TestSuite() + "\n")
	}

	return util.GoFmt(buf.String() + innerBuf.String())
}

func main() {
	out := GenerateTestSuite(os.Args[1])
	if len(os.Args) == 3 {
		f, err := os.Create(os.Args[2])
		defer f.Close()
		if err != nil {
			panic(err)
		}
		f.WriteString(out + "\n")
	} else {
		fmt.Println(out)
	}
}
<|endoftext|>


<commit_before>package notification

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net/smtp"
	"strings"
	"time"

	"github.com/authelia/authelia/internal/configuration/schema"
	"github.com/authelia/authelia/internal/logging"
	"github.com/authelia/authelia/internal/utils"
)

// SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct {
	username            string
	password            string
	sender              string
	identifier          string
	host                string
	port                int
	disableRequireTLS   bool
	address             string
	subject             string
	startupCheckAddress string
	client              *smtp.Client
	tlsConfig           *tls.Config
}

// NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration, certPool *x509.CertPool) *SMTPNotifier {
	notifier := &SMTPNotifier{
		username:            configuration.Username,
		password:            configuration.Password,
		sender:              configuration.Sender,
		identifier:          configuration.Identifier,
		host:                configuration.Host,
		port:                configuration.Port,
		disableRequireTLS:   configuration.DisableRequireTLS,
		address:             fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
		subject:             configuration.Subject,
		startupCheckAddress: configuration.StartupCheckAddress,
		tlsConfig:           utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
	}

	return notifier
}

// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
func (n *SMTPNotifier) startTLS() error {
	logger := logging.Logger()
	// Only start if not already encrypted
	if _, ok := n.client.TLSConnectionState(); ok {
		logger.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
		return nil
	}

	switch ok, _ := n.client.Extension("STARTTLS"); ok {
	case true:
		logger.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName)

		if err := n.client.StartTLS(n.tlsConfig); err != nil {
			return err
		}

		logger.Debug("Notifier SMTP STARTTLS completed without error")
	default:
		switch n.disableRequireTLS {
		case true:
			logger.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
		default:
			return errors.New("Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)")
		}
	}

	return nil
}

// Attempt Authentication.
func (n *SMTPNotifier) auth() error {
	logger := logging.Logger()
	// Attempt AUTH if password is specified only.
	if n.password != "" {
		_, ok := n.client.TLSConnectionState()
		if !ok {
			return errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
		}

		// Check the server supports AUTH, and get the mechanisms.
		ok, m := n.client.Extension("AUTH")
		if ok {
			var auth smtp.Auth

			logger.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
			mechanisms := strings.Split(m, " ")

			// Adaptively select the AUTH mechanism to use based on what the server advertised.
			if utils.IsStringInSlice("PLAIN", mechanisms) {
				auth = smtp.PlainAuth("", n.username, n.password, n.host)

				logger.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
			} else if utils.IsStringInSlice("LOGIN", mechanisms) {
				auth = newLoginAuth(n.username, n.password, n.host)

				logger.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
			}

			// Throw error since AUTH extension is not supported.
			if auth == nil {
				return fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
			}

			// Authenticate.
			if err := n.client.Auth(auth); err != nil {
				return err
			}

			logger.Debug("Notifier SMTP client authenticated successfully with the server")

			return nil
		}

		return errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
	}

	logger.Debug("Notifier SMTP config has no password specified so authentication is being skipped")

	return nil
}

func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error {
	logger := logging.Logger()
	logger.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)

	if !n.disableRequireTLS {
		_, ok := n.client.TLSConnectionState()
		if !ok {
			return errors.New("Notifier SMTP client can't send an email over plain text connection")
		}
	}

	wc, err := n.client.Data()
	if err != nil {
		logger.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err)
		return err
	}

	boundary := utils.RandomString(30, utils.AlphaNumericCharacters)

	now := time.Now()

	msg := "Date:" + now.Format(rfc5322DateTimeLayout) + "\n" +
		"From: " + n.sender + "\n" +
		"To: " + recipient + "\n" +
		"Subject: " + subject + "\n" +
		"MIME-version: 1.0\n" +
		"Content-Type: multipart/alternative; boundary=" + boundary + "\n\n" +
		"--" + boundary + "\n" +
		"Content-Type: text/plain; charset=\"UTF-8\"\n" +
		"Content-Transfer-Encoding: quoted-printable\n" +
		"Content-Disposition: inline\n\n" +
		body + "\n"

	if htmlBody != "" {
		msg += "--" + boundary + "\n" +
			"Content-Type: text/html; charset=\"UTF-8\"\n\n" +
			htmlBody + "\n"
	}

	msg += "--" + boundary + "--"

	_, err = fmt.Fprint(wc, msg)
	if err != nil {
		logger.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
		return err
	}

	err = wc.Close()
	if err != nil {
		logger.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err)
		return err
	}

	return nil
}

// Dial the SMTP server with the SMTPNotifier config.
func (n *SMTPNotifier) dial() error {
	logger := logging.Logger()
	logger.Debugf("Notifier SMTP client attempting connection to %s", n.address)

	if n.port == 465 {
		logger.Warnf("Notifier SMTP client configured to connect to a SMTPS server. It's highly recommended you use a non SMTPS port and STARTTLS instead of SMTPS, as the protocol is long deprecated.")

		conn, err := tls.Dial("tcp", n.address, n.tlsConfig)
		if err != nil {
			return err
		}

		client, err := smtp.NewClient(conn, n.host)
		if err != nil {
			return err
		}

		n.client = client
	} else {
		client, err := smtp.Dial(n.address)
		if err != nil {
			return err
		}

		n.client = client
	}

	logger.Debug("Notifier SMTP client connected successfully")

	return nil
}

// Closes the connection properly.
func (n *SMTPNotifier) cleanup() {
	logger := logging.Logger()

	err := n.client.Quit()
	if err != nil {
		logger.Warnf("Notifier SMTP client encountered error during cleanup: %s", err)
	}
}

// StartupCheck checks the server is functioning correctly and the configuration is correct.
func (n *SMTPNotifier) StartupCheck() (bool, error) {
	if err := n.dial(); err != nil {
		return false, err
	}

	defer n.cleanup()

	if err := n.client.Hello(n.identifier); err != nil {
		return false, err
	}

	if err := n.startTLS(); err != nil {
		return false, err
	}

	if err := n.auth(); err != nil {
		return false, err
	}

	if err := n.client.Mail(n.sender); err != nil {
		return false, err
	}

	if err := n.client.Rcpt(n.startupCheckAddress); err != nil {
		return false, err
	}

	if err := n.client.Reset(); err != nil {
		return false, err
	}

	return true, nil
}

// Send is used to send an email to a recipient.
func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
	logger := logging.Logger()
	subject := strings.ReplaceAll(n.subject, "{title}", title)

	if err := n.dial(); err != nil {
		return err
	}

	// Always execute QUIT at the end once we're connected.
	defer n.cleanup()

	if err := n.client.Hello(n.identifier); err != nil {
		return err
	}

	// Start TLS and then Authenticate.
	if err := n.startTLS(); err != nil {
		return err
	}

	if err := n.auth(); err != nil {
		return err
	}

	// Set the sender and recipient first.
	if err := n.client.Mail(n.sender); err != nil {
		logger.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
		return err
	}

	if err := n.client.Rcpt(recipient); err != nil {
		logger.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err)
		return err
	}

	// Compose and send the email body to the server.
	if err := n.compose(recipient, subject, body, htmlBody); err != nil {
		return err
	}

	logger.Debug("Notifier SMTP client successfully sent email")

	return nil
}
<commit_msg>fix(notifier): remove SMTPS warning (#2200)<commit_after>package notification

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net/smtp"
	"strings"
	"time"

	"github.com/authelia/authelia/internal/configuration/schema"
	"github.com/authelia/authelia/internal/logging"
	"github.com/authelia/authelia/internal/utils"
)

// SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct {
	username            string
	password            string
	sender              string
	identifier          string
	host                string
	port                int
	disableRequireTLS   bool
	address             string
	subject             string
	startupCheckAddress string
	client              *smtp.Client
	tlsConfig           *tls.Config
}

// NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration, certPool *x509.CertPool) *SMTPNotifier {
	notifier := &SMTPNotifier{
		username:            configuration.Username,
		password:            configuration.Password,
		sender:              configuration.Sender,
		identifier:          configuration.Identifier,
		host:                configuration.Host,
		port:                configuration.Port,
		disableRequireTLS:   configuration.DisableRequireTLS,
		address:             fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
		subject:             configuration.Subject,
		startupCheckAddress: configuration.StartupCheckAddress,
		tlsConfig:           utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
	}

	return notifier
}

// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
func (n *SMTPNotifier) startTLS() error {
	logger := logging.Logger()
	// Only start if not already encrypted
	if _, ok := n.client.TLSConnectionState(); ok {
		logger.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
		return nil
	}

	switch ok, _ := n.client.Extension("STARTTLS"); ok {
	case true:
		logger.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName)

		if err := n.client.StartTLS(n.tlsConfig); err != nil {
			return err
		}

		logger.Debug("Notifier SMTP STARTTLS completed without error")
	default:
		switch n.disableRequireTLS {
		case true:
			logger.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
		default:
			return errors.New("Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)")
		}
	}

	return nil
}

// Attempt Authentication.
func (n *SMTPNotifier) auth() error {
	logger := logging.Logger()
	// Attempt AUTH if password is specified only.
	if n.password != "" {
		_, ok := n.client.TLSConnectionState()
		if !ok {
			return errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
		}

		// Check the server supports AUTH, and get the mechanisms.
		ok, m := n.client.Extension("AUTH")
		if ok {
			var auth smtp.Auth

			logger.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
			mechanisms := strings.Split(m, " ")

			// Adaptively select the AUTH mechanism to use based on what the server advertised.
			if utils.IsStringInSlice("PLAIN", mechanisms) {
				auth = smtp.PlainAuth("", n.username, n.password, n.host)

				logger.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
			} else if utils.IsStringInSlice("LOGIN", mechanisms) {
				auth = newLoginAuth(n.username, n.password, n.host)

				logger.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
			}

			// Throw error since AUTH extension is not supported.
			if auth == nil {
				return fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
			}

			// Authenticate.
			if err := n.client.Auth(auth); err != nil {
				return err
			}

			logger.Debug("Notifier SMTP client authenticated successfully with the server")

			return nil
		}

		return errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
	}

	logger.Debug("Notifier SMTP config has no password specified so authentication is being skipped")

	return nil
}

func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error {
	logger := logging.Logger()
	logger.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)

	if !n.disableRequireTLS {
		_, ok := n.client.TLSConnectionState()
		if !ok {
			return errors.New("Notifier SMTP client can't send an email over plain text connection")
		}
	}

	wc, err := n.client.Data()
	if err != nil {
		logger.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err)
		return err
	}

	boundary := utils.RandomString(30, utils.AlphaNumericCharacters)

	now := time.Now()

	msg := "Date:" + now.Format(rfc5322DateTimeLayout) + "\n" +
		"From: " + n.sender + "\n" +
		"To: " + recipient + "\n" +
		"Subject: " + subject + "\n" +
		"MIME-version: 1.0\n" +
		"Content-Type: multipart/alternative; boundary=" + boundary + "\n\n" +
		"--" + boundary + "\n" +
		"Content-Type: text/plain; charset=\"UTF-8\"\n" +
		"Content-Transfer-Encoding: quoted-printable\n" +
		"Content-Disposition: inline\n\n" +
		body + "\n"

	if htmlBody != "" {
		msg += "--" + boundary + "\n" +
			"Content-Type: text/html; charset=\"UTF-8\"\n\n" +
			htmlBody + "\n"
	}

	msg += "--" + boundary + "--"

	_, err = fmt.Fprint(wc, msg)
	if err != nil {
		logger.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
		return err
	}

	err = wc.Close()
	if err != nil {
		logger.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err)
		return err
	}

	return nil
}

// Dial the SMTP server with the SMTPNotifier config.
func (n *SMTPNotifier) dial() error {
	logger := logging.Logger()
	logger.Debugf("Notifier SMTP client attempting connection to %s", n.address)

	if n.port == 465 {
		logger.Infof("Notifier SMTP client using submissions port 465. Make sure the mail server you are connecting to is configured for submissions and not SMTPS.")

		conn, err := tls.Dial("tcp", n.address, n.tlsConfig)
		if err != nil {
			return err
		}

		client, err := smtp.NewClient(conn, n.host)
		if err != nil {
			return err
		}

		n.client = client
	} else {
		client, err := smtp.Dial(n.address)
		if err != nil {
			return err
		}

		n.client = client
	}

	logger.Debug("Notifier SMTP client connected successfully")

	return nil
}

// Closes the connection properly.
func (n *SMTPNotifier) cleanup() {
	logger := logging.Logger()

	err := n.client.Quit()
	if err != nil {
		logger.Warnf("Notifier SMTP client encountered error during cleanup: %s", err)
	}
}

// StartupCheck checks the server is functioning correctly and the configuration is correct.
func (n *SMTPNotifier) StartupCheck() (bool, error) {
	if err := n.dial(); err != nil {
		return false, err
	}

	defer n.cleanup()

	if err := n.client.Hello(n.identifier); err != nil {
		return false, err
	}

	if err := n.startTLS(); err != nil {
		return false, err
	}

	if err := n.auth(); err != nil {
		return false, err
	}

	if err := n.client.Mail(n.sender); err != nil {
		return false, err
	}

	if err := n.client.Rcpt(n.startupCheckAddress); err != nil {
		return false, err
	}

	if err := n.client.Reset(); err != nil {
		return false, err
	}

	return true, nil
}

// Send is used to send an email to a recipient.
func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
	logger := logging.Logger()
	subject := strings.ReplaceAll(n.subject, "{title}", title)

	if err := n.dial(); err != nil {
		return err
	}

	// Always execute QUIT at the end once we're connected.
	defer n.cleanup()

	if err := n.client.Hello(n.identifier); err != nil {
		return err
	}

	// Start TLS and then Authenticate.
	if err := n.startTLS(); err != nil {
		return err
	}

	if err := n.auth(); err != nil {
		return err
	}

	// Set the sender and recipient first.
	if err := n.client.Mail(n.sender); err != nil {
		logger.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
		return err
	}

	if err := n.client.Rcpt(recipient); err != nil {
		logger.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err)
		return err
	}

	// Compose and send the email body to the server.
	if err := n.compose(recipient, subject, body, htmlBody); err != nil {
		return err
	}

	logger.Debug("Notifier SMTP client successfully sent email")

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 v2

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"github.com/google/cadvisor/info/v1"
)

func machineFsStatsFromV1(fsStats []v1.FsStats) []MachineFsStats {
	var result []MachineFsStats
	for i := range fsStats {
		stat := fsStats[i]
		readDuration := time.Millisecond * time.Duration(stat.ReadTime)
		writeDuration := time.Millisecond * time.Duration(stat.WriteTime)
		ioDuration := time.Millisecond * time.Duration(stat.IoTime)
		weightedDuration := time.Millisecond * time.Duration(stat.WeightedIoTime)
		machineFsStat := MachineFsStats{
			Device:    stat.Device,
			Type:      stat.Type,
			Capacity:  &stat.Limit,
			Usage:     &stat.Usage,
			Available: &stat.Available,
			DiskStats: DiskStats{
				ReadsCompleted:     &stat.ReadsCompleted,
				ReadsMerged:        &stat.ReadsMerged,
				SectorsRead:        &stat.SectorsRead,
				ReadDuration:       &readDuration,
				WritesCompleted:    &stat.WritesCompleted,
				WritesMerged:       &stat.WritesMerged,
				SectorsWritten:     &stat.SectorsWritten,
				WriteDuration:      &writeDuration,
				IoInProgress:       &stat.IoInProgress,
				IoDuration:         &ioDuration,
				WeightedIoDuration: &weightedDuration,
			},
		}
		if stat.HasInodes {
			machineFsStat.InodesFree = &stat.InodesFree
		}
		result = append(result, machineFsStat)
	}
	return result
}

func MachineStatsFromV1(cont *v1.ContainerInfo) []MachineStats {
	var stats []MachineStats
	var last *v1.ContainerStats
	for i := range cont.Stats {
		val := cont.Stats[i]
		stat := MachineStats{
			Timestamp: val.Timestamp,
		}
		if cont.Spec.HasCpu {
			stat.Cpu = &val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if cont.Spec.HasMemory {
			stat.Memory = &val.Memory
		}
		if cont.Spec.HasNetwork {
			stat.Network = &NetworkStats{
				// FIXME: Use reflection instead.
				Tcp:        TcpStat(val.Network.Tcp),
				Tcp6:       TcpStat(val.Network.Tcp6),
				Interfaces: val.Network.Interfaces,
			}
		}
		if cont.Spec.HasFilesystem {
			stat.Filesystem = machineFsStatsFromV1(val.Filesystem)
		}
		// TODO(rjnagal): Handle load stats.
		stats = append(stats, stat)
	}
	return stats
}

func ContainerStatsFromV1(containerName string, spec *v1.ContainerSpec, stats []*v1.ContainerStats) []*ContainerStats {
	newStats := make([]*ContainerStats, 0, len(stats))
	var last *v1.ContainerStats
	for _, val := range stats {
		stat := &ContainerStats{
			Timestamp: val.Timestamp,
		}
		if spec.HasCpu {
			stat.Cpu = &val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if spec.HasMemory {
			stat.Memory = &val.Memory
		}
		if spec.HasNetwork {
			// TODO: Handle TcpStats
			stat.Network = &NetworkStats{
				Tcp:        TcpStat(val.Network.Tcp),
				Tcp6:       TcpStat(val.Network.Tcp6),
				Interfaces: val.Network.Interfaces,
			}
		}
		if spec.HasFilesystem {
			if len(val.Filesystem) == 1 {
				stat.Filesystem = &FilesystemStats{
					TotalUsageBytes: &val.Filesystem[0].Usage,
					BaseUsageBytes:  &val.Filesystem[0].BaseUsage,
					InodeUsage:      &val.Filesystem[0].Inodes,
				}
			} else if len(val.Filesystem) > 1 && containerName != "/" {
				// Cannot handle multiple devices per container.
				glog.V(2).Infof("failed to handle multiple devices for container %s. Skipping Filesystem stats", containerName)
			}
		}
		if spec.HasDiskIo {
			stat.DiskIo = &val.DiskIo
		}
		if spec.HasCustomMetrics {
			stat.CustomMetrics = val.CustomMetrics
		}
		// TODO(rjnagal): Handle load stats.
		newStats = append(newStats, stat)
	}
	return newStats
}

func DeprecatedStatsFromV1(cont *v1.ContainerInfo) []DeprecatedContainerStats {
	stats := make([]DeprecatedContainerStats, 0, len(cont.Stats))
	var last *v1.ContainerStats
	for _, val := range cont.Stats {
		stat := DeprecatedContainerStats{
			Timestamp:        val.Timestamp,
			HasCpu:           cont.Spec.HasCpu,
			HasMemory:        cont.Spec.HasMemory,
			HasNetwork:       cont.Spec.HasNetwork,
			HasFilesystem:    cont.Spec.HasFilesystem,
			HasDiskIo:        cont.Spec.HasDiskIo,
			HasCustomMetrics: cont.Spec.HasCustomMetrics,
		}
		if stat.HasCpu {
			stat.Cpu = val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if stat.HasMemory {
			stat.Memory = val.Memory
		}
		if stat.HasNetwork {
			stat.Network.Interfaces = val.Network.Interfaces
		}
		if stat.HasFilesystem {
			stat.Filesystem = val.Filesystem
		}
		if stat.HasDiskIo {
			stat.DiskIo = val.DiskIo
		}
		if stat.HasCustomMetrics {
			stat.CustomMetrics = val.CustomMetrics
		}
		// TODO(rjnagal): Handle load stats.
		stats = append(stats, stat)
	}
	return stats
}

func InstCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) {
	if last == nil {
		return nil, nil
	}
	if !cur.Timestamp.After(last.Timestamp) {
		return nil, fmt.Errorf("container stats move backwards in time")
	}
	if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
		return nil, fmt.Errorf("different number of cpus")
	}
	timeDelta := cur.Timestamp.Sub(last.Timestamp)
	if timeDelta <= 100*time.Millisecond {
		return nil, fmt.Errorf("time delta unexpectedly small")
	}
	// Nanoseconds to gain precision and avoid having zero seconds if the
	// difference between the timestamps is just under a second
	timeDeltaNs := uint64(timeDelta.Nanoseconds())
	convertToRate := func(lastValue, curValue uint64) (uint64, error) {
		if curValue < lastValue {
			return 0, fmt.Errorf("cumulative stats decrease")
		}
		valueDelta := curValue - lastValue
		// Use float64 to keep precision
		return uint64(float64(valueDelta) / float64(timeDeltaNs) * 1e9), nil
	}
	total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
	if err != nil {
		return nil, err
	}
	percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
	for i := range percpu {
		var err error
		percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
		if err != nil {
			return nil, err
		}
	}
	user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
	if err != nil {
		return nil, err
	}
	system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
	if err != nil {
		return nil, err
	}
	return &CpuInstStats{
		Usage: CpuInstUsage{
			Total:  total,
			PerCpu: percpu,
			User:   user,
			System: system,
		},
	}, nil
}

// Get V2 container spec from v1 container info.
func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace string) ContainerSpec {
	specV2 := ContainerSpec{
		CreationTime:     specV1.CreationTime,
		HasCpu:           specV1.HasCpu,
		HasMemory:        specV1.HasMemory,
		HasFilesystem:    specV1.HasFilesystem,
		HasNetwork:       specV1.HasNetwork,
		HasDiskIo:        specV1.HasDiskIo,
		HasCustomMetrics: specV1.HasCustomMetrics,
		Image:            specV1.Image,
		Labels:           specV1.Labels,
	}
	if specV1.HasCpu {
		specV2.Cpu.Limit = specV1.Cpu.Limit
		specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
		specV2.Cpu.Mask = specV1.Cpu.Mask
	}
	if specV1.HasMemory {
		specV2.Memory.Limit = specV1.Memory.Limit
		specV2.Memory.Reservation = specV1.Memory.Reservation
		specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
	}
	if specV1.HasCustomMetrics {
		specV2.CustomMetrics = specV1.CustomMetrics
	}
	specV2.Aliases = aliases
	specV2.Namespace = namespace
	return specV2
}
<commit_msg>UPSTREAM: google/cadvisor: 1639: Reduce cAdvisor log spam with multiple devices<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 v2

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"github.com/google/cadvisor/info/v1"
)

func machineFsStatsFromV1(fsStats []v1.FsStats) []MachineFsStats {
	var result []MachineFsStats
	for i := range fsStats {
		stat := fsStats[i]
		readDuration := time.Millisecond * time.Duration(stat.ReadTime)
		writeDuration := time.Millisecond * time.Duration(stat.WriteTime)
		ioDuration := time.Millisecond * time.Duration(stat.IoTime)
		weightedDuration := time.Millisecond * time.Duration(stat.WeightedIoTime)
		machineFsStat := MachineFsStats{
			Device:    stat.Device,
			Type:      stat.Type,
			Capacity:  &stat.Limit,
			Usage:     &stat.Usage,
			Available: &stat.Available,
			DiskStats: DiskStats{
				ReadsCompleted:     &stat.ReadsCompleted,
				ReadsMerged:        &stat.ReadsMerged,
				SectorsRead:        &stat.SectorsRead,
				ReadDuration:       &readDuration,
				WritesCompleted:    &stat.WritesCompleted,
				WritesMerged:       &stat.WritesMerged,
				SectorsWritten:     &stat.SectorsWritten,
				WriteDuration:      &writeDuration,
				IoInProgress:       &stat.IoInProgress,
				IoDuration:         &ioDuration,
				WeightedIoDuration: &weightedDuration,
			},
		}
		if stat.HasInodes {
			machineFsStat.InodesFree = &stat.InodesFree
		}
		result = append(result, machineFsStat)
	}
	return result
}

func MachineStatsFromV1(cont *v1.ContainerInfo) []MachineStats {
	var stats []MachineStats
	var last *v1.ContainerStats
	for i := range cont.Stats {
		val := cont.Stats[i]
		stat := MachineStats{
			Timestamp: val.Timestamp,
		}
		if cont.Spec.HasCpu {
			stat.Cpu = &val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if cont.Spec.HasMemory {
			stat.Memory = &val.Memory
		}
		if cont.Spec.HasNetwork {
			stat.Network = &NetworkStats{
				// FIXME: Use reflection instead.
				Tcp:        TcpStat(val.Network.Tcp),
				Tcp6:       TcpStat(val.Network.Tcp6),
				Interfaces: val.Network.Interfaces,
			}
		}
		if cont.Spec.HasFilesystem {
			stat.Filesystem = machineFsStatsFromV1(val.Filesystem)
		}
		// TODO(rjnagal): Handle load stats.
		stats = append(stats, stat)
	}
	return stats
}

func ContainerStatsFromV1(containerName string, spec *v1.ContainerSpec, stats []*v1.ContainerStats) []*ContainerStats {
	newStats := make([]*ContainerStats, 0, len(stats))
	var last *v1.ContainerStats
	for _, val := range stats {
		stat := &ContainerStats{
			Timestamp: val.Timestamp,
		}
		if spec.HasCpu {
			stat.Cpu = &val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if spec.HasMemory {
			stat.Memory = &val.Memory
		}
		if spec.HasNetwork {
			// TODO: Handle TcpStats
			stat.Network = &NetworkStats{
				Tcp:        TcpStat(val.Network.Tcp),
				Tcp6:       TcpStat(val.Network.Tcp6),
				Interfaces: val.Network.Interfaces,
			}
		}
		if spec.HasFilesystem {
			if len(val.Filesystem) == 1 {
				stat.Filesystem = &FilesystemStats{
					TotalUsageBytes: &val.Filesystem[0].Usage,
					BaseUsageBytes:  &val.Filesystem[0].BaseUsage,
					InodeUsage:      &val.Filesystem[0].Inodes,
				}
			} else if len(val.Filesystem) > 1 && containerName != "/" {
				// Cannot handle multiple devices per container.
				glog.V(4).Infof("failed to handle multiple devices for container %s. Skipping Filesystem stats", containerName)
			}
		}
		if spec.HasDiskIo {
			stat.DiskIo = &val.DiskIo
		}
		if spec.HasCustomMetrics {
			stat.CustomMetrics = val.CustomMetrics
		}
		// TODO(rjnagal): Handle load stats.
		newStats = append(newStats, stat)
	}
	return newStats
}

func DeprecatedStatsFromV1(cont *v1.ContainerInfo) []DeprecatedContainerStats {
	stats := make([]DeprecatedContainerStats, 0, len(cont.Stats))
	var last *v1.ContainerStats
	for _, val := range cont.Stats {
		stat := DeprecatedContainerStats{
			Timestamp:        val.Timestamp,
			HasCpu:           cont.Spec.HasCpu,
			HasMemory:        cont.Spec.HasMemory,
			HasNetwork:       cont.Spec.HasNetwork,
			HasFilesystem:    cont.Spec.HasFilesystem,
			HasDiskIo:        cont.Spec.HasDiskIo,
			HasCustomMetrics: cont.Spec.HasCustomMetrics,
		}
		if stat.HasCpu {
			stat.Cpu = val.Cpu
			cpuInst, err := InstCpuStats(last, val)
			if err != nil {
				glog.Warningf("Could not get instant cpu stats: %v", err)
			} else {
				stat.CpuInst = cpuInst
			}
			last = val
		}
		if stat.HasMemory {
			stat.Memory = val.Memory
		}
		if stat.HasNetwork {
			stat.Network.Interfaces = val.Network.Interfaces
		}
		if stat.HasFilesystem {
			stat.Filesystem = val.Filesystem
		}
		if stat.HasDiskIo {
			stat.DiskIo = val.DiskIo
		}
		if stat.HasCustomMetrics {
			stat.CustomMetrics = val.CustomMetrics
		}
		// TODO(rjnagal): Handle load stats.
		stats = append(stats, stat)
	}
	return stats
}

func InstCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) {
	if last == nil {
		return nil, nil
	}
	if !cur.Timestamp.After(last.Timestamp) {
		return nil, fmt.Errorf("container stats move backwards in time")
	}
	if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
		return nil, fmt.Errorf("different number of cpus")
	}
	timeDelta := cur.Timestamp.Sub(last.Timestamp)
	if timeDelta <= 100*time.Millisecond {
		return nil, fmt.Errorf("time delta unexpectedly small")
	}
	// Nanoseconds to gain precision and avoid having zero seconds if the
	// difference between the timestamps is just under a second
	timeDeltaNs := uint64(timeDelta.Nanoseconds())
	convertToRate := func(lastValue, curValue uint64) (uint64, error) {
		if curValue < lastValue {
			return 0, fmt.Errorf("cumulative stats decrease")
		}
		valueDelta := curValue - lastValue
		// Use float64 to keep precision
		return uint64(float64(valueDelta) / float64(timeDeltaNs) * 1e9), nil
	}
	total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
	if err != nil {
		return nil, err
	}
	percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
	for i := range percpu {
		var err error
		percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
		if err != nil {
			return nil, err
		}
	}
	user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
	if err != nil {
		return nil, err
	}
	system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
	if err != nil {
		return nil, err
	}
	return &CpuInstStats{
		Usage: CpuInstUsage{
			Total:  total,
			PerCpu: percpu,
			User:   user,
			System: system,
		},
	}, nil
}

// Get V2 container spec from v1 container info.
func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace string) ContainerSpec {
	specV2 := ContainerSpec{
		CreationTime:     specV1.CreationTime,
		HasCpu:           specV1.HasCpu,
		HasMemory:        specV1.HasMemory,
		HasFilesystem:    specV1.HasFilesystem,
		HasNetwork:       specV1.HasNetwork,
		HasDiskIo:        specV1.HasDiskIo,
		HasCustomMetrics: specV1.HasCustomMetrics,
		Image:            specV1.Image,
		Labels:           specV1.Labels,
	}
	if specV1.HasCpu {
		specV2.Cpu.Limit = specV1.Cpu.Limit
		specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
		specV2.Cpu.Mask = specV1.Cpu.Mask
	}
	if specV1.HasMemory {
		specV2.Memory.Limit = specV1.Memory.Limit
		specV2.Memory.Reservation = specV1.Memory.Reservation
		specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
	}
	if specV1.HasCustomMetrics {
		specV2.CustomMetrics = specV1.CustomMetrics
	}
	specV2.Aliases = aliases
	specV2.Namespace = namespace
	return specV2
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes 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 resource

import (
	"fmt"
	"reflect"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
)

// ErrMatchFunc can be used to filter errors that may not be true failures.
type ErrMatchFunc func(error) bool

// Result contains helper methods for dealing with the outcome of a Builder.
type Result struct {
	err     error
	visitor Visitor

	sources            []Visitor
	singleItemImplied  bool
	targetsSingleItems bool

	ignoreErrors []utilerrors.Matcher

	// populated by a call to Infos
	info []*Info
}

// withError allows a fluent style for internal result code.
func (r *Result) withError(err error) *Result {
	r.err = err
	return r
}

// TargetsSingleItems returns true if any of the builder arguments pointed
// to non-list calls (if the user explicitly asked for any object by name).
// This includes directories, streams, URLs, and resource name tuples.
func (r *Result) TargetsSingleItems() bool {
	return r.targetsSingleItems
}

// IgnoreErrors will filter errors that occur when by visiting the result
// (but not errors that occur by creating the result in the first place),
// eliminating any that match fns. This is best used in combination with
// Builder.ContinueOnError(), where the visitors accumulate errors and return
// them after visiting as a slice of errors. If no errors remain after
// filtering, the various visitor methods on Result will return nil for
// err.
func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
	for _, fn := range fns {
		r.ignoreErrors = append(r.ignoreErrors, utilerrors.Matcher(fn))
	}
	return r
}

// Err returns one or more errors (via a util.ErrorList) that occurred prior
// to visiting the elements in the visitor. To see all errors including those
// that occur during visitation, invoke Infos().
func (r *Result) Err() error {
	return r.err
}

// Visit implements the Visitor interface on the items described in the Builder.
// Note that some visitor sources are not traversable more than once, or may
// return different results.  If you wish to operate on the same set of resources
// multiple times, use the Infos() method.
func (r *Result) Visit(fn VisitorFunc) error {
	if r.err != nil {
		return r.err
	}
	err := r.visitor.Visit(fn)
	return utilerrors.FilterOut(err, r.ignoreErrors...)
}

// IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
// implies a single item, or multiple.
func (r *Result) IntoSingleItemImplied(b *bool) *Result {
	*b = r.singleItemImplied
	return r
}

// Infos returns an array of all of the resource infos retrieved via traversal.
// Will attempt to traverse the entire set of visitors only once, and will return
// a cached list on subsequent calls.
func (r *Result) Infos() ([]*Info, error) {
	if r.err != nil {
		return nil, r.err
	}
	if r.info != nil {
		return r.info, nil
	}

	infos := []*Info{}
	err := r.visitor.Visit(func(info *Info, err error) error {
		if err != nil {
			return err
		}
		infos = append(infos, info)
		return nil
	})
	err = utilerrors.FilterOut(err, r.ignoreErrors...)

	r.info, r.err = infos, err
	return infos, err
}

// Object returns a single object representing the output of a single visit to all
// found resources.  If the Builder was a singular context (expected to return a
// single resource by user input) and only a single resource was found, the resource
// will be returned as is.  Otherwise, the returned resources will be part of an
// api.List. The ResourceVersion of the api.List will be set only if it is identical
// across all infos returned.
func (r *Result) Object() (runtime.Object, error) {
	infos, err := r.Infos()
	if err != nil {
		return nil, err
	}

	versions := sets.String{}
	objects := []runtime.Object{}
	for _, info := range infos {
		if info.Object != nil {
			objects = append(objects, info.Object)
			versions.Insert(info.ResourceVersion)
		}
	}

	if len(objects) == 1 {
		if r.singleItemImplied {
			return objects[0], nil
		}
		// if the item is a list already, don't create another list
		if meta.IsListType(objects[0]) {
			return objects[0], nil
		}
	}

	version := ""
	if len(versions) == 1 {
		version = versions.List()[0]
	}
	return &api.List{
		ListMeta: metav1.ListMeta{
			ResourceVersion: version,
		},
		Items: objects,
	}, err
}

// ResourceMapping returns a single meta.RESTMapping representing the
// resources located by the builder, or an error if more than one
// mapping was found.
func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
	if r.err != nil {
		return nil, r.err
	}
	mappings := map[string]*meta.RESTMapping{}
	for i := range r.sources {
		m, ok := r.sources[i].(ResourceMapping)
		if !ok {
			return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
		}
		mapping := m.ResourceMapping()
		mappings[mapping.Resource] = mapping
	}
	if len(mappings) != 1 {
		return nil, fmt.Errorf("expected only a single resource type")
	}
	for _, mapping := range mappings {
		return mapping, nil
	}
	return nil, nil
}

// Watch retrieves changes that occur on the server to the specified resource.
// It currently supports watching a single source - if the resource source
// (selectors or pure types) can be watched, they will be, otherwise the list
// will be visited (equivalent to the Infos() call) and if there is a single
// resource present, it will be watched, otherwise an error will be returned.
func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
	if r.err != nil {
		return nil, r.err
	}
	if len(r.sources) != 1 {
		return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
	}
	w, ok := r.sources[0].(Watchable)
	if !ok {
		info, err := r.Infos()
		if err != nil {
			return nil, err
		}
		if len(info) != 1 {
			return nil, fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(info))
		}
		return info[0].Watch(resourceVersion)
	}
	return w.Watch(resourceVersion)
}

// AsVersionedObject converts a list of infos into a single object - either a List containing
// the objects as children, or if only a single Object is present, as that object. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObject(infos []*Info, forceList bool, version schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) {
	objects, err := AsVersionedObjects(infos, version, encoder)
	if err != nil {
		return nil, err
	}

	var object runtime.Object
	if len(objects) == 1 && !forceList {
		object = objects[0]
	} else {
		object = &api.List{Items: objects}
		converted, err := TryConvert(api.Scheme, object, version, api.Registry.GroupOrDie(api.GroupName).GroupVersion)
		if err != nil {
			return nil, err
		}
		object = converted
	}

	actualVersion := object.GetObjectKind().GroupVersionKind()
	if actualVersion.Version != version.Version {
		defaultVersionInfo := ""
		if len(actualVersion.Version) > 0 {
			defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version)
		}
		glog.V(1).Infof("info: the output version specified is invalid. %s\n", defaultVersionInfo)
	}
	return object, nil
}

// AsVersionedObjects converts a list of infos into versioned objects. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObjects(infos []*Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) {
	objects := []runtime.Object{}
	for _, info := range infos {
		if info.Object == nil {
			continue
		}

		// TODO: use info.VersionedObject as the value?
		switch obj := info.Object.(type) {
		case *extensions.ThirdPartyResourceData:
			objects = append(objects, &runtime.Unknown{Raw: obj.Data})
			continue
		}

		// objects that are not part of api.Scheme must be converted to JSON
		// TODO: convert to map[string]interface{}, attach to runtime.Unknown?
		if !version.Empty() {
			if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) {
				// TODO: ideally this would encode to version, but we don't expose multiple codecs here.
				data, err := runtime.Encode(encoder, info.Object)
				if err != nil {
					return nil, err
				}
				// TODO: Set ContentEncoding and ContentType.
				objects = append(objects, &runtime.Unknown{Raw: data})
				continue
			}
		}

		converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion())
		if err != nil {
			return nil, err
		}
		objects = append(objects, converted)
	}
	return objects, nil
}

// TryConvert attempts to convert the given object to the provided versions in order. This function assumes
// the object is in internal version.
func TryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) {
	var last error
	for _, version := range versions {
		if version.Empty() {
			return object, nil
		}
		obj, err := converter.ConvertToVersion(object, version)
		if err != nil {
			last = err
			continue
		}
		return obj, nil
	}
	return nil, last
}
<commit_msg>UPSTREAM: 00000: make AsVersionedObjects default cleanly<commit_after>/*
Copyright 2014 The Kubernetes 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 resource

import (
	"fmt"
	"reflect"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
)

// ErrMatchFunc can be used to filter errors that may not be true failures.
type ErrMatchFunc func(error) bool

// Result contains helper methods for dealing with the outcome of a Builder.
type Result struct {
	err     error
	visitor Visitor

	sources            []Visitor
	singleItemImplied  bool
	targetsSingleItems bool

	ignoreErrors []utilerrors.Matcher

	// populated by a call to Infos
	info []*Info
}

// withError allows a fluent style for internal result code.
func (r *Result) withError(err error) *Result {
	r.err = err
	return r
}

// TargetsSingleItems returns true if any of the builder arguments pointed
// to non-list calls (if the user explicitly asked for any object by name).
// This includes directories, streams, URLs, and resource name tuples.
func (r *Result) TargetsSingleItems() bool {
	return r.targetsSingleItems
}

// IgnoreErrors will filter errors that occur when by visiting the result
// (but not errors that occur by creating the result in the first place),
// eliminating any that match fns. This is best used in combination with
// Builder.ContinueOnError(), where the visitors accumulate errors and return
// them after visiting as a slice of errors. If no errors remain after
// filtering, the various visitor methods on Result will return nil for
// err.
func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
	for _, fn := range fns {
		r.ignoreErrors = append(r.ignoreErrors, utilerrors.Matcher(fn))
	}
	return r
}

// Err returns one or more errors (via a util.ErrorList) that occurred prior
// to visiting the elements in the visitor. To see all errors including those
// that occur during visitation, invoke Infos().
func (r *Result) Err() error {
	return r.err
}

// Visit implements the Visitor interface on the items described in the Builder.
// Note that some visitor sources are not traversable more than once, or may
// return different results.  If you wish to operate on the same set of resources
// multiple times, use the Infos() method.
func (r *Result) Visit(fn VisitorFunc) error {
	if r.err != nil {
		return r.err
	}
	err := r.visitor.Visit(fn)
	return utilerrors.FilterOut(err, r.ignoreErrors...)
}

// IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
// implies a single item, or multiple.
func (r *Result) IntoSingleItemImplied(b *bool) *Result {
	*b = r.singleItemImplied
	return r
}

// Infos returns an array of all of the resource infos retrieved via traversal.
// Will attempt to traverse the entire set of visitors only once, and will return
// a cached list on subsequent calls.
func (r *Result) Infos() ([]*Info, error) {
	if r.err != nil {
		return nil, r.err
	}
	if r.info != nil {
		return r.info, nil
	}

	infos := []*Info{}
	err := r.visitor.Visit(func(info *Info, err error) error {
		if err != nil {
			return err
		}
		infos = append(infos, info)
		return nil
	})
	err = utilerrors.FilterOut(err, r.ignoreErrors...)

	r.info, r.err = infos, err
	return infos, err
}

// Object returns a single object representing the output of a single visit to all
// found resources.  If the Builder was a singular context (expected to return a
// single resource by user input) and only a single resource was found, the resource
// will be returned as is.  Otherwise, the returned resources will be part of an
// api.List. The ResourceVersion of the api.List will be set only if it is identical
// across all infos returned.
func (r *Result) Object() (runtime.Object, error) {
	infos, err := r.Infos()
	if err != nil {
		return nil, err
	}

	versions := sets.String{}
	objects := []runtime.Object{}
	for _, info := range infos {
		if info.Object != nil {
			objects = append(objects, info.Object)
			versions.Insert(info.ResourceVersion)
		}
	}

	if len(objects) == 1 {
		if r.singleItemImplied {
			return objects[0], nil
		}
		// if the item is a list already, don't create another list
		if meta.IsListType(objects[0]) {
			return objects[0], nil
		}
	}

	version := ""
	if len(versions) == 1 {
		version = versions.List()[0]
	}
	return &api.List{
		ListMeta: metav1.ListMeta{
			ResourceVersion: version,
		},
		Items: objects,
	}, err
}

// ResourceMapping returns a single meta.RESTMapping representing the
// resources located by the builder, or an error if more than one
// mapping was found.
func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
	if r.err != nil {
		return nil, r.err
	}
	mappings := map[string]*meta.RESTMapping{}
	for i := range r.sources {
		m, ok := r.sources[i].(ResourceMapping)
		if !ok {
			return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
		}
		mapping := m.ResourceMapping()
		mappings[mapping.Resource] = mapping
	}
	if len(mappings) != 1 {
		return nil, fmt.Errorf("expected only a single resource type")
	}
	for _, mapping := range mappings {
		return mapping, nil
	}
	return nil, nil
}

// Watch retrieves changes that occur on the server to the specified resource.
// It currently supports watching a single source - if the resource source
// (selectors or pure types) can be watched, they will be, otherwise the list
// will be visited (equivalent to the Infos() call) and if there is a single
// resource present, it will be watched, otherwise an error will be returned.
func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
	if r.err != nil {
		return nil, r.err
	}
	if len(r.sources) != 1 {
		return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
	}
	w, ok := r.sources[0].(Watchable)
	if !ok {
		info, err := r.Infos()
		if err != nil {
			return nil, err
		}
		if len(info) != 1 {
			return nil, fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(info))
		}
		return info[0].Watch(resourceVersion)
	}
	return w.Watch(resourceVersion)
}

// AsVersionedObject converts a list of infos into a single object - either a List containing
// the objects as children, or if only a single Object is present, as that object. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObject(infos []*Info, forceList bool, version schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) {
	objects, err := AsVersionedObjects(infos, version, encoder)
	if err != nil {
		return nil, err
	}

	var object runtime.Object
	if len(objects) == 1 && !forceList {
		object = objects[0]
	} else {
		object = &api.List{Items: objects}
		targetVersions := []schema.GroupVersion{}
		if !version.Empty() {
			targetVersions = append(targetVersions, version)
		}
		targetVersions = append(targetVersions, api.Registry.GroupOrDie(api.GroupName).GroupVersion)

		converted, err := TryConvert(api.Scheme, object, targetVersions...)
		if err != nil {
			return nil, err
		}
		object = converted
	}

	actualVersion := object.GetObjectKind().GroupVersionKind()
	if actualVersion.Version != version.Version {
		defaultVersionInfo := ""
		if len(actualVersion.Version) > 0 {
			defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version)
		}
		glog.V(1).Infof("info: the output version specified is invalid. %s\n", defaultVersionInfo)
	}
	return object, nil
}

// AsVersionedObjects converts a list of infos into versioned objects. The provided
// version will be preferred as the conversion target, but the Object's mapping version will be
// used if that version is not present.
func AsVersionedObjects(infos []*Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) {
	objects := []runtime.Object{}
	for _, info := range infos {
		if info.Object == nil {
			continue
		}

		// TODO: use info.VersionedObject as the value?
		switch obj := info.Object.(type) {
		case *extensions.ThirdPartyResourceData:
			objects = append(objects, &runtime.Unknown{Raw: obj.Data})
			continue
		}

		targetVersions := []schema.GroupVersion{}
		// objects that are not part of api.Scheme must be converted to JSON
		// TODO: convert to map[string]interface{}, attach to runtime.Unknown?
		if !version.Empty() {
			if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) {
				// TODO: ideally this would encode to version, but we don't expose multiple codecs here.
				data, err := runtime.Encode(encoder, info.Object)
				if err != nil {
					return nil, err
				}
				// TODO: Set ContentEncoding and ContentType.
				objects = append(objects, &runtime.Unknown{Raw: data})
				continue
			}
			targetVersions = append(targetVersions, version)
		}
		targetVersions = append(targetVersions, info.Mapping.GroupVersionKind.GroupVersion())

		converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, targetVersions...)
		if err != nil {
			return nil, err
		}
		objects = append(objects, converted)
	}
	return objects, nil
}

// TryConvert attempts to convert the given object to the provided versions in order. This function assumes
// the object is in internal version.
func TryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) {
	var last error
	for _, version := range versions {
		if version.Empty() {
			return object, nil
		}
		obj, err := converter.ConvertToVersion(object, version)
		if err != nil {
			last = err
			continue
		}
		return obj, nil
	}
	return nil, last
}
<|endoftext|>


<commit_before>package rpg2d_test

import (
	"bytes"
	"encoding/gob"
	"encoding/json"

	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/entity/entitytest"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/rpg2d/rpg2dtest"
	"github.com/ghthor/engine/sim/stime"

	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
)

func init() {
	gob.Register(entitytest.MockEntityState{})
}

func DescribeWorldState(c gospec.Context) {
	quadTree, err := quad.New(coord.Bounds{
		coord.Cell{-4, 4},
		coord.Cell{3, -3},
	}, 20, nil)
	c.Assume(err, IsNil)

	terrain, err := rpg2d.NewTerrainMap(quadTree.Bounds(), `
DDDDDDDD
DGGGGGGD
DGGRRGGD
DGRRRRGD
DGRRRRGD
DGGRRGGD
DGGGGGGD
DDDDDDDD
`)
	c.Assume(err, IsNil)

	world := rpg2d.NewWorld(stime.Time(0), quadTree, terrain)

	mockEntity := entitytest.MockEntity{EntityId: 0}
	world.Insert(mockEntity)

	worldState := world.ToState()

	c.Assume(worldState.Time, Equals, stime.Time(0))
	c.Assume(len(worldState.Entities), Equals, 1)

	c.Specify("a world state", func() {
		c.Specify("can be encoded as json", func() {
			jsonBytes, err := json.Marshal(worldState)
			c.Expect(err, IsNil)
			c.Expect(string(jsonBytes), Equals, `{"time":0,"bounds":{"tl":{"x":-4,"y":4},"br":{"x":3,"y":-3}},"entities":[{"id":0,"name":"MockEntity0","cell":{"x":0,"y":0}}],"terrainMap":{"bounds":{"tl":{"x":-4,"y":4},"br":{"x":3,"y":-3}},"terrain":"\nDDDDDDDD\nDGGGGGGD\nDGGRRGGD\nDGRRRRGD\nDGRRRRGD\nDGGRRGGD\nDGGGGGGD\nDDDDDDDD\n"}}`)
		})

		func() {
			buf := bytes.NewBuffer(make([]byte, 0, 1024))
			enc := gob.NewEncoder(buf)

			c.Specify("can be encoded as a gob object", func() {
				c.Expect(enc.Encode(worldState), IsNil)
			})

			c.Specify("can be decoded from a gob object", func() {
				dec := gob.NewDecoder(buf)
				c.Assume(enc.Encode(worldState), IsNil)

				state := rpg2d.WorldState{}
				c.Expect(dec.Decode(&state), IsNil)
				c.Expect(state, rpg2dtest.StateEquals, worldState)
			})
		}()

		c.Specify("can be cloned and modified", func() {
			world.Insert(entitytest.MockEntity{EntityId: 1})
			world.Insert(entitytest.MockEntity{EntityId: 2})
			world.Insert(entitytest.MockEntity{EntityId: 3})
			world.Insert(entitytest.MockEntity{EntityId: 4})

			worldState = world.ToState()
			clone := worldState.Clone()

			// Modify the clone
			clone.Entities = append(clone.Entities[:2], clone.Entities[3:]...)

			// Check that the modification didn't effect the original
			for i, e := range worldState.Entities {
				e, isMockEntity := e.(entitytest.MockEntityState)
				c.Assume(isMockEntity, IsTrue)
				c.Expect(e.EntityId(), Equals, entity.Id(i))
			}
		})

		c.Specify("can be culled by a bounding rectangle", func() {
			toBeCulled := []entity.State{
				entitytest.MockEntity{EntityCell: coord.Cell{-3, 3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{3, 3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{3, -3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{-3, -3}}.ToState(),
			}

			wontBeCulled := []entity.State{
				entitytest.MockEntity{EntityCell: coord.Cell{-2, 2}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{2, 2}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{2, -2}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{-2, -2}}.ToState(),
			}

			worldState.Entities = append(worldState.Entities[:0], wontBeCulled...)
			worldState.Entities = append(worldState.Entities, toBeCulled...)

			worldState = worldState.Cull(coord.Bounds{
				coord.Cell{-2, 2},
				coord.Cell{2, -2},
			})

			c.Expect(worldState.Bounds, Equals, coord.Bounds{
				coord.Cell{-2, 2},
				coord.Cell{2, -2},
			})

			c.Expect(worldState.Entities, Not(ContainsAll), toBeCulled)
			c.Expect(worldState.Entities, ContainsAll, wontBeCulled)
			c.Expect(worldState.TerrainMap.String(), Equals, `
GRRGG
RRRRG
RRRRG
GRRGG
GGGGG
`)
		})

		c.Specify("can calculate the differences with a previous worldState state", func() {
			c.Specify("when there are no differences", func() {
				c.Expect(len(worldState.Diff(worldState).Entities), Equals, 0)
			})

			c.Specify("when an entity has changed state", func() {
				clone := worldState.Clone()
				entity := clone.Entities[0].(entitytest.MockEntityState)

				// This is a state change
				entity.Cell = coord.Cell{-1, 0}
				clone.Entities[0] = entity

				c.Expect(len(worldState.Diff(clone).Entities), Equals, 1)
			})

			c.Specify("when there is a new entity", func() {
				clone := worldState.Clone()
				clone.Entities = append(clone.Entities, entitytest.MockEntity{EntityId: 1}.ToState())
				c.Expect(len(worldState.Diff(clone).Entities), Equals, 1)
			})

			c.Specify("when an entity doesn't exist anymore", func() {
				clone := worldState.Clone()
				clone.Entities = clone.Entities[:0]
				c.Expect(len(worldState.Diff(clone).Removed), Equals, 1)
			})

			c.Specify("when the viewport has changed", func() {
				clone := worldState.Clone()
				worldState = worldState.Cull(coord.Bounds{
					coord.Cell{-2, 2},
					coord.Cell{2, -2},
				})

				// TODO Specify all 4 directions and all 4 corners
				clone = clone.Cull(coord.Bounds{
					coord.Cell{-3, 2},
					coord.Cell{1, -2},
				})
				c.Expect(worldState.Diff(clone).TerrainMapSlices, Not(IsNil))
			})

			c.Specify("when the viewport hasn't changed", func() {
				clone := worldState.Clone()
				c.Expect(worldState.Diff(clone).TerrainMapSlices, ContainsExactly, []*rpg2d.TerrainMapState{})
			})
		})

		c.Specify("can be updated with a world state diff", func() {
			c.Specify("that updates the time", func() {
				nextState := world.ToState()
				nextState.Time++

				worldState.Apply(worldState.Diff(nextState))

				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})

			c.Specify("that contains a new entity", func() {
				world.Insert(entitytest.MockEntity{EntityId: 1})
				nextState := world.ToState()
				diff := worldState.Diff(nextState)

				worldState.Apply(diff)
				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})

			c.Specify("that removes an entity", func() {
				world.Remove(mockEntity)
				nextState := world.ToState()
				diff := worldState.Diff(nextState)

				worldState.Apply(diff)
				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})
		})
	})
}
<commit_msg>[rpg2d] simplify the bounds used for cull specification<commit_after>package rpg2d_test

import (
	"bytes"
	"encoding/gob"
	"encoding/json"

	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/entity/entitytest"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/rpg2d/rpg2dtest"
	"github.com/ghthor/engine/sim/stime"

	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
)

func init() {
	gob.Register(entitytest.MockEntityState{})
}

func DescribeWorldState(c gospec.Context) {
	quadTree, err := quad.New(coord.Bounds{
		coord.Cell{-4, 4},
		coord.Cell{3, -3},
	}, 20, nil)
	c.Assume(err, IsNil)

	terrain, err := rpg2d.NewTerrainMap(quadTree.Bounds(), `
DDDDDDDD
DGGGGGGD
DGGRRGGD
DGRRRRGD
DGRRRRGD
DGGRRGGD
DGGGGGGD
DDDDDDDD
`)
	c.Assume(err, IsNil)

	world := rpg2d.NewWorld(stime.Time(0), quadTree, terrain)

	mockEntity := entitytest.MockEntity{EntityId: 0}
	world.Insert(mockEntity)

	worldState := world.ToState()

	c.Assume(worldState.Time, Equals, stime.Time(0))
	c.Assume(len(worldState.Entities), Equals, 1)

	c.Specify("a world state", func() {
		c.Specify("can be encoded as json", func() {
			jsonBytes, err := json.Marshal(worldState)
			c.Expect(err, IsNil)
			c.Expect(string(jsonBytes), Equals, `{"time":0,"bounds":{"tl":{"x":-4,"y":4},"br":{"x":3,"y":-3}},"entities":[{"id":0,"name":"MockEntity0","cell":{"x":0,"y":0}}],"terrainMap":{"bounds":{"tl":{"x":-4,"y":4},"br":{"x":3,"y":-3}},"terrain":"\nDDDDDDDD\nDGGGGGGD\nDGGRRGGD\nDGRRRRGD\nDGRRRRGD\nDGGRRGGD\nDGGGGGGD\nDDDDDDDD\n"}}`)
		})

		func() {
			buf := bytes.NewBuffer(make([]byte, 0, 1024))
			enc := gob.NewEncoder(buf)

			c.Specify("can be encoded as a gob object", func() {
				c.Expect(enc.Encode(worldState), IsNil)
			})

			c.Specify("can be decoded from a gob object", func() {
				dec := gob.NewDecoder(buf)
				c.Assume(enc.Encode(worldState), IsNil)

				state := rpg2d.WorldState{}
				c.Expect(dec.Decode(&state), IsNil)
				c.Expect(state, rpg2dtest.StateEquals, worldState)
			})
		}()

		c.Specify("can be cloned and modified", func() {
			world.Insert(entitytest.MockEntity{EntityId: 1})
			world.Insert(entitytest.MockEntity{EntityId: 2})
			world.Insert(entitytest.MockEntity{EntityId: 3})
			world.Insert(entitytest.MockEntity{EntityId: 4})

			worldState = world.ToState()
			clone := worldState.Clone()

			// Modify the clone
			clone.Entities = append(clone.Entities[:2], clone.Entities[3:]...)

			// Check that the modification didn't effect the original
			for i, e := range worldState.Entities {
				e, isMockEntity := e.(entitytest.MockEntityState)
				c.Assume(isMockEntity, IsTrue)
				c.Expect(e.EntityId(), Equals, entity.Id(i))
			}
		})

		c.Specify("can be culled by a bounding rectangle", func() {
			toBeCulled := []entity.State{
				entitytest.MockEntity{EntityCell: coord.Cell{-3, 3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{3, 3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{3, -3}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{-3, -3}}.ToState(),
			}

			wontBeCulled := []entity.State{
				entitytest.MockEntity{EntityCell: coord.Cell{-2, 2}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{1, 2}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{1, -1}}.ToState(),
				entitytest.MockEntity{EntityCell: coord.Cell{-2, -1}}.ToState(),
			}

			worldState.Entities = append(worldState.Entities[:0], wontBeCulled...)
			worldState.Entities = append(worldState.Entities, toBeCulled...)

			worldState = worldState.Cull(coord.Bounds{
				coord.Cell{-2, 2},
				coord.Cell{1, -1},
			})

			c.Expect(worldState.Bounds, Equals, coord.Bounds{
				coord.Cell{-2, 2},
				coord.Cell{1, -1},
			})

			c.Expect(worldState.Entities, Not(ContainsAll), toBeCulled)
			c.Expect(worldState.Entities, ContainsAll, wontBeCulled)
			c.Expect(worldState.TerrainMap.String(), Equals, `
GRRG
RRRR
RRRR
GRRG
`)
		})

		c.Specify("can calculate the differences with a previous worldState state", func() {
			c.Specify("when there are no differences", func() {
				c.Expect(len(worldState.Diff(worldState).Entities), Equals, 0)
			})

			c.Specify("when an entity has changed state", func() {
				clone := worldState.Clone()
				entity := clone.Entities[0].(entitytest.MockEntityState)

				// This is a state change
				entity.Cell = coord.Cell{-1, 0}
				clone.Entities[0] = entity

				c.Expect(len(worldState.Diff(clone).Entities), Equals, 1)
			})

			c.Specify("when there is a new entity", func() {
				clone := worldState.Clone()
				clone.Entities = append(clone.Entities, entitytest.MockEntity{EntityId: 1}.ToState())
				c.Expect(len(worldState.Diff(clone).Entities), Equals, 1)
			})

			c.Specify("when an entity doesn't exist anymore", func() {
				clone := worldState.Clone()
				clone.Entities = clone.Entities[:0]
				c.Expect(len(worldState.Diff(clone).Removed), Equals, 1)
			})

			c.Specify("when the viewport has changed", func() {
				clone := worldState.Clone()
				worldState = worldState.Cull(coord.Bounds{
					coord.Cell{-2, 2},
					coord.Cell{2, -2},
				})

				// TODO Specify all 4 directions and all 4 corners
				clone = clone.Cull(coord.Bounds{
					coord.Cell{-3, 2},
					coord.Cell{1, -2},
				})
				c.Expect(worldState.Diff(clone).TerrainMapSlices, Not(IsNil))
			})

			c.Specify("when the viewport hasn't changed", func() {
				clone := worldState.Clone()
				c.Expect(worldState.Diff(clone).TerrainMapSlices, ContainsExactly, []*rpg2d.TerrainMapState{})
			})
		})

		c.Specify("can be updated with a world state diff", func() {
			c.Specify("that updates the time", func() {
				nextState := world.ToState()
				nextState.Time++

				worldState.Apply(worldState.Diff(nextState))

				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})

			c.Specify("that contains a new entity", func() {
				world.Insert(entitytest.MockEntity{EntityId: 1})
				nextState := world.ToState()
				diff := worldState.Diff(nextState)

				worldState.Apply(diff)
				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})

			c.Specify("that removes an entity", func() {
				world.Remove(mockEntity)
				nextState := world.ToState()
				diff := worldState.Diff(nextState)

				worldState.Apply(diff)
				c.Expect(worldState, rpg2dtest.StateEquals, nextState)
			})
		})
	})
}
<|endoftext|>


<commit_before>package github

import (
	"github.com/google/go-github/github"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathLogin(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "login",
		Fields: map[string]*framework.FieldSchema{
			"token": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "GitHub personal API token",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.WriteOperation: b.pathLogin,
		},
	}
}

func (b *backend) pathLogin(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	// Get all our stored state
	config, err := b.Config(req.Storage)
	if err != nil {
		return nil, err
	}
	if config.Org == "" {
		return logical.ErrorResponse(
			"configure the github credential backend first"), nil
	}

	client, err := b.Client(data.Get("token").(string))
	if err != nil {
		return nil, err
	}

	// Get the user
	user, _, err := client.Users.Get("")
	if err != nil {
		return nil, err
	}

	// Verify that the user is part of the organization
	var org *github.Organization
	orgs, _, err := client.Organizations.List("", nil)
	if err != nil {
		return nil, err
	}

	for _, o := range orgs {
		if *o.Login == config.Org {
			org = &o
			break
		}
	}
	if org == nil {
		return logical.ErrorResponse("user is not part of required org"), nil
	}

	// Get the teams that this user is part of to determine the policies
	var teamNames []string
	teams, _, err := client.Organizations.ListUserTeams(nil)
	if err != nil {
		return nil, err
	}
	for _, t := range teams {
		// We only care about teams that are part of the organization we use
		if *t.Organization.ID != *org.ID {
			continue
		}

		// Append the names so we can get the policies
		teamNames = append(teamNames, *t.Name)
	}

	policiesList, err := b.Map.Policies(req.Storage, teamNames...)
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Auth: &logical.Auth{
			Policies: policiesList,
			Metadata: map[string]string{
				"username": *user.Login,
				"org":      *org.Login,
			},
			DisplayName: *user.Login,
		},
	}, nil
}
<commit_msg>    name slug check<commit_after>package github

import (
	"github.com/google/go-github/github"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathLogin(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "login",
		Fields: map[string]*framework.FieldSchema{
			"token": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "GitHub personal API token",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.WriteOperation: b.pathLogin,
		},
	}
}

func (b *backend) pathLogin(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	// Get all our stored state
	config, err := b.Config(req.Storage)
	if err != nil {
		return nil, err
	}
	if config.Org == "" {
		return logical.ErrorResponse(
			"configure the github credential backend first"), nil
	}

	client, err := b.Client(data.Get("token").(string))
	if err != nil {
		return nil, err
	}

	// Get the user
	user, _, err := client.Users.Get("")
	if err != nil {
		return nil, err
	}

	// Verify that the user is part of the organization
	var org *github.Organization
	orgs, _, err := client.Organizations.List("", nil)
	if err != nil {
		return nil, err
	}

	for _, o := range orgs {
		if *o.Login == config.Org {
			org = &o
			break
		}
	}
	if org == nil {
		return logical.ErrorResponse("user is not part of required org"), nil
	}

	// Get the teams that this user is part of to determine the policies
	var teamNames []string
	teams, _, err := client.Organizations.ListUserTeams(nil)
	if err != nil {
		return nil, err
	}
	for _, t := range teams {
		// We only care about teams that are part of the organization we use
		if *t.Organization.ID != *org.ID {
			continue
		}

		// Append the names AND slug so we can get the policies
                // Slug is needed for teamnames with whitespaces
		teamNames = append(teamNames, *t.Name)
                if *t.Name != *t.Slug {
                        teamNames = append(teamNames, *t.Slug)
                }
	}

	policiesList, err := b.Map.Policies(req.Storage, teamNames...)
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Auth: &logical.Auth{
			Policies: policiesList,
			Metadata: map[string]string{
				"username": *user.Login,
				"org":      *org.Login,
			},
			DisplayName: *user.Login,
		},
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net"

	"os"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	"github.com/projectcalico/calico-cni/utils"
	"github.com/tigera/libcalico-go/lib/client"
	cnet "github.com/tigera/libcalico-go/lib/net"
)

func main() {
	skel.PluginMain(cmdAdd, cmdDel)
}

type ipamArgs struct {
	types.CommonArgs
	IP net.IP `json:"ip,omitempty"`
}

func cmdAdd(args *skel.CmdArgs) error {
	conf := utils.NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if conf.Debug {
		utils.EnableDebugLogging()
	}

	calicoClient, err := utils.CreateClient(conf)
	if err != nil {
		return err
	}

	workloadID, _, err := utils.GetIdentifiers(args)
	if err != nil {
		return err
	}

	ipamArgs := ipamArgs{}
	if err = types.LoadArgs(args.Args, &ipamArgs); err != nil {
		return err
	}

	r := &types.Result{}
	if ipamArgs.IP != nil {
		fmt.Fprintf(os.Stderr, "Calico CNI IPAM request IP: %v\n", ipamArgs.IP)

		// The hostname will be defaulted to the actual hostname if cong.Hostname is empty
		assignArgs := client.AssignIPArgs{IP: cnet.IP{ipamArgs.IP}, HandleID: &workloadID, Hostname: conf.Hostname}
		err := calicoClient.IPAM().AssignIP(assignArgs)
		if err != nil {
			return err
		}

		ipV4Network := net.IPNet{IP: ipamArgs.IP, Mask: net.CIDRMask(32, 32)}
		r.IP4 = &types.IPConfig{IP: ipV4Network}
	} else {
		// Default to assigning an IPv4 address
		num4 := 1
		if conf.IPAM.AssignIpv4 != nil && *conf.IPAM.AssignIpv4 == "false" {
			num4 = 0
		}

		// Default to NOT assigning an IPv6 address
		num6 := 0
		if conf.IPAM.AssignIpv6 != nil && *conf.IPAM.AssignIpv6 == "true" {
			num6 = 1
		}

		fmt.Fprintf(os.Stderr, "Calico CNI IPAM request count IPv4=%d IPv6=%d\n", num4, num6)

		assignArgs := client.AutoAssignArgs{Num4: num4, Num6: num6, HandleID: &args.ContainerID, Hostname: conf.Hostname}
		assignedV4, assignedV6, err := calicoClient.IPAM().AutoAssign(assignArgs)
		fmt.Fprintf(os.Stderr, "Calico CNI IPAM assigned addresses IPv4=%v IPv6=%v\n", assignedV4, assignedV6)
		if err != nil {
			return err
		}

		if num4 == 1 {
			ipV4Network := net.IPNet{IP: assignedV4[0].IP, Mask: net.CIDRMask(32, 32)}
			r.IP4 = &types.IPConfig{IP: ipV4Network}
		}

		if num6 == 1 {
			ipV6Network := net.IPNet{IP: assignedV6[0].IP, Mask: net.CIDRMask(128, 128)}
			r.IP6 = &types.IPConfig{IP: ipV6Network}
		}
	}

	return r.Print()
}

func cmdDel(args *skel.CmdArgs) error {
	conf := utils.NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if conf.Debug {
		utils.EnableDebugLogging()
	}

	calicoClient, err := utils.CreateClient(conf)
	if err != nil {
		return err
	}

	// Release the IP address by using the handle - which is workloadID.
	workloadID, _, err := utils.GetIdentifiers(args)
	if err != nil {
		return err
	}

	if err := calicoClient.IPAM().ReleaseByHandle(workloadID); err != nil {
		return err
	}

	return nil

}
<commit_msg>Fix IPAM handles<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net"

	"os"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	"github.com/projectcalico/calico-cni/utils"
	"github.com/tigera/libcalico-go/lib/client"
	cnet "github.com/tigera/libcalico-go/lib/net"
)

func main() {
	skel.PluginMain(cmdAdd, cmdDel)
}

type ipamArgs struct {
	types.CommonArgs
	IP net.IP `json:"ip,omitempty"`
}

func cmdAdd(args *skel.CmdArgs) error {
	conf := utils.NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if conf.Debug {
		utils.EnableDebugLogging()
	}

	calicoClient, err := utils.CreateClient(conf)
	if err != nil {
		return err
	}

	workloadID, _, err := utils.GetIdentifiers(args)
	if err != nil {
		return err
	}

	ipamArgs := ipamArgs{}
	if err = types.LoadArgs(args.Args, &ipamArgs); err != nil {
		return err
	}

	r := &types.Result{}
	if ipamArgs.IP != nil {
		fmt.Fprintf(os.Stderr, "Calico CNI IPAM request IP: %v\n", ipamArgs.IP)

		// The hostname will be defaulted to the actual hostname if cong.Hostname is empty
		assignArgs := client.AssignIPArgs{IP: cnet.IP{ipamArgs.IP}, HandleID: &workloadID, Hostname: conf.Hostname}
		err := calicoClient.IPAM().AssignIP(assignArgs)
		if err != nil {
			return err
		}

		ipV4Network := net.IPNet{IP: ipamArgs.IP, Mask: net.CIDRMask(32, 32)}
		r.IP4 = &types.IPConfig{IP: ipV4Network}
	} else {
		// Default to assigning an IPv4 address
		num4 := 1
		if conf.IPAM.AssignIpv4 != nil && *conf.IPAM.AssignIpv4 == "false" {
			num4 = 0
		}

		// Default to NOT assigning an IPv6 address
		num6 := 0
		if conf.IPAM.AssignIpv6 != nil && *conf.IPAM.AssignIpv6 == "true" {
			num6 = 1
		}

		fmt.Fprintf(os.Stderr, "Calico CNI IPAM request count IPv4=%d IPv6=%d\n", num4, num6)

		assignArgs := client.AutoAssignArgs{Num4: num4, Num6: num6, HandleID: &workloadID, Hostname: conf.Hostname}
		assignedV4, assignedV6, err := calicoClient.IPAM().AutoAssign(assignArgs)
		fmt.Fprintf(os.Stderr, "Calico CNI IPAM assigned addresses IPv4=%v IPv6=%v\n", assignedV4, assignedV6)
		if err != nil {
			return err
		}

		if num4 == 1 {
			ipV4Network := net.IPNet{IP: assignedV4[0].IP, Mask: net.CIDRMask(32, 32)}
			r.IP4 = &types.IPConfig{IP: ipV4Network}
		}

		if num6 == 1 {
			ipV6Network := net.IPNet{IP: assignedV6[0].IP, Mask: net.CIDRMask(128, 128)}
			r.IP6 = &types.IPConfig{IP: ipV6Network}
		}
	}

	return r.Print()
}

func cmdDel(args *skel.CmdArgs) error {
	conf := utils.NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if conf.Debug {
		utils.EnableDebugLogging()
	}

	calicoClient, err := utils.CreateClient(conf)
	if err != nil {
		return err
	}

	// Release the IP address by using the handle - which is workloadID.
	workloadID, _, err := utils.GetIdentifiers(args)
	if err != nil {
		return err
	}

	if err := calicoClient.IPAM().ReleaseByHandle(workloadID); err != nil {
		return err
	}

	return nil

}
<|endoftext|>