<commit_before>// Copyright 2020 Datawire.  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.

///////////////////////////////////////////////////////////////////////////
// Important: Run "make update-yaml" to regenerate code after modifying
// this file.
///////////////////////////////////////////////////////////////////////////

// I'm not sure where a better place to put this is, so I'm putting it here:
//
// # API design guidelines
//
// Ambassador's API has inconsistencies because it has historical
// baggage.  Not all of Ambassador's existing API (or even most of
// it!?) follow these guidelines, but new additions to the API should.
// If/when we advance to getambassador.io/v3 and we can break
// compatibility, these are things that we should apply everywhere.
//
// - Prefer `camelCase` to `snake_case`
//   * Exception: Except for consistency with existing fields in the
//     same resource, or symmetry with identical fields in another
//     resource.
//   * Justification: Kubernetes style is to use camelCase. But
//     historically Ambassador used snake_case for everything.
//
// - Prefer for object references to not support namespacing
//   * Exception: If there's a real use-case for it.
//   * Justification: Most native Kubernetes resources don't support
//     referencing things in a different namespace.  We should be
//     opinionated and not support it either, unless there's a good
//     reason to in a specific case.
//
// - Prefer to use `corev1.LocalObjectReference` or
//   `corev1.SecretReference` references instead of
//   `{name}.{namespace}` strings.
//   * Justification: The `{name}.{namespace}` thing evolved "an
//     opaque DNS name" in the `service` field of Mappings, and that
//     was generalized to other things.  Outside of the context of
//     "this is usable as a DNS name to make a request to", it's just
//     confusing and introduces needless ambiguity.  Nothing other
//     than Ambassador uses that notation.
//   * Notes: For things that don't support cross-namespace references
//     (see above), use LocalObjectReference; if you really must
//     support cross-namespace references, then use SecretReference.
// - Prefer to use `metav1.Duration` fields instead of "_s" or "_ms"
//   numeric fields.
//
// - Don't have Ambassador populate anything in the `.spec`, only let
//   Ambassador set things in the `.status`.

package v2

import (
	"encoding/json"
)

// The old `k8s.io/kube-openapi/cmd/openapi-gen` command had ways to
// specify custom schemas for your types (1: define a "OpenAPIDefinition"
// method, or 2: define a "OpenAPIV3Definition" method, or 3: define
// "OpenAPISchemaType" and "OpenAPISchemaFormat" methods).  But the new
// `sigs.k8s.io/controller-tools/controller-gen` command doesn't; it just
// has a small number of "+kubebuilder:" magic comments ("markers") that we
// can use to influence the schema it generates.
//
// So, for example, we'd like to define the AmbassadorID schema as:
//
//    oneOf:
//    - type: "string"
//    - type: "array"
//    items:             # only matters if type=array
//      type: "string"
//
// but if we're going to use just vanilla controller-gen, we're forced to
// be dumb and say `+kubebuilder:validation:Type=""`, to define its schema
// as
//
//    # no `type:` setting because of the +kubebuilder marker
//    items:
//      type: "string"  # because of the raw type
//
// and then kubectl and/or the apiserver won't be able to validate
// AmbassadorID, because it won't be validated until we actually go to
// UnmarshalJSON it when it makes it to Ambassador.  That's pretty much
// what Kubernetes itself[1] does for the JSON Schema types that are unions
// like that.
//
//  > Aside: Some recent work in controller-gen[2] *strongly* suggests that
//  > setting `+kubebuilder:validation:Type=Any` instead of `:Type=""` is
//  > the proper thing to do.  But, um, it doesn't work... kubectl would
//  > say things like:
//  >
//  >    Invalid value: "array": spec.ambassador_id in body must be of type Any: "array"
//
// But honestly that's dumb, and we can do better than that.
//
// So, option one choice would be to send the controller-tools folks a PR
// to support the openapi-gen methods to allow that customization.  That's
// probably the Right Thing, but that seemed like more work than option
// two.  FIXME(lukeshu): Send the controller-tools folks a PR.
//
// Option two: Say something nonsensical like
// `+kubebuilder:validation:Type="d6e-union"`, and teach the `fix-crds`
// script to notice that and delete that nonsensical `type`, replacing it
// with the appropriate `oneOf: [type: A, type: B]` (note that the version
// of JSONSchema that OpenAPI/Kubernetes uses doesn't support type being an
// array).  And so that's what I did.
//
// FIXME(lukeshu): But all of that is still terrible.  Because the very
// structure of our data inherently means that we must have a
// non-structural[3] schema.  With "apiextensions.k8s.io/v1beta1" CRDs,
// non-structural schemas disable several features; and in v1 CRDs,
// non-structural schemas are entirely forbidden.  I mean it doesn't
// _really_ matter right now, because we give out v1beta1 CRDs anyway
// because v1 only became available in Kubernetes 1.16 and we still support
// down to Kubernetes 1.11; but I don't think that we want to lock
// ourselves out from v1 forever.  So I guess that means when it comes time
// for `getambassador.io/v3` (`ambassadorlabs.com/v1`?), we need to
// strictly avoid union types, in order to avoid violating rule 3 of
// structural schemas.  Or hope that the Kubernetes folks decide to relax
// some of the structural-schema rules.
//
// [1]: https://github.com/kubernetes/apiextensions-apiserver/blob/kubernetes-1.18.4/pkg/apis/apiextensions/v1beta1/types_jsonschema.go#L195-L206
// [2]: https://github.com/kubernetes-sigs/controller-tools/pull/427
// [3]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema

type CircuitBreaker struct {
	// +kubebuilder:validation:Enum={"default", "high"}
	Priority           string `json:"priority,omitempty"`
	MaxConnections     int    `json:"max_connections,omitempty"`
	MaxPendingRequests int    `json:"max_pending_requests,omitempty"`
	MaxRequests        int    `json:"max_requests,omitempty"`
	MaxRetries         int    `json:"max_retries,omitempty"`
}

// AmbassadorID declares which Ambassador instances should pay
// attention to this resource.  May either be a string or a list of
// strings.  If no value is provided, the default is:
//
//    ambassador_id:
//    - "default"
//
// +kubebuilder:validation:Type="d6e-union:string,array"
type AmbassadorID []string

func (aid *AmbassadorID) UnmarshalJSON(data []byte) error {
	return (*StringOrStringList)(aid).UnmarshalJSON(data)
}

// +kubebuilder:validation:Type="d6e-union:string,array"
type StringOrStringList []string

func (sl *StringOrStringList) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		*sl = nil
		return nil
	}

	var err error
	var list []string
	var single string

	if err = json.Unmarshal(data, &single); err == nil {
		*sl = StringOrStringList([]string{single})
		return nil
	}

	if err = json.Unmarshal(data, &list); err == nil {
		*sl = StringOrStringList(list)
		return nil
	}

	return err
}

// BoolOrString is a type that can hold a Boolean or a string.
//
// +kubebuilder:validation:Type="d6e-union:string,boolean"
type BoolOrString struct {
	String *string
	Bool   *bool
}

// MarshalJSON is important both so that we generate the proper
// output, and to trigger controller-gen to not try to generate
// jsonschema for our sub-fields:
// https://github.com/kubernetes-sigs/controller-tools/pull/427
func (o BoolOrString) MarshalJSON() ([]byte, error) {
	switch {
	case o.String == nil && o.Bool == nil:
		return json.Marshal(nil)
	case o.String == nil && o.Bool != nil:
		return json.Marshal(o.Bool)
	case o.String != nil && o.Bool == nil:
		return json.Marshal(o.String)
	case o.String != nil && o.Bool != nil:
		panic("invalid BoolOrString")
	}
	panic("not reached")
}

func (o *BoolOrString) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		*o = BoolOrString{}
		return nil
	}

	var err error

	var b bool
	if err = json.Unmarshal(data, &b); err == nil {
		*o = BoolOrString{Bool: &b}
		return nil
	}

	var str string
	if err = json.Unmarshal(data, &str); err == nil {
		*o = BoolOrString{String: &str}
		return nil
	}

	return err
}

// UntypedDict is relatively opaque as a Go type, but it preserves its contents in a roundtrippable
// way.
// +kubebuilder:validation:Type="object"
type UntypedDict struct {
	Values map[string]UntypedValue
}

func (u UntypedDict) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.Values)
}

func (u *UntypedDict) UnmarshalJSON(data []byte) error {
	var values map[string]UntypedValue
	err := json.Unmarshal(data, &values)
	if err != nil {
		return err
	}
	*u = UntypedDict{Values: values}
	return nil
}

type UntypedValue struct {
	raw json.RawMessage
}

func (u UntypedValue) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.raw)
}

func (u *UntypedValue) UnmarshalJSON(data []byte) error {
	*u = UntypedValue{raw: json.RawMessage(data)}
	return nil
}
<commit_msg>(from AES) API design guidelines: Justify the .status thing<commit_after>// Copyright 2020 Datawire.  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.

///////////////////////////////////////////////////////////////////////////
// Important: Run "make update-yaml" to regenerate code after modifying
// this file.
///////////////////////////////////////////////////////////////////////////

// I'm not sure where a better place to put this is, so I'm putting it here:
//
// # API design guidelines
//
// Ambassador's API has inconsistencies because it has historical
// baggage.  Not all of Ambassador's existing API (or even most of
// it!?) follow these guidelines, but new additions to the API should.
// If/when we advance to getambassador.io/v3 and we can break
// compatibility, these are things that we should apply everywhere.
//
// - Prefer `camelCase` to `snake_case`
//   * Exception: Except for consistency with existing fields in the
//     same resource, or symmetry with identical fields in another
//     resource.
//   * Justification: Kubernetes style is to use camelCase. But
//     historically Ambassador used snake_case for everything.
//
// - Prefer for object references to not support namespacing
//   * Exception: If there's a real use-case for it.
//   * Justification: Most native Kubernetes resources don't support
//     referencing things in a different namespace.  We should be
//     opinionated and not support it either, unless there's a good
//     reason to in a specific case.
//
// - Prefer to use `corev1.LocalObjectReference` or
//   `corev1.SecretReference` references instead of
//   `{name}.{namespace}` strings.
//   * Justification: The `{name}.{namespace}` thing evolved "an
//     opaque DNS name" in the `service` field of Mappings, and that
//     was generalized to other things.  Outside of the context of
//     "this is usable as a DNS name to make a request to", it's just
//     confusing and introduces needless ambiguity.  Nothing other
//     than Ambassador uses that notation.
//   * Notes: For things that don't support cross-namespace references
//     (see above), use LocalObjectReference; if you really must
//     support cross-namespace references, then use SecretReference.
//
// - Prefer to use `metav1.Duration` fields instead of "_s" or "_ms"
//   numeric fields.
//
// - Don't have Ambassador populate anything in the `.spec` or
//   `.metadata` of something a user might edit, only let Ambassador
//   set things in the `.status`.
//   * Exception: If Ambassador 100% owns the resource and a user will
//     never edit it.
//   * Notes: I didn't write "Prefer" on this one.  Don't violate it.
//     Just don't do it.  Ever.  Designing the Host resource in
//     violation of this was a HUGE mistake and one that I regret very
//     much.  Learn from my mistakes.
//   * Justification: Having Ambassador-set things in a subresource
//     from user-set things:
//     1. avoids races between the user updating the spec and us
//        updating the status
//     2. allows watt/whatever to only pay attention to
//        .metadata.generation instead of .metadata.resourceVersion;
//        avoiding pointless reconfigures.
//     3. allows the RBAC to be simpler
//     4. avoids the whole class of bugs where we need to make sure
//        that everything round-trips correctly
//     5. provides clarity on which things a user is expected to know
//        how to fill in

package v2

import (
	"encoding/json"
)

// The old `k8s.io/kube-openapi/cmd/openapi-gen` command had ways to
// specify custom schemas for your types (1: define a "OpenAPIDefinition"
// method, or 2: define a "OpenAPIV3Definition" method, or 3: define
// "OpenAPISchemaType" and "OpenAPISchemaFormat" methods).  But the new
// `sigs.k8s.io/controller-tools/controller-gen` command doesn't; it just
// has a small number of "+kubebuilder:" magic comments ("markers") that we
// can use to influence the schema it generates.
//
// So, for example, we'd like to define the AmbassadorID schema as:
//
//    oneOf:
//    - type: "string"
//    - type: "array"
//    items:             # only matters if type=array
//      type: "string"
//
// but if we're going to use just vanilla controller-gen, we're forced to
// be dumb and say `+kubebuilder:validation:Type=""`, to define its schema
// as
//
//    # no `type:` setting because of the +kubebuilder marker
//    items:
//      type: "string"  # because of the raw type
//
// and then kubectl and/or the apiserver won't be able to validate
// AmbassadorID, because it won't be validated until we actually go to
// UnmarshalJSON it when it makes it to Ambassador.  That's pretty much
// what Kubernetes itself[1] does for the JSON Schema types that are unions
// like that.
//
//  > Aside: Some recent work in controller-gen[2] *strongly* suggests that
//  > setting `+kubebuilder:validation:Type=Any` instead of `:Type=""` is
//  > the proper thing to do.  But, um, it doesn't work... kubectl would
//  > say things like:
//  >
//  >    Invalid value: "array": spec.ambassador_id in body must be of type Any: "array"
//
// But honestly that's dumb, and we can do better than that.
//
// So, option one choice would be to send the controller-tools folks a PR
// to support the openapi-gen methods to allow that customization.  That's
// probably the Right Thing, but that seemed like more work than option
// two.  FIXME(lukeshu): Send the controller-tools folks a PR.
//
// Option two: Say something nonsensical like
// `+kubebuilder:validation:Type="d6e-union"`, and teach the `fix-crds`
// script to notice that and delete that nonsensical `type`, replacing it
// with the appropriate `oneOf: [type: A, type: B]` (note that the version
// of JSONSchema that OpenAPI/Kubernetes uses doesn't support type being an
// array).  And so that's what I did.
//
// FIXME(lukeshu): But all of that is still terrible.  Because the very
// structure of our data inherently means that we must have a
// non-structural[3] schema.  With "apiextensions.k8s.io/v1beta1" CRDs,
// non-structural schemas disable several features; and in v1 CRDs,
// non-structural schemas are entirely forbidden.  I mean it doesn't
// _really_ matter right now, because we give out v1beta1 CRDs anyway
// because v1 only became available in Kubernetes 1.16 and we still support
// down to Kubernetes 1.11; but I don't think that we want to lock
// ourselves out from v1 forever.  So I guess that means when it comes time
// for `getambassador.io/v3` (`ambassadorlabs.com/v1`?), we need to
// strictly avoid union types, in order to avoid violating rule 3 of
// structural schemas.  Or hope that the Kubernetes folks decide to relax
// some of the structural-schema rules.
//
// [1]: https://github.com/kubernetes/apiextensions-apiserver/blob/kubernetes-1.18.4/pkg/apis/apiextensions/v1beta1/types_jsonschema.go#L195-L206
// [2]: https://github.com/kubernetes-sigs/controller-tools/pull/427
// [3]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema

type CircuitBreaker struct {
	// +kubebuilder:validation:Enum={"default", "high"}
	Priority           string `json:"priority,omitempty"`
	MaxConnections     int    `json:"max_connections,omitempty"`
	MaxPendingRequests int    `json:"max_pending_requests,omitempty"`
	MaxRequests        int    `json:"max_requests,omitempty"`
	MaxRetries         int    `json:"max_retries,omitempty"`
}

// AmbassadorID declares which Ambassador instances should pay
// attention to this resource.  May either be a string or a list of
// strings.  If no value is provided, the default is:
//
//    ambassador_id:
//    - "default"
//
// +kubebuilder:validation:Type="d6e-union:string,array"
type AmbassadorID []string

func (aid *AmbassadorID) UnmarshalJSON(data []byte) error {
	return (*StringOrStringList)(aid).UnmarshalJSON(data)
}

// +kubebuilder:validation:Type="d6e-union:string,array"
type StringOrStringList []string

func (sl *StringOrStringList) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		*sl = nil
		return nil
	}

	var err error
	var list []string
	var single string

	if err = json.Unmarshal(data, &single); err == nil {
		*sl = StringOrStringList([]string{single})
		return nil
	}

	if err = json.Unmarshal(data, &list); err == nil {
		*sl = StringOrStringList(list)
		return nil
	}

	return err
}

// BoolOrString is a type that can hold a Boolean or a string.
//
// +kubebuilder:validation:Type="d6e-union:string,boolean"
type BoolOrString struct {
	String *string
	Bool   *bool
}

// MarshalJSON is important both so that we generate the proper
// output, and to trigger controller-gen to not try to generate
// jsonschema for our sub-fields:
// https://github.com/kubernetes-sigs/controller-tools/pull/427
func (o BoolOrString) MarshalJSON() ([]byte, error) {
	switch {
	case o.String == nil && o.Bool == nil:
		return json.Marshal(nil)
	case o.String == nil && o.Bool != nil:
		return json.Marshal(o.Bool)
	case o.String != nil && o.Bool == nil:
		return json.Marshal(o.String)
	case o.String != nil && o.Bool != nil:
		panic("invalid BoolOrString")
	}
	panic("not reached")
}

func (o *BoolOrString) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		*o = BoolOrString{}
		return nil
	}

	var err error

	var b bool
	if err = json.Unmarshal(data, &b); err == nil {
		*o = BoolOrString{Bool: &b}
		return nil
	}

	var str string
	if err = json.Unmarshal(data, &str); err == nil {
		*o = BoolOrString{String: &str}
		return nil
	}

	return err
}

// UntypedDict is relatively opaque as a Go type, but it preserves its contents in a roundtrippable
// way.
// +kubebuilder:validation:Type="object"
type UntypedDict struct {
	Values map[string]UntypedValue
}

func (u UntypedDict) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.Values)
}

func (u *UntypedDict) UnmarshalJSON(data []byte) error {
	var values map[string]UntypedValue
	err := json.Unmarshal(data, &values)
	if err != nil {
		return err
	}
	*u = UntypedDict{Values: values}
	return nil
}

type UntypedValue struct {
	raw json.RawMessage
}

func (u UntypedValue) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.raw)
}

func (u *UntypedValue) UnmarshalJSON(data []byte) error {
	*u = UntypedValue{raw: json.RawMessage(data)}
	return nil
}
<|endoftext|>


<commit_before>package warded

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path"
	"path/filepath"
	"regexp"
)

// Ward holds data needed to work with a ward.
type Ward struct {
	Dir string
	key []byte
}

// NewWard creates a Ward.
func NewWard(masterKey []byte) Ward {
	return Ward{
		key: masterKey,
	}
}

// SearchResult contains information about a matched search
type SearchResult struct {
	Passphrase string
	Line       []byte
	LineNum    int
	IndexStart int
	IndexEnd   int
}

// Statistics holds statistics about the entire ward
type Statistics struct {
	Groups    []Group `json:"groups"`
	Count     int     `json:"count"`
	SumLength int     `json:"sum"`
	MaxLength int     `json:"max"`
}

// A Group holds the names and some statistics about a group of common passphrases
type Group struct {
	Length      int      `json:"len"`
	Passphrases []string `json:"pass"`
}

// Edit sets the entire content of the warded passphrase.
func (w Ward) Edit(passName string, content []byte) (err error) {
	var pass *Passphrase
	if pass, err = NewPassphrase(w.key, content); err == nil {
		pass.Filename = w.Path(passName)
		err = pass.Write(0600)
	}
	return
}

// Get returns the decrypted passphrase content.
func (w Ward) Get(passName string) ([]byte, error) {
	warded, err := ReadPassphrase(w.Path(passName))
	if err != nil {
		return nil, err
	}
	return warded.Decrypt(w.key)
}

// GetOrCheck returns the decrypted passphrase content.
// If Get throws an error, the Ward's key is checked
// against a random passphrase in the Ward.
func (w Ward) GetOrCheck(passName string) ([]byte, error) {
	pass, err := w.Get(passName)
	if err != nil {
		err = w.checkKey()
	}
	return pass, err
}

// List returns a list of passphrase names in the ward
func (w Ward) List() ([]string, error) {
	passphrases := make([]string, 0)
	err := filepath.Walk(w.Dir, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		var rel string

		if !info.IsDir() {
			if rel, err = filepath.Rel(w.Dir, p); err != nil {
				return err
			}

			passphrases = append(passphrases, rel)
		}

		return nil
	})
	return passphrases, err
}

// Map returns a map of passphrase names to the warded passphrase.
func (w Ward) Map() (map[string]*Passphrase, error) {
	passphrases := make(map[string]*Passphrase)
	err := filepath.Walk(w.Dir, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		var rel string
		var pass *Passphrase

		if !info.IsDir() {
			if rel, err = filepath.Rel(w.Dir, p); err != nil {
				return err
			}

			if pass, err = ReadPassphrase(p); err != nil {
				return err
			}

			passphrases[rel] = pass
		}
		return nil
	})

	return passphrases, err
}

// Path returns the path to a passphrase.
// Generated by joining the ward directory with the cleaned passphrase name
func (w Ward) Path(passName string) string {
	return path.Join(w.Dir, path.Clean(passName))
}

// Rekey changes the master key for the entire ward.
// Any errors will cancel the operation, leaving the ward with the existing key.
func (w Ward) Rekey(newMasterKey []byte) error {
	passphrases, err := w.Map()
	if err != nil {
		return err
	}

	tmpDir, err := ioutil.TempDir("", "warded")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpDir)

	newWard := NewWard(newMasterKey)
	newWard.Dir = tmpDir

	var plaintext []byte
	for passName, warded := range passphrases {
		if plaintext, err = warded.Decrypt(w.key); err != nil {
			return fmt.Errorf("Invalid master key for %s\n", passName)
		}

		if err = newWard.Edit(passName, plaintext); err != nil {
			return err
		}
	}

	if err = os.RemoveAll(w.Dir); err == nil {
		err = os.Rename(tmpDir, w.Dir)
	}
	return err
}

// Search searches through a ward, printing lines
// that match the given regular expression.
func (w Ward) Search(regex *regexp.Regexp) ([]SearchResult, error) {
	var err error
	var passphrases map[string]*Passphrase
	if passphrases, err = w.Map(); err != nil {
		return nil, err
	}

	var pass []byte
	var results []SearchResult
	for passName, warded := range passphrases {
		if pass, err = warded.Decrypt(w.key); err != nil {
			return nil, err
		}

		for lineNum, line := range bytes.Split(pass, []byte("\n")) {
			for _, match := range regex.FindAllIndex(line, -1) {
				results = append(results, SearchResult{
					Passphrase: passName,
					Line:       line,
					LineNum:    lineNum,
					IndexStart: match[0],
					IndexEnd:   match[1],
				})
			}
		}
	}

	return results, nil
}

// Stats returns statistics for the current ward.
func (w Ward) Stats() (*Statistics, error) {
	passphrases, err := w.Map()
	if err != nil {
		return nil, err
	}

	groupMap := make(map[string][]string)
	sumLen := 0
	maxLen := 0

	for name, pass := range passphrases {
		plaintext, err := pass.Decrypt(w.key)
		if err != nil {
			return nil, err
		}
		lines := bytes.SplitN(plaintext, []byte("\n"), 2)
		first := string(lines[0])
		passLen := len(first)

		groupMap[first] = append(groupMap[first], name)

		if passLen > maxLen {
			maxLen = passLen
		}
		sumLen += passLen
	}

	groups := make([]Group, len(groupMap))
	ind := 0
	for key, val := range groupMap {
		groups[ind] = Group{
			Passphrases: val,
			Length:      len(key),
		}
		ind++
	}

	return &Statistics{
		Groups:    groups,
		Count:     len(passphrases),
		MaxLength: maxLen,
		SumLength: sumLen,
	}, nil
}

// Update replaces the first line of a passphrase with the given string.
func (w Ward) Update(passName string, passStr []byte) (string, error) {
	passPath := w.Path(passName)

	pass, err := w.GetOrCheck(passName)
	if err != nil {
		return "", err
	}

	split := bytes.SplitN(pass, []byte("\n"), 2)
	if len(split) < 2 {
		// there was no existing passphrase, so we need to pretend there was
		split = make([][]byte, 2)
	}

	newPass := append(passStr, '\n')
	newPass = append(newPass, split[1]...)
	if err = w.Edit(passPath, newPass); err != nil {
		return "", err
	}

	return string(split[0]), nil
}

func (w Ward) checkKey() (err error) {
	var passphrases []string
	if passphrases, err = w.List(); err != nil {
		return
	}

	plen := int64(len(passphrases))
	if plen == 0 {
		// there were no existing passphrases in the ward
		// this isn't considered an error
		return
	}

	var rind *big.Int
	if rind, err = rand.Int(rand.Reader, big.NewInt(plen)); err != nil {
		return
	}

	var pass *Passphrase
	if pass, err = ReadPassphrase(w.Path(passphrases[rind.Int64()])); err != nil {
		return
	}

	// check that the provided master key can decrypt the random passphrase
	if _, err = pass.Decrypt(w.key); err != nil {
		err = errors.New("Only one master key is allowed per ward")
	}

	return
}
<commit_msg>Fix error message failing to decrypt the only passphrase in a ward<commit_after>package warded

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path"
	"path/filepath"
	"regexp"
)

// Ward holds data needed to work with a ward.
type Ward struct {
	Dir string
	key []byte
}

// NewWard creates a Ward.
func NewWard(masterKey []byte) Ward {
	return Ward{
		key: masterKey,
	}
}

// SearchResult contains information about a matched search
type SearchResult struct {
	Passphrase string
	Line       []byte
	LineNum    int
	IndexStart int
	IndexEnd   int
}

// Statistics holds statistics about the entire ward
type Statistics struct {
	Groups    []Group `json:"groups"`
	Count     int     `json:"count"`
	SumLength int     `json:"sum"`
	MaxLength int     `json:"max"`
}

// A Group holds the names and some statistics about a group of common passphrases
type Group struct {
	Length      int      `json:"len"`
	Passphrases []string `json:"pass"`
}

// Edit sets the entire content of the warded passphrase.
func (w Ward) Edit(passName string, content []byte) (err error) {
	var pass *Passphrase
	if pass, err = NewPassphrase(w.key, content); err == nil {
		pass.Filename = w.Path(passName)
		err = pass.Write(0600)
	}
	return
}

// Get returns the decrypted passphrase content.
func (w Ward) Get(passName string) ([]byte, error) {
	warded, err := ReadPassphrase(w.Path(passName))
	if err != nil {
		return nil, err
	}
	return warded.Decrypt(w.key)
}

// GetOrCheck returns the decrypted passphrase content.
// If Get throws an error, the Ward's key is checked
// against a random passphrase in the Ward.
func (w Ward) GetOrCheck(passName string) ([]byte, error) {
	pass, err := w.Get(passName)
	if err != nil {
		err = w.checkKey()
	}
	return pass, err
}

// List returns a list of passphrase names in the ward
func (w Ward) List() ([]string, error) {
	passphrases := make([]string, 0)
	err := filepath.Walk(w.Dir, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		var rel string

		if !info.IsDir() {
			if rel, err = filepath.Rel(w.Dir, p); err != nil {
				return err
			}

			passphrases = append(passphrases, rel)
		}

		return nil
	})
	return passphrases, err
}

// Map returns a map of passphrase names to the warded passphrase.
func (w Ward) Map() (map[string]*Passphrase, error) {
	passphrases := make(map[string]*Passphrase)
	err := filepath.Walk(w.Dir, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		var rel string
		var pass *Passphrase

		if !info.IsDir() {
			if rel, err = filepath.Rel(w.Dir, p); err != nil {
				return err
			}

			if pass, err = ReadPassphrase(p); err != nil {
				return err
			}

			passphrases[rel] = pass
		}
		return nil
	})

	return passphrases, err
}

// Path returns the path to a passphrase.
// Generated by joining the ward directory with the cleaned passphrase name
func (w Ward) Path(passName string) string {
	return path.Join(w.Dir, path.Clean(passName))
}

// Rekey changes the master key for the entire ward.
// Any errors will cancel the operation, leaving the ward with the existing key.
func (w Ward) Rekey(newMasterKey []byte) error {
	passphrases, err := w.Map()
	if err != nil {
		return err
	}

	tmpDir, err := ioutil.TempDir("", "warded")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpDir)

	newWard := NewWard(newMasterKey)
	newWard.Dir = tmpDir

	var plaintext []byte
	for passName, warded := range passphrases {
		if plaintext, err = warded.Decrypt(w.key); err != nil {
			return fmt.Errorf("Invalid master key for %s\n", passName)
		}

		if err = newWard.Edit(passName, plaintext); err != nil {
			return err
		}
	}

	if err = os.RemoveAll(w.Dir); err == nil {
		err = os.Rename(tmpDir, w.Dir)
	}
	return err
}

// Search searches through a ward, printing lines
// that match the given regular expression.
func (w Ward) Search(regex *regexp.Regexp) ([]SearchResult, error) {
	var err error
	var passphrases map[string]*Passphrase
	if passphrases, err = w.Map(); err != nil {
		return nil, err
	}

	var pass []byte
	var results []SearchResult
	for passName, warded := range passphrases {
		if pass, err = warded.Decrypt(w.key); err != nil {
			return nil, err
		}

		for lineNum, line := range bytes.Split(pass, []byte("\n")) {
			for _, match := range regex.FindAllIndex(line, -1) {
				results = append(results, SearchResult{
					Passphrase: passName,
					Line:       line,
					LineNum:    lineNum,
					IndexStart: match[0],
					IndexEnd:   match[1],
				})
			}
		}
	}

	return results, nil
}

// Stats returns statistics for the current ward.
func (w Ward) Stats() (*Statistics, error) {
	passphrases, err := w.Map()
	if err != nil {
		return nil, err
	}

	groupMap := make(map[string][]string)
	sumLen := 0
	maxLen := 0

	for name, pass := range passphrases {
		plaintext, err := pass.Decrypt(w.key)
		if err != nil {
			return nil, err
		}
		lines := bytes.SplitN(plaintext, []byte("\n"), 2)
		first := string(lines[0])
		passLen := len(first)

		groupMap[first] = append(groupMap[first], name)

		if passLen > maxLen {
			maxLen = passLen
		}
		sumLen += passLen
	}

	groups := make([]Group, len(groupMap))
	ind := 0
	for key, val := range groupMap {
		groups[ind] = Group{
			Passphrases: val,
			Length:      len(key),
		}
		ind++
	}

	return &Statistics{
		Groups:    groups,
		Count:     len(passphrases),
		MaxLength: maxLen,
		SumLength: sumLen,
	}, nil
}

// Update replaces the first line of a passphrase with the given string.
func (w Ward) Update(passName string, passStr []byte) (string, error) {
	passPath := w.Path(passName)

	pass, err := w.GetOrCheck(passName)
	if err != nil {
		return "", err
	}

	split := bytes.SplitN(pass, []byte("\n"), 2)
	if len(split) < 2 {
		// there was no existing passphrase, so we need to pretend there was
		split = make([][]byte, 2)
	}

	newPass := append(passStr, '\n')
	newPass = append(newPass, split[1]...)
	if err = w.Edit(passPath, newPass); err != nil {
		return "", err
	}

	return string(split[0]), nil
}

func (w Ward) checkKey() (err error) {
	var passphrases []string
	if passphrases, err = w.List(); err != nil {
		return
	}

	plen := int64(len(passphrases))
	if plen == 0 {
		// there were no existing passphrases in the ward
		// this isn't considered an error
		return
	} else if plen == 1 {
		return fmt.Errorf("Invalid master key")
	}

	var rind *big.Int
	if rind, err = rand.Int(rand.Reader, big.NewInt(plen)); err != nil {
		return
	}

	var pass *Passphrase
	if pass, err = ReadPassphrase(w.Path(passphrases[rind.Int64()])); err != nil {
		return
	}

	// check that the provided master key can decrypt the random passphrase
	if _, err = pass.Decrypt(w.key); err != nil {
		err = errors.New("Only one master key is allowed per ward")
	}

	return
}
<|endoftext|>


<commit_before>package strategy

import (
	"fmt"
	"path/filepath"
	"strconv"

	"github.com/golang/glog"
	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
	imageapi "github.com/openshift/origin/pkg/image/api"
	"github.com/openshift/origin/pkg/util/namer"
	"github.com/openshift/origin/pkg/version"
	kapi "k8s.io/kubernetes/pkg/api"
	kvalidation "k8s.io/kubernetes/pkg/util/validation"
)

const (
	// dockerSocketPath is the default path for the Docker socket inside the builder container
	dockerSocketPath               = "/var/run/docker.sock"
	DockerPushSecretMountPath      = "/var/run/secrets/openshift.io/push"
	DockerPullSecretMountPath      = "/var/run/secrets/openshift.io/pull"
	SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build"
	SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image"
	sourceSecretMountPath          = "/var/run/secrets/openshift.io/source"
)

var whitelistEnvVarNames = []string{"BUILD_LOGLEVEL"}

// FatalError is an error which can't be retried.
type FatalError string

// Error implements the error interface.
func (e FatalError) Error() string {
	return string(e)
}

// IsFatal returns true if the error is fatal
func IsFatal(err error) bool {
	_, isFatal := err.(FatalError)
	return isFatal
}

// setupDockerSocket configures the pod to support the host's Docker socket
func setupDockerSocket(podSpec *kapi.Pod) {
	dockerSocketVolume := kapi.Volume{
		Name: "docker-socket",
		VolumeSource: kapi.VolumeSource{
			HostPath: &kapi.HostPathVolumeSource{
				Path: dockerSocketPath,
			},
		},
	}

	dockerSocketVolumeMount := kapi.VolumeMount{
		Name:      "docker-socket",
		MountPath: dockerSocketPath,
	}

	podSpec.Spec.Volumes = append(podSpec.Spec.Volumes,
		dockerSocketVolume)
	podSpec.Spec.Containers[0].VolumeMounts =
		append(podSpec.Spec.Containers[0].VolumeMounts,
			dockerSocketVolumeMount)
}

// mountSecretVolume is a helper method responsible for actual mounting secret
// volumes into a pod.
func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumeSuffix string) {
	volumeName := namer.GetName(secretName, volumeSuffix, kvalidation.DNS1123SubdomainMaxLength)
	volume := kapi.Volume{
		Name: volumeName,
		VolumeSource: kapi.VolumeSource{
			Secret: &kapi.SecretVolumeSource{
				SecretName: secretName,
			},
		},
	}
	volumeMount := kapi.VolumeMount{
		Name:      volumeName,
		MountPath: mountPath,
		ReadOnly:  true,
	}
	pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)
}

// setupDockerSecrets mounts Docker Registry secrets into Pod running the build,
// allowing Docker to authenticate against private registries or Docker Hub.
func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectReference, imageSources []buildapi.ImageSource) {
	if pushSecret != nil {
		mountSecretVolume(pod, pushSecret.Name, DockerPushSecretMountPath, "push")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: "PUSH_DOCKERCFG_PATH", Value: DockerPushSecretMountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker push in %s", DockerPullSecretMountPath, pod.Name)
	}

	if pullSecret != nil {
		mountSecretVolume(pod, pullSecret.Name, DockerPullSecretMountPath, "pull")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: "PULL_DOCKERCFG_PATH", Value: DockerPullSecretMountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name)
	}

	for i, imageSource := range imageSources {
		if imageSource.PullSecret == nil {
			continue
		}
		mountPath := filepath.Join(SourceImagePullSecretMountPath, strconv.Itoa(i))
		mountSecretVolume(pod, imageSource.PullSecret.Name, mountPath, "source-image")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, i), Value: mountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker pull in %s", mountPath, pod.Name)

	}
}

// setupSourceSecrets mounts SSH key used for accessing private SCM to clone
// application source code during build.
func setupSourceSecrets(pod *kapi.Pod, sourceSecret *kapi.LocalObjectReference) {
	if sourceSecret == nil {
		return
	}

	mountSecretVolume(pod, sourceSecret.Name, sourceSecretMountPath, "source")
	glog.V(3).Infof("Installed source secrets in %s, in Pod %s/%s", sourceSecretMountPath, pod.Namespace, pod.Name)
	pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
		{Name: "SOURCE_SECRET_PATH", Value: sourceSecretMountPath},
	}...)
}

// setupSecrets mounts the secrets referenced by the SecretBuildSource
// into a builder container. It also sets an environment variable that contains
// a name of the secret and the destination directory.
func setupSecrets(pod *kapi.Pod, secrets []buildapi.SecretBuildSource) {
	for _, s := range secrets {
		mountSecretVolume(pod, s.Secret.Name, filepath.Join(SecretBuildSourceBaseMountPath, s.Secret.Name), "build")
		glog.V(3).Infof("%s will be used as a build secret in %s", s.Secret.Name, SecretBuildSourceBaseMountPath)
	}
}

// addSourceEnvVars adds environment variables related to the source code
// repository to builder container
func addSourceEnvVars(source buildapi.BuildSource, output *[]kapi.EnvVar) {
	sourceVars := []kapi.EnvVar{}
	if source.Git != nil {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REPOSITORY", Value: source.Git.URI})
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_URI", Value: source.Git.URI})
	}
	if len(source.ContextDir) > 0 {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_CONTEXT_DIR", Value: source.ContextDir})
	}
	if source.Git != nil && len(source.Git.Ref) > 0 {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REF", Value: source.Git.Ref})
	}
	*output = append(*output, sourceVars...)
}

func addOriginVersionVar(output *[]kapi.EnvVar) {
	version := kapi.EnvVar{Name: buildapi.OriginVersion, Value: version.Get().String()}
	*output = append(*output, version)
}

// addOutputEnvVars adds env variables that provide information about the output
// target for the build
func addOutputEnvVars(buildOutput *kapi.ObjectReference, output *[]kapi.EnvVar) error {
	if buildOutput == nil {
		return nil
	}

	// output must always be a DockerImage type reference at this point.
	if buildOutput.Kind != "DockerImage" {
		return fmt.Errorf("invalid build output kind %s, must be DockerImage", buildOutput.Kind)
	}
	ref, err := imageapi.ParseDockerImageReference(buildOutput.Name)
	if err != nil {
		return err
	}
	registry := ref.Registry
	ref.Registry = ""
	image := ref.String()

	outputVars := []kapi.EnvVar{
		{Name: "OUTPUT_REGISTRY", Value: registry},
		{Name: "OUTPUT_IMAGE", Value: image},
	}

	*output = append(*output, outputVars...)
	return nil
}

// setupAdditionalSecrets creates secret volume mounts in the given pod for the given list of secrets
func setupAdditionalSecrets(pod *kapi.Pod, secrets []buildapi.SecretSpec) {
	for _, secretSpec := range secrets {
		mountSecretVolume(pod, secretSpec.SecretSource.Name, secretSpec.MountPath, "secret")
		glog.V(3).Infof("Installed additional secret in %s, in Pod %s/%s", secretSpec.MountPath, pod.Namespace, pod.Name)
	}
}

// mergeTrustedEnvWithoutDuplicates merges two environment lists without having
// duplicate items in the output list.  Only trusted environment variables
// will be merged.
func mergeTrustedEnvWithoutDuplicates(source []kapi.EnvVar, output *[]kapi.EnvVar) {

	// filter out all environment variables except trusted/well known
	// values, because we do not want random environment variables being
	// fed into the privileged STI container via the BuildConfig definition.
	filteredSource := []kapi.EnvVar{}
	for _, env := range source {
		trusted := false
		for _, acceptable := range whitelistEnvVarNames {
			if env.Name == acceptable {
				trusted = true
				break
			}
		}
		if !trusted {
			continue
		}
		filteredSource = append(filteredSource, env)
	}

	type sourceMapItem struct {
		index int
		value string
	}
	// Convert source to Map for faster access
	sourceMap := make(map[string]sourceMapItem)
	for i, env := range filteredSource {
		sourceMap[env.Name] = sourceMapItem{i, env.Value}
	}
	result := *output
	for i, env := range result {
		// If the value exists in output, override it and remove it
		// from the source list
		if v, found := sourceMap[env.Name]; found {
			result[i].Value = v.value
			filteredSource = append(filteredSource[:v.index], filteredSource[v.index+1:]...)
		}
	}
	*output = append(result, filteredSource...)
}

// getContainerVerbosity returns the defined BUILD_LOGLEVEL value
func getContainerVerbosity(containerEnv []kapi.EnvVar) (verbosity string) {
	for _, env := range containerEnv {
		if env.Name == "BUILD_LOGLEVEL" {
			verbosity = env.Value
			break
		}
	}
	return
}

// getPodLabels creates labels for the Build Pod
func getPodLabels(build *buildapi.Build) map[string]string {
	return map[string]string{buildapi.BuildLabel: build.Name}
}
<commit_msg>fix push path in log message<commit_after>package strategy

import (
	"fmt"
	"path/filepath"
	"strconv"

	"github.com/golang/glog"
	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
	imageapi "github.com/openshift/origin/pkg/image/api"
	"github.com/openshift/origin/pkg/util/namer"
	"github.com/openshift/origin/pkg/version"
	kapi "k8s.io/kubernetes/pkg/api"
	kvalidation "k8s.io/kubernetes/pkg/util/validation"
)

const (
	// dockerSocketPath is the default path for the Docker socket inside the builder container
	dockerSocketPath               = "/var/run/docker.sock"
	DockerPushSecretMountPath      = "/var/run/secrets/openshift.io/push"
	DockerPullSecretMountPath      = "/var/run/secrets/openshift.io/pull"
	SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build"
	SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image"
	sourceSecretMountPath          = "/var/run/secrets/openshift.io/source"
)

var whitelistEnvVarNames = []string{"BUILD_LOGLEVEL"}

// FatalError is an error which can't be retried.
type FatalError string

// Error implements the error interface.
func (e FatalError) Error() string {
	return string(e)
}

// IsFatal returns true if the error is fatal
func IsFatal(err error) bool {
	_, isFatal := err.(FatalError)
	return isFatal
}

// setupDockerSocket configures the pod to support the host's Docker socket
func setupDockerSocket(podSpec *kapi.Pod) {
	dockerSocketVolume := kapi.Volume{
		Name: "docker-socket",
		VolumeSource: kapi.VolumeSource{
			HostPath: &kapi.HostPathVolumeSource{
				Path: dockerSocketPath,
			},
		},
	}

	dockerSocketVolumeMount := kapi.VolumeMount{
		Name:      "docker-socket",
		MountPath: dockerSocketPath,
	}

	podSpec.Spec.Volumes = append(podSpec.Spec.Volumes,
		dockerSocketVolume)
	podSpec.Spec.Containers[0].VolumeMounts =
		append(podSpec.Spec.Containers[0].VolumeMounts,
			dockerSocketVolumeMount)
}

// mountSecretVolume is a helper method responsible for actual mounting secret
// volumes into a pod.
func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumeSuffix string) {
	volumeName := namer.GetName(secretName, volumeSuffix, kvalidation.DNS1123SubdomainMaxLength)
	volume := kapi.Volume{
		Name: volumeName,
		VolumeSource: kapi.VolumeSource{
			Secret: &kapi.SecretVolumeSource{
				SecretName: secretName,
			},
		},
	}
	volumeMount := kapi.VolumeMount{
		Name:      volumeName,
		MountPath: mountPath,
		ReadOnly:  true,
	}
	pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)
}

// setupDockerSecrets mounts Docker Registry secrets into Pod running the build,
// allowing Docker to authenticate against private registries or Docker Hub.
func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectReference, imageSources []buildapi.ImageSource) {
	if pushSecret != nil {
		mountSecretVolume(pod, pushSecret.Name, DockerPushSecretMountPath, "push")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: "PUSH_DOCKERCFG_PATH", Value: DockerPushSecretMountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker push in %s", DockerPushSecretMountPath, pod.Name)
	}

	if pullSecret != nil {
		mountSecretVolume(pod, pullSecret.Name, DockerPullSecretMountPath, "pull")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: "PULL_DOCKERCFG_PATH", Value: DockerPullSecretMountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name)
	}

	for i, imageSource := range imageSources {
		if imageSource.PullSecret == nil {
			continue
		}
		mountPath := filepath.Join(SourceImagePullSecretMountPath, strconv.Itoa(i))
		mountSecretVolume(pod, imageSource.PullSecret.Name, mountPath, "source-image")
		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
			{Name: fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, i), Value: mountPath},
		}...)
		glog.V(3).Infof("%s will be used for docker pull in %s", mountPath, pod.Name)

	}
}

// setupSourceSecrets mounts SSH key used for accessing private SCM to clone
// application source code during build.
func setupSourceSecrets(pod *kapi.Pod, sourceSecret *kapi.LocalObjectReference) {
	if sourceSecret == nil {
		return
	}

	mountSecretVolume(pod, sourceSecret.Name, sourceSecretMountPath, "source")
	glog.V(3).Infof("Installed source secrets in %s, in Pod %s/%s", sourceSecretMountPath, pod.Namespace, pod.Name)
	pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
		{Name: "SOURCE_SECRET_PATH", Value: sourceSecretMountPath},
	}...)
}

// setupSecrets mounts the secrets referenced by the SecretBuildSource
// into a builder container. It also sets an environment variable that contains
// a name of the secret and the destination directory.
func setupSecrets(pod *kapi.Pod, secrets []buildapi.SecretBuildSource) {
	for _, s := range secrets {
		mountSecretVolume(pod, s.Secret.Name, filepath.Join(SecretBuildSourceBaseMountPath, s.Secret.Name), "build")
		glog.V(3).Infof("%s will be used as a build secret in %s", s.Secret.Name, SecretBuildSourceBaseMountPath)
	}
}

// addSourceEnvVars adds environment variables related to the source code
// repository to builder container
func addSourceEnvVars(source buildapi.BuildSource, output *[]kapi.EnvVar) {
	sourceVars := []kapi.EnvVar{}
	if source.Git != nil {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REPOSITORY", Value: source.Git.URI})
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_URI", Value: source.Git.URI})
	}
	if len(source.ContextDir) > 0 {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_CONTEXT_DIR", Value: source.ContextDir})
	}
	if source.Git != nil && len(source.Git.Ref) > 0 {
		sourceVars = append(sourceVars, kapi.EnvVar{Name: "SOURCE_REF", Value: source.Git.Ref})
	}
	*output = append(*output, sourceVars...)
}

func addOriginVersionVar(output *[]kapi.EnvVar) {
	version := kapi.EnvVar{Name: buildapi.OriginVersion, Value: version.Get().String()}
	*output = append(*output, version)
}

// addOutputEnvVars adds env variables that provide information about the output
// target for the build
func addOutputEnvVars(buildOutput *kapi.ObjectReference, output *[]kapi.EnvVar) error {
	if buildOutput == nil {
		return nil
	}

	// output must always be a DockerImage type reference at this point.
	if buildOutput.Kind != "DockerImage" {
		return fmt.Errorf("invalid build output kind %s, must be DockerImage", buildOutput.Kind)
	}
	ref, err := imageapi.ParseDockerImageReference(buildOutput.Name)
	if err != nil {
		return err
	}
	registry := ref.Registry
	ref.Registry = ""
	image := ref.String()

	outputVars := []kapi.EnvVar{
		{Name: "OUTPUT_REGISTRY", Value: registry},
		{Name: "OUTPUT_IMAGE", Value: image},
	}

	*output = append(*output, outputVars...)
	return nil
}

// setupAdditionalSecrets creates secret volume mounts in the given pod for the given list of secrets
func setupAdditionalSecrets(pod *kapi.Pod, secrets []buildapi.SecretSpec) {
	for _, secretSpec := range secrets {
		mountSecretVolume(pod, secretSpec.SecretSource.Name, secretSpec.MountPath, "secret")
		glog.V(3).Infof("Installed additional secret in %s, in Pod %s/%s", secretSpec.MountPath, pod.Namespace, pod.Name)
	}
}

// mergeTrustedEnvWithoutDuplicates merges two environment lists without having
// duplicate items in the output list.  Only trusted environment variables
// will be merged.
func mergeTrustedEnvWithoutDuplicates(source []kapi.EnvVar, output *[]kapi.EnvVar) {

	// filter out all environment variables except trusted/well known
	// values, because we do not want random environment variables being
	// fed into the privileged STI container via the BuildConfig definition.
	filteredSource := []kapi.EnvVar{}
	for _, env := range source {
		trusted := false
		for _, acceptable := range whitelistEnvVarNames {
			if env.Name == acceptable {
				trusted = true
				break
			}
		}
		if !trusted {
			continue
		}
		filteredSource = append(filteredSource, env)
	}

	type sourceMapItem struct {
		index int
		value string
	}
	// Convert source to Map for faster access
	sourceMap := make(map[string]sourceMapItem)
	for i, env := range filteredSource {
		sourceMap[env.Name] = sourceMapItem{i, env.Value}
	}
	result := *output
	for i, env := range result {
		// If the value exists in output, override it and remove it
		// from the source list
		if v, found := sourceMap[env.Name]; found {
			result[i].Value = v.value
			filteredSource = append(filteredSource[:v.index], filteredSource[v.index+1:]...)
		}
	}
	*output = append(result, filteredSource...)
}

// getContainerVerbosity returns the defined BUILD_LOGLEVEL value
func getContainerVerbosity(containerEnv []kapi.EnvVar) (verbosity string) {
	for _, env := range containerEnv {
		if env.Name == "BUILD_LOGLEVEL" {
			verbosity = env.Value
			break
		}
	}
	return
}

// getPodLabels creates labels for the Build Pod
func getPodLabels(build *buildapi.Build) map[string]string {
	return map[string]string{buildapi.BuildLabel: build.Name}
}
<|endoftext|>


<commit_before>// Copyright 2014 Richard Lehane. 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 containermatcher

import (
	"strings"

	"github.com/richardlehane/siegfried/pkg/core"
	"github.com/richardlehane/siegfried/pkg/core/priority"
	"github.com/richardlehane/siegfried/pkg/core/siegreader"
)

func (m Matcher) Identify(n string, b siegreader.Buffer) (chan core.Result, error) {
	res := make(chan core.Result)
	// check trigger
	buf, err := b.Slice(0, 8)
	if err != nil {
		close(res)
		return res, nil
	}
	for _, c := range m {
		if c.trigger(buf) {
			rdr, err := c.rdr(b)
			if err != nil {
				close(res)
				return res, err
			}
			go c.identify(rdr, res)
			return res, nil
		}
	}
	// nothing ... move on
	close(res)
	return res, nil
}

type identifier struct {
	partsMatched [][]hit // hits for parts
	ruledOut     []bool  // mark additional signatures as negatively matched
	waitSet      *priority.WaitSet
	hits         []hit // shared buffer of hits used when matching
}

func (c *ContainerMatcher) newIdentifier(numParts int) *identifier {
	return &identifier{
		make([][]hit, numParts),
		make([]bool, numParts),
		c.Priorities.WaitSet(),
		make([]hit, 0, 1),
	}
}

func (c *ContainerMatcher) identify(rdr Reader, res chan core.Result) {
	// safe to call on a nil matcher (i.e. container matching switched off)
	if c == nil {
		close(res)
		return
	}
	id := c.newIdentifier(len(c.Parts))
	var err error
	var hit bool
	for err = rdr.Next(); err == nil; err = rdr.Next() {
		ct, ok := c.NameCTest[rdr.Name()]
		if !ok {
			continue
		}
		// name has matched, lets test the CTests
		// ct.identify will generate a slice of hits which pass to
		// processHits which will return true if we can stop
		if c.processHits(ct.identify(c, id, rdr, rdr.Name()), id, ct, rdr.Name(), res) {
			hit = true
			break
		}
	}
	// if we have no hits and a default value for this matcher, send it
	if !hit && c.Default {
		// the default is a negative value calculated from the CType
		res <- defaultHit(-1 - int(c.CType))
	}
	close(res)
}

func (ct *CTest) identify(c *ContainerMatcher, id *identifier, rdr Reader, name string) []hit {
	// reset hits
	id.hits = id.hits[:0]
	for _, h := range ct.Satisfied {
		if id.waitSet.Check(h) {
			id.hits = append(id.hits, hit{h, name, "name only"})
		}
	}
	if ct.Unsatisfied != nil {
		buf, _ := rdr.SetSource(c.entryBufs) // NOTE: an error is ignored here.
		bmc, _ := ct.BM.Identify("", buf)
		for r := range bmc {
			h := ct.Unsatisfied[r.Index()]
			if id.waitSet.Check(h) && id.checkHits(h) {
				id.hits = append(id.hits, hit{h, name, r.Basis()})
			}
		}
		rdr.Close()
		c.entryBufs.Put(buf)
	}
	return id.hits
}

// process the hits from the ctest: adding hits to the parts matched, checking priorities
// return true if satisfied and can quit
func (c *ContainerMatcher) processHits(hits []hit, id *identifier, ct *CTest, name string, res chan core.Result) bool {
	// if there are no hits, rule out any sigs in the ctest
	if len(hits) == 0 {
		for _, v := range ct.Satisfied {
			id.ruledOut[v] = true
		}
		for _, v := range ct.Unsatisfied {
			id.ruledOut[v] = true
		}
		return false
	}
	for _, h := range hits {
		id.partsMatched[h.id] = append(id.partsMatched[h.id], h)
		if len(id.partsMatched[h.id]) == c.Parts[h.id] {
			if id.waitSet.Check(h.id) {
				idx, _ := c.Priorities.Index(h.id)
				res <- toResult(c.Sindexes[idx], id.partsMatched[h.id]) // send a Result here
				// set a priority list and return early if can
				if id.waitSet.Put(h.id) {
					return true
				}
			}
		}
	}
	// if nothing ruled out by this test, then we must continue
	if len(hits) == len(ct.Satisfied)+len(ct.Unsatisfied) {
		return false
	}
	// we can rule some possible matches out...
	for _, v := range ct.Satisfied {
		if len(id.partsMatched[v]) == 0 || id.partsMatched[v][len(id.partsMatched[v])-1].name != name {
			id.ruledOut[v] = true
		}
	}
	for _, v := range ct.Unsatisfied {
		if len(id.partsMatched[v]) == 0 || id.partsMatched[v][len(id.partsMatched[v])-1].name != name {
			id.ruledOut[v] = true
		}
	}
	// if we haven't got a waitList yet, then we should return false
	waitingOn := id.waitSet.WaitingOn()
	if waitingOn == nil {
		return false
	}
	// loop over the wait list, seeing if they are all ruled out
	for _, v := range waitingOn {
		if !id.ruledOut[v] {
			return false
		}
	}
	return true
}

// eliminate duplicate hits - must do this since rely on number of matches for each sig as test for full match
func (id *identifier) checkHits(i int) bool {
	for _, h := range id.hits {
		if i == h.id {
			return false
		}
	}
	return true
}

func toResult(i int, h []hit) result {
	if len(h) == 0 {
		return result(h)
	}
	h[0].id += i
	return result(h)
}

type result []hit

func (r result) Index() int {
	if len(r) == 0 {
		return -1
	}
	return r[0].id
}

func (r result) Basis() string {
	var basis string
	for i, v := range r {
		if i < 1 {
			basis += "container "
		} else {
			basis += "; "
		}
		basis += "name " + v.name
		if len(v.basis) > 0 {
			basis += " with " + v.basis
		}
	}
	return basis
}

type hit struct {
	id    int
	name  string
	basis string
}

type defaultHit int

func (d defaultHit) Index() int {
	return int(d)
}

func (d defaultHit) Basis() string {
	return "container match with trigger and default extension"
}
<commit_msg>further proof of benefits coding offline<commit_after>// Copyright 2014 Richard Lehane. 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 containermatcher

import (
	"github.com/richardlehane/siegfried/pkg/core"
	"github.com/richardlehane/siegfried/pkg/core/priority"
	"github.com/richardlehane/siegfried/pkg/core/siegreader"
)

func (m Matcher) Identify(n string, b siegreader.Buffer) (chan core.Result, error) {
	res := make(chan core.Result)
	// check trigger
	buf, err := b.Slice(0, 8)
	if err != nil {
		close(res)
		return res, nil
	}
	for _, c := range m {
		if c.trigger(buf) {
			rdr, err := c.rdr(b)
			if err != nil {
				close(res)
				return res, err
			}
			go c.identify(rdr, res)
			return res, nil
		}
	}
	// nothing ... move on
	close(res)
	return res, nil
}

type identifier struct {
	partsMatched [][]hit // hits for parts
	ruledOut     []bool  // mark additional signatures as negatively matched
	waitSet      *priority.WaitSet
	hits         []hit // shared buffer of hits used when matching
}

func (c *ContainerMatcher) newIdentifier(numParts int) *identifier {
	return &identifier{
		make([][]hit, numParts),
		make([]bool, numParts),
		c.Priorities.WaitSet(),
		make([]hit, 0, 1),
	}
}

func (c *ContainerMatcher) identify(rdr Reader, res chan core.Result) {
	// safe to call on a nil matcher (i.e. container matching switched off)
	if c == nil {
		close(res)
		return
	}
	id := c.newIdentifier(len(c.Parts))
	var err error
	var hit bool
	for err = rdr.Next(); err == nil; err = rdr.Next() {
		ct, ok := c.NameCTest[rdr.Name()]
		if !ok {
			continue
		}
		// name has matched, lets test the CTests
		// ct.identify will generate a slice of hits which pass to
		// processHits which will return true if we can stop
		if c.processHits(ct.identify(c, id, rdr, rdr.Name()), id, ct, rdr.Name(), res) {
			hit = true
			break
		}
	}
	// if we have no hits and a default value for this matcher, send it
	if !hit && c.Default {
		// the default is a negative value calculated from the CType
		res <- defaultHit(-1 - int(c.CType))
	}
	close(res)
}

func (ct *CTest) identify(c *ContainerMatcher, id *identifier, rdr Reader, name string) []hit {
	// reset hits
	id.hits = id.hits[:0]
	for _, h := range ct.Satisfied {
		if id.waitSet.Check(h) {
			id.hits = append(id.hits, hit{h, name, "name only"})
		}
	}
	if ct.Unsatisfied != nil {
		buf, _ := rdr.SetSource(c.entryBufs) // NOTE: an error is ignored here.
		bmc, _ := ct.BM.Identify("", buf)
		for r := range bmc {
			h := ct.Unsatisfied[r.Index()]
			if id.waitSet.Check(h) && id.checkHits(h) {
				id.hits = append(id.hits, hit{h, name, r.Basis()})
			}
		}
		rdr.Close()
		c.entryBufs.Put(buf)
	}
	return id.hits
}

// process the hits from the ctest: adding hits to the parts matched, checking priorities
// return true if satisfied and can quit
func (c *ContainerMatcher) processHits(hits []hit, id *identifier, ct *CTest, name string, res chan core.Result) bool {
	// if there are no hits, rule out any sigs in the ctest
	if len(hits) == 0 {
		for _, v := range ct.Satisfied {
			id.ruledOut[v] = true
		}
		for _, v := range ct.Unsatisfied {
			id.ruledOut[v] = true
		}
		return false
	}
	for _, h := range hits {
		id.partsMatched[h.id] = append(id.partsMatched[h.id], h)
		if len(id.partsMatched[h.id]) == c.Parts[h.id] {
			if id.waitSet.Check(h.id) {
				idx, _ := c.Priorities.Index(h.id)
				res <- toResult(c.Sindexes[idx], id.partsMatched[h.id]) // send a Result here
				// set a priority list and return early if can
				if id.waitSet.Put(h.id) {
					return true
				}
			}
		}
	}
	// if nothing ruled out by this test, then we must continue
	if len(hits) == len(ct.Satisfied)+len(ct.Unsatisfied) {
		return false
	}
	// we can rule some possible matches out...
	for _, v := range ct.Satisfied {
		if len(id.partsMatched[v]) == 0 || id.partsMatched[v][len(id.partsMatched[v])-1].name != name {
			id.ruledOut[v] = true
		}
	}
	for _, v := range ct.Unsatisfied {
		if len(id.partsMatched[v]) == 0 || id.partsMatched[v][len(id.partsMatched[v])-1].name != name {
			id.ruledOut[v] = true
		}
	}
	// if we haven't got a waitList yet, then we should return false
	waitingOn := id.waitSet.WaitingOn()
	if waitingOn == nil {
		return false
	}
	// loop over the wait list, seeing if they are all ruled out
	for _, v := range waitingOn {
		if !id.ruledOut[v] {
			return false
		}
	}
	return true
}

// eliminate duplicate hits - must do this since rely on number of matches for each sig as test for full match
func (id *identifier) checkHits(i int) bool {
	for _, h := range id.hits {
		if i == h.id {
			return false
		}
	}
	return true
}

func toResult(i int, h []hit) result {
	if len(h) == 0 {
		return result(h)
	}
	h[0].id += i
	return result(h)
}

type result []hit

func (r result) Index() int {
	if len(r) == 0 {
		return -1
	}
	return r[0].id
}

func (r result) Basis() string {
	var basis string
	for i, v := range r {
		if i < 1 {
			basis += "container "
		} else {
			basis += "; "
		}
		basis += "name " + v.name
		if len(v.basis) > 0 {
			basis += " with " + v.basis
		}
	}
	return basis
}

type hit struct {
	id    int
	name  string
	basis string
}

type defaultHit int

func (d defaultHit) Index() int {
	return int(d)
}

func (d defaultHit) Basis() string {
	return "container match with trigger and default extension"
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 set

import (
	"bytes"
	"net/http"
	"strings"
	"testing"

	"k8s.io/apimachinery/pkg/runtime"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/rest/fake"
	"k8s.io/kubernetes/pkg/api"
	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/printers"
)

func TestImageLocal(t *testing.T) {
	f, tf, codec, ns := cmdtesting.NewAPIFactory()
	tf.Client = &fake.RESTClient{
		APIRegistry:          api.Registry,
		NegotiatedSerializer: ns,
		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
			return nil, nil
		}),
	}
	tf.Namespace = "test"
	tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}

	buf := bytes.NewBuffer([]byte{})
	cmd := NewCmdImage(f, buf, buf)
	cmd.SetOutput(buf)
	cmd.Flags().Set("output", "name")
	cmd.Flags().Set("local", "true")
	mapper, typer := f.Object()
	tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper}

	opts := ImageOptions{FilenameOptions: resource.FilenameOptions{
		Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}},
		Out:   buf,
		Local: true}
	err := opts.Complete(f, cmd, []string{"cassandra=thingy"})
	if err == nil {
		err = opts.Validate()
	}
	if err == nil {
		err = opts.Run()
	}
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") {
		t.Errorf("did not set image: %s", buf.String())
	}
}
<commit_msg>add test for set image validation<commit_after>/*
Copyright 2016 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 set

import (
	"bytes"
	"net/http"
	"strings"
	"testing"

	"k8s.io/apimachinery/pkg/runtime"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/rest/fake"
	"k8s.io/kubernetes/pkg/api"
	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/printers"
)

func TestImageLocal(t *testing.T) {
	f, tf, codec, ns := cmdtesting.NewAPIFactory()
	tf.Client = &fake.RESTClient{
		APIRegistry:          api.Registry,
		NegotiatedSerializer: ns,
		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
			return nil, nil
		}),
	}
	tf.Namespace = "test"
	tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}

	buf := bytes.NewBuffer([]byte{})
	cmd := NewCmdImage(f, buf, buf)
	cmd.SetOutput(buf)
	cmd.Flags().Set("output", "name")
	cmd.Flags().Set("local", "true")
	mapper, typer := f.Object()
	tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper}

	opts := ImageOptions{FilenameOptions: resource.FilenameOptions{
		Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}},
		Out:   buf,
		Local: true}
	err := opts.Complete(f, cmd, []string{"cassandra=thingy"})
	if err == nil {
		err = opts.Validate()
	}
	if err == nil {
		err = opts.Run()
	}
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") {
		t.Errorf("did not set image: %s", buf.String())
	}
}

func TestSetImageValidation(t *testing.T) {
	testCases := []struct {
		name         string
		imageOptions *ImageOptions
		expectErr    string
	}{
		{
			name:         "test resource < 1 and filenames empty",
			imageOptions: &ImageOptions{},
			expectErr:    "[one or more resources must be specified as <resource> <name> or <resource>/<name>, at least one image update is required]",
		},
		{
			name: "test containerImages < 1",
			imageOptions: &ImageOptions{
				Resources: []string{"a", "b", "c"},

				FilenameOptions: resource.FilenameOptions{
					Filenames: []string{"testFile"},
				},
			},
			expectErr: "at least one image update is required",
		},
		{
			name: "test containerImages > 1 and all containers are already specified by *",
			imageOptions: &ImageOptions{
				Resources: []string{"a", "b", "c"},
				FilenameOptions: resource.FilenameOptions{
					Filenames: []string{"testFile"},
				},
				ContainerImages: map[string]string{
					"test": "test",
					"*":    "test",
				},
			},
			expectErr: "all containers are already specified by *, but saw more than one container_name=container_image pairs",
		},
		{
			name: "sucess case",
			imageOptions: &ImageOptions{
				Resources: []string{"a", "b", "c"},
				FilenameOptions: resource.FilenameOptions{
					Filenames: []string{"testFile"},
				},
				ContainerImages: map[string]string{
					"test": "test",
				},
			},
			expectErr: "",
		},
	}
	for _, testCase := range testCases {
		err := testCase.imageOptions.Validate()
		if err != nil {
			if err.Error() != testCase.expectErr {
				t.Errorf("[%s]:expect err:%s got err:%s", testCase.name, testCase.expectErr, err.Error())
			}
		}
		if err == nil && (testCase.expectErr != "") {
			t.Errorf("[%s]:expect err:%s got err:%v", testCase.name, testCase.expectErr, err)
		}
	}
}
<|endoftext|>


<commit_before>package netutils

import (
	"testing"
)

func TestAllocateSubnet(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, nil)
	if err != nil {
		t.Fatal("Failed to initialize subnet allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 0, sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.1.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 1, sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.2.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 2, sn.String())
	}
}

func TestAllocateSubnetInUse(t *testing.T) {
	inUse := []string{"10.1.0.0/24", "10.1.2.0/24", "10.2.2.2/24", "Invalid"}
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, inUse)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.1.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.3.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}
}

func TestAllocateReleaseSubnet(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, nil)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}

	if err := sna.ReleaseNetwork(sn); err != nil {
		t.Fatal("Failed to release the subnet")
	}

	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}
}

func TestGenerateGateway(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, nil)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}

	gatewayIP := GenerateDefaultGateway(sn)
	if gatewayIP.String() != "10.1.0.1" {
		t.Fatalf("Did not get expected gateway IP Address (gatewayIP=%s)", gatewayIP.String())
	}
}
<commit_msg>Extend TestAllocateReleaseSubnet() to test subnet exhaustion<commit_after>package netutils

import (
	"fmt"
	"net"
	"testing"
)

func TestAllocateSubnet(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, nil)
	if err != nil {
		t.Fatal("Failed to initialize subnet allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 0, sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.1.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 1, sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.2.0/24" {
		t.Fatalf("Did not get expected subnet (n=%d, sn=%s)", 2, sn.String())
	}
}

func TestAllocateSubnetInUse(t *testing.T) {
	inUse := []string{"10.1.0.0/24", "10.1.2.0/24", "10.2.2.2/24", "Invalid"}
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, inUse)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.1.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}
	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.3.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}
}

func TestAllocateReleaseSubnet(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 14, nil)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	var releaseSn *net.IPNet

	for i := 0; i < 4; i++ {
		sn, err := sna.GetNetwork()
		if err != nil {
			t.Fatal("Failed to get network: ", err)
		}
		if sn.String() != fmt.Sprintf("10.1.%d.0/18", i*64) {
			t.Fatalf("Did not get expected subnet (i=%d, sn=%s)", i, sn.String())
		}
		if i == 2 {
			releaseSn = sn
		}
	}

	sn, err := sna.GetNetwork()
	if err == nil {
		t.Fatalf("Unexpectedly succeeded in getting network (sn=%s)", sn.String())
	}

	if err := sna.ReleaseNetwork(releaseSn); err != nil {
		t.Fatal("Failed to release the subnet: ", err)
	}

	sn, err = sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != releaseSn.String() {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}

	sn, err = sna.GetNetwork()
	if err == nil {
		t.Fatalf("Unexpectedly succeeded in getting network (sn=%s)", sn.String())
	}
}

func TestGenerateGateway(t *testing.T) {
	sna, err := NewSubnetAllocator("10.1.0.0/16", 8, nil)
	if err != nil {
		t.Fatal("Failed to initialize IP allocator: ", err)
	}

	sn, err := sna.GetNetwork()
	if err != nil {
		t.Fatal("Failed to get network: ", err)
	}
	if sn.String() != "10.1.0.0/24" {
		t.Fatalf("Did not get expected subnet (sn=%s)", sn.String())
	}

	gatewayIP := GenerateDefaultGateway(sn)
	if gatewayIP.String() != "10.1.0.1" {
		t.Fatalf("Did not get expected gateway IP Address (gatewayIP=%s)", gatewayIP.String())
	}
}
<|endoftext|>


<commit_before>package googleapps

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/versent/saml2aws/pkg/cfg"
	"github.com/versent/saml2aws/pkg/creds"
	"github.com/versent/saml2aws/pkg/prompter"
	"github.com/versent/saml2aws/pkg/provider"
)

var logger = logrus.WithField("provider", "googleapps")

// Client wrapper around Google Apps.
type Client struct {
	client *provider.HTTPClient
}

// New create a new Google Apps Client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {

	tr := provider.NewDefaultTransport(idpAccount.SkipVerify)

	client, err := provider.NewHTTPClient(tr)
	if err != nil {
		return nil, errors.Wrap(err, "error building http client")
	}

	return &Client{
		client: client,
	}, nil
}

// Authenticate logs into Google Apps and returns a SAML response
func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {

	// Get the first page
	authURL, authForm, err := kc.loadFirstPage(loginDetails)
	if err != nil {
		return "", errors.Wrap(err, "error loading first page")
	}

	authForm.Set("Email", loginDetails.Username)

	passwordURL, _, err := kc.loadLoginPage(authURL, loginDetails.URL, authForm)
	if err != nil {
		return "", errors.Wrap(err, "error loading login page")
	}

	logger.Debugf("loginURL: %s", passwordURL)

	authForm.Set("Passwd", loginDetails.Password)
	authForm.Set("rawidentifier", loginDetails.Username)

	responseDoc, err := kc.loadChallengePage(passwordURL, authURL, authForm)
	if err != nil {
		return "", errors.Wrap(err, "error loading challenge page")
	}

	// extract the saml assertion
	samlAssertion := mustFindInputByName(responseDoc, "SAMLResponse")
	if samlAssertion == "" {
		return "", errors.New("page is missing saml assertion")
	}

	return samlAssertion, nil
}

func (kc *Client) loadFirstPage(loginDetails *creds.LoginDetails) (string, url.Values, error) {

	req, err := http.NewRequest("GET", loginDetails.URL, nil)
	if err != nil {
		return "", nil, errors.Wrap(err, "error retrieving login form from idp")
	}

	res, err := kc.client.Do(req)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return "", nil, errors.Wrap(err, "error parsing first page html document")
	}

	authForm, submitURL, err := extractInputsByFormID(doc, "gaia_loginform")
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to build login form data")
	}

	postForm := url.Values{
		"bgresponse":      []string{"js_disabled"},
		"checkConnection": []string{""},
		"checkedDomains":  []string{"youtube"},
		"continue":        []string{authForm.Get("continue")},
		"gxf":             []string{authForm.Get("gxf")},
		"identifier-captcha-input": []string{""},
		"identifiertoken":          []string{""},
		"identifiertoken_audio":    []string{""},
		"ltmpl":                    []string{"popup"},
		"oauth":                    []string{"1"},
		"Page":                     []string{authForm.Get("Page")},
		"Passwd":                   []string{""},
		"PersistentCookie":         []string{"yes"},
		"ProfileInformation":       []string{""},
		"pstMsg":                   []string{"0"},
		"sarp":                     []string{"1"},
		"scc":                      []string{"1"},
		"SessionState":             []string{authForm.Get("SessionState")},
		"signIn":                   []string{authForm.Get("signIn")},
		"_utf8":                    []string{authForm.Get("_utf8")},
		"GALX":                     []string{authForm.Get("GALX")},
	}

	return submitURL, postForm, err
}

func (kc *Client) loadLoginPage(submitURL string, referer string, authForm url.Values) (string, url.Values, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(authForm.Encode()))
	if err != nil {
		return "", nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return "", nil, errors.Wrap(err, "error parsing login page html document")
	}

	loginForm, loginURL, err := extractInputsByFormID(doc, "gaia_loginform")
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to build login form data")
	}

	return loginURL, loginForm, err
}

func (kc *Client) loadChallengePage(submitURL string, referer string, authForm url.Values) (*goquery.Document, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(authForm.Encode()))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return nil, errors.Wrap(err, "error parsing login page html document")
	}

	errMsg := mustFindErrorMsg(doc)

	if errMsg != "" {
		return nil, errors.New("Invalid username or password")
	}

	secondFactorHeader := "This extra step shows it’s really you trying to sign in"

	// have we been asked for 2-Step Verification
	if extractNodeText(doc, "h2", secondFactorHeader) != "" {

		responseForm, secondActionURL, err := extractInputsByFormID(doc, "challenge")
		if err != nil {
			return nil, errors.Wrap(err, "unable to extract challenge form")
		}

		logrus.Debugf("secondActionURL: %s", secondActionURL)

		u, _ := url.Parse(submitURL)
		u.Path = secondActionURL // we are just updating the path with the action as it is a relative path

		switch {
		case strings.Contains(secondActionURL, "challenge/totp/"): // handle TOTP challenge

			var token = prompter.RequestSecurityCode("000000")

			responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)
		case strings.Contains(secondActionURL, "challenge/ipp/"): // handle SMS challenge

			var token = prompter.StringRequired("Enter SMS token: G-")

			responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)

		case strings.Contains(secondActionURL, "challenge/az/"): // handle phone challenge

			dataAttrs := extractDataAttributes(doc, "div[data-context]", []string{"data-context", "data-gapi-url", "data-tx-id", "data-api-key", "data-tx-lifetime"})

			logrus.Debugf("prompt with data values: %+v", dataAttrs)

			waitValues := map[string]string{
				"txId": dataAttrs["data-tx-id"],
			}

			fmt.Println("Open the Google App, and tap 'Yes' on the prompt to sign in")

			_, err := kc.postJSON(fmt.Sprintf("https://content.googleapis.com/cryptauth/v1/authzen/awaittx?alt=json&key=%s", dataAttrs["data-api-key"]), waitValues, submitURL)
			if err != nil {
				return nil, errors.Wrap(err, "unable to extract post wait tx form")
			}

			// responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)
		}

		return nil, errors.Errorf("unsupported second factor: %s", secondActionURL)
	}

	return doc, nil

}

func (kc *Client) postJSON(submitURL string, values map[string]string, referer string) (*http.Response, error) {

	data, _ := json.Marshal(values)

	req, err := http.NewRequest("POST", submitURL, bytes.NewReader(data))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to post JSON")
	}

	return res, nil
}

func (kc *Client) loadResponsePage(submitURL string, referer string, responseForm url.Values) (*goquery.Document, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(responseForm.Encode()))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving response page")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", submitURL)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return nil, errors.Wrap(err, "error parsing login page html document")
	}

	return doc, nil
}

func mustFindInputByName(doc *goquery.Document, name string) string {

	var fieldValue string

	q := fmt.Sprintf(`input[name="%s"]`, name)

	doc.Find(q).Each(func(i int, s *goquery.Selection) {
		val, ok := s.Attr("value")
		if !ok {
			log.Fatal("unable to locate field value")
		}
		fieldValue = val
	})

	return fieldValue
}

func mustFindErrorMsg(doc *goquery.Document) string {
	var fieldValue string
	doc.Find(".error-msg").Each(func(i int, s *goquery.Selection) {
		fieldValue = s.Text()

	})
	return fieldValue
}

func extractInputsByFormID(doc *goquery.Document, formID string) (url.Values, string, error) {
	formData := url.Values{}
	var actionURL string

	query := fmt.Sprintf("form#%s", formID)

	//get action url
	doc.Find(query).Each(func(i int, s *goquery.Selection) {
		action, ok := s.Attr("action")
		if !ok {
			return
		}
		actionURL = action
	})

	query = fmt.Sprintf("form#%s", formID)

	// extract form data to passthrough
	doc.Find(query).Find("input").Each(func(i int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}
		logger.Info("name: ", name)
		val, ok := s.Attr("value")
		if !ok {
			return
		}
		formData.Add(name, val)
	})

	return formData, actionURL, nil
}

func extractNodeText(doc *goquery.Document, tag, txt string) string {

	var res string

	doc.Find(tag).Each(func(i int, s *goquery.Selection) {
		if s.Text() == txt {
			res = s.Text()
		}
	})

	return res
}

func extractDataAttributes(doc *goquery.Document, query string, attrsToSelect []string) map[string]string {

	dataAttrs := make(map[string]string)

	doc.Find(query).Each(func(_ int, sel *goquery.Selection) {
		for _, f := range attrsToSelect {
			if val, ok := sel.Attr(f); ok {
				dataAttrs[f] = val
			}
		}
	})

	return dataAttrs
}
<commit_msg>fix(Google Apps) Reduced logging and fixed logger usage.<commit_after>package googleapps

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/versent/saml2aws/pkg/cfg"
	"github.com/versent/saml2aws/pkg/creds"
	"github.com/versent/saml2aws/pkg/prompter"
	"github.com/versent/saml2aws/pkg/provider"
)

var logger = logrus.WithField("provider", "googleapps")

// Client wrapper around Google Apps.
type Client struct {
	client *provider.HTTPClient
}

// New create a new Google Apps Client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {

	tr := provider.NewDefaultTransport(idpAccount.SkipVerify)

	client, err := provider.NewHTTPClient(tr)
	if err != nil {
		return nil, errors.Wrap(err, "error building http client")
	}

	return &Client{
		client: client,
	}, nil
}

// Authenticate logs into Google Apps and returns a SAML response
func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {

	// Get the first page
	authURL, authForm, err := kc.loadFirstPage(loginDetails)
	if err != nil {
		return "", errors.Wrap(err, "error loading first page")
	}

	authForm.Set("Email", loginDetails.Username)

	passwordURL, _, err := kc.loadLoginPage(authURL, loginDetails.URL, authForm)
	if err != nil {
		return "", errors.Wrap(err, "error loading login page")
	}

	logger.Debugf("loginURL: %s", passwordURL)

	authForm.Set("Passwd", loginDetails.Password)
	authForm.Set("rawidentifier", loginDetails.Username)

	responseDoc, err := kc.loadChallengePage(passwordURL, authURL, authForm)
	if err != nil {
		return "", errors.Wrap(err, "error loading challenge page")
	}

	// extract the saml assertion
	samlAssertion := mustFindInputByName(responseDoc, "SAMLResponse")
	if samlAssertion == "" {
		return "", errors.New("page is missing saml assertion")
	}

	return samlAssertion, nil
}

func (kc *Client) loadFirstPage(loginDetails *creds.LoginDetails) (string, url.Values, error) {

	req, err := http.NewRequest("GET", loginDetails.URL, nil)
	if err != nil {
		return "", nil, errors.Wrap(err, "error retrieving login form from idp")
	}

	res, err := kc.client.Do(req)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return "", nil, errors.Wrap(err, "error parsing first page html document")
	}

	authForm, submitURL, err := extractInputsByFormID(doc, "gaia_loginform")
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to build login form data")
	}

	postForm := url.Values{
		"bgresponse":      []string{"js_disabled"},
		"checkConnection": []string{""},
		"checkedDomains":  []string{"youtube"},
		"continue":        []string{authForm.Get("continue")},
		"gxf":             []string{authForm.Get("gxf")},
		"identifier-captcha-input": []string{""},
		"identifiertoken":          []string{""},
		"identifiertoken_audio":    []string{""},
		"ltmpl":                    []string{"popup"},
		"oauth":                    []string{"1"},
		"Page":                     []string{authForm.Get("Page")},
		"Passwd":                   []string{""},
		"PersistentCookie":         []string{"yes"},
		"ProfileInformation":       []string{""},
		"pstMsg":                   []string{"0"},
		"sarp":                     []string{"1"},
		"scc":                      []string{"1"},
		"SessionState":             []string{authForm.Get("SessionState")},
		"signIn":                   []string{authForm.Get("signIn")},
		"_utf8":                    []string{authForm.Get("_utf8")},
		"GALX":                     []string{authForm.Get("GALX")},
	}

	return submitURL, postForm, err
}

func (kc *Client) loadLoginPage(submitURL string, referer string, authForm url.Values) (string, url.Values, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(authForm.Encode()))
	if err != nil {
		return "", nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return "", nil, errors.Wrap(err, "error parsing login page html document")
	}

	loginForm, loginURL, err := extractInputsByFormID(doc, "gaia_loginform")
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to build login form data")
	}

	return loginURL, loginForm, err
}

func (kc *Client) loadChallengePage(submitURL string, referer string, authForm url.Values) (*goquery.Document, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(authForm.Encode()))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return nil, errors.Wrap(err, "error parsing login page html document")
	}

	errMsg := mustFindErrorMsg(doc)

	if errMsg != "" {
		return nil, errors.New("Invalid username or password")
	}

	secondFactorHeader := "This extra step shows it’s really you trying to sign in"

	// have we been asked for 2-Step Verification
	if extractNodeText(doc, "h2", secondFactorHeader) != "" {

		responseForm, secondActionURL, err := extractInputsByFormID(doc, "challenge")
		if err != nil {
			return nil, errors.Wrap(err, "unable to extract challenge form")
		}

		logger.Debugf("secondActionURL: %s", secondActionURL)

		u, _ := url.Parse(submitURL)
		u.Path = secondActionURL // we are just updating the path with the action as it is a relative path

		switch {
		case strings.Contains(secondActionURL, "challenge/totp/"): // handle TOTP challenge

			var token = prompter.RequestSecurityCode("000000")

			responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)
		case strings.Contains(secondActionURL, "challenge/ipp/"): // handle SMS challenge

			var token = prompter.StringRequired("Enter SMS token: G-")

			responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)

		case strings.Contains(secondActionURL, "challenge/az/"): // handle phone challenge

			dataAttrs := extractDataAttributes(doc, "div[data-context]", []string{"data-context", "data-gapi-url", "data-tx-id", "data-api-key", "data-tx-lifetime"})

			logger.Debugf("prompt with data values: %+v", dataAttrs)

			waitValues := map[string]string{
				"txId": dataAttrs["data-tx-id"],
			}

			fmt.Println("Open the Google App, and tap 'Yes' on the prompt to sign in")

			_, err := kc.postJSON(fmt.Sprintf("https://content.googleapis.com/cryptauth/v1/authzen/awaittx?alt=json&key=%s", dataAttrs["data-api-key"]), waitValues, submitURL)
			if err != nil {
				return nil, errors.Wrap(err, "unable to extract post wait tx form")
			}

			// responseForm.Set("Pin", token)
			responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

			return kc.loadResponsePage(u.String(), submitURL, responseForm)
		}

		return nil, errors.Errorf("unsupported second factor: %s", secondActionURL)
	}

	return doc, nil

}

func (kc *Client) postJSON(submitURL string, values map[string]string, referer string) (*http.Response, error) {

	data, _ := json.Marshal(values)

	req, err := http.NewRequest("POST", submitURL, bytes.NewReader(data))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving login form")
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Referer", referer)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to post JSON")
	}

	return res, nil
}

func (kc *Client) loadResponsePage(submitURL string, referer string, responseForm url.Values) (*goquery.Document, error) {

	req, err := http.NewRequest("POST", submitURL, strings.NewReader(responseForm.Encode()))
	if err != nil {
		return nil, errors.Wrap(err, "error retrieving response page")
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", submitURL)

	res, err := kc.client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to make request to login form")
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return nil, errors.Wrap(err, "error parsing login page html document")
	}

	return doc, nil
}

func mustFindInputByName(doc *goquery.Document, name string) string {

	var fieldValue string

	q := fmt.Sprintf(`input[name="%s"]`, name)

	doc.Find(q).Each(func(i int, s *goquery.Selection) {
		val, ok := s.Attr("value")
		if !ok {
			logger.Fatal("unable to locate field value")
		}
		fieldValue = val
	})

	return fieldValue
}

func mustFindErrorMsg(doc *goquery.Document) string {
	var fieldValue string
	doc.Find(".error-msg").Each(func(i int, s *goquery.Selection) {
		fieldValue = s.Text()

	})
	return fieldValue
}

func extractInputsByFormID(doc *goquery.Document, formID string) (url.Values, string, error) {
	formData := url.Values{}
	var actionURL string

	query := fmt.Sprintf("form#%s", formID)

	//get action url
	doc.Find(query).Each(func(i int, s *goquery.Selection) {
		action, ok := s.Attr("action")
		if !ok {
			return
		}
		actionURL = action
	})

	query = fmt.Sprintf("form#%s", formID)

	// extract form data to passthrough
	doc.Find(query).Find("input").Each(func(i int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}
		val, ok := s.Attr("value")
		if !ok {
			return
		}
		logger.Debugf("name: %s value: %s", name, val)
		formData.Add(name, val)
	})

	return formData, actionURL, nil
}

func extractNodeText(doc *goquery.Document, tag, txt string) string {

	var res string

	doc.Find(tag).Each(func(i int, s *goquery.Selection) {
		if s.Text() == txt {
			res = s.Text()
		}
	})

	return res
}

func extractDataAttributes(doc *goquery.Document, query string, attrsToSelect []string) map[string]string {

	dataAttrs := make(map[string]string)

	doc.Find(query).Each(func(_ int, sel *goquery.Selection) {
		for _, f := range attrsToSelect {
			if val, ok := sel.Attr(f); ok {
				dataAttrs[f] = val
			}
		}
	})

	return dataAttrs
}
<|endoftext|>