<commit_before>package jsonutil

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"reflect"
)

func EncodeJSON(in interface{}) ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	if err := enc.Encode(in); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func DecodeJSON(data []byte, out interface{}) error {
	// Decoding requires a pointer type to be supplied
	value := reflect.ValueOf(out)
	if value.Kind() != reflect.Ptr {
		return fmt.Errorf("decoding the value into an invalid type: %v", reflect.TypeOf(out))
	}

	return DecodeJSONFromReader(bytes.NewReader(data), out)
}

func DecodeJSONFromReader(r io.Reader, out interface{}) error {
	dec := json.NewDecoder(r)

	// While decoding JSON values, intepret the integer values as numbers instead of floats.
	dec.UseNumber()

	// Since 'out' is an interface representing a pointer, pass it to the decoder without an '&'
	return dec.Decode(out)
}
<commit_msg>Input validations for jsonutil<commit_after>package jsonutil

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"reflect"
)

// Encodes/Marshals the given object into JSON
func EncodeJSON(in interface{}) ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	if err := enc.Encode(in); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// Decodes/Unmarshals the given JSON into a desired object
func DecodeJSON(data []byte, out interface{}) error {
	if data == nil {
		return fmt.Errorf("'data' being decoded is nil")
	}
	if out == nil {
		return fmt.Errorf("output parameter 'out' is nil")
	}

	return DecodeJSONFromReader(bytes.NewReader(data), out)
}

// Decodes/Unmarshals the given io.Reader pointing to a JSON, into a desired object
func DecodeJSONFromReader(r io.Reader, out interface{}) error {
	if r == nil {
		return fmt.Errorf("'io.Reader' being decoded is nil")
	}
	if out == nil {
		return fmt.Errorf("output parameter 'out' is nil")
	}

	// Decoding requires a pointer type to be supplied
	value := reflect.ValueOf(out)
	if value.Kind() != reflect.Ptr {
		return fmt.Errorf("decoding the value into a non-pointer type: %v", reflect.TypeOf(out))
	}

	dec := json.NewDecoder(r)

	// While decoding JSON values, intepret the integer values as `json.Number`s instead of `float64`.
	dec.UseNumber()

	// Since 'out' is an interface representing a pointer, pass it to the decoder without an '&'
	return dec.Decode(out)
}
<|endoftext|>


<commit_before>package schema

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/mapstructure"
)

// ValueType is an enum of the type that can be represented by a schema.
type ValueType int

const (
	TypeInvalid ValueType = iota
	TypeBool
	TypeInt
	TypeString
	TypeList
	TypeMap
)

// Schema is used to describe the structure of a value.
type Schema struct {
	// Type is the type of the value and must be one of the ValueType values.
	Type ValueType

	// If one of these is set, then this item can come from the configuration.
	// Both cannot be set. If Optional is set, the value is optional. If
	// Required is set, the value is required.
	Optional bool
	Required bool

	// The fields below relate to diffs.
	//
	// If Computed is true, then the result of this value is computed
	// (unless specified by config) on creation.
	//
	// If ForceNew is true, then a change in this resource necessitates
	// the creation of a new resource.
	Computed bool
	ForceNew bool

	// The following fields are only set for a TypeList Type.
	//
	// Elem must be either a *Schema or a *Resource only if the Type is
	// TypeList, and represents what the element type is. If it is *Schema,
	// the element type is just a simple value. If it is *Resource, the
	// element type is a complex structure, potentially with its own lifecycle.
	Elem interface{}

	// ComputedWhen is a set of queries on the configuration. Whenever any
	// of these things is changed, it will require a recompute (this requires
	// that Computed is set to true).
	ComputedWhen []string
}

func (s *Schema) finalizeDiff(
	d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
	if d == nil {
		return d
	}

	if s.Computed {
		if d.Old != "" && d.New == "" {
			// This is a computed value with an old value set already,
			// just let it go.
			return nil
		}

		if d.New == "" {
			// Computed attribute without a new value set
			d.NewComputed = true
		}
	}

	if s.ForceNew {
		// Force new, set it to true in the diff
		d.RequiresNew = true
	}

	return d
}

// schemaMap is a wrapper that adds nice functions on top of schemas.
type schemaMap map[string]*Schema

// Data returns a ResourceData for the given schema, state, and diff.
//
// The diff is optional.
func (m schemaMap) Data(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff) (*ResourceData, error) {
	return &ResourceData{
		schema: m,
		state:  s,
		diff:   d,
	}, nil
}

// Diff returns the diff for a resource given the schema map,
// state, and configuration.
func (m schemaMap) Diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
	result := new(terraform.ResourceDiff)
	result.Attributes = make(map[string]*terraform.ResourceAttrDiff)

	for k, schema := range m {
		err := m.diff(k, schema, result, s, c)
		if err != nil {
			return nil, err
		}
	}

	// Remove any nil diffs just to keep things clean
	for k, v := range result.Attributes {
		if v == nil {
			delete(result.Attributes, k)
		}
	}

	// Go through and detect all of the ComputedWhens now that we've
	// finished the diff.
	// TODO

	if result.Empty() {
		// If we don't have any diff elements, just return nil
		return nil, nil
	}

	return result, nil
}

// Validate validates the configuration against this schema mapping.
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	return m.validateObject("", m, c)
}

// InternalValidate validates the format of this schema. This should be called
// from a unit test (and not in user-path code) to verify that a schema
// is properly built.
func (m schemaMap) InternalValidate() error {
	for k, v := range m {
		if v.Type == TypeInvalid {
			return fmt.Errorf("%s: Type must be specified", k)
		}

		if v.Optional && v.Required {
			return fmt.Errorf("%s: Optional or Required must be set, not both", k)
		}

		if v.Required && v.Computed {
			return fmt.Errorf("%s: Cannot be both Required and Computed", k)
		}

		if len(v.ComputedWhen) > 0 && !v.Computed {
			return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
		}

		if v.Type == TypeList {
			if v.Elem == nil {
				return fmt.Errorf("%s: Elem must be set for lists", k)
			}

			switch t := v.Elem.(type) {
			case *Resource:
				if err := t.InternalValidate(); err != nil {
					return err
				}
			case *Schema:
				bad := t.Computed || t.Optional || t.Required
				if bad {
					return fmt.Errorf(
						"%s: Elem must have only Type set", k)
				}
			}
		}
	}

	return nil
}

func (m schemaMap) diff(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	var err error
	switch schema.Type {
	case TypeBool:
		fallthrough
	case TypeInt:
		fallthrough
	case TypeString:
		err = m.diffString(k, schema, diff, s, c)
	case TypeList:
		err = m.diffList(k, schema, diff, s, c)
	default:
		err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
	}

	return err
}

func (m schemaMap) diffList(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	v, ok := c.Get(k)
	if !ok {
		// We don't have a value, if it is required then it is an error
		if schema.Required {
			return fmt.Errorf("%s: required field not set", k)
		}

		// We don't have a configuration value.
		if !schema.Computed {
			return nil
		}
	}

	vs, ok := v.([]interface{})
	if !ok {
		return fmt.Errorf("%s: must be a list", k)
	}

	// If this field is required, then it must also be non-empty
	if len(vs) == 0 && schema.Required {
		return fmt.Errorf("%s: required field is not set", k)
	}

	// Diff the count no matter what
	countSchema := &Schema{
		Type:     TypeInt,
		ForceNew: schema.ForceNew,
	}
	m.diffString(k+".#", countSchema, diff, s, c)

	switch t := schema.Elem.(type) {
	case *Schema:
		// Copy the schema so that we can set Computed/ForceNew from
		// the parent schema (the TypeList).
		t2 := *t
		t2.Computed = schema.Computed
		t2.ForceNew = schema.ForceNew

		// This is just a primitive element, so go through each and
		// just diff each.
		for i, _ := range vs {
			subK := fmt.Sprintf("%s.%d", k, i)
			err := m.diff(subK, &t2, diff, s, c)
			if err != nil {
				return err
			}
		}
	case *Resource:
		// This is a complex resource
		for i, _ := range vs {
			for k2, schema := range t.Schema {
				subK := fmt.Sprintf("%s.%d.%s", k, i, k2)
				err := m.diff(subK, schema, diff, s, c)
				if err != nil {
					return err
				}
			}
		}
	default:
		return fmt.Errorf("%s: unknown element type (internal)", k)
	}

	return nil
}

func (m schemaMap) diffString(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	var old, n string
	if s != nil {
		old = s.Attributes[k]
	}

	v, ok := c.Get(k)
	if !ok {
		// We don't have a value, if it is required then it is an error
		if schema.Required {
			return fmt.Errorf("%s: required field not set", k)
		}

		// We don't have a configuration value.
		if !schema.Computed {
			return nil
		}
	} else {
		if err := mapstructure.WeakDecode(v, &n); err != nil {
			return fmt.Errorf("%s: %s", k, err)
		}

		if old == n {
			// They're the same value
			return nil
		}
	}

	diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
		Old: old,
		New: n,
	})

	return nil
}

func (m schemaMap) diffPrimitive(
	k string,
	nraw interface{},
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState) error {
	var old, n string
	if s != nil {
		old = s.Attributes[k]
	}

	if err := mapstructure.WeakDecode(nraw, &n); err != nil {
		return fmt.Errorf("%s: %s", k, err)
	}

	if old == n {
		// They're the same value
		return nil
	}

	diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
		Old: old,
		New: n,
	})

	return nil
}

func (m schemaMap) validate(
	k string,
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	raw, ok := c.Get(k)
	if !ok {
		if schema.Required {
			return nil, []error{fmt.Errorf(
				"%s: required field is not set", k)}
		}

		return nil, nil
	}

	if !schema.Required && !schema.Optional {
		// This is a computed-only field
		return nil, []error{fmt.Errorf(
			"%s: this field cannot be set", k)}
	}

	return m.validatePrimitive(k, raw, schema, c)
}

func (m schemaMap) validateList(
	k string,
	raw interface{},
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	// We use reflection to verify the slice because you can't
	// case to []interface{} unless the slice is exactly that type.
	rawV := reflect.ValueOf(raw)
	if rawV.Kind() != reflect.Slice {
		return nil, []error{fmt.Errorf(
			"%s: should be a list", k)}
	}

	// Now build the []interface{}
	raws := make([]interface{}, rawV.Len())
	for i, _ := range raws {
		raws[i] = rawV.Index(i).Interface()
	}

	var ws []string
	var es []error
	for i, raw := range raws {
		key := fmt.Sprintf("%s.%d", k, i)

		var ws2 []string
		var es2 []error
		switch t := schema.Elem.(type) {
		case *Resource:
			// This is a sub-resource
			ws2, es2 = m.validateObject(key, t.Schema, c)
		case *Schema:
			// This is some sort of primitive
			ws2, es2 = m.validatePrimitive(key, raw, t, c)
		}

		if len(ws2) > 0 {
			ws = append(ws, ws2...)
		}
		if len(es2) > 0 {
			es = append(es, es2...)
		}
	}

	return ws, es
}

func (m schemaMap) validateObject(
	k string,
	schema map[string]*Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	var ws []string
	var es []error
	for subK, s := range schema {
		key := subK
		if k != "" {
			key = fmt.Sprintf("%s.%s", k, subK)
		}

		ws2, es2 := m.validate(key, s, c)
		if len(ws2) > 0 {
			ws = append(ws, ws2...)
		}
		if len(es2) > 0 {
			es = append(es, es2...)
		}
	}

	// Detect any extra/unknown keys and report those as errors.
	prefix := k + "."
	for configK, _ := range c.Raw {
		if k != "" {
			if !strings.HasPrefix(configK, prefix) {
				continue
			}

			configK = configK[len(prefix):]
		}

		if _, ok := schema[configK]; !ok {
			es = append(es, fmt.Errorf(
				"%s: invalid or unknown key: %s", k, configK))
		}
	}

	return ws, es
}

func (m schemaMap) validatePrimitive(
	k string,
	raw interface{},
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	switch schema.Type {
	case TypeList:
		return m.validateList(k, raw, schema, c)
	case TypeInt:
		// Verify that we can parse this as an int
		var n int
		if err := mapstructure.WeakDecode(raw, &n); err != nil {
			return nil, []error{err}
		}
	}

	return nil, nil
}
<commit_msg>helper/schema: use reflection to build []interface{}<commit_after>package schema

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/mapstructure"
)

// ValueType is an enum of the type that can be represented by a schema.
type ValueType int

const (
	TypeInvalid ValueType = iota
	TypeBool
	TypeInt
	TypeString
	TypeList
	TypeMap
)

// Schema is used to describe the structure of a value.
type Schema struct {
	// Type is the type of the value and must be one of the ValueType values.
	Type ValueType

	// If one of these is set, then this item can come from the configuration.
	// Both cannot be set. If Optional is set, the value is optional. If
	// Required is set, the value is required.
	Optional bool
	Required bool

	// The fields below relate to diffs.
	//
	// If Computed is true, then the result of this value is computed
	// (unless specified by config) on creation.
	//
	// If ForceNew is true, then a change in this resource necessitates
	// the creation of a new resource.
	Computed bool
	ForceNew bool

	// The following fields are only set for a TypeList Type.
	//
	// Elem must be either a *Schema or a *Resource only if the Type is
	// TypeList, and represents what the element type is. If it is *Schema,
	// the element type is just a simple value. If it is *Resource, the
	// element type is a complex structure, potentially with its own lifecycle.
	Elem interface{}

	// ComputedWhen is a set of queries on the configuration. Whenever any
	// of these things is changed, it will require a recompute (this requires
	// that Computed is set to true).
	ComputedWhen []string
}

func (s *Schema) finalizeDiff(
	d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
	if d == nil {
		return d
	}

	if s.Computed {
		if d.Old != "" && d.New == "" {
			// This is a computed value with an old value set already,
			// just let it go.
			return nil
		}

		if d.New == "" {
			// Computed attribute without a new value set
			d.NewComputed = true
		}
	}

	if s.ForceNew {
		// Force new, set it to true in the diff
		d.RequiresNew = true
	}

	return d
}

// schemaMap is a wrapper that adds nice functions on top of schemas.
type schemaMap map[string]*Schema

// Data returns a ResourceData for the given schema, state, and diff.
//
// The diff is optional.
func (m schemaMap) Data(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff) (*ResourceData, error) {
	return &ResourceData{
		schema: m,
		state:  s,
		diff:   d,
	}, nil
}

// Diff returns the diff for a resource given the schema map,
// state, and configuration.
func (m schemaMap) Diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
	result := new(terraform.ResourceDiff)
	result.Attributes = make(map[string]*terraform.ResourceAttrDiff)

	for k, schema := range m {
		err := m.diff(k, schema, result, s, c)
		if err != nil {
			return nil, err
		}
	}

	// Remove any nil diffs just to keep things clean
	for k, v := range result.Attributes {
		if v == nil {
			delete(result.Attributes, k)
		}
	}

	// Go through and detect all of the ComputedWhens now that we've
	// finished the diff.
	// TODO

	if result.Empty() {
		// If we don't have any diff elements, just return nil
		return nil, nil
	}

	return result, nil
}

// Validate validates the configuration against this schema mapping.
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	return m.validateObject("", m, c)
}

// InternalValidate validates the format of this schema. This should be called
// from a unit test (and not in user-path code) to verify that a schema
// is properly built.
func (m schemaMap) InternalValidate() error {
	for k, v := range m {
		if v.Type == TypeInvalid {
			return fmt.Errorf("%s: Type must be specified", k)
		}

		if v.Optional && v.Required {
			return fmt.Errorf("%s: Optional or Required must be set, not both", k)
		}

		if v.Required && v.Computed {
			return fmt.Errorf("%s: Cannot be both Required and Computed", k)
		}

		if len(v.ComputedWhen) > 0 && !v.Computed {
			return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
		}

		if v.Type == TypeList {
			if v.Elem == nil {
				return fmt.Errorf("%s: Elem must be set for lists", k)
			}

			switch t := v.Elem.(type) {
			case *Resource:
				if err := t.InternalValidate(); err != nil {
					return err
				}
			case *Schema:
				bad := t.Computed || t.Optional || t.Required
				if bad {
					return fmt.Errorf(
						"%s: Elem must have only Type set", k)
				}
			}
		}
	}

	return nil
}

func (m schemaMap) diff(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	var err error
	switch schema.Type {
	case TypeBool:
		fallthrough
	case TypeInt:
		fallthrough
	case TypeString:
		err = m.diffString(k, schema, diff, s, c)
	case TypeList:
		err = m.diffList(k, schema, diff, s, c)
	default:
		err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
	}

	return err
}

func (m schemaMap) diffList(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	v, ok := c.Get(k)
	if !ok {
		// We don't have a value, if it is required then it is an error
		if schema.Required {
			return fmt.Errorf("%s: required field not set", k)
		}

		// We don't have a configuration value.
		if !schema.Computed {
			return nil
		}
	}

	// We have to use reflection to build the []interface{} list
	rawV := reflect.ValueOf(v)
	if rawV.Kind() != reflect.Slice {
		return fmt.Errorf("%s: must be a list", k)
	}
	vs := make([]interface{}, rawV.Len())
	for i, _ := range vs {
		vs[i] = rawV.Index(i).Interface()
	}

	// If this field is required, then it must also be non-empty
	if len(vs) == 0 && schema.Required {
		return fmt.Errorf("%s: required field is not set", k)
	}

	// Diff the count no matter what
	countSchema := &Schema{
		Type:     TypeInt,
		ForceNew: schema.ForceNew,
	}
	m.diffString(k+".#", countSchema, diff, s, c)

	switch t := schema.Elem.(type) {
	case *Schema:
		// Copy the schema so that we can set Computed/ForceNew from
		// the parent schema (the TypeList).
		t2 := *t
		t2.Computed = schema.Computed
		t2.ForceNew = schema.ForceNew

		// This is just a primitive element, so go through each and
		// just diff each.
		for i, _ := range vs {
			subK := fmt.Sprintf("%s.%d", k, i)
			err := m.diff(subK, &t2, diff, s, c)
			if err != nil {
				return err
			}
		}
	case *Resource:
		// This is a complex resource
		for i, _ := range vs {
			for k2, schema := range t.Schema {
				subK := fmt.Sprintf("%s.%d.%s", k, i, k2)
				err := m.diff(subK, schema, diff, s, c)
				if err != nil {
					return err
				}
			}
		}
	default:
		return fmt.Errorf("%s: unknown element type (internal)", k)
	}

	return nil
}

func (m schemaMap) diffString(
	k string,
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) error {
	var old, n string
	if s != nil {
		old = s.Attributes[k]
	}

	v, ok := c.Get(k)
	if !ok {
		// We don't have a value, if it is required then it is an error
		if schema.Required {
			return fmt.Errorf("%s: required field not set", k)
		}

		// We don't have a configuration value.
		if !schema.Computed {
			return nil
		}
	} else {
		if err := mapstructure.WeakDecode(v, &n); err != nil {
			return fmt.Errorf("%s: %s", k, err)
		}

		if old == n {
			// They're the same value
			return nil
		}
	}

	diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
		Old: old,
		New: n,
	})

	return nil
}

func (m schemaMap) diffPrimitive(
	k string,
	nraw interface{},
	schema *Schema,
	diff *terraform.ResourceDiff,
	s *terraform.ResourceState) error {
	var old, n string
	if s != nil {
		old = s.Attributes[k]
	}

	if err := mapstructure.WeakDecode(nraw, &n); err != nil {
		return fmt.Errorf("%s: %s", k, err)
	}

	if old == n {
		// They're the same value
		return nil
	}

	diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
		Old: old,
		New: n,
	})

	return nil
}

func (m schemaMap) validate(
	k string,
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	raw, ok := c.Get(k)
	if !ok {
		if schema.Required {
			return nil, []error{fmt.Errorf(
				"%s: required field is not set", k)}
		}

		return nil, nil
	}

	if !schema.Required && !schema.Optional {
		// This is a computed-only field
		return nil, []error{fmt.Errorf(
			"%s: this field cannot be set", k)}
	}

	return m.validatePrimitive(k, raw, schema, c)
}

func (m schemaMap) validateList(
	k string,
	raw interface{},
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	// We use reflection to verify the slice because you can't
	// case to []interface{} unless the slice is exactly that type.
	rawV := reflect.ValueOf(raw)
	if rawV.Kind() != reflect.Slice {
		return nil, []error{fmt.Errorf(
			"%s: should be a list", k)}
	}

	// Now build the []interface{}
	raws := make([]interface{}, rawV.Len())
	for i, _ := range raws {
		raws[i] = rawV.Index(i).Interface()
	}

	var ws []string
	var es []error
	for i, raw := range raws {
		key := fmt.Sprintf("%s.%d", k, i)

		var ws2 []string
		var es2 []error
		switch t := schema.Elem.(type) {
		case *Resource:
			// This is a sub-resource
			ws2, es2 = m.validateObject(key, t.Schema, c)
		case *Schema:
			// This is some sort of primitive
			ws2, es2 = m.validatePrimitive(key, raw, t, c)
		}

		if len(ws2) > 0 {
			ws = append(ws, ws2...)
		}
		if len(es2) > 0 {
			es = append(es, es2...)
		}
	}

	return ws, es
}

func (m schemaMap) validateObject(
	k string,
	schema map[string]*Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	var ws []string
	var es []error
	for subK, s := range schema {
		key := subK
		if k != "" {
			key = fmt.Sprintf("%s.%s", k, subK)
		}

		ws2, es2 := m.validate(key, s, c)
		if len(ws2) > 0 {
			ws = append(ws, ws2...)
		}
		if len(es2) > 0 {
			es = append(es, es2...)
		}
	}

	// Detect any extra/unknown keys and report those as errors.
	prefix := k + "."
	for configK, _ := range c.Raw {
		if k != "" {
			if !strings.HasPrefix(configK, prefix) {
				continue
			}

			configK = configK[len(prefix):]
		}

		if _, ok := schema[configK]; !ok {
			es = append(es, fmt.Errorf(
				"%s: invalid or unknown key: %s", k, configK))
		}
	}

	return ws, es
}

func (m schemaMap) validatePrimitive(
	k string,
	raw interface{},
	schema *Schema,
	c *terraform.ResourceConfig) ([]string, []error) {
	switch schema.Type {
	case TypeList:
		return m.validateList(k, raw, schema, c)
	case TypeInt:
		// Verify that we can parse this as an int
		var n int
		if err := mapstructure.WeakDecode(raw, &n); err != nil {
			return nil, []error{err}
		}
	}

	return nil, nil
}
<|endoftext|>


<commit_before>package aggro

import (
	"errors"
	"fmt"
	"time"
)

// MetricDelimeter is a string used to separate metric field's from names.
var MetricDelimeter = ":"

type queryProcessor struct {
	dataset     *Dataset
	query       *Query
	tipBuckets  map[*ResultBucket]bool
	measurables []*[]Cell
	err         error
	results     *Resultset
	hasDatetime bool
}

func (p *queryProcessor) Run() (*Resultset, error) {
	p.prepare()
	p.aggregate()
	p.measure()
	return p.results, p.err
}

func (p *queryProcessor) prepare() {
	if p.err != nil {
		return
	}

	// Initialise the root & tip buckets, and full bucket lookup.
	p.tipBuckets = map[*ResultBucket]bool{}
}

// aggregate is responsible for sorting the dataset's rows into buckets.
func (p *queryProcessor) aggregate() {
	if p.err != nil {
		return
	}
	if p.query.Bucket == nil {
		p.err = errors.New("Query has no root bucket")
		return
	}
	// Loop over each row, adding all nest query buckets to the value buckets.
	buckets := map[string]*ResultBucket{}
	for i, row := range p.dataset.Rows {
		buckets = p.recurse(0, i, row, p.query.Bucket, buckets)
	}

	buckets = p.fillDatetimeGaps(buckets)

	p.results = &Resultset{
		Buckets: buckets,
	}
}

func (p *queryProcessor) recurse(depth, index int, row map[string]Cell, aggregate *Bucket, results map[string]*ResultBucket) map[string]*ResultBucket {
	// If there's no aggregate, we're done.
	if aggregate == nil {
		return results
	}

	// Ensure we have the details required to bucket on.
	if aggregate.Field.Type == "datetime" && aggregate.DatetimeOptions == nil {
		p.err = errors.New("Bucketing by datetime without DatetimeOptions set")
    return results
	}

	// Grab the cell that we're aggregating on.
	cell := row[aggregate.Field.Name]
	// And grab the underlying aggregatable string value.
	value := ""
	switch tCell := cell.(type) {
	case *StringCell:
		// String Cell's are easy, it's just the value.
		value = tCell.value
	case *DatetimeCell:
		p.hasDatetime = true
		// Datetime Cell's are a bit more complicated, and need the period value.
		value, p.err = tCell.ValueForPeriod(aggregate.DatetimeOptions.Period, aggregate.DatetimeOptions.Location)
		if p.err != nil {
			return results
		}
	default:
		p.err = fmt.Errorf("Non aggregatable cell found at depth %d, index %d", depth, index)
		return results
	}

	// Ensure we have a result bucket for this value, making one if we don't.
	bucket := ensureValueBucket(results, value)

	// If there's no next bucket, we're at the deepest point. Add data to measure.
	if aggregate.Bucket == nil {
		bucket.sourceRows = append(bucket.sourceRows, row)
		p.tipBuckets[bucket] = true
	}

	// Bump depth and recurse to next level, passing in the children as the results.
	depth++
	bucket.Buckets = p.recurse(depth, index, row, aggregate.Bucket, bucket.Buckets)

	// Update the current results bucket with the new values, then return.
	results[value] = bucket
	return results
}

func ensureValueBucket(results map[string]*ResultBucket, value string) *ResultBucket {
	bucket := results[value]
	if bucket == nil {
		bucket = &ResultBucket{
			Value:   value,
			Buckets: map[string]*ResultBucket{},
		}
	}
	return bucket
}

func (p *queryProcessor) fillDatetimeGaps(results map[string]*ResultBucket) map[string]*ResultBucket {
	if !p.hasDatetime {
		return results
	}
	return p.fillBucketDatetimeGaps(p.query.Bucket, results)
}

func (p *queryProcessor) fillBucketDatetimeGaps(bucket *Bucket, results map[string]*ResultBucket) map[string]*ResultBucket {
	if bucket == nil || len(results) < 0 {
		return results
	}
	if bucket.Field.Type == "datetime" {
		// Get the max and min values.
		var min, max *string
		// Set the min to the start if there is one.
		if bucket.DatetimeOptions.Start != nil {
			var start string
			start, p.err = datetimeValueForPeriod(
				bucket.DatetimeOptions.Start,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
			min = &start
		}
		// Set the max to the end if there is one.
		if bucket.DatetimeOptions.End != nil {
			var end string
			end, p.err = datetimeValueForPeriod(
				bucket.DatetimeOptions.End,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
			max = &end
		}
		// Now extend the start and end depending on the values in the results.
		for key, _ := range results {
			value := key
			// Min being nil means max is too. Set them to the first result.
			if min == nil {
				min = &value
				max = &value
			} else {
				if *min > value {
					min = &value
				}
				if *max < value {
					max = &value
				}
			}
		}
		// No need to do anything if we only have a single bucket length.
		if *min == *max {
			return results
		}

		loopValue := *min
		var loopDate time.Time
		loopDate, p.err = time.Parse(time.RFC3339, loopValue)
		if p.err != nil {
			return results
		}
		// Now loop until we hit the max point, ensuring each period exists.
		for loopValue <= *max {
			// Make sure this period exists.
			results[loopValue] = ensureValueBucket(results, loopValue)
			if bucket.Bucket == nil {
				p.tipBuckets[results[loopValue]] = true
			}
			// Now bump the date up one period, and loop.
			date, err := datetimeAddPeriod(&loopDate, bucket.DatetimeOptions.Period)
			if err != nil {
				p.err = err
				return results
			}
			loopDate = *date
			loopValue, p.err = datetimeValueForPeriod(
				&loopDate,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
		}
	}

	// Now recurse into any children result sets.
	for _, result := range results {
		result.Buckets = p.fillBucketDatetimeGaps(bucket.Bucket, result.Buckets)
	}

	return results
}

func (p *queryProcessor) measure() {
	if p.err != nil {
		return
	}

	// We only add metrics for the tip buckets, i.e. the deepest nesting.
	for bucket := range p.tipBuckets {
		// Create measurers for each of the metrics, then feed data into them.
		bucket.Metrics = map[string]interface{}{}
		var m measurer

		for i := range p.query.Metrics {
			metric := &p.query.Metrics[i]
			// Create a measurer.
			m, p.err = metric.measurer()
			if p.err != nil {
				return
			}
			// Now add all of the data to the measurer.
			for j := range bucket.sourceRows {
				row := bucket.sourceRows[j]
				m.AddDatum(row[metric.Field].MeasurableCell().Value())
			}
			// And then push the result into the metric resultset.
			bucket.Metrics[metric.Field+MetricDelimeter+metric.Type] = m.Result()
		}
	}
}
<commit_msg>Pass nil cells during aggregation recursion<commit_after>package aggro

import (
	"errors"
	"fmt"
	"time"
)

// MetricDelimeter is a string used to separate metric field's from names.
var MetricDelimeter = ":"

type queryProcessor struct {
	dataset     *Dataset
	query       *Query
	tipBuckets  map[*ResultBucket]bool
	measurables []*[]Cell
	err         error
	results     *Resultset
	hasDatetime bool
}

func (p *queryProcessor) Run() (*Resultset, error) {
	p.prepare()
	p.aggregate()
	p.measure()
	return p.results, p.err
}

func (p *queryProcessor) prepare() {
	if p.err != nil {
		return
	}

	// Initialise the root & tip buckets, and full bucket lookup.
	p.tipBuckets = map[*ResultBucket]bool{}
}

// aggregate is responsible for sorting the dataset's rows into buckets.
func (p *queryProcessor) aggregate() {
	if p.err != nil {
		return
	}
	if p.query.Bucket == nil {
		p.err = errors.New("Query has no root bucket")
		return
	}
	// Loop over each row, adding all nest query buckets to the value buckets.
	buckets := map[string]*ResultBucket{}
	for i, row := range p.dataset.Rows {
		buckets = p.recurse(0, i, row, p.query.Bucket, buckets)
	}

	buckets = p.fillDatetimeGaps(buckets)

	p.results = &Resultset{
		Buckets: buckets,
	}
}

func (p *queryProcessor) recurse(depth, index int, row map[string]Cell, aggregate *Bucket, results map[string]*ResultBucket) map[string]*ResultBucket {
	// If there's no aggregate, we're done.
	if aggregate == nil {
		return results
	}

	// Ensure we have the details required to bucket on.
	if aggregate.Field.Type == "datetime" && aggregate.DatetimeOptions == nil {
		p.err = errors.New("Bucketing by datetime without DatetimeOptions set")
    return results
	}

	// Grab the cell that we're aggregating on.
	cell := row[aggregate.Field.Name]

	// Handle nil cell.
	if cell == nil {
		return results
	}

	// And grab the underlying aggregatable string value.
	value := ""
	switch tCell := cell.(type) {
	case *StringCell:
		// String Cell's are easy, it's just the value.
		value = tCell.value
	case *DatetimeCell:
		p.hasDatetime = true
		// Datetime Cell's are a bit more complicated, and need the period value.
		value, p.err = tCell.ValueForPeriod(aggregate.DatetimeOptions.Period, aggregate.DatetimeOptions.Location)
		if p.err != nil {
			return results
		}
	default:
		p.err = fmt.Errorf("Non aggregatable cell found at depth %d, index %d", depth, index)
		return results
	}

	// Ensure we have a result bucket for this value, making one if we don't.
	bucket := ensureValueBucket(results, value)

	// If there's no next bucket, we're at the deepest point. Add data to measure.
	if aggregate.Bucket == nil {
		bucket.sourceRows = append(bucket.sourceRows, row)
		p.tipBuckets[bucket] = true
	}

	// Bump depth and recurse to next level, passing in the children as the results.
	depth++
	bucket.Buckets = p.recurse(depth, index, row, aggregate.Bucket, bucket.Buckets)

	// Update the current results bucket with the new values, then return.
	results[value] = bucket
	return results
}

func ensureValueBucket(results map[string]*ResultBucket, value string) *ResultBucket {
	bucket := results[value]
	if bucket == nil {
		bucket = &ResultBucket{
			Value:   value,
			Buckets: map[string]*ResultBucket{},
		}
	}
	return bucket
}

func (p *queryProcessor) fillDatetimeGaps(results map[string]*ResultBucket) map[string]*ResultBucket {
	if !p.hasDatetime {
		return results
	}
	return p.fillBucketDatetimeGaps(p.query.Bucket, results)
}

func (p *queryProcessor) fillBucketDatetimeGaps(bucket *Bucket, results map[string]*ResultBucket) map[string]*ResultBucket {
	if bucket == nil || len(results) < 0 {
		return results
	}
	if bucket.Field.Type == "datetime" {
		// Get the max and min values.
		var min, max *string
		// Set the min to the start if there is one.
		if bucket.DatetimeOptions.Start != nil {
			var start string
			start, p.err = datetimeValueForPeriod(
				bucket.DatetimeOptions.Start,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
			min = &start
		}
		// Set the max to the end if there is one.
		if bucket.DatetimeOptions.End != nil {
			var end string
			end, p.err = datetimeValueForPeriod(
				bucket.DatetimeOptions.End,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
			max = &end
		}
		// Now extend the start and end depending on the values in the results.
		for key, _ := range results {
			value := key
			// Min being nil means max is too. Set them to the first result.
			if min == nil {
				min = &value
				max = &value
			} else {
				if *min > value {
					min = &value
				}
				if *max < value {
					max = &value
				}
			}
		}
		// No need to do anything if we only have a single bucket length.
		if *min == *max {
			return results
		}

		loopValue := *min
		var loopDate time.Time
		loopDate, p.err = time.Parse(time.RFC3339, loopValue)
		if p.err != nil {
			return results
		}
		// Now loop until we hit the max point, ensuring each period exists.
		for loopValue <= *max {
			// Make sure this period exists.
			results[loopValue] = ensureValueBucket(results, loopValue)
			if bucket.Bucket == nil {
				p.tipBuckets[results[loopValue]] = true
			}
			// Now bump the date up one period, and loop.
			date, err := datetimeAddPeriod(&loopDate, bucket.DatetimeOptions.Period)
			if err != nil {
				p.err = err
				return results
			}
			loopDate = *date
			loopValue, p.err = datetimeValueForPeriod(
				&loopDate,
				bucket.DatetimeOptions.Period,
				bucket.DatetimeOptions.Location,
			)
			if p.err != nil {
				return results
			}
		}
	}

	// Now recurse into any children result sets.
	for _, result := range results {
		result.Buckets = p.fillBucketDatetimeGaps(bucket.Bucket, result.Buckets)
	}

	return results
}

func (p *queryProcessor) measure() {
	if p.err != nil {
		return
	}

	// We only add metrics for the tip buckets, i.e. the deepest nesting.
	for bucket := range p.tipBuckets {
		// Create measurers for each of the metrics, then feed data into them.
		bucket.Metrics = map[string]interface{}{}
		var m measurer

		for i := range p.query.Metrics {
			metric := &p.query.Metrics[i]
			// Create a measurer.
			m, p.err = metric.measurer()
			if p.err != nil {
				return
			}
			// Now add all of the data to the measurer.
			for j := range bucket.sourceRows {
				row := bucket.sourceRows[j]
				m.AddDatum(row[metric.Field].MeasurableCell().Value())
			}
			// And then push the result into the metric resultset.
			bucket.Metrics[metric.Field+MetricDelimeter+metric.Type] = m.Result()
		}
	}
}
<|endoftext|>


<commit_before>package terraform

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"io"
	"io/ioutil"
	"regexp"
	"strings"
	"testing"
)

// debugInfo should be safe when nil
func TestDebugInfo_nil(t *testing.T) {
	var d *debugInfo

	d.SetPhase("none")
	d.WriteFile("none", nil)
	d.Close()
}

func TestDebugInfo_basicFile(t *testing.T) {
	var w bytes.Buffer
	debug, err := newDebugInfo("test-debug-info", &w)
	if err != nil {
		t.Fatal(err)
	}
	debug.SetPhase("test")

	fileData := map[string][]byte{
		"file1": []byte("file 1 data"),
		"file2": []byte("file 2 data"),
		"file3": []byte("file 3 data"),
	}

	for f, d := range fileData {
		err = debug.WriteFile(f, d)
		if err != nil {
			t.Fatal(err)
		}
	}

	err = debug.Close()
	if err != nil {
		t.Fatal(err)
	}

	gz, err := gzip.NewReader(&w)
	if err != nil {
		t.Fatal(err)
	}
	tr := tar.NewReader(gz)

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		// get the filename part of the archived file
		name := regexp.MustCompile(`\w+$`).FindString(hdr.Name)
		data := fileData[name]

		delete(fileData, name)

		tarData, err := ioutil.ReadAll(tr)
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(data, tarData) {
			t.Fatalf("got '%s' for file '%s'", tarData, name)
		}
	}

	for k := range fileData {
		t.Fatalf("didn't find file %s", k)
	}
}

// Test that we get logs and graphs from a walk. We're not looking for anything
// specific, since the output is going to change in the near future.
func TestDebug_plan(t *testing.T) {
	var out bytes.Buffer
	d, err := newDebugInfo("test-debug-info", &out)
	if err != nil {
		t.Fatal(err)
	}
	// set the global debug value
	dbug = d

	// run a basic plan
	m := testModule(t, "plan-good")
	p := testProvider("aws")
	p.DiffFn = testDiffFn
	ctx := testContext2(t, &ContextOpts{
		Config: m,
		ProviderResolver: ResourceProviderResolverFixed(
			map[string]ResourceProviderFactory{
				"aws": testProviderFuncFixed(p),
			},
		),
	})

	_, diags := ctx.Plan()
	if diags.HasErrors() {
		t.Fatalf("err: %s", diags.Err())
	}

	err = CloseDebugInfo()
	if err != nil {
		t.Fatal(err)
	}

	gz, err := gzip.NewReader(&out)
	if err != nil {
		t.Fatal(err)
	}
	tr := tar.NewReader(gz)

	graphLogs := 0
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		// record any file that contains data
		if hdr.Size > 0 {
			if strings.HasSuffix(hdr.Name, "graph.json") {
				graphLogs++
			}
		}
	}

	if graphLogs == 0 {
		t.Fatal("no json graphs")
	}
}

// verify that no hooks panic on nil input
func TestDebugHook_nilArgs(t *testing.T) {
	// make sure debug isn't nil, so the hooks try to execute
	var w bytes.Buffer
	var err error
	dbug, err = newDebugInfo("test-debug-info", &w)
	if err != nil {
		t.Fatal(err)
	}

	var h DebugHook
	h.PostApply(nil, nil, nil)
	h.PostDiff(nil, nil)
	h.PostImportState(nil, nil)
	h.PostProvision(nil, "", nil)
	h.PostProvisionResource(nil, nil)
	h.PostRefresh(nil, nil)
	h.PostStateUpdate(nil)
	h.PreApply(nil, nil, nil)
	h.PreDiff(nil, nil)
	h.PreImportState(nil, "")
	h.PreProvision(nil, "")
	h.PreProvisionResource(nil, nil)
	h.PreRefresh(nil, nil)
	h.ProvisionOutput(nil, "", "")
}
<commit_msg>core: fix tests for debug.go<commit_after>package terraform

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"io"
	"io/ioutil"
	"regexp"
	"strings"
	"testing"

	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/terraform/config/configschema"
)

// debugInfo should be safe when nil
func TestDebugInfo_nil(t *testing.T) {
	var d *debugInfo

	d.SetPhase("none")
	d.WriteFile("none", nil)
	d.Close()
}

func TestDebugInfo_basicFile(t *testing.T) {
	var w bytes.Buffer
	debug, err := newDebugInfo("test-debug-info", &w)
	if err != nil {
		t.Fatal(err)
	}
	debug.SetPhase("test")

	fileData := map[string][]byte{
		"file1": []byte("file 1 data"),
		"file2": []byte("file 2 data"),
		"file3": []byte("file 3 data"),
	}

	for f, d := range fileData {
		err = debug.WriteFile(f, d)
		if err != nil {
			t.Fatal(err)
		}
	}

	err = debug.Close()
	if err != nil {
		t.Fatal(err)
	}

	gz, err := gzip.NewReader(&w)
	if err != nil {
		t.Fatal(err)
	}
	tr := tar.NewReader(gz)

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		// get the filename part of the archived file
		name := regexp.MustCompile(`\w+$`).FindString(hdr.Name)
		data := fileData[name]

		delete(fileData, name)

		tarData, err := ioutil.ReadAll(tr)
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(data, tarData) {
			t.Fatalf("got '%s' for file '%s'", tarData, name)
		}
	}

	for k := range fileData {
		t.Fatalf("didn't find file %s", k)
	}
}

// Test that we get logs and graphs from a walk. We're not looking for anything
// specific, since the output is going to change in the near future.
func TestDebug_plan(t *testing.T) {
	var out bytes.Buffer
	d, err := newDebugInfo("test-debug-info", &out)
	if err != nil {
		t.Fatal(err)
	}
	// set the global debug value
	dbug = d

	// run a basic plan
	m := testModule(t, "plan-good")
	p := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
		Attributes: map[string]*configschema.Attribute{
			"num": {
				Type:     cty.Number,
				Optional: true,
			},
			"foo": {
				Type:     cty.Number,
				Optional: true,
			},
		},
	})
	p.DiffFn = testDiffFn
	ctx := testContext2(t, &ContextOpts{
		Config: m,
		ProviderResolver: ResourceProviderResolverFixed(
			map[string]ResourceProviderFactory{
				"aws": testProviderFuncFixed(p),
			},
		),
	})

	_, diags := ctx.Plan()
	if diags.HasErrors() {
		t.Fatalf("err: %s", diags.Err())
	}

	err = CloseDebugInfo()
	if err != nil {
		t.Fatal(err)
	}

	gz, err := gzip.NewReader(&out)
	if err != nil {
		t.Fatal(err)
	}
	tr := tar.NewReader(gz)

	graphLogs := 0
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}

		// record any file that contains data
		if hdr.Size > 0 {
			if strings.HasSuffix(hdr.Name, "graph.json") {
				graphLogs++
			}
		}
	}

	if graphLogs == 0 {
		t.Fatal("no json graphs")
	}
}

// verify that no hooks panic on nil input
func TestDebugHook_nilArgs(t *testing.T) {
	// make sure debug isn't nil, so the hooks try to execute
	var w bytes.Buffer
	var err error
	dbug, err = newDebugInfo("test-debug-info", &w)
	if err != nil {
		t.Fatal(err)
	}

	var h DebugHook
	h.PostApply(nil, nil, nil)
	h.PostDiff(nil, nil)
	h.PostImportState(nil, nil)
	h.PostProvision(nil, "", nil)
	h.PostProvisionResource(nil, nil)
	h.PostRefresh(nil, nil)
	h.PostStateUpdate(nil)
	h.PreApply(nil, nil, nil)
	h.PreDiff(nil, nil)
	h.PreImportState(nil, "")
	h.PreProvision(nil, "")
	h.PreProvisionResource(nil, nil)
	h.PreRefresh(nil, nil)
	h.ProvisionOutput(nil, "", "")
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"fmt"
	net2 "net"
	"os"
	"path/filepath"
	"strings"

	systemd "github.com/coreos/go-systemd/daemon"
	"github.com/pkg/errors"
	"github.com/rancher/k3s/pkg/agent"
	"github.com/rancher/k3s/pkg/cli/cmds"
	"github.com/rancher/k3s/pkg/datadir"
	"github.com/rancher/k3s/pkg/netutil"
	"github.com/rancher/k3s/pkg/rootless"
	"github.com/rancher/k3s/pkg/server"
	"github.com/rancher/wrangler/pkg/signals"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/kubernetes/pkg/master"

	_ "github.com/go-sql-driver/mysql" // ensure we have mysql
	_ "github.com/lib/pq"              // ensure we have postgres
	_ "github.com/mattn/go-sqlite3"    // ensure we have sqlite
)

func Run(app *cli.Context) error {
	if err := cmds.InitLogging(); err != nil {
		return err
	}
	return run(app, &cmds.ServerConfig)
}

func run(app *cli.Context, cfg *cmds.Server) error {
	var (
		err error
	)

	if !cfg.DisableAgent && os.Getuid() != 0 && !cfg.Rootless {
		return fmt.Errorf("must run as root unless --disable-agent is specified")
	}

	if cfg.Rootless {
		dataDir, err := datadir.LocalHome(cfg.DataDir, true)
		if err != nil {
			return err
		}
		cfg.DataDir = dataDir
		if err := rootless.Rootless(dataDir); err != nil {
			return err
		}
	}

	serverConfig := server.Config{}
	serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret
	serverConfig.ControlConfig.DataDir = cfg.DataDir
	serverConfig.ControlConfig.KubeConfigOutput = cfg.KubeConfigOutput
	serverConfig.ControlConfig.KubeConfigMode = cfg.KubeConfigMode
	serverConfig.ControlConfig.NoScheduler = cfg.DisableScheduler
	serverConfig.Rootless = cfg.Rootless
	serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort
	for _, san := range knownIPs(cfg.TLSSan) {
		addr := net2.ParseIP(san)
		if addr != nil {
			serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, san)
		} else {
			serverConfig.TLSConfig.Domains = append(serverConfig.TLSConfig.Domains, san)
		}
	}
	serverConfig.TLSConfig.BindAddress = cfg.BindAddress
	serverConfig.ControlConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs
	serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs
	serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs
	serverConfig.ControlConfig.ClusterDomain = cfg.ClusterDomain
	serverConfig.ControlConfig.Storage.Endpoint = cfg.StorageEndpoint
	serverConfig.ControlConfig.Storage.CAFile = cfg.StorageCAFile
	serverConfig.ControlConfig.Storage.CertFile = cfg.StorageCertFile
	serverConfig.ControlConfig.Storage.KeyFile = cfg.StorageKeyFile
	serverConfig.ControlConfig.AdvertiseIP = cfg.AdvertiseIP
	serverConfig.ControlConfig.AdvertisePort = cfg.AdvertisePort
	serverConfig.ControlConfig.BootstrapReadOnly = !cfg.StoreBootstrap
	serverConfig.ControlConfig.FlannelBackend = cfg.FlannelBackend
	serverConfig.ControlConfig.ExtraCloudControllerArgs = cfg.ExtraCloudControllerArgs
	serverConfig.ControlConfig.DisableCCM = cfg.DisableCCM
	serverConfig.ControlConfig.DisableNPC = cfg.DisableNPC

	if cmds.AgentConfig.FlannelIface != "" && cmds.AgentConfig.NodeIP == "" {
		cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface)
	}

	if serverConfig.ControlConfig.AdvertiseIP == "" && cmds.AgentConfig.NodeIP != "" {
		serverConfig.ControlConfig.AdvertiseIP = cmds.AgentConfig.NodeIP
	}
	if serverConfig.ControlConfig.AdvertiseIP != "" {
		serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, serverConfig.ControlConfig.AdvertiseIP)
	}

	_, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ClusterCIDR, err)
	}
	_, serverConfig.ControlConfig.ServiceIPRange, err = net2.ParseCIDR(cfg.ServiceCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err)
	}

	_, apiServerServiceIP, err := master.DefaultServiceIPRange(*serverConfig.ControlConfig.ServiceIPRange)
	if err != nil {
		return err
	}
	serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, apiServerServiceIP.String())

	// If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10,
	// i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10
	if cfg.ClusterDNS == "" {
		serverConfig.ControlConfig.ClusterDNS = make(net2.IP, 4)
		copy(serverConfig.ControlConfig.ClusterDNS, serverConfig.ControlConfig.ServiceIPRange.IP.To4())
		serverConfig.ControlConfig.ClusterDNS[3] = 10
	} else {
		serverConfig.ControlConfig.ClusterDNS = net2.ParseIP(cfg.ClusterDNS)
	}

	if cfg.DefaultLocalStoragePath == "" {
		dataDir, err := datadir.LocalHome(cfg.DataDir, false)
		if err != nil {
			return err
		}
		serverConfig.ControlConfig.DefaultLocalStoragePath = filepath.Join(dataDir, "/storage")
	} else {
		serverConfig.ControlConfig.DefaultLocalStoragePath = cfg.DefaultLocalStoragePath
	}

	noDeploys := make([]string, 0)
	for _, noDeploy := range app.StringSlice("no-deploy") {
		for _, splitNoDeploy := range strings.Split(noDeploy, ",") {
			noDeploys = append(noDeploys, splitNoDeploy)
		}
	}

	for _, noDeploy := range noDeploys {
		if noDeploy == "servicelb" {
			serverConfig.DisableServiceLB = true
			continue
		}

		if !strings.HasSuffix(noDeploy, ".yaml") {
			noDeploy = noDeploy + ".yaml"
		}
		serverConfig.ControlConfig.Skips = append(serverConfig.ControlConfig.Skips, noDeploy)
	}

	logrus.Info("Starting k3s ", app.App.Version)
	notifySocket := os.Getenv("NOTIFY_SOCKET")
	os.Unsetenv("NOTIFY_SOCKET")

	ctx := signals.SetupSignalHandler(context.Background())
	certs, err := server.StartServer(ctx, &serverConfig)
	if err != nil {
		return err
	}

	logrus.Info("k3s is up and running")
	if notifySocket != "" {
		os.Setenv("NOTIFY_SOCKET", notifySocket)
		systemd.SdNotify(true, "READY=1\n")
	}

	if cfg.DisableAgent {
		<-ctx.Done()
		return nil
	}
	ip := serverConfig.TLSConfig.BindAddress
	if ip == "" {
		ip = "127.0.0.1"
	}
	url := fmt.Sprintf("https://%s:%d", ip, serverConfig.TLSConfig.HTTPSPort)
	token := server.FormatToken(serverConfig.ControlConfig.Runtime.NodeToken, certs)

	agentConfig := cmds.AgentConfig
	agentConfig.Debug = app.GlobalBool("bool")
	agentConfig.DataDir = filepath.Dir(serverConfig.ControlConfig.DataDir)
	agentConfig.ServerURL = url
	agentConfig.Token = token
	agentConfig.DisableLoadBalancer = true
	agentConfig.Rootless = cfg.Rootless
	if agentConfig.Rootless {
		// let agent specify Rootless kubelet flags, but not unshare twice
		agentConfig.RootlessAlreadyUnshared = true
	}

	return agent.Run(ctx, agentConfig)
}

func knownIPs(ips []string) []string {
	ips = append(ips, "127.0.0.1")
	ip, err := net.ChooseHostInterface()
	if err == nil {
		ips = append(ips, ip.String())
	}
	return ips
}
<commit_msg>Propagate DisableAgent flag<commit_after>package server

import (
	"context"
	"fmt"
	net2 "net"
	"os"
	"path/filepath"
	"strings"

	systemd "github.com/coreos/go-systemd/daemon"
	"github.com/pkg/errors"
	"github.com/rancher/k3s/pkg/agent"
	"github.com/rancher/k3s/pkg/cli/cmds"
	"github.com/rancher/k3s/pkg/datadir"
	"github.com/rancher/k3s/pkg/netutil"
	"github.com/rancher/k3s/pkg/rootless"
	"github.com/rancher/k3s/pkg/server"
	"github.com/rancher/wrangler/pkg/signals"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/kubernetes/pkg/master"

	_ "github.com/go-sql-driver/mysql" // ensure we have mysql
	_ "github.com/lib/pq"              // ensure we have postgres
	_ "github.com/mattn/go-sqlite3"    // ensure we have sqlite
)

func Run(app *cli.Context) error {
	if err := cmds.InitLogging(); err != nil {
		return err
	}
	return run(app, &cmds.ServerConfig)
}

func run(app *cli.Context, cfg *cmds.Server) error {
	var (
		err error
	)

	if !cfg.DisableAgent && os.Getuid() != 0 && !cfg.Rootless {
		return fmt.Errorf("must run as root unless --disable-agent is specified")
	}

	if cfg.Rootless {
		dataDir, err := datadir.LocalHome(cfg.DataDir, true)
		if err != nil {
			return err
		}
		cfg.DataDir = dataDir
		if err := rootless.Rootless(dataDir); err != nil {
			return err
		}
	}

	serverConfig := server.Config{}
	serverConfig.DisableAgent = cfg.DisableAgent
	serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret
	serverConfig.ControlConfig.DataDir = cfg.DataDir
	serverConfig.ControlConfig.KubeConfigOutput = cfg.KubeConfigOutput
	serverConfig.ControlConfig.KubeConfigMode = cfg.KubeConfigMode
	serverConfig.ControlConfig.NoScheduler = cfg.DisableScheduler
	serverConfig.Rootless = cfg.Rootless
	serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort
	for _, san := range knownIPs(cfg.TLSSan) {
		addr := net2.ParseIP(san)
		if addr != nil {
			serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, san)
		} else {
			serverConfig.TLSConfig.Domains = append(serverConfig.TLSConfig.Domains, san)
		}
	}
	serverConfig.TLSConfig.BindAddress = cfg.BindAddress
	serverConfig.ControlConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs
	serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs
	serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs
	serverConfig.ControlConfig.ClusterDomain = cfg.ClusterDomain
	serverConfig.ControlConfig.Storage.Endpoint = cfg.StorageEndpoint
	serverConfig.ControlConfig.Storage.CAFile = cfg.StorageCAFile
	serverConfig.ControlConfig.Storage.CertFile = cfg.StorageCertFile
	serverConfig.ControlConfig.Storage.KeyFile = cfg.StorageKeyFile
	serverConfig.ControlConfig.AdvertiseIP = cfg.AdvertiseIP
	serverConfig.ControlConfig.AdvertisePort = cfg.AdvertisePort
	serverConfig.ControlConfig.BootstrapReadOnly = !cfg.StoreBootstrap
	serverConfig.ControlConfig.FlannelBackend = cfg.FlannelBackend
	serverConfig.ControlConfig.ExtraCloudControllerArgs = cfg.ExtraCloudControllerArgs
	serverConfig.ControlConfig.DisableCCM = cfg.DisableCCM
	serverConfig.ControlConfig.DisableNPC = cfg.DisableNPC

	if cmds.AgentConfig.FlannelIface != "" && cmds.AgentConfig.NodeIP == "" {
		cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface)
	}

	if serverConfig.ControlConfig.AdvertiseIP == "" && cmds.AgentConfig.NodeIP != "" {
		serverConfig.ControlConfig.AdvertiseIP = cmds.AgentConfig.NodeIP
	}
	if serverConfig.ControlConfig.AdvertiseIP != "" {
		serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, serverConfig.ControlConfig.AdvertiseIP)
	}

	_, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ClusterCIDR, err)
	}
	_, serverConfig.ControlConfig.ServiceIPRange, err = net2.ParseCIDR(cfg.ServiceCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err)
	}

	_, apiServerServiceIP, err := master.DefaultServiceIPRange(*serverConfig.ControlConfig.ServiceIPRange)
	if err != nil {
		return err
	}
	serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, apiServerServiceIP.String())

	// If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10,
	// i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10
	if cfg.ClusterDNS == "" {
		serverConfig.ControlConfig.ClusterDNS = make(net2.IP, 4)
		copy(serverConfig.ControlConfig.ClusterDNS, serverConfig.ControlConfig.ServiceIPRange.IP.To4())
		serverConfig.ControlConfig.ClusterDNS[3] = 10
	} else {
		serverConfig.ControlConfig.ClusterDNS = net2.ParseIP(cfg.ClusterDNS)
	}

	if cfg.DefaultLocalStoragePath == "" {
		dataDir, err := datadir.LocalHome(cfg.DataDir, false)
		if err != nil {
			return err
		}
		serverConfig.ControlConfig.DefaultLocalStoragePath = filepath.Join(dataDir, "/storage")
	} else {
		serverConfig.ControlConfig.DefaultLocalStoragePath = cfg.DefaultLocalStoragePath
	}

	noDeploys := make([]string, 0)
	for _, noDeploy := range app.StringSlice("no-deploy") {
		for _, splitNoDeploy := range strings.Split(noDeploy, ",") {
			noDeploys = append(noDeploys, splitNoDeploy)
		}
	}

	for _, noDeploy := range noDeploys {
		if noDeploy == "servicelb" {
			serverConfig.DisableServiceLB = true
			continue
		}

		if !strings.HasSuffix(noDeploy, ".yaml") {
			noDeploy = noDeploy + ".yaml"
		}
		serverConfig.ControlConfig.Skips = append(serverConfig.ControlConfig.Skips, noDeploy)
	}

	logrus.Info("Starting k3s ", app.App.Version)
	notifySocket := os.Getenv("NOTIFY_SOCKET")
	os.Unsetenv("NOTIFY_SOCKET")

	ctx := signals.SetupSignalHandler(context.Background())
	certs, err := server.StartServer(ctx, &serverConfig)
	if err != nil {
		return err
	}

	logrus.Info("k3s is up and running")
	if notifySocket != "" {
		os.Setenv("NOTIFY_SOCKET", notifySocket)
		systemd.SdNotify(true, "READY=1\n")
	}

	if cfg.DisableAgent {
		<-ctx.Done()
		return nil
	}
	ip := serverConfig.TLSConfig.BindAddress
	if ip == "" {
		ip = "127.0.0.1"
	}
	url := fmt.Sprintf("https://%s:%d", ip, serverConfig.TLSConfig.HTTPSPort)
	token := server.FormatToken(serverConfig.ControlConfig.Runtime.NodeToken, certs)

	agentConfig := cmds.AgentConfig
	agentConfig.Debug = app.GlobalBool("bool")
	agentConfig.DataDir = filepath.Dir(serverConfig.ControlConfig.DataDir)
	agentConfig.ServerURL = url
	agentConfig.Token = token
	agentConfig.DisableLoadBalancer = true
	agentConfig.Rootless = cfg.Rootless
	if agentConfig.Rootless {
		// let agent specify Rootless kubelet flags, but not unshare twice
		agentConfig.RootlessAlreadyUnshared = true
	}

	return agent.Run(ctx, agentConfig)
}

func knownIPs(ips []string) []string {
	ips = append(ips, "127.0.0.1")
	ip, err := net.ChooseHostInterface()
	if err == nil {
		ips = append(ips, ip.String())
	}
	return ips
}
<|endoftext|>


<commit_before>package util_test

import (
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/runtime/schema"
	kapi "k8s.io/kubernetes/pkg/api"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

	_ "github.com/openshift/origin/pkg/api/install"
	"github.com/openshift/origin/pkg/cmd/util"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)

func TestResolveResource(t *testing.T) {
	mapper := clientcmd.ShortcutExpander{RESTMapper: kcmdutil.ShortcutExpander{RESTMapper: kapi.Registry.RESTMapper()}}

	tests := []struct {
		name             string
		defaultResource  schema.GroupResource
		resourceString   string
		expectedResource schema.GroupResource
		expectedName     string
		expectedErr      bool
	}{
		{
			name:             "invalid case #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "a/b/c",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "invalid case #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "foo/bar",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "empty resource string case #1",
			defaultResource:  schema.GroupResource{Resource: ""},
			resourceString:   "",
			expectedResource: schema.GroupResource{Resource: ""},
			expectedName:     "",
			expectedErr:      false,
		},
		{
			name:             "empty resource string case #2",
			defaultResource:  schema.GroupResource{Resource: ""},
			resourceString:   "bar",
			expectedResource: schema.GroupResource{Resource: ""},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "empty resource string case #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "bar",
			expectedResource: schema.GroupResource{Resource: "foo"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) short name",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "rc/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "replicationcontroller/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "replicationcontrollers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #4",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #5",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/Bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "Bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) short name",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "bc/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "buildconfig/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "buildconfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #4",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #5",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/Bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "Bar",
			expectedErr:      false,
		},

		{
			name:             "singular, implicit api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job/myjob",
			expectedResource: schema.GroupResource{Group: "extensions", Resource: "jobs"},
			expectedName:     "myjob",
			expectedErr:      false,
		},
		{
			name:             "singular, explicit extensions api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job.extensions/myjob",
			expectedResource: schema.GroupResource{Group: "extensions", Resource: "jobs"},
			expectedName:     "myjob",
			expectedErr:      false,
		},
		{
			name:             "singular, explicit batch api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job.batch/myjob",
			expectedResource: schema.GroupResource{Group: "batch", Resource: "jobs"},
			expectedName:     "myjob",
			expectedErr:      false,
		},
		{
			name:             "shortname, implicit api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa/myhpa",
			expectedResource: schema.GroupResource{Group: "extensions", Resource: "horizontalpodautoscalers"},
			expectedName:     "myhpa",
			expectedErr:      false,
		},
		{
			name:             "shortname, explicit extensions api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa.extensions/myhpa",
			expectedResource: schema.GroupResource{Group: "extensions", Resource: "horizontalpodautoscalers"},
			expectedName:     "myhpa",
			expectedErr:      false,
		},
		{
			name:             "shortname, explicit autoscaling api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa.autoscaling/myhpa",
			expectedResource: schema.GroupResource{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
			expectedName:     "myhpa",
			expectedErr:      false,
		},
	}

	for _, test := range tests {
		gotResource, gotName, gotErr := util.ResolveResource(test.defaultResource, test.resourceString, mapper)
		if gotErr != nil && !test.expectedErr {
			t.Errorf("%s: expected no error, got %v", test.name, gotErr)
			continue
		}
		if gotErr == nil && test.expectedErr {
			t.Errorf("%s: expected error but got none", test.name)
			continue
		}
		if !reflect.DeepEqual(gotResource, test.expectedResource) {
			t.Errorf("%s: expected resource type %#v, got %#v", test.name, test.expectedResource, gotResource)
			continue
		}
		if gotName != test.expectedName {
			t.Errorf("%s: expected resource name %s, got %s", test.name, test.expectedName, gotName)
			continue
		}
	}
}
<commit_msg>adapt: pkg/cmd/util new upstream batch and hpa apigroups<commit_after>package util_test

import (
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes/fake"
	kapi "k8s.io/kubernetes/pkg/api"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

	_ "github.com/openshift/origin/pkg/api/install"
	"github.com/openshift/origin/pkg/cmd/util"
)

func TestResolveResource(t *testing.T) {
	dc := fake.NewSimpleClientset().Discovery()
	mapper, err := kcmdutil.NewShortcutExpander(kapi.Registry.RESTMapper(), dc)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	tests := []struct {
		name             string
		defaultResource  schema.GroupResource
		resourceString   string
		expectedResource schema.GroupResource
		expectedName     string
		expectedErr      bool
	}{
		{
			name:             "invalid case #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "a/b/c",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "invalid case #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "foo/bar",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "empty resource string case #1",
			defaultResource:  schema.GroupResource{Resource: ""},
			resourceString:   "",
			expectedResource: schema.GroupResource{Resource: ""},
			expectedName:     "",
			expectedErr:      false,
		},
		{
			name:             "empty resource string case #2",
			defaultResource:  schema.GroupResource{Resource: ""},
			resourceString:   "bar",
			expectedResource: schema.GroupResource{Resource: ""},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "empty resource string case #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "bar",
			expectedResource: schema.GroupResource{Resource: "foo"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) short name",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "rc/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "replicationcontroller/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "replicationcontrollers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #4",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(KUBE) long name, case insensitive #5",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "ReplicationControllers/Bar",
			expectedResource: schema.GroupResource{Resource: "replicationcontrollers"},
			expectedName:     "Bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) short name",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "bc/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #1",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "buildconfig/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #2",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "buildconfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #3",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #4",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "bar",
			expectedErr:      false,
		},
		{
			name:             "(ORIGIN) long name, case insensitive #5",
			defaultResource:  schema.GroupResource{Resource: "foo"},
			resourceString:   "BuildConfigs/Bar",
			expectedResource: schema.GroupResource{Resource: "buildconfigs"},
			expectedName:     "Bar",
			expectedErr:      false,
		},

		{
			name:             "singular, implicit api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job/myjob",
			expectedResource: schema.GroupResource{Group: "batch", Resource: "jobs"},
			expectedName:     "myjob",
			expectedErr:      false,
		},
		{
			name:             "singular, explicit extensions api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job.extensions/myjob",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "singular, explicit batch api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "job.batch/myjob",
			expectedResource: schema.GroupResource{Group: "batch", Resource: "jobs"},
			expectedName:     "myjob",
			expectedErr:      false,
		},
		{
			name:             "shortname, implicit api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa/myhpa",
			expectedResource: schema.GroupResource{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
			expectedName:     "myhpa",
			expectedErr:      false,
		},
		{
			name:             "shortname, explicit extensions api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa.extensions/myhpa",
			expectedResource: schema.GroupResource{},
			expectedName:     "",
			expectedErr:      true,
		},
		{
			name:             "shortname, explicit autoscaling api group",
			defaultResource:  schema.GroupResource{},
			resourceString:   "hpa.autoscaling/myhpa",
			expectedResource: schema.GroupResource{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
			expectedName:     "myhpa",
			expectedErr:      false,
		},
	}

	for _, test := range tests {
		gotResource, gotName, gotErr := util.ResolveResource(test.defaultResource, test.resourceString, mapper)
		if gotErr != nil && !test.expectedErr {
			t.Errorf("%s: expected no error, got %v", test.name, gotErr)
			continue
		}
		if gotErr == nil && test.expectedErr {
			t.Errorf("%s: expected error but got none", test.name)
			continue
		}
		if !reflect.DeepEqual(gotResource, test.expectedResource) {
			t.Errorf("%s: expected resource type %#v, got %#v", test.name, test.expectedResource, gotResource)
			continue
		}
		if gotName != test.expectedName {
			t.Errorf("%s: expected resource name %s, got %s", test.name, test.expectedName, gotName)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package v1

import (
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/miekg/dns"
)

// Server implements DNS server with dns.Handler
type Server struct {
	ListenAddress  string
	ListenProtocol []string

	mux *dns.ServeMux
	wg  *sync.WaitGroup
}

// Run server
func (s *Server) Run() {
	log.Infof("Starting service at %s on ", s.ListenAddress, s.ListenProtocol)

	s.mux = dns.NewServeMux()
	log.Debugln("Setting DNS server handle")
	s.mux.Handle(".", s.mux)

	s.wg = new(sync.WaitGroup)
	// Add wait group
	s.wg.Add(len(s.ListenProtocol))

	for _, proto := range s.ListenProtocol {
		log.Infof("Ready to serve at [%s]%s", proto, s.ListenAddress)
		go func(network string) {
			if err := dns.ListenAndServe(s.ListenAddress, network, s.mux); err != nil {
				log.Panicf("Serve at [%s]%s error: %v", network, s.ListenAddress, err)
			}
		}(proto)
	}
	s.wg.Wait()
}

// ServeDNS implements dns.Handler interface
func (s *Server) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {

}
<commit_msg>ADD: close func for server<commit_after>package v1

import (
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/miekg/dns"
)

// Server implements DNS server with dns.Handler
type Server struct {
	ListenAddress  string
	ListenProtocol []string

	mux *dns.ServeMux
	wg  *sync.WaitGroup
}

// Run server
func (s *Server) Run() {
	log.Infof("Starting service at %s on ", s.ListenAddress, s.ListenProtocol)

	s.mux = dns.NewServeMux()
	log.Debugln("Setting DNS server handle")
	s.mux.Handle(".", s.mux)

	s.wg = new(sync.WaitGroup)
	// Add wait group
	s.wg.Add(len(s.ListenProtocol))

	for _, proto := range s.ListenProtocol {
		log.Infof("Ready to serve at [%s]%s", proto, s.ListenAddress)
		go func(network string) {
			if err := dns.ListenAndServe(s.ListenAddress, network, s.mux); err != nil {
				log.Panicf("Serve at [%s]%s error: %v", network, s.ListenAddress, err)
			}
		}(proto)
	}
	s.wg.Wait()
}

// ServeDNS implements dns.Handler interface
func (s *Server) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {

}

// Stop the server graceful
func (s *Server) Stop() {
	// @TODO should have a graceful way to close dns server and clean up
	log.Infoln("Graceful shutdown")
}
<|endoftext|>


<commit_before>package acme

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"fmt"

	"github.com/golang/glog"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/util/errors"
	"github.com/jetstack/cert-manager/pkg/util/kube"
	"github.com/jetstack/cert-manager/pkg/util/pki"
)

const (
	errorIssueCert = "ErrIssueCert"

	successCertIssued = "CertIssueSuccess"

	messageErrorIssueCert = "Error issuing TLS certificate: "

	messageCertIssued = "Certificate issued successfully"
)

func (a *Acme) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []byte, error) {
	commonName := crt.Spec.CommonName
	altNames := crt.Spec.DNSNames
	if len(commonName) == 0 && len(altNames) == 0 {
		return nil, nil, fmt.Errorf("no domains specified on certificate")
	}

	cl, err := a.acmeClient()
	if err != nil {
		return nil, nil, fmt.Errorf("error creating ACME client: %s", err.Error())
	}

	orderURL := crt.Status.ACMEStatus().Order.URL
	if orderURL == "" {
		return nil, nil, fmt.Errorf("certificate order url cannot be blank")
	}
	order, err := cl.GetOrder(ctx, orderURL)
	if err != nil {
		return nil, nil, fmt.Errorf("error getting order details: %v", err)
	}

	// get existing certificate private key
	key, err := kube.SecretTLSKey(a.secretsLister, crt.Namespace, crt.Spec.SecretName)
	if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) {
		key, err = pki.GenerateRSAPrivateKey(2048)
		if err != nil {
			return nil, nil, fmt.Errorf("error generating private key: %s", err.Error())
		}
	}
	if err != nil {
		return nil, nil, fmt.Errorf("error getting certificate private key: %s", err.Error())
	}

	// generate a csr
	template := pki.GenerateCSR(commonName, altNames...)
	csr, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		return nil, nil, fmt.Errorf("error creating certificate request: %s", err)
	}

	// obtain a certificate from the acme server
	certSlice, err := cl.FinalizeOrder(ctx, order.FinalizeURL, csr)
	if err != nil {
		return nil, nil, fmt.Errorf("error getting certificate for acme server: %s", err)
	}

	// encode the retrieved certificate
	certBuffer := bytes.NewBuffer([]byte{})
	for _, cert := range certSlice {
		pem.Encode(certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
	}

	glog.V(2).Infof("successfully got certificate: cn=%q altNames=%+v url=%q", commonName, altNames, orderURL)
	// encode the private key and return
	return pki.EncodePKCS1PrivateKey(key), certBuffer.Bytes(), nil
}

func (a *Acme) Issue(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []byte, error) {
	key, cert, err := a.obtainCertificate(ctx, crt)
	if err != nil {
		s := messageErrorIssueCert + err.Error()
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueCert, s)
		return nil, nil, err
	}

	crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertIssued, messageCertIssued)

	return key, cert, err
}
<commit_msg>Fix ACME CSR generation<commit_after>package acme

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"fmt"

	"github.com/golang/glog"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/util/errors"
	"github.com/jetstack/cert-manager/pkg/util/kube"
	"github.com/jetstack/cert-manager/pkg/util/pki"
)

const (
	errorIssueCert = "ErrIssueCert"

	successCertIssued = "CertIssueSuccess"

	messageErrorIssueCert = "Error issuing TLS certificate: "

	messageCertIssued = "Certificate issued successfully"
)

func (a *Acme) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []byte, error) {
	commonName, err := pki.CommonNameForCertificate(crt)
	if err != nil {
		return nil, nil, err
	}
	altNames, err := pki.DNSNamesForCertificate(crt)
	if err != nil {
		return nil, nil, err
	}

	cl, err := a.acmeClient()
	if err != nil {
		return nil, nil, fmt.Errorf("error creating ACME client: %s", err.Error())
	}

	orderURL := crt.Status.ACMEStatus().Order.URL
	if orderURL == "" {
		return nil, nil, fmt.Errorf("certificate order url cannot be blank")
	}
	order, err := cl.GetOrder(ctx, orderURL)
	if err != nil {
		return nil, nil, fmt.Errorf("error getting order details: %v", err)
	}

	// get existing certificate private key
	key, err := kube.SecretTLSKey(a.secretsLister, crt.Namespace, crt.Spec.SecretName)
	if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) {
		key, err = pki.GenerateRSAPrivateKey(2048)
		if err != nil {
			return nil, nil, fmt.Errorf("error generating private key: %s", err.Error())
		}
	}
	if err != nil {
		return nil, nil, fmt.Errorf("error getting certificate private key: %s", err.Error())
	}

	// generate a csr
	template := pki.GenerateCSR(commonName, altNames...)
	csr, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		return nil, nil, fmt.Errorf("error creating certificate request: %s", err)
	}

	// obtain a certificate from the acme server
	certSlice, err := cl.FinalizeOrder(ctx, order.FinalizeURL, csr)
	if err != nil {
		return nil, nil, fmt.Errorf("error getting certificate for acme server: %s", err)
	}

	// encode the retrieved certificate
	certBuffer := bytes.NewBuffer([]byte{})
	for _, cert := range certSlice {
		pem.Encode(certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
	}

	glog.V(2).Infof("successfully got certificate: cn=%q altNames=%+v url=%q", commonName, altNames, orderURL)
	// encode the private key and return
	return pki.EncodePKCS1PrivateKey(key), certBuffer.Bytes(), nil
}

func (a *Acme) Issue(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []byte, error) {
	key, cert, err := a.obtainCertificate(ctx, crt)
	if err != nil {
		s := messageErrorIssueCert + err.Error()
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueCert, s)
		return nil, nil, err
	}

	crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertIssued, messageCertIssued)

	return key, cert, err
}
<|endoftext|>


<commit_before>// Copyright 2019 Authors of Cilium
//
// 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 k8s

import (
	"strings"
	"time"

	"github.com/cilium/cilium/pkg/lock"
)

var (

	// k8sErrMsgMU guards additions and removals to k8sErrMsg, which stores a
	// time after which a repeat error message can be printed
	k8sErrMsgMU      lock.Mutex
	k8sErrMsg        = map[string]time.Time{}
	k8sErrLogTimeout = time.Minute
)

// k8sErrorUpdateCheckUnmuteTime returns a boolean indicating whether we should
// log errmsg or not. It manages once-per-k8sErrLogTimeout entry in k8sErrMsg.
// When errmsg is new or more than k8sErrLogTimeout has passed since the last
// invocation that returned true, it returns true.
func k8sErrorUpdateCheckUnmuteTime(errstr string, now time.Time) bool {
	k8sErrMsgMU.Lock()
	defer k8sErrMsgMU.Unlock()

	if unmuteDeadline, ok := k8sErrMsg[errstr]; !ok || now.After(unmuteDeadline) {
		k8sErrMsg[errstr] = now.Add(k8sErrLogTimeout)
		return true
	}

	return false
}

// K8sErrorHandler handles the error messages in a non verbose way by omitting
// repeated instances of the same error message for a timeout defined with
// k8sErrLogTimeout.
func K8sErrorHandler(e error) {
	if e == nil {
		return
	}

	// We rate-limit certain categories of error message. These are matched
	// below, with a default behaviour to print everything else without
	// rate-limiting.
	// Note: We also have side-effects in some of the special cases.
	now := time.Now()
	errstr := e.Error()
	switch {
	// This can occur when cilium comes up before the k8s API server, and keeps
	// trying to connect.
	case strings.Contains(errstr, "connection refused"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("k8sError")
		}

	// k8s does not allow us to watch both ThirdPartyResource and
	// CustomResourceDefinition. This would occur when a user mixes these within
	// the k8s cluster, and might occur when upgrading from versions of cilium
	// that used ThirdPartyResource to define CiliumNetworkPolicy.
	case strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: the server could not find the requested resource"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("Conflicting TPR and CRD resources")
			log.Warn("Detected conflicting TPR and CRD, please migrate all ThirdPartyResource to CustomResourceDefinition! More info: https://cilium.link/migrate-tpr")
			log.Warn("Due to conflicting TPR and CRD rules, CiliumNetworkPolicy enforcement can't be guaranteed!")
		}

	// fromCIDR and toCIDR used to expect an "ip" subfield (so, they were a YAML
	// map with one field) but common usage and expectation would simply list the
	// CIDR ranges and IPs desired as a YAML list. In these cases we would see
	// this decode error. We have since changed the definition to be a simple
	// list of strings.
	case strings.Contains(errstr, "Unable to decode an event from the watch stream: unable to decode watch event"),
		strings.Contains(errstr, "Failed to list *v1.CiliumNetworkPolicy: only encoded map or array can be decoded into a struct"),
		strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: only encoded map or array can be decoded into a struct"),
		strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: v2.CiliumNetworkPolicyList:"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("Unable to decode k8s watch event")
		}

	default:
		log.WithError(e).Error("k8sError")
	}
}
<commit_msg>pkg/k8s: remove TPR vs CRD error<commit_after>// Copyright 2019 Authors of Cilium
//
// 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 k8s

import (
	"strings"
	"time"

	"github.com/cilium/cilium/pkg/lock"
)

var (

	// k8sErrMsgMU guards additions and removals to k8sErrMsg, which stores a
	// time after which a repeat error message can be printed
	k8sErrMsgMU      lock.Mutex
	k8sErrMsg        = map[string]time.Time{}
	k8sErrLogTimeout = time.Minute
)

// k8sErrorUpdateCheckUnmuteTime returns a boolean indicating whether we should
// log errmsg or not. It manages once-per-k8sErrLogTimeout entry in k8sErrMsg.
// When errmsg is new or more than k8sErrLogTimeout has passed since the last
// invocation that returned true, it returns true.
func k8sErrorUpdateCheckUnmuteTime(errstr string, now time.Time) bool {
	k8sErrMsgMU.Lock()
	defer k8sErrMsgMU.Unlock()

	if unmuteDeadline, ok := k8sErrMsg[errstr]; !ok || now.After(unmuteDeadline) {
		k8sErrMsg[errstr] = now.Add(k8sErrLogTimeout)
		return true
	}

	return false
}

// K8sErrorHandler handles the error messages in a non verbose way by omitting
// repeated instances of the same error message for a timeout defined with
// k8sErrLogTimeout.
func K8sErrorHandler(e error) {
	if e == nil {
		return
	}

	// We rate-limit certain categories of error message. These are matched
	// below, with a default behaviour to print everything else without
	// rate-limiting.
	// Note: We also have side-effects in some of the special cases.
	now := time.Now()
	errstr := e.Error()
	switch {
	// This can occur when cilium comes up before the k8s API server, and keeps
	// trying to connect.
	case strings.Contains(errstr, "connection refused"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("k8sError")
		}

	// k8s does not allow us to watch both ThirdPartyResource and
	// CustomResourceDefinition. This would occur when a user mixes these within
	// the k8s cluster, and might occur when upgrading from versions of cilium
	// that used ThirdPartyResource to define CiliumNetworkPolicy.
	case strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: the server could not find the requested resource"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("No Cilium Network Policy CRD defined in the cluster, please set `--skip-crd-creation=false` to avoid seeing this error.")
		}

	// fromCIDR and toCIDR used to expect an "ip" subfield (so, they were a YAML
	// map with one field) but common usage and expectation would simply list the
	// CIDR ranges and IPs desired as a YAML list. In these cases we would see
	// this decode error. We have since changed the definition to be a simple
	// list of strings.
	case strings.Contains(errstr, "Unable to decode an event from the watch stream: unable to decode watch event"),
		strings.Contains(errstr, "Failed to list *v1.CiliumNetworkPolicy: only encoded map or array can be decoded into a struct"),
		strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: only encoded map or array can be decoded into a struct"),
		strings.Contains(errstr, "Failed to list *v2.CiliumNetworkPolicy: v2.CiliumNetworkPolicyList:"):
		if k8sErrorUpdateCheckUnmuteTime(errstr, now) {
			log.WithError(e).Error("Unable to decode k8s watch event")
		}

	default:
		log.WithError(e).Error("k8sError")
	}
}
<|endoftext|>