WriteWrapped

// WriteWrapped writes a string with the indentation set for the
// Formatter, and wraps as needed.
func (f *Formatter) WriteWrapped(str string, a ...interface{}) error {
	if f.Wrap == 0 {
		return f.Write(str, a...)
	}
	text := fmt.Sprintf(str, a...)
	strs := wrapString(text, f.Wrap-f.IndentLevel)
	for _, substr := range strs {
		if err := f.Write(substr); err != nil {
			return err
		}
	}
	return nil
}


String

func (l *line) String() string {
	return strings.Join(l.words, " ")
}


Empty

func (l *line) Empty() bool {
	return len(l.words) == 0
}


Len

func (l *line) Len() int {
	return len(l.String())
}


Add

// Add adds the word to the line, returns true if we could, false if we
// didn't have enough room. It's always possible to add to an empty line.
func (l *line) Add(word string) bool {
	newLine := line{
		wrap:  l.wrap,
		words: append(l.words, word),
	}
	if newLine.Len() <= l.wrap || len(l.words) == 0 {
		l.words = newLine.words
		return true
	}
	return false
}


shouldStartNewLine

func shouldStartNewLine(lastWord, str string) bool {
	// preserve line breaks ending in :
	if strings.HasSuffix(lastWord, ":") {
		return true
	}

	// preserve code blocks
	if strings.HasPrefix(str, "    ") {
		return true
	}
	str = strings.TrimSpace(str)
	// preserve empty lines
	if len(str) == 0 {
		return true
	}
	// preserve lines that look like they're starting lists
	if bullet.MatchString(str) {
		return true
	}
	// otherwise combine
	return false
}


wrapString

func wrapString(str string, wrap int) []string {
	wrapped := []string{}
	l := line{wrap: wrap}
	// track the last word added to the current line
	lastWord := ""
	flush := func() {
		if !l.Empty() {
			lastWord = ""
			wrapped = append(wrapped, l.String())
			l = line{wrap: wrap}
		}
	}

	// iterate over the lines in the original description
	for _, str := range strings.Split(str, "\n") {
		// preserve code blocks and blockquotes as-is
		if strings.HasPrefix(str, "    ") {
			flush()
			wrapped = append(wrapped, str)
			continue
		}

		// preserve empty lines after the first line, since they can separate logical sections
		if len(wrapped) > 0 && len(strings.TrimSpace(str)) == 0 {
			flush()
			wrapped = append(wrapped, "")
			continue
		}

		// flush if we should start a new line
		if shouldStartNewLine(lastWord, str) {
			flush()
		}
		words := strings.Fields(str)
		for _, word := range words {
			lastWord = word
			if !l.Add(word) {
				flush()
				if !l.Add(word) {
					panic("Couldn't add to empty line.")
				}
			}
		}
	}
	flush()
	return wrapped
}


PrintKindAndVersion

func (m *modelPrinter) PrintKindAndVersion() error {
	if err := m.Writer.Write("KIND:     %s", m.GVK.Kind); err != nil {
		return err
	}
	return m.Writer.Write("VERSION:  %s\n", m.GVK.GroupVersion())
}


PrintDescription

// PrintDescription prints the description for a given schema. There
// might be multiple description, since we collect descriptions when we
// go through references, arrays and maps.
func (m *modelPrinter) PrintDescription(schema proto.Schema) error {
	if err := m.Writer.Write("DESCRIPTION:"); err != nil {
		return err
	}
	empty := true
	for i, desc := range append(m.Descriptions, schema.GetDescription()) {
		if desc == "" {
			continue
		}
		empty = false
		if i != 0 {
			if err := m.Writer.Write(""); err != nil {
				return err
			}
		}
		if err := m.Writer.Indent(descriptionIndentLevel).WriteWrapped("%s", desc); err != nil {
			return err
		}
	}
	if empty {
		return m.Writer.Indent(descriptionIndentLevel).WriteWrapped("<empty>")
	}
	return nil
}


VisitArray

// VisitArray recurses inside the subtype, while collecting the type if
// not done yet, and the description.
func (m *modelPrinter) VisitArray(a *proto.Array) {
	m.Descriptions = append(m.Descriptions, a.GetDescription())
	if m.Type == "" {
		m.Type = GetTypeName(a)
	}
	a.SubType.Accept(m)
}


VisitKind

// VisitKind prints a full resource with its fields.
func (m *modelPrinter) VisitKind(k *proto.Kind) {
	if err := m.PrintKindAndVersion(); err != nil {
		m.Error = err
		return
	}

	if m.Type == "" {
		m.Type = GetTypeName(k)
	}
	if m.Name != "" {
		m.Writer.Write("RESOURCE: %s <%s>\n", m.Name, m.Type)
	}

	if err := m.PrintDescription(k); err != nil {
		m.Error = err
		return
	}
	if err := m.Writer.Write("\nFIELDS:"); err != nil {
		m.Error = err
		return
	}
	m.Error = m.Builder.BuildFieldsPrinter(m.Writer.Indent(fieldIndentLevel)).PrintFields(k)
}


VisitMap

// VisitMap recurses inside the subtype, while collecting the type if
// not done yet, and the description.
func (m *modelPrinter) VisitMap(om *proto.Map) {
	m.Descriptions = append(m.Descriptions, om.GetDescription())
	if m.Type == "" {
		m.Type = GetTypeName(om)
	}
	om.SubType.Accept(m)
}


VisitPrimitive

// VisitPrimitive prints a field type and its description.
func (m *modelPrinter) VisitPrimitive(p *proto.Primitive) {
	if err := m.PrintKindAndVersion(); err != nil {
		m.Error = err
		return
	}

	if m.Type == "" {
		m.Type = GetTypeName(p)
	}
	if err := m.Writer.Write("FIELD:    %s <%s>\n", m.Name, m.Type); err != nil {
		m.Error = err
		return
	}
	m.Error = m.PrintDescription(p)
}


VisitArbitrary

func (m *modelPrinter) VisitArbitrary(a *proto.Arbitrary) {
	if err := m.PrintKindAndVersion(); err != nil {
		m.Error = err
		return
	}

	m.Error = m.PrintDescription(a)
}


VisitReference

// VisitReference recurses inside the subtype, while collecting the description.
func (m *modelPrinter) VisitReference(r proto.Reference) {
	m.Descriptions = append(m.Descriptions, r.GetDescription())
	r.SubSchema().Accept(m)
}


PrintModel

// PrintModel prints the description of a schema in writer.
func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema proto.Schema, gvk schema.GroupVersionKind) error {
	m := &modelPrinter{Name: name, Writer: writer, Builder: builder, GVK: gvk}
	schema.Accept(m)
	return m.Error
}


VisitArray

// VisitArray is just a passthrough.
func (f *recursiveFieldsPrinter) VisitArray(a *proto.Array) {
	a.SubType.Accept(f)
}


VisitKind

// VisitKind prints all its fields with their type, and then recurses
// inside each of these (pre-order).
func (f *recursiveFieldsPrinter) VisitKind(k *proto.Kind) {
	for _, key := range k.Keys() {
		v := k.Fields[key]
		f.Writer.Write("%s\t<%s>", key, GetTypeName(v))
		subFields := &recursiveFieldsPrinter{
			Writer: f.Writer.Indent(indentPerLevel),
		}
		if err := subFields.PrintFields(v); err != nil {
			f.Error = err
			return
		}
	}
}


VisitMap

// VisitMap is just a passthrough.
func (f *recursiveFieldsPrinter) VisitMap(m *proto.Map) {
	m.SubType.Accept(f)
}


VisitPrimitive

// VisitPrimitive does nothing, since it doesn't have sub-fields.
func (f *recursiveFieldsPrinter) VisitPrimitive(p *proto.Primitive) {
	// Nothing to do.
}


VisitReference

// VisitReference is just a passthrough.
func (f *recursiveFieldsPrinter) VisitReference(r proto.Reference) {
	if _, ok := visitedReferences[r.Reference()]; ok {
		return
	}
	visitedReferences[r.Reference()] = struct{}{}
	r.SubSchema().Accept(f)
	delete(visitedReferences, r.Reference())
}


PrintFields

// PrintFields will recursively print all the fields for the given
// schema.
func (f *recursiveFieldsPrinter) PrintFields(schema proto.Schema) error {
	schema.Accept(f)
	return f.Error
}


VisitArray

// VisitArray adds the [] prefix and recurses.
func (t *typeName) VisitArray(a *proto.Array) {
	s := &typeName{}
	a.SubType.Accept(s)
	t.Name = fmt.Sprintf("[]%s", s.Name)
}


VisitKind

// VisitKind just returns "Object".
func (t *typeName) VisitKind(k *proto.Kind) {
	t.Name = "Object"
}


VisitMap

// VisitMap adds the map[string] prefix and recurses.
func (t *typeName) VisitMap(m *proto.Map) {
	s := &typeName{}
	m.SubType.Accept(s)
	t.Name = fmt.Sprintf("map[string]%s", s.Name)
}


VisitPrimitive

// VisitPrimitive returns the name of the primitive.
func (t *typeName) VisitPrimitive(p *proto.Primitive) {
	t.Name = p.Type
}


VisitReference

// VisitReference is just a passthrough.
func (t *typeName) VisitReference(r proto.Reference) {
	r.SubSchema().Accept(t)
}


GetTypeName

// GetTypeName returns the type of a schema.
func GetTypeName(schema proto.Schema) string {
	t := &typeName{}
	schema.Accept(t)
	return t.Name
}


PrintModelDescription

// PrintModelDescription prints the description of a specific model or dot path.
// If recursive, all components nested within the fields of the schema will be
// printed.
func PrintModelDescription(
	fieldsPath []string,
	w io.Writer,
	client openapi.Client,
	gvr schema.GroupVersionResource,
	recursive bool,
	outputFormat string,
) error {
	generator := NewGenerator()
	if err := registerBuiltinTemplates(generator); err != nil {
		return fmt.Errorf("error parsing builtin templates. Please file a bug on GitHub: %w", err)
	}

	return printModelDescriptionWithGenerator(
		generator, fieldsPath, w, client, gvr, recursive, outputFormat)
}


printModelDescriptionWithGenerator

// Factored out for testability
func printModelDescriptionWithGenerator(
	generator Generator,
	fieldsPath []string,
	w io.Writer,
	client openapi.Client,
	gvr schema.GroupVersionResource,
	recursive bool,
	outputFormat string,
) error {
	paths, err := client.Paths()

	if err != nil {
		return fmt.Errorf("failed to fetch list of groupVersions: %w", err)
	}

	var resourcePath string
	if len(gvr.Group) == 0 {
		resourcePath = fmt.Sprintf("api/%s", gvr.Version)
	} else {
		resourcePath = fmt.Sprintf("apis/%s/%s", gvr.Group, gvr.Version)
	}

	gv, exists := paths[resourcePath]

	if !exists {
		return fmt.Errorf("couldn't find resource for \"%v\"", gvr)
	}

	openAPISchemaBytes, err := gv.Schema(runtime.ContentTypeJSON)
	if err != nil {
		return fmt.Errorf("failed to fetch openapi schema for %s: %w", resourcePath, err)
	}

	var parsedV3Schema map[string]interface{}
	if err := json.Unmarshal(openAPISchemaBytes, &parsedV3Schema); err != nil {
		return fmt.Errorf("failed to parse openapi schema for %s: %w", resourcePath, err)
	}

	err = generator.Render(outputFormat, parsedV3Schema, gvr, fieldsPath, recursive, w)

	explainErr := explainError("")
	if errors.As(err, &explainErr) {
		return explainErr
	}

	return err
}


Error

func (e explainError) Error() string {
	return string(e)
}


WithBuiltinTemplateFuncs

func WithBuiltinTemplateFuncs(tmpl *template.Template) *template.Template {
	return tmpl.Funcs(map[string]interface{}{
		"throw": func(e string, args ...any) (string, error) {
			errString := fmt.Sprintf(e, args...)
			return "", explainError(errString)
		},
		"toJson": func(obj any) (string, error) {
			res, err := json.Marshal(obj)
			return string(res), err
		},
		"toPrettyJson": func(obj any) (string, error) {
			res, err := json.MarshalIndent(obj, "", "    ")
			if err != nil {
				return "", err
			}
			return string(res), err
		},
		"fail": func(message string) (string, error) {
			return "", errors.New(message)
		},
		"wrap": func(l int, s string) (string, error) {
			buf := bytes.NewBuffer(nil)
			writer := term.NewWordWrapWriter(buf, uint(l))
			_, err := writer.Write([]byte(s))
			if err != nil {
				return "", err
			}
			return buf.String(), nil
		},
		"split": func(s string, sep string) []string {
			return strings.Split(s, sep)
		},
		"join": func(sep string, strs ...string) string {
			return strings.Join(strs, sep)
		},
		"include": func(name string, data interface{}) (string, error) {
			buf := bytes.NewBuffer(nil)
			if err := tmpl.ExecuteTemplate(buf, name, data); err != nil {
				return "", err
			}
			return buf.String(), nil
		},
		"ternary": func(a, b any, condition bool) any {
			if condition {
				return a
			}
			return b
		},
		"first": func(list any) (any, error) {
			if list == nil {
				return nil, errors.New("list is empty")
			}

			tp := reflect.TypeOf(list).Kind()
			switch tp {
			case reflect.Slice, reflect.Array:
				l2 := reflect.ValueOf(list)

				l := l2.Len()
				if l == 0 {
					return nil, errors.New("list is empty")
				}

				return l2.Index(0).Interface(), nil
			default:
				return nil, fmt.Errorf("first cannot be used on type: %T", list)
			}
		},
		"last": func(list any) (any, error) {
			if list == nil {
				return nil, errors.New("list is empty")
			}

			tp := reflect.TypeOf(list).Kind()
			switch tp {
			case reflect.Slice, reflect.Array:
				l2 := reflect.ValueOf(list)

				l := l2.Len()
				if l == 0 {
					return nil, errors.New("list is empty")
				}

				return l2.Index(l - 1).Interface(), nil
			default:
				return nil, fmt.Errorf("last cannot be used on type: %T", list)
			}
		},
		"indent": func(amount int, str string) string {
			pad := strings.Repeat(" ", amount)
			return pad + strings.Replace(str, "\n", "\n"+pad, -1)
		},
		"dict": func(keysAndValues ...any) (map[string]any, error) {
			if len(keysAndValues)%2 != 0 {
				return nil, errors.New("expected even # of arguments")
			}

			res := map[string]any{}
			for i := 0; i+1 < len(keysAndValues); i = i + 2 {
				if key, ok := keysAndValues[i].(string); ok {
					res[key] = keysAndValues[i+1]
				} else {
					return nil, fmt.Errorf("key of type %T is not a string as expected", key)
				}
			}

			return res, nil
		},
		"contains": func(list any, value any) bool {
			if list == nil {
				return false
			}

			val := reflect.ValueOf(list)
			switch val.Kind() {
			case reflect.Array:
			case reflect.Slice:
				for i := 0; i < val.Len(); i++ {
					cur := val.Index(i)
					if cur.CanInterface() && reflect.DeepEqual(cur.Interface(), value) {
						return true
					}
				}
				return false
			default:
				return false
			}
			return false
		},
		"set": func(dict map[string]any, keysAndValues ...any) (any, error) {
			if len(keysAndValues)%2 != 0 {
				return nil, errors.New("expected even number of arguments")
			}

			copyDict := make(map[string]any, len(dict))
			for k, v := range dict {
				copyDict[k] = v
			}

			for i := 0; i < len(keysAndValues); i += 2 {
				key, ok := keysAndValues[i].(string)
				if !ok {
					return nil, errors.New("keys must be strings")
				}

				copyDict[key] = keysAndValues[i+1]
			}

			return copyDict, nil
		},
		"list": func(values ...any) ([]any, error) {
			return values, nil
		},
		"add": func(value, operand int) int {
			return value + operand
		},
		"sub": func(value, operand int) int {
			return value - operand
		},
		"mul": func(value, operand int) int {
			return value * operand
		},
		"resolveRef": func(refAny any, document map[string]any) map[string]any {
			refString, ok := refAny.(string)
			if !ok {
				// if passed nil, or wrong type just treat the same
				// way as unresolved reference (makes for easier templates)
				return nil
			}

			// Resolve field path encoded by the ref
			ref, err := jsonreference.New(refString)
			if err != nil {
				// Unrecognized ref format.
				return nil
			}

			if !ref.HasFragmentOnly {
				// Downloading is not supported. Treat as not found
				return nil
			}

			fragment := ref.GetURL().Fragment
			components := strings.Split(fragment, "/")
			cur := document

			for _, k := range components {
				if len(k) == 0 {
					// first component is usually empty (#/components/) , etc
					continue
				}

				next, ok := cur[k].(map[string]any)
				if !ok {
					return nil
				}

				cur = next
			}
			return cur
		},
	})
}


NewGenerator

func NewGenerator() Generator {
	return &generator{
		templates: make(map[string]*template.Template),
	}
}


AddTemplate

func (g *generator) AddTemplate(name string, contents string) error {
	compiled, err := WithBuiltinTemplateFuncs(template.New(name)).Parse(contents)

	if err != nil {
		return err
	}

	g.templates[name] = compiled
	return nil
}


Render

func (g *generator) Render(
	// Template to use for rendering
	templateName string,
	// Self-Contained OpenAPI Document Containing all schemas used by $ref
	// Only OpenAPI V3 documents are supported
	document map[string]interface{},
	// Resource within OpenAPI document for which to render explain schema
	gvr schema.GroupVersionResource,
	// Field path of child of resource to focus output onto
	fieldSelector []string,
	// Boolean indicating whether the fields should be rendered recursively/deeply
	recursive bool,
	// Output writer
	writer io.Writer,
) error {
	compiledTemplate, ok := g.templates[templateName]
	if !ok {
		return fmt.Errorf("unrecognized format: %s", templateName)
	}

	err := compiledTemplate.Execute(writer, TemplateContext{
		Document:  document,
		Recursive: recursive,
		FieldPath: fieldSelector,
		GVR:       gvr,
	})
	return err
}


registerBuiltinTemplates

func registerBuiltinTemplates(gen Generator) error {
	files, err := rawBuiltinTemplates.ReadDir("templates")
	if err != nil {
		return err
	}

	for _, entry := range files {
		contents, err := rawBuiltinTemplates.ReadFile("templates/" + entry.Name())
		if err != nil {
			return err
		}

		err = gen.AddTemplate(
			strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())),
			string(contents))

		if err != nil {
			return err
		}
	}

	return nil
}


IsZero

func IsZero(i interface{}) bool {
	if i == nil {
		return true
	}
	return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface())
}


ValidateParams

// ValidateParams ensures that all required params are present in the params map
func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error {
	allErrs := []error{}
	for ix := range paramSpec {
		if paramSpec[ix].Required {
			value, found := params[paramSpec[ix].Name]
			if !found || IsZero(value) {
				allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name))
			}
		}
	}
	return utilerrors.NewAggregate(allErrs)
}


AnnotateFlags

// AnnotateFlags annotates all flags that are used by generators.
func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) {
	// Iterate over all generators and mark any flags used by them.
	for name, generator := range generators {
		generatorParams := map[string]struct{}{}
		for _, param := range generator.ParamNames() {
			generatorParams[param.Name] = struct{}{}
		}

		cmd.Flags().VisitAll(func(flag *pflag.Flag) {
			if _, found := generatorParams[flag.Name]; !found {
				// This flag is not used by the current generator
				// so skip it.
				return
			}
			if flag.Annotations == nil {
				flag.Annotations = map[string][]string{}
			}
			if annotations := flag.Annotations["generator"]; annotations == nil {
				flag.Annotations["generator"] = []string{}
			}
			flag.Annotations["generator"] = append(flag.Annotations["generator"], name)
		})
	}
}


EnsureFlagsValid

// EnsureFlagsValid ensures that no invalid flags are being used against a
func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error {
	AnnotateFlags(cmd, generators)

	allErrs := []error{}
	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
		// If the flag hasn't changed, don't validate it.
		if !flag.Changed {
			return
		}
		// Look into the flag annotations for the generators that can use it.
		if annotations := flag.Annotations["generator"]; len(annotations) > 0 {
			annotationMap := map[string]struct{}{}
			for _, ann := range annotations {
				annotationMap[ann] = struct{}{}
			}
			// If the current generator is not annotated, then this flag shouldn't
			// be used with it.
			if _, found := annotationMap[generatorInUse]; !found {
				allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse))
			}
		}
	})
	return utilerrors.NewAggregate(allErrs)
}


MakeParams

// MakeParams is a utility that creates generator parameters from a command line
func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} {
	result := map[string]interface{}{}
	for ix := range params {
		f := cmd.Flags().Lookup(params[ix].Name)
		if f != nil {
			result[params[ix].Name] = f.Value.String()
		}
	}
	return result
}


MakeProtocols

func MakeProtocols(protocols map[string]string) string {
	out := []string{}
	for key, value := range protocols {
		out = append(out, fmt.Sprintf("%s/%s", key, value))
	}
	return strings.Join(out, ",")
}


ParseProtocols

func ParseProtocols(protocols interface{}) (map[string]string, error) {
	protocolsString, isString := protocols.(string)
	if !isString {
		return nil, fmt.Errorf("expected string, found %v", protocols)
	}
	if len(protocolsString) == 0 {
		return nil, fmt.Errorf("no protocols passed")
	}
	portProtocolMap := map[string]string{}
	protocolsSlice := strings.Split(protocolsString, ",")
	for ix := range protocolsSlice {
		portProtocol := strings.Split(protocolsSlice[ix], "/")
		if len(portProtocol) != 2 {
			return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix])
		}
		if len(portProtocol[0]) == 0 {
			return nil, fmt.Errorf("unexpected empty port")
		}
		if len(portProtocol[1]) == 0 {
			return nil, fmt.Errorf("unexpected empty protocol")
		}
		portProtocolMap[portProtocol[0]] = portProtocol[1]
	}
	return portProtocolMap, nil
}


ParseLabels

// ParseLabels turns a string representation of a label set into a map[string]string
func ParseLabels(labelSpec interface{}) (map[string]string, error) {
	labelString, isString := labelSpec.(string)
	if !isString {
		return nil, fmt.Errorf("expected string, found %v", labelSpec)
	}
	if len(labelString) == 0 {
		return nil, fmt.Errorf("no label spec passed")
	}
	labels := map[string]string{}
	labelSpecs := strings.Split(labelString, ",")
	for ix := range labelSpecs {
		labelSpec := strings.Split(labelSpecs[ix], "=")
		if len(labelSpec) != 2 {
			return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
		}
		if len(labelSpec[0]) == 0 {
			return nil, fmt.Errorf("unexpected empty label key")
		}
		labels[labelSpec[0]] = labelSpec[1]
	}
	return labels, nil
}


GetBool

func GetBool(params map[string]string, key string, defValue bool) (bool, error) {
	if val, found := params[key]; !found {
		return defValue, nil
	} else {
		return strconv.ParseBool(val)
	}
}


DefaultGenerators

// DefaultGenerators returns the set of default generators for use in Factory instances
func DefaultGenerators(cmdName string) map[string]generate.Generator {
	var generator map[string]generate.Generator
	switch cmdName {
	case "expose":
		generator = map[string]generate.Generator{
			ServiceV2GeneratorName: ServiceGeneratorV2{},
		}
	case "run":
		generator = map[string]generate.Generator{
			RunPodV1GeneratorName: BasicPod{},
		}
	}

	return generator
}


getLabels

// getLabels returns map of labels.
func getLabels(params map[string]string, name string) (map[string]string, error) {
	labelString, found := params["labels"]
	var labels map[string]string
	var err error
	if found && len(labelString) > 0 {
		labels, err = generate.ParseLabels(labelString)
		if err != nil {
			return nil, err
		}
	} else {
		labels = map[string]string{
			"run": name,
		}
	}
	return labels, nil
}


getName

// getName returns the name of newly created resource.
func getName(params map[string]string) (string, error) {
	name, found := params["name"]
	if !found || len(name) == 0 {
		name, found = params["default-name"]
		if !found || len(name) == 0 {
			return "", fmt.Errorf("'name' is a required parameter")
		}
	}
	return name, nil
}


getParams

// getParams returns map of generic parameters.
func getParams(genericParams map[string]interface{}) (map[string]string, error) {
	params := map[string]string{}
	for key, value := range genericParams {
		strVal, isString := value.(string)
		if !isString {
			return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
		}
		params[key] = strVal
	}
	return params, nil
}


getArgs

// getArgs returns arguments for the container command.
func getArgs(genericParams map[string]interface{}) ([]string, error) {
	args := []string{}
	val, found := genericParams["args"]
	if found {
		var isArray bool
		args, isArray = val.([]string)
		if !isArray {
			return nil, fmt.Errorf("expected []string, found: %v", val)
		}
		delete(genericParams, "args")
	}
	return args, nil
}


getAnnotations

// getAnnotations returns map of annotations.
func getAnnotations(genericParams map[string]interface{}) (map[string]string, error) {
	annotationStrings, ok := genericParams["annotations"]
	if !ok {
		return nil, nil
	}

	annotationStringArray, ok := annotationStrings.([]string)
	if !ok {
		return nil, fmt.Errorf("expected []string, found: %v", annotationStrings)
	}

	annotations, _, err := cmdutil.ParsePairs(annotationStringArray, "annotations", false)
	if err != nil {
		return nil, err
	}

	delete(genericParams, "annotations")
	return annotations, nil
}


getEnvs

// getEnvs returns environment variables.
func getEnvs(genericParams map[string]interface{}) ([]v1.EnvVar, error) {
	var envs []v1.EnvVar
	envStrings, found := genericParams["env"]
	if found {
		if envStringArray, isArray := envStrings.([]string); isArray {
			var err error
			envs, err = parseEnvs(envStringArray)
			if err != nil {
				return nil, err
			}
			delete(genericParams, "env")
		} else {
			return nil, fmt.Errorf("expected []string, found: %v", envStrings)
		}
	}
	return envs, nil
}


// populateResourceListV1 takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
// and returns ResourceList.
func populateResourceListV1(spec string) (v1.ResourceList, error) {
	// empty input gets a nil response to preserve generator test expected behaviors
	if spec == "" {
		return nil, nil
	}

	result := v1.ResourceList{}
	resourceStatements := strings.Split(spec, ",")
	for _, resourceStatement := range resourceStatements {
		parts := strings.Split(resourceStatement, "=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
		}
		resourceName := v1.ResourceName(parts[0])
		resourceQuantity, err := resource.ParseQuantity(parts[1])
		if err != nil {
			return nil, err
		}
		result[resourceName] = resourceQuantity
	}
	return result, nil
}


// HandleResourceRequirementsV1 parses the limits and requests parameters if specified
// and returns ResourceRequirements.
func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) {
	result := v1.ResourceRequirements{}
	limits, err := populateResourceListV1(params["limits"])
	if err != nil {
		return result, err
	}
	result.Limits = limits
	requests, err := populateResourceListV1(params["requests"])
	if err != nil {
		return result, err
	}
	result.Requests = requests
	return result, nil
}


updatePodContainers

// updatePodContainers updates PodSpec.Containers with passed parameters.
func updatePodContainers(params map[string]string, args []string, envs []v1.EnvVar, imagePullPolicy v1.PullPolicy, podSpec *v1.PodSpec) error {
	if len(args) > 0 {
		command, err := generate.GetBool(params, "command", false)
		if err != nil {
			return err
		}
		if command {
			podSpec.Containers[0].Command = args
		} else {
			podSpec.Containers[0].Args = args
		}
	}

	if len(envs) > 0 {
		podSpec.Containers[0].Env = envs
	}

	if len(imagePullPolicy) > 0 {
		// imagePullPolicy should be valid here since we have verified it before.
		podSpec.Containers[0].ImagePullPolicy = imagePullPolicy
	}
	return nil
}


updatePodPorts

// updatePodContainers updates PodSpec.Containers.Ports with passed parameters.
func updatePodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) {
	port := -1
	hostPort := -1
	if len(params["port"]) > 0 {
		port, err = strconv.Atoi(params["port"])
		if err != nil {
			return err
		}
	}

	if len(params["hostport"]) > 0 {
		hostPort, err = strconv.Atoi(params["hostport"])
		if err != nil {
			return err
		}
		if hostPort > 0 && port < 0 {
			return fmt.Errorf("--hostport requires --port to be specified")
		}
	}

	// Don't include the port if it was not specified.
	if len(params["port"]) > 0 {
		podSpec.Containers[0].Ports = []v1.ContainerPort{
			{
				ContainerPort: int32(port),
			},
		}
		if hostPort > 0 {
			podSpec.Containers[0].Ports[0].HostPort = int32(hostPort)
		}
	}
	return nil
}


ParamNames

func (BasicPod) ParamNames() []generate.GeneratorParam {
	return []generate.GeneratorParam{
		{Name: "labels", Required: false},
		{Name: "annotations", Required: false},
		{Name: "default-name", Required: false},
		{Name: "name", Required: true},
		{Name: "image", Required: true},
		{Name: "image-pull-policy", Required: false},
		{Name: "port", Required: false},
		{Name: "hostport", Required: false},
		{Name: "stdin", Required: false},
		{Name: "leave-stdin-open", Required: false},
		{Name: "tty", Required: false},
		{Name: "restart", Required: false},
		{Name: "command", Required: false},
		{Name: "args", Required: false},
		{Name: "env", Required: false},
		{Name: "requests", Required: false},
		{Name: "limits", Required: false},
		{Name: "serviceaccount", Required: false},
		{Name: "privileged", Required: false},
	}
}


Generate

func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
	args, err := getArgs(genericParams)
	if err != nil {
		return nil, err
	}

	envs, err := getEnvs(genericParams)
	if err != nil {
		return nil, err
	}

	annotations, err := getAnnotations(genericParams)
	if err != nil {
		return nil, err
	}

	params, err := getParams(genericParams)
	if err != nil {
		return nil, err
	}

	name, err := getName(params)
	if err != nil {
		return nil, err
	}

	labels, err := getLabels(params, name)
	if err != nil {
		return nil, err
	}

	stdin, err := generate.GetBool(params, "stdin", false)
	if err != nil {
		return nil, err
	}
	leaveStdinOpen, err := generate.GetBool(params, "leave-stdin-open", false)
	if err != nil {
		return nil, err
	}

	tty, err := generate.GetBool(params, "tty", false)
	if err != nil {
		return nil, err
	}

	resourceRequirements, err := HandleResourceRequirementsV1(params)
	if err != nil {
		return nil, err
	}

	restartPolicy := v1.RestartPolicy(params["restart"])
	if len(restartPolicy) == 0 {
		restartPolicy = v1.RestartPolicyAlways
	}

	privileged, err := generate.GetBool(params, "privileged", false)
	if err != nil {
		return nil, err
	}
	var securityContext *v1.SecurityContext
	if privileged {
		securityContext = &v1.SecurityContext{
			Privileged: &privileged,
		}
	}

	pod := v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        name,
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: v1.PodSpec{
			ServiceAccountName: params["serviceaccount"],
			Containers: []v1.Container{
				{
					Name:            name,
					Image:           params["image"],
					Stdin:           stdin,
					StdinOnce:       !leaveStdinOpen && stdin,
					TTY:             tty,
					Resources:       resourceRequirements,
					SecurityContext: securityContext,
				},
			},
			DNSPolicy:     v1.DNSClusterFirst,
			RestartPolicy: restartPolicy,
		},
	}
	imagePullPolicy := v1.PullPolicy(params["image-pull-policy"])
	if err = updatePodContainers(params, args, envs, imagePullPolicy, &pod.Spec); err != nil {
		return nil, err
	}

	if err := updatePodPorts(params, &pod.Spec); err != nil {
		return nil, err
	}
	return &pod, nil
}


parseEnvs

// parseEnvs converts string into EnvVar objects.
func parseEnvs(envArray []string) ([]v1.EnvVar, error) {
	envs := make([]v1.EnvVar, 0, len(envArray))
	for _, env := range envArray {
		pos := strings.Index(env, "=")
		if pos == -1 {
			return nil, fmt.Errorf("invalid env: %v", env)
		}
		name := env[:pos]
		value := env[pos+1:]
		if len(name) == 0 {
			return nil, fmt.Errorf("invalid env: %v", env)
		}
		if len(validation.IsEnvVarName(name)) != 0 {
			return nil, fmt.Errorf("invalid env: %v", env)
		}
		envVar := v1.EnvVar{Name: name, Value: value}
		envs = append(envs, envVar)
	}
	return envs, nil
}


ParamNames

func (ServiceGeneratorV1) ParamNames() []generate.GeneratorParam {
	return paramNames()
}


Generate

func (ServiceGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
	params["port-name"] = "default"
	return generateService(params)
}


ParamNames

func (ServiceGeneratorV2) ParamNames() []generate.GeneratorParam {
	return paramNames()
}


Generate

func (ServiceGeneratorV2) Generate(params map[string]interface{}) (runtime.Object, error) {
	return generateService(params)
}


paramNames

func paramNames() []generate.GeneratorParam {
	return []generate.GeneratorParam{
		{Name: "default-name", Required: true},
		{Name: "name", Required: false},
		{Name: "selector", Required: true},
		// port will be used if a user specifies --port OR the exposed object
		// has one port
		{Name: "port", Required: false},
		// ports will be used iff a user doesn't specify --port AND the
		// exposed object has multiple ports
		{Name: "ports", Required: false},
		{Name: "labels", Required: false},
		{Name: "external-ip", Required: false},
		{Name: "load-balancer-ip", Required: false},
		{Name: "type", Required: false},
		{Name: "protocol", Required: false},
		// protocols will be used to keep port-protocol mapping derived from
		// exposed object
		{Name: "protocols", Required: false},
		{Name: "container-port", Required: false}, // alias of target-port
		{Name: "target-port", Required: false},
		{Name: "port-name", Required: false},
		{Name: "session-affinity", Required: false},
		{Name: "cluster-ip", Required: false},
	}
}


generateService

func generateService(genericParams map[string]interface{}) (runtime.Object, error) {
	params := map[string]string{}
	for key, value := range genericParams {
		strVal, isString := value.(string)
		if !isString {
			return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
		}
		params[key] = strVal
	}
	selectorString, found := params["selector"]
	if !found || len(selectorString) == 0 {
		return nil, fmt.Errorf("'selector' is a required parameter")
	}
	selector, err := generate.ParseLabels(selectorString)
	if err != nil {
		return nil, err
	}

	labelsString, found := params["labels"]
	var labels map[string]string
	if found && len(labelsString) > 0 {
		labels, err = generate.ParseLabels(labelsString)
		if err != nil {
			return nil, err
		}
	}

	name, found := params["name"]
	if !found || len(name) == 0 {
		name, found = params["default-name"]
		if !found || len(name) == 0 {
			return nil, fmt.Errorf("'name' is a required parameter")
		}
	}

	isHeadlessService := params["cluster-ip"] == "None"

	ports := []v1.ServicePort{}
	servicePortName, found := params["port-name"]
	if !found {
		// Leave the port unnamed.
		servicePortName = ""
	}

	protocolsString, found := params["protocols"]
	var portProtocolMap map[string]string
	if found && len(protocolsString) > 0 {
		portProtocolMap, err = generate.ParseProtocols(protocolsString)
		if err != nil {
			return nil, err
		}
	}
	// ports takes precedence over port since it will be
	// specified only when the user hasn't specified a port
	// via --port and the exposed object has multiple ports.
	var portString string
	if portString, found = params["ports"]; !found {
		portString, found = params["port"]
		if !found && !isHeadlessService {
			return nil, fmt.Errorf("'ports' or 'port' is a required parameter")
		}
	}

	if portString != "" {
		portStringSlice := strings.Split(portString, ",")
		for i, stillPortString := range portStringSlice {
			port, err := strconv.Atoi(stillPortString)
			if err != nil {
				return nil, err
			}
			name := servicePortName
			// If we are going to assign multiple ports to a service, we need to
			// generate a different name for each one.
			if len(portStringSlice) > 1 {
				name = fmt.Sprintf("port-%d", i+1)
			}
			protocol := params["protocol"]

			switch {
			case len(protocol) == 0 && len(portProtocolMap) == 0:
				// Default to TCP, what the flag was doing previously.
				protocol = "TCP"
			case len(protocol) > 0 && len(portProtocolMap) > 0:
				// User has specified the --protocol while exposing a multiprotocol resource
				// We should stomp multiple protocols with the one specified ie. do nothing
			case len(protocol) == 0 && len(portProtocolMap) > 0:
				// no --protocol and we expose a multiprotocol resource
				protocol = "TCP" // have the default so we can stay sane
				if exposeProtocol, found := portProtocolMap[stillPortString]; found {
					protocol = exposeProtocol
				}
			}
			ports = append(ports, v1.ServicePort{
				Name:     name,
				Port:     int32(port),
				Protocol: v1.Protocol(protocol),
			})
		}
	}

	service := v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: v1.ServiceSpec{
			Selector: selector,
			Ports:    ports,
		},
	}
	targetPortString := params["target-port"]
	if len(targetPortString) == 0 {
		targetPortString = params["container-port"]
	}
	if len(targetPortString) > 0 {
		var targetPort intstr.IntOrString
		if portNum, err := strconv.Atoi(targetPortString); err != nil {
			targetPort = intstr.FromString(targetPortString)
		} else {
			targetPort = intstr.FromInt(portNum)
		}
		// Use the same target-port for every port
		for i := range service.Spec.Ports {
			service.Spec.Ports[i].TargetPort = targetPort
		}
	} else {
		// If --target-port or --container-port haven't been specified, this
		// should be the same as Port
		for i := range service.Spec.Ports {
			port := service.Spec.Ports[i].Port
			service.Spec.Ports[i].TargetPort = intstr.FromInt(int(port))
		}
	}
	if len(params["external-ip"]) > 0 {
		service.Spec.ExternalIPs = []string{params["external-ip"]}
	}
	if len(params["type"]) != 0 {
		service.Spec.Type = v1.ServiceType(params["type"])
	}
	if service.Spec.Type == v1.ServiceTypeLoadBalancer {
		service.Spec.LoadBalancerIP = params["load-balancer-ip"]
	}
	if len(params["session-affinity"]) != 0 {
		switch v1.ServiceAffinity(params["session-affinity"]) {
		case v1.ServiceAffinityNone:
			service.Spec.SessionAffinity = v1.ServiceAffinityNone
		case v1.ServiceAffinityClientIP:
			service.Spec.SessionAffinity = v1.ServiceAffinityClientIP
		default:
			return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"])
		}
	}
	if len(params["cluster-ip"]) != 0 {
		if params["cluster-ip"] == "None" {
			service.Spec.ClusterIP = v1.ClusterIPNone
		} else {
			service.Spec.ClusterIP = params["cluster-ip"]
		}
	}
	return &service, nil
}


NewTopCmdPrinter

func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
	return &TopCmdPrinter{out: out}
}


PrintNodeMetrics

func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
	if len(metrics) == 0 {
		return nil
	}
	w := printers.GetNewTabWriter(printer.out)
	defer w.Flush()

	sort.Sort(NewNodeMetricsSorter(metrics, sortBy))

	if !noHeaders {
		printColumnNames(w, NodeColumns)
	}
	var usage v1.ResourceList
	for _, m := range metrics {
		m.Usage.DeepCopyInto(&usage)
		printMetricsLine(w, &ResourceMetricsInfo{
			Name:      m.Name,
			Metrics:   usage,
			Available: availableResources[m.Name],
		})
		delete(availableResources, m.Name)
	}

	// print lines for nodes of which the metrics is unreachable.
	for nodeName := range availableResources {
		printMissingMetricsNodeLine(w, nodeName)
	}
	return nil
}


PrintPodMetrics

func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool, sortBy string, sum bool) error {
	if len(metrics) == 0 {
		return nil
	}
	w := printers.GetNewTabWriter(printer.out)
	defer w.Flush()

	columnWidth := len(PodColumns)
	if !noHeaders {
		if withNamespace {
			printValue(w, NamespaceColumn)
			columnWidth++
		}
		if printContainers {
			printValue(w, PodColumn)
			columnWidth++
		}
		printColumnNames(w, PodColumns)
	}

	sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy))

	for _, m := range metrics {
		if printContainers {
			sort.Sort(NewContainerMetricsSorter(m.Containers, sortBy))
			printSinglePodContainerMetrics(w, &m, withNamespace)
		} else {
			printSinglePodMetrics(w, &m, withNamespace)
		}

	}

	if sum {
		adder := NewResourceAdder(MeasuredResources)
		for _, m := range metrics {
			adder.AddPodMetrics(&m)
		}
		printPodResourcesSum(w, adder.total, columnWidth)
	}

	return nil
}


printColumnNames

func printColumnNames(out io.Writer, names []string) {
	for _, name := range names {
		printValue(out, name)
	}
	fmt.Fprint(out, "\n")
}


printSinglePodMetrics

func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
	podMetrics := getPodMetrics(m)
	if withNamespace {
		printValue(out, m.Namespace)
	}
	printMetricsLine(out, &ResourceMetricsInfo{
		Name:      m.Name,
		Metrics:   podMetrics,
		Available: v1.ResourceList{},
	})
}


printSinglePodContainerMetrics

func printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
	for _, c := range m.Containers {
		if withNamespace {
			printValue(out, m.Namespace)
		}
		printValue(out, m.Name)
		printMetricsLine(out, &ResourceMetricsInfo{
			Name:      c.Name,
			Metrics:   c.Usage,
			Available: v1.ResourceList{},
		})
	}
}


getPodMetrics

func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
	podMetrics := make(v1.ResourceList)
	for _, res := range MeasuredResources {
		podMetrics[res], _ = resource.ParseQuantity("0")
	}

	for _, c := range m.Containers {
		for _, res := range MeasuredResources {
			quantity := podMetrics[res]
			quantity.Add(c.Usage[res])
			podMetrics[res] = quantity
		}
	}
	return podMetrics
}


printMetricsLine

func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
	printValue(out, metrics.Name)
	printAllResourceUsages(out, metrics)
	fmt.Fprint(out, "\n")
}


printMissingMetricsNodeLine

func printMissingMetricsNodeLine(out io.Writer, nodeName string) {
	printValue(out, nodeName)
	unknownMetricsStatus := "<unknown>"
	for i := 0; i < len(MeasuredResources); i++ {
		printValue(out, unknownMetricsStatus)
		printValue(out, unknownMetricsStatus)
	}
	fmt.Fprint(out, "\n")
}


printValue

func printValue(out io.Writer, value interface{}) {
	fmt.Fprintf(out, "%v\t", value)
}


printAllResourceUsages

func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
	for _, res := range MeasuredResources {
		quantity := metrics.Metrics[res]
		printSingleResourceUsage(out, res, quantity)
		fmt.Fprint(out, "\t")
		if available, found := metrics.Available[res]; found {
			fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
			fmt.Fprintf(out, "%d%%\t", int64(fraction))
		}
	}
}


printSingleResourceUsage

func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quantity resource.Quantity) {
	switch resourceType {
	case v1.ResourceCPU:
		fmt.Fprintf(out, "%vm", quantity.MilliValue())
	case v1.ResourceMemory:
		fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
	default:
		fmt.Fprintf(out, "%v", quantity.Value())
	}
}


printPodResourcesSum

func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int) {
	for i := 0; i < columnWidth-2; i++ {
		printValue(out, "")
	}
	printValue(out, "________")
	printValue(out, "________")
	fmt.Fprintf(out, "\n")
	for i := 0; i < columnWidth-3; i++ {
		printValue(out, "")
	}
	printMetricsLine(out, &ResourceMetricsInfo{
		Name:      "",
		Metrics:   total,
		Available: v1.ResourceList{},
	})

}


NewResourceAdder

func NewResourceAdder(resources []corev1.ResourceName) *ResourceAdder {
	return &ResourceAdder{
		resources: resources,
		total:     make(corev1.ResourceList),
	}
}


AddPodMetrics

// AddPodMetrics adds each pod metric to the total
func (adder *ResourceAdder) AddPodMetrics(m *metricsapi.PodMetrics) {
	for _, c := range m.Containers {
		for _, res := range adder.resources {
			total := adder.total[res]
			total.Add(c.Usage[res])
			adder.total[res] = total
		}
	}
}


Len

func (n *NodeMetricsSorter) Len() int {
	return len(n.metrics)
}


Swap

func (n *NodeMetricsSorter) Swap(i, j int) {
	n.metrics[i], n.metrics[j] = n.metrics[j], n.metrics[i]
}


Less

func (n *NodeMetricsSorter) Less(i, j int) bool {
	switch n.sortBy {
	case "cpu":
		return n.metrics[i].Usage.Cpu().MilliValue() > n.metrics[j].Usage.Cpu().MilliValue()
	case "memory":
		return n.metrics[i].Usage.Memory().Value() > n.metrics[j].Usage.Memory().Value()
	default:
		return n.metrics[i].Name < n.metrics[j].Name
	}
}


NewNodeMetricsSorter

func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) *NodeMetricsSorter {
	return &NodeMetricsSorter{
		metrics: metrics,
		sortBy:  sortBy,
	}
}


Len

func (p *PodMetricsSorter) Len() int {
	return len(p.metrics)
}


Swap

func (p *PodMetricsSorter) Swap(i, j int) {
	p.metrics[i], p.metrics[j] = p.metrics[j], p.metrics[i]
	p.podMetrics[i], p.podMetrics[j] = p.podMetrics[j], p.podMetrics[i]
}


Less

func (p *PodMetricsSorter) Less(i, j int) bool {
	switch p.sortBy {
	case "cpu":
		return p.podMetrics[i].Cpu().MilliValue() > p.podMetrics[j].Cpu().MilliValue()
	case "memory":
		return p.podMetrics[i].Memory().Value() > p.podMetrics[j].Memory().Value()
	default:
		if p.withNamespace && p.metrics[i].Namespace != p.metrics[j].Namespace {
			return p.metrics[i].Namespace < p.metrics[j].Namespace
		}
		return p.metrics[i].Name < p.metrics[j].Name
	}
}


NewPodMetricsSorter

func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
	var podMetrics = make([]v1.ResourceList, len(metrics))
	if len(sortBy) > 0 {
		for i, v := range metrics {
			podMetrics[i] = getPodMetrics(&v)
		}
	}

	return &PodMetricsSorter{
		metrics:       metrics,
		sortBy:        sortBy,
		withNamespace: withNamespace,
		podMetrics:    podMetrics,
	}
}


Len

func (s *ContainerMetricsSorter) Len() int {
	return len(s.metrics)
}


Swap

func (s *ContainerMetricsSorter) Swap(i, j int) {
	s.metrics[i], s.metrics[j] = s.metrics[j], s.metrics[i]
}


Less

func (s *ContainerMetricsSorter) Less(i, j int) bool {
	switch s.sortBy {
	case "cpu":
		return s.metrics[i].Usage.Cpu().MilliValue() > s.metrics[j].Usage.Cpu().MilliValue()
	case "memory":
		return s.metrics[i].Usage.Memory().Value() > s.metrics[j].Usage.Memory().Value()
	default:
		return s.metrics[i].Name < s.metrics[j].Name
	}
}


NewContainerMetricsSorter

func NewContainerMetricsSorter(metrics []metricsapi.ContainerMetrics, sortBy string) *ContainerMetricsSorter {
	return &ContainerMetricsSorter{
		metrics: metrics,
		sortBy:  sortBy,
	}
}


attachablePodForObject

// attachablePodForObject returns the pod to which to attach given an object.
func attachablePodForObject(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*corev1.Pod, error) {
	switch t := object.(type) {
	case *corev1.Pod:
		return t, nil
	}

	clientConfig, err := restClientGetter.ToRESTConfig()
	if err != nil {
		return nil, err
	}
	clientset, err := corev1client.NewForConfig(clientConfig)
	if err != nil {
		return nil, err
	}

	namespace, selector, err := SelectorsForObject(object)
	if err != nil {
		return nil, fmt.Errorf("cannot attach to %T: %v", object, err)
	}
	sortBy := func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) }
	pod, _, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
	return pod, err
}


canBeExposed

// Check whether the kind of resources could be exposed
func canBeExposed(kind schema.GroupKind) error {
	switch kind {
	case
		corev1.SchemeGroupVersion.WithKind("ReplicationController").GroupKind(),
		corev1.SchemeGroupVersion.WithKind("Service").GroupKind(),
		corev1.SchemeGroupVersion.WithKind("Pod").GroupKind(),
		appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
		appsv1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind(),
		extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
		extensionsv1beta1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind():
		// nothing to do here
	default:
		return fmt.Errorf("cannot expose a %s", kind)
	}
	return nil
}


GetFirstPod

// GetFirstPod returns a pod matching the namespace and label selector
// and the number of all pods that match the label selector.
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*corev1.Pod) sort.Interface) (*corev1.Pod, int, error) {
	options := metav1.ListOptions{LabelSelector: selector}

	podList, err := client.Pods(namespace).List(context.TODO(), options)
	if err != nil {
		return nil, 0, err
	}
	pods := []*corev1.Pod{}
	for i := range podList.Items {
		pod := podList.Items[i]
		pods = append(pods, &pod)
	}
	if len(pods) > 0 {
		sort.Sort(sortBy(pods))
		return pods[0], len(podList.Items), nil
	}

	// Watch until we observe a pod
	options.ResourceVersion = podList.ResourceVersion
	w, err := client.Pods(namespace).Watch(context.TODO(), options)
	if err != nil {
		return nil, 0, err
	}
	defer w.Stop()

	condition := func(event watch.Event) (bool, error) {
		return event.Type == watch.Added || event.Type == watch.Modified, nil
	}

	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
	defer cancel()
	event, err := watchtools.UntilWithoutRetry(ctx, w, condition)
	if err != nil {
		return nil, 0, err
	}
	pod, ok := event.Object.(*corev1.Pod)
	if !ok {
		return nil, 0, fmt.Errorf("%#v is not a pod event", event)
	}
	return pod, 1, nil
}


SelectorsForObject

// SelectorsForObject returns the pod label selector for a given object
func SelectorsForObject(object runtime.Object) (namespace string, selector labels.Selector, err error) {
	switch t := object.(type) {
	case *extensionsv1beta1.ReplicaSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1.ReplicaSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta2.ReplicaSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}

	case *corev1.ReplicationController:
		namespace = t.Namespace
		selector = labels.SelectorFromSet(t.Spec.Selector)

	case *appsv1.StatefulSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta1.StatefulSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta2.StatefulSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}

	case *extensionsv1beta1.DaemonSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1.DaemonSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta2.DaemonSet:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}

	case *extensionsv1beta1.Deployment:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1.Deployment:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta1.Deployment:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}
	case *appsv1beta2.Deployment:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}

	case *batchv1.Job:
		namespace = t.Namespace
		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
		if err != nil {
			return "", nil, fmt.Errorf("invalid label selector: %v", err)
		}

	case *corev1.Service:
		namespace = t.Namespace
		if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 {
			return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name)
		}
		selector = labels.SelectorFromSet(t.Spec.Selector)

	default:
		return "", nil, fmt.Errorf("selector for %T not implemented", object)
	}

	return namespace, selector, nil
}


VisitDeployment

func (v *HistoryVisitor) VisitDeployment(elem apps.GroupKindElement) {
	v.result = &DeploymentHistoryViewer{v.clientset}
}


VisitStatefulSet

func (v *HistoryVisitor) VisitStatefulSet(kind apps.GroupKindElement) {
	v.result = &StatefulSetHistoryViewer{v.clientset}
}


VisitDaemonSet

func (v *HistoryVisitor) VisitDaemonSet(kind apps.GroupKindElement) {
	v.result = &DaemonSetHistoryViewer{v.clientset}
}


VisitJob

func (v *HistoryVisitor) VisitJob(kind apps.GroupKindElement)                   {}


VisitPod

func (v *HistoryVisitor) VisitPod(kind apps.GroupKindElement)                   {}


VisitReplicaSet

func (v *HistoryVisitor) VisitReplicaSet(kind apps.GroupKindElement)            {}


VisitReplicationController

func (v *HistoryVisitor) VisitReplicationController(kind apps.GroupKindElement) {}


VisitCronJob

func (v *HistoryVisitor) VisitCronJob(kind apps.GroupKindElement)               {}


HistoryViewerFor

// HistoryViewerFor returns an implementation of HistoryViewer interface for the given schema kind
func HistoryViewerFor(kind schema.GroupKind, c kubernetes.Interface) (HistoryViewer, error) {
	elem := apps.GroupKindElement(kind)
	visitor := &HistoryVisitor{
		clientset: c,
	}

	// Determine which HistoryViewer we need here
	err := elem.Accept(visitor)

	if err != nil {
		return nil, fmt.Errorf("error retrieving history for %q, %v", kind.String(), err)
	}

	if visitor.result == nil {
		return nil, fmt.Errorf("no history viewer has been implemented for %q", kind.String())
	}

	return visitor.result, nil
}


ViewHistory

// ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
// TODO: this should be a describer
func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
	allRSs, err := getDeploymentReplicaSets(h.c.AppsV1(), namespace, name)
	if err != nil {
		return "", err
	}

	historyInfo := make(map[int64]*corev1.PodTemplateSpec)
	for _, rs := range allRSs {
		v, err := deploymentutil.Revision(rs)
		if err != nil {
			klog.Warningf("unable to get revision from replicaset %s for deployment %s in namespace %s: %v", rs.Name, name, namespace, err)
			continue
		}
		historyInfo[v] = &rs.Spec.Template
		changeCause := getChangeCause(rs)
		if historyInfo[v].Annotations == nil {
			historyInfo[v].Annotations = make(map[string]string)
		}
		if len(changeCause) > 0 {
			historyInfo[v].Annotations[ChangeCauseAnnotation] = changeCause
		}
	}

	if len(historyInfo) == 0 {
		return "No rollout history found.", nil
	}

	if revision > 0 {
		// Print details of a specific revision
		template, ok := historyInfo[revision]
		if !ok {
			return "", fmt.Errorf("unable to find the specified revision")
		}
		return printTemplate(template)
	}

	// Sort the revisionToChangeCause map by revision
	revisions := make([]int64, 0, len(historyInfo))
	for r := range historyInfo {
		revisions = append(revisions, r)
	}
	sliceutil.SortInts64(revisions)

	return tabbedString(func(out io.Writer) error {
		fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
		for _, r := range revisions {
			// Find the change-cause of revision r
			changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
			if len(changeCause) == 0 {
				changeCause = "<none>"
			}
			fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
		}
		return nil
	})
}


GetHistory

// GetHistory returns the ReplicaSet revisions associated with a Deployment
func (h *DeploymentHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
	allRSs, err := getDeploymentReplicaSets(h.c.AppsV1(), namespace, name)
	if err != nil {
		return nil, err
	}

	result := make(map[int64]runtime.Object)
	for _, rs := range allRSs {
		v, err := deploymentutil.Revision(rs)
		if err != nil {
			klog.Warningf("unable to get revision from replicaset %s for deployment %s in namespace %s: %v", rs.Name, name, namespace, err)
			continue
		}
		result[v] = rs
	}

	return result, nil
}


printTemplate

func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
	buf := bytes.NewBuffer([]byte{})
	w := describe.NewPrefixWriter(buf)
	describe.DescribePodTemplate(template, w)
	return buf.String(), nil
}


ViewHistory

// ViewHistory returns a revision-to-history map as the revision history of a deployment
// TODO: this should be a describer
func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
	ds, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
	if err != nil {
		return "", err
	}
	return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) {
		dsOfHistory, err := applyDaemonSetHistory(ds, history)
		if err != nil {
			return nil, err
		}
		return &dsOfHistory.Spec.Template, err
	})
}


GetHistory

// GetHistory returns the revisions associated with a DaemonSet
func (h *DaemonSetHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
	ds, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
	if err != nil {
		return nil, err
	}

	result := make(map[int64]runtime.Object)
	for _, h := range history {
		applied, err := applyDaemonSetHistory(ds, h)
		if err != nil {
			return nil, err
		}
		result[h.Revision] = applied
	}

	return result, nil
}


printHistory

// printHistory returns the podTemplate of the given revision if it is non-zero
// else returns the overall revisions
func printHistory(history []*appsv1.ControllerRevision, revision int64, getPodTemplate func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error)) (string, error) {
	historyInfo := make(map[int64]*appsv1.ControllerRevision)
	for _, history := range history {
		// TODO: for now we assume revisions don't overlap, we may need to handle it
		historyInfo[history.Revision] = history
	}
	if len(historyInfo) == 0 {
		return "No rollout history found.", nil
	}

	// Print details of a specific revision
	if revision > 0 {
		history, ok := historyInfo[revision]
		if !ok {
			return "", fmt.Errorf("unable to find the specified revision")
		}
		podTemplate, err := getPodTemplate(history)
		if err != nil {
			return "", fmt.Errorf("unable to parse history %s", history.Name)
		}
		return printTemplate(podTemplate)
	}

	// Print an overview of all Revisions
	// Sort the revisionToChangeCause map by revision
	revisions := make([]int64, 0, len(historyInfo))
	for r := range historyInfo {
		revisions = append(revisions, r)
	}
	sliceutil.SortInts64(revisions)

	return tabbedString(func(out io.Writer) error {
		fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
		for _, r := range revisions {
			// Find the change-cause of revision r
			changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
			if len(changeCause) == 0 {
				changeCause = "<none>"
			}
			fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
		}
		return nil
	})
}


ViewHistory

// ViewHistory returns a list of the revision history of a statefulset
// TODO: this should be a describer
func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
	sts, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
	if err != nil {
		return "", err
	}
	return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) {
		stsOfHistory, err := applyStatefulSetHistory(sts, history)
		if err != nil {
			return nil, err
		}
		return &stsOfHistory.Spec.Template, err
	})
}


GetHistory

// GetHistory returns the revisions associated with a StatefulSet
func (h *StatefulSetHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
	sts, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
	if err != nil {
		return nil, err
	}

	result := make(map[int64]runtime.Object)
	for _, h := range history {
		applied, err := applyStatefulSetHistory(sts, h)
		if err != nil {
			return nil, err
		}
		result[h.Revision] = applied
	}

	return result, nil
}


getDeploymentReplicaSets

func getDeploymentReplicaSets(apps clientappsv1.AppsV1Interface, namespace, name string) ([]*appsv1.ReplicaSet, error) {
	deployment, err := apps.Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
	}

	_, oldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, apps)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
	}

	if newRS == nil {
		return oldRSs, nil
	}
	return append(oldRSs, newRS), nil
}


// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
// TODO: Rename this to controllerHistory when other controllers have been upgraded
func controlledHistoryV1(
	apps clientappsv1.AppsV1Interface,
	namespace string,
	selector labels.Selector,
	accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
	var result []*appsv1.ControllerRevision
	historyList, err := apps.ControllerRevisions(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
	if err != nil {
		return nil, err
	}
	for i := range historyList.Items {
		history := historyList.Items[i]
		// Only add history that belongs to the API object
		if metav1.IsControlledBy(&history, accessor) {
			result = append(result, &history)
		}
	}
	return result, nil
}


controlledHistory

// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
func controlledHistory(
	apps clientappsv1.AppsV1Interface,
	namespace string,
	selector labels.Selector,
	accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
	var result []*appsv1.ControllerRevision
	historyList, err := apps.ControllerRevisions(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
	if err != nil {
		return nil, err
	}
	for i := range historyList.Items {
		history := historyList.Items[i]
		// Only add history that belongs to the API object
		if metav1.IsControlledBy(&history, accessor) {
			result = append(result, &history)
		}
	}
	return result, nil
}


daemonSetHistory

// daemonSetHistory returns the DaemonSet named name in namespace and all ControllerRevisions in its history.
func daemonSetHistory(
	apps clientappsv1.AppsV1Interface,
	namespace, name string) (*appsv1.DaemonSet, []*appsv1.ControllerRevision, error) {
	ds, err := apps.DaemonSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
	}
	selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create selector for DaemonSet %s: %v", ds.Name, err)
	}
	accessor, err := meta.Accessor(ds)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create accessor for DaemonSet %s: %v", ds.Name, err)
	}
	history, err := controlledHistory(apps, ds.Namespace, selector, accessor)
	if err != nil {
		return nil, nil, fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
	}
	return ds, history, nil
}


statefulSetHistory

// statefulSetHistory returns the StatefulSet named name in namespace and all ControllerRevisions in its history.
func statefulSetHistory(
	apps clientappsv1.AppsV1Interface,
	namespace, name string) (*appsv1.StatefulSet, []*appsv1.ControllerRevision, error) {
	sts, err := apps.StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, nil, fmt.Errorf("failed to retrieve Statefulset %s: %s", name, err.Error())
	}
	selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create selector for StatefulSet %s: %s", name, err.Error())
	}
	accessor, err := meta.Accessor(sts)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to obtain accessor for StatefulSet %s: %s", name, err.Error())
	}
	history, err := controlledHistoryV1(apps, namespace, selector, accessor)
	if err != nil {
		return nil, nil, fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", name, err)
	}
	return sts, history, nil
}


applyDaemonSetHistory

// applyDaemonSetHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet
func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (*appsv1.DaemonSet, error) {
	dsBytes, err := json.Marshal(ds)
	if err != nil {
		return nil, err
	}
	patched, err := strategicpatch.StrategicMergePatch(dsBytes, history.Data.Raw, ds)
	if err != nil {
		return nil, err
	}
	result := &appsv1.DaemonSet{}
	err = json.Unmarshal(patched, result)
	if err != nil {
		return nil, err
	}
	return result, nil
}


applyStatefulSetHistory

// applyStatefulSetHistory returns a specific revision of StatefulSet by applying the given history to a copy of the given StatefulSet
func applyStatefulSetHistory(sts *appsv1.StatefulSet, history *appsv1.ControllerRevision) (*appsv1.StatefulSet, error) {
	stsBytes, err := json.Marshal(sts)
	if err != nil {
		return nil, err
	}
	patched, err := strategicpatch.StrategicMergePatch(stsBytes, history.Data.Raw, sts)
	if err != nil {
		return nil, err
	}
	result := &appsv1.StatefulSet{}
	err = json.Unmarshal(patched, result)
	if err != nil {
		return nil, err
	}
	return result, nil
}


tabbedString

// TODO: copied here until this becomes a describer
func tabbedString(f func(io.Writer) error) (string, error) {
	out := new(tabwriter.Writer)
	buf := &bytes.Buffer{}
	out.Init(buf, 0, 8, 2, ' ', 0)

	err := f(out)
	if err != nil {
		return "", err
	}

	out.Flush()
	str := string(buf.String())
	return str, nil
}


getChangeCause

// getChangeCause returns the change-cause annotation of the input object
func getChangeCause(obj runtime.Object) string {
	accessor, err := meta.Accessor(obj)
	if err != nil {
		return ""
	}
	return accessor.GetAnnotations()[ChangeCauseAnnotation]
}


historyViewer

// historyViewer Returns a HistoryViewer for viewing change history
func historyViewer(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (HistoryViewer, error) {
	clientConfig, err := restClientGetter.ToRESTConfig()
	if err != nil {
		return nil, err
	}

	external, err := kubernetes.NewForConfig(clientConfig)
	if err != nil {
		return nil, err
	}
	return HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), external)
}


logsForObject

func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) {
	clientConfig, err := restClientGetter.ToRESTConfig()
	if err != nil {
		return nil, err
	}

	clientset, err := corev1client.NewForConfig(clientConfig)
	if err != nil {
		return nil, err
	}
	return logsForObjectWithClient(clientset, object, options, timeout, allContainers)
}


logsForObjectWithClient

// this is split for easy test-ability
func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) {
	opts, ok := options.(*corev1.PodLogOptions)
	if !ok {
		return nil, errors.New("provided options object is not a PodLogOptions")
	}

	switch t := object.(type) {
	case *corev1.PodList:
		ret := make(map[corev1.ObjectReference]rest.ResponseWrapper)
		for i := range t.Items {
			currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers)
			if err != nil {
				return nil, err
			}
			for k, v := range currRet {
				ret[k] = v
			}
		}
		return ret, nil

	case *corev1.Pod:
		// if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false
		if !allContainers {
			currOpts := new(corev1.PodLogOptions)
			if opts != nil {
				opts.DeepCopyInto(currOpts)
			}
			// in case the "kubectl.kubernetes.io/default-container" annotation is present, we preset the opts.Containers to default to selected
			// container. This gives users ability to preselect the most interesting container in pod.
			if annotations := t.GetAnnotations(); annotations != nil && currOpts.Container == "" {
				var defaultContainer string
				if len(annotations[podcmd.DefaultContainerAnnotationName]) > 0 {
					defaultContainer = annotations[podcmd.DefaultContainerAnnotationName]
				}
				if len(defaultContainer) > 0 {
					if exists, _ := podcmd.FindContainerByName(t, defaultContainer); exists == nil {
						fmt.Fprintf(os.Stderr, "Default container name %q not found in pod %s\n", defaultContainer, t.Name)
					} else {
						currOpts.Container = defaultContainer
					}
				}
			}

			if currOpts.Container == "" {
				// Default to the first container name(aligning behavior with `kubectl exec').
				currOpts.Container = t.Spec.Containers[0].Name
				if len(t.Spec.Containers) > 1 || len(t.Spec.InitContainers) > 0 || len(t.Spec.EphemeralContainers) > 0 {
					fmt.Fprintf(os.Stderr, "Defaulted container %q out of: %s\n", currOpts.Container, podcmd.AllContainerNames(t))
				}
			}

			container, fieldPath := podcmd.FindContainerByName(t, currOpts.Container)
			if container == nil {
				return nil, fmt.Errorf("container %s is not valid for pod %s", currOpts.Container, t.Name)
			}
			ref, err := reference.GetPartialReference(scheme.Scheme, t, fieldPath)
			if err != nil {
				return nil, fmt.Errorf("Unable to construct reference to '%#v': %v", t, err)
			}

			ret := make(map[corev1.ObjectReference]rest.ResponseWrapper, 1)
			ret[*ref] = clientset.Pods(t.Namespace).GetLogs(t.Name, currOpts)
			return ret, nil
		}

		ret := make(map[corev1.ObjectReference]rest.ResponseWrapper)
		for _, c := range t.Spec.InitContainers {
			currOpts := opts.DeepCopy()
			currOpts.Container = c.Name
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
			if err != nil {
				return nil, err
			}
			for k, v := range currRet {
				ret[k] = v
			}
		}
		for _, c := range t.Spec.Containers {
			currOpts := opts.DeepCopy()
			currOpts.Container = c.Name
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
			if err != nil {
				return nil, err
			}
			for k, v := range currRet {
				ret[k] = v
			}
		}
		for _, c := range t.Spec.EphemeralContainers {
			currOpts := opts.DeepCopy()
			currOpts.Container = c.Name
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
			if err != nil {
				return nil, err
			}
			for k, v := range currRet {
				ret[k] = v
			}
		}

		return ret, nil
	}

	namespace, selector, err := SelectorsForObject(object)
	if err != nil {
		return nil, fmt.Errorf("cannot get the logs from %T: %v", object, err)
	}

	sortBy := func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) }
	pod, numPods, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
	if err != nil {
		return nil, err
	}
	if numPods > 1 {
		fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
	}

	return logsForObjectWithClient(clientset, pod, options, timeout, allContainers)
}


mapBasedSelectorForObject

// mapBasedSelectorForObject returns the map-based selector associated with the provided object. If a
// new set-based selector is provided, an error is returned if the selector cannot be converted to a
// map-based selector
func mapBasedSelectorForObject(object runtime.Object) (string, error) {
	// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
	switch t := object.(type) {
	case *corev1.ReplicationController:
		return MakeLabels(t.Spec.Selector), nil

	case *corev1.Pod:
		if len(t.Labels) == 0 {
			return "", fmt.Errorf("the pod has no labels and cannot be exposed")
		}
		return MakeLabels(t.Labels), nil

	case *corev1.Service:
		if t.Spec.Selector == nil {
			return "", fmt.Errorf("the service has no pod selector set")
		}
		return MakeLabels(t.Spec.Selector), nil

	case *extensionsv1beta1.Deployment:
		// "extensions" deployments use pod template labels if selector is not set.
		var labels map[string]string
		if t.Spec.Selector != nil {
			// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
			// operator, DoubleEquals operator and In operator with only one element in the set.
			if len(t.Spec.Selector.MatchExpressions) > 0 {
				return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
			}
			labels = t.Spec.Selector.MatchLabels
		} else {
			labels = t.Spec.Template.Labels
		}
		if len(labels) == 0 {
			return "", fmt.Errorf("the deployment has no labels or selectors and cannot be exposed")
		}
		return MakeLabels(labels), nil

	case *appsv1.Deployment:
		// "apps" deployments must have the selector set.
		if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
			return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
		}
		// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
		// operator, DoubleEquals operator and In operator with only one element in the set.
		if len(t.Spec.Selector.MatchExpressions) > 0 {
			return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
		}
		return MakeLabels(t.Spec.Selector.MatchLabels), nil

	case *appsv1beta2.Deployment:
		// "apps" deployments must have the selector set.
		if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
			return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
		}
		// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
		// operator, DoubleEquals operator and In operator with only one element in the set.
		if len(t.Spec.Selector.MatchExpressions) > 0 {
			return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
		}
		return MakeLabels(t.Spec.Selector.MatchLabels), nil

	case *appsv1beta1.Deployment:
		// "apps" deployments must have the selector set.
		if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
			return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
		}
		// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
		// operator, DoubleEquals operator and In operator with only one element in the set.
		if len(t.Spec.Selector.MatchExpressions) > 0 {
			return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
		}
		return MakeLabels(t.Spec.Selector.MatchLabels), nil

	case *extensionsv1beta1.ReplicaSet:
		// "extensions" replicasets use pod template labels if selector is not set.
		var labels map[string]string
		if t.Spec.Selector != nil {
			// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
			// operator, DoubleEquals operator and In operator with only one element in the set.
			if len(t.Spec.Selector.MatchExpressions) > 0 {
				return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
			}
			labels = t.Spec.Selector.MatchLabels
		} else {
			labels = t.Spec.Template.Labels
		}
		if len(labels) == 0 {
			return "", fmt.Errorf("the replica set has no labels or selectors and cannot be exposed")
		}
		return MakeLabels(labels), nil

	case *appsv1.ReplicaSet:
		// "apps" replicasets must have the selector set.
		if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
			return "", fmt.Errorf("invalid replicaset: no selectors, therefore cannot be exposed")
		}
		// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
		// operator, DoubleEquals operator and In operator with only one element in the set.
		if len(t.Spec.Selector.MatchExpressions) > 0 {
			return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
		}
		return MakeLabels(t.Spec.Selector.MatchLabels), nil

	case *appsv1beta2.ReplicaSet:
		// "apps" replicasets must have the selector set.
		if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
			return "", fmt.Errorf("invalid replicaset: no selectors, therefore cannot be exposed")
		}
		// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
		// operator, DoubleEquals operator and In operator with only one element in the set.
		if len(t.Spec.Selector.MatchExpressions) > 0 {
			return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
		}
		return MakeLabels(t.Spec.Selector.MatchLabels), nil

	default:
		return "", fmt.Errorf("cannot extract pod selector from %T", object)
	}

}


MakeLabels

func MakeLabels(labels map[string]string) string {
	out := []string{}
	for key, value := range labels {
		out = append(out, fmt.Sprintf("%s=%s", key, value))
	}
	return strings.Join(out, ",")
}


defaultObjectPauser

// Currently only supports Deployments.
func defaultObjectPauser(obj runtime.Object) ([]byte, error) {
	switch obj := obj.(type) {
	case *extensionsv1beta1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("is already paused")
		}
		obj.Spec.Paused = true
		return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)

	case *appsv1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("is already paused")
		}
		obj.Spec.Paused = true
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)

	case *appsv1beta2.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("is already paused")
		}
		obj.Spec.Paused = true
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)

	case *appsv1beta1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("is already paused")
		}
		obj.Spec.Paused = true
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)

	default:
		return nil, fmt.Errorf("pausing is not supported")
	}
}


defaultObjectRestarter

func defaultObjectRestarter(obj runtime.Object) ([]byte, error) {
	switch obj := obj.(type) {
	case *extensionsv1beta1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("can't restart paused deployment (run rollout resume first)")
		}
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)

	case *appsv1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("can't restart paused deployment (run rollout resume first)")
		}
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)

	case *appsv1beta2.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("can't restart paused deployment (run rollout resume first)")
		}
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)

	case *appsv1beta1.Deployment:
		if obj.Spec.Paused {
			return nil, errors.New("can't restart paused deployment (run rollout resume first)")
		}
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)

	case *extensionsv1beta1.DaemonSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)

	case *appsv1.DaemonSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)

	case *appsv1beta2.DaemonSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)

	case *appsv1.StatefulSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)

	case *appsv1beta1.StatefulSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)

	case *appsv1beta2.StatefulSet:
		if obj.Spec.Template.ObjectMeta.Annotations == nil {
			obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
		}
		obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)

	default:
		return nil, fmt.Errorf("restarting is not supported")
	}
}


defaultObjectResumer

func defaultObjectResumer(obj runtime.Object) ([]byte, error) {
	switch obj := obj.(type) {
	case *extensionsv1beta1.Deployment:
		if !obj.Spec.Paused {
			return nil, errors.New("is not paused")
		}
		obj.Spec.Paused = false
		return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)

	case *appsv1.Deployment:
		if !obj.Spec.Paused {
			return nil, errors.New("is not paused")
		}
		obj.Spec.Paused = false
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)

	case *appsv1beta2.Deployment:
		if !obj.Spec.Paused {
			return nil, errors.New("is not paused")
		}
		obj.Spec.Paused = false
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)

	case *appsv1beta1.Deployment:
		if !obj.Spec.Paused {
			return nil, errors.New("is not paused")
		}
		obj.Spec.Paused = false
		return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)

	default:
		return nil, fmt.Errorf("resuming is not supported")
	}
}


portsForObject

func portsForObject(object runtime.Object) ([]string, error) {
	switch t := object.(type) {
	case *corev1.ReplicationController:
		return getPorts(t.Spec.Template.Spec), nil

	case *corev1.Pod:
		return getPorts(t.Spec), nil

	case *corev1.Service:
		return getServicePorts(t.Spec), nil

	case *extensionsv1beta1.Deployment:
		return getPorts(t.Spec.Template.Spec), nil
	case *appsv1.Deployment:
		return getPorts(t.Spec.Template.Spec), nil
	case *appsv1beta2.Deployment:
		return getPorts(t.Spec.Template.Spec), nil
	case *appsv1beta1.Deployment:
		return getPorts(t.Spec.Template.Spec), nil

	case *extensionsv1beta1.ReplicaSet:
		return getPorts(t.Spec.Template.Spec), nil
	case *appsv1.ReplicaSet:
		return getPorts(t.Spec.Template.Spec), nil
	case *appsv1beta2.ReplicaSet:
		return getPorts(t.Spec.Template.Spec), nil
	default:
		return nil, fmt.Errorf("cannot extract ports from %T", object)
	}
}


getPorts

func getPorts(spec corev1.PodSpec) []string {
	result := []string{}
	for _, container := range spec.Containers {
		for _, port := range container.Ports {
			result = append(result, strconv.Itoa(int(port.ContainerPort)))
		}
	}
	return result
}


getServicePorts

func getServicePorts(spec corev1.ServiceSpec) []string {
	result := []string{}
	for _, servicePort := range spec.Ports {
		result = append(result, strconv.Itoa(int(servicePort.Port)))
	}
	return result
}


protocolsForObject

func protocolsForObject(object runtime.Object) (map[string]string, error) {
	// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
	switch t := object.(type) {
	case *corev1.ReplicationController:
		return getProtocols(t.Spec.Template.Spec), nil

	case *corev1.Pod:
		return getProtocols(t.Spec), nil

	case *corev1.Service:
		return getServiceProtocols(t.Spec), nil

	case *extensionsv1beta1.Deployment:
		return getProtocols(t.Spec.Template.Spec), nil
	case *appsv1.Deployment:
		return getProtocols(t.Spec.Template.Spec), nil
	case *appsv1beta2.Deployment:
		return getProtocols(t.Spec.Template.Spec), nil
	case *appsv1beta1.Deployment:
		return getProtocols(t.Spec.Template.Spec), nil

	case *extensionsv1beta1.ReplicaSet:
		return getProtocols(t.Spec.Template.Spec), nil
	case *appsv1.ReplicaSet:
		return getProtocols(t.Spec.Template.Spec), nil
	case *appsv1beta2.ReplicaSet:
		return getProtocols(t.Spec.Template.Spec), nil

	default:
		return nil, fmt.Errorf("cannot extract protocols from %T", object)
	}
}


getProtocols

func getProtocols(spec corev1.PodSpec) map[string]string {
	result := make(map[string]string)
	for _, container := range spec.Containers {
		for _, port := range container.Ports {
			// Empty protocol must be defaulted (TCP)
			if len(port.Protocol) == 0 {
				port.Protocol = corev1.ProtocolTCP
			}
			result[strconv.Itoa(int(port.ContainerPort))] = string(port.Protocol)
		}
	}
	return result
}


getServiceProtocols

// Extracts the protocols exposed by a service from the given service spec.
func getServiceProtocols(spec corev1.ServiceSpec) map[string]string {
	result := make(map[string]string)
	for _, servicePort := range spec.Ports {
		// Empty protocol must be defaulted (TCP)
		if len(servicePort.Protocol) == 0 {
			servicePort.Protocol = corev1.ProtocolTCP
		}
		result[strconv.Itoa(int(servicePort.Port))] = string(servicePort.Protocol)
	}
	return result
}


VisitDeployment

func (v *RollbackVisitor) VisitDeployment(elem apps.GroupKindElement) {
	v.result = &DeploymentRollbacker{v.clientset}
}


VisitStatefulSet

func (v *RollbackVisitor) VisitStatefulSet(kind apps.GroupKindElement) {
	v.result = &StatefulSetRollbacker{v.clientset}
}


VisitDaemonSet

func (v *RollbackVisitor) VisitDaemonSet(kind apps.GroupKindElement) {
	v.result = &DaemonSetRollbacker{v.clientset}
}


VisitJob

func (v *RollbackVisitor) VisitJob(kind apps.GroupKindElement)                   {}


VisitPod

func (v *RollbackVisitor) VisitPod(kind apps.GroupKindElement)                   {}


VisitReplicaSet

func (v *RollbackVisitor) VisitReplicaSet(kind apps.GroupKindElement)            {}


VisitReplicationController

func (v *RollbackVisitor) VisitReplicationController(kind apps.GroupKindElement) {}


VisitCronJob

func (v *RollbackVisitor) VisitCronJob(kind apps.GroupKindElement)               {}


RollbackerFor

// RollbackerFor returns an implementation of Rollbacker interface for the given schema kind
func RollbackerFor(kind schema.GroupKind, c kubernetes.Interface) (Rollbacker, error) {
	elem := apps.GroupKindElement(kind)
	visitor := &RollbackVisitor{
		clientset: c,
	}

	err := elem.Accept(visitor)

	if err != nil {
		return nil, fmt.Errorf("error retrieving rollbacker for %q, %v", kind.String(), err)
	}

	if visitor.result == nil {
		return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind)
	}

	return visitor.result, nil
}


Rollback

func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRunStrategy cmdutil.DryRunStrategy) (string, error) {
	if toRevision < 0 {
		return "", revisionNotFoundErr(toRevision)
	}
	accessor, err := meta.Accessor(obj)
	if err != nil {
		return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
	}
	name := accessor.GetName()
	namespace := accessor.GetNamespace()

	// TODO: Fix this after kubectl has been removed from core. It is not possible to convert the runtime.Object
	// to the external appsv1 Deployment without round-tripping through an internal version of Deployment. We're
	// currently getting rid of all internal versions of resources. So we specifically request the appsv1 version
	// here. This follows the same pattern as for DaemonSet and StatefulSet.
	deployment, err := r.c.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return "", fmt.Errorf("failed to retrieve Deployment %s: %v", name, err)
	}

	rsForRevision, err := deploymentRevision(deployment, r.c, toRevision)
	if err != nil {
		return "", err
	}
	if dryRunStrategy == cmdutil.DryRunClient {
		return printTemplate(&rsForRevision.Spec.Template)
	}
	if deployment.Spec.Paused {
		return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume' and try again")
	}

	// Skip if the revision already matches current Deployment
	if equalIgnoreHash(&rsForRevision.Spec.Template, &deployment.Spec.Template) {
		return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
	}

	// remove hash label before patching back into the deployment
	delete(rsForRevision.Spec.Template.Labels, appsv1.DefaultDeploymentUniqueLabelKey)

	// compute deployment annotations
	annotations := map[string]string{}
	for k := range annotationsToSkip {
		if v, ok := deployment.Annotations[k]; ok {
			annotations[k] = v
		}
	}
	for k, v := range rsForRevision.Annotations {
		if !annotationsToSkip[k] {
			annotations[k] = v
		}
	}

	// make patch to restore
	patchType, patch, err := getDeploymentPatch(&rsForRevision.Spec.Template, annotations)
	if err != nil {
		return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
	}

	patchOptions := metav1.PatchOptions{}
	if dryRunStrategy == cmdutil.DryRunServer {
		patchOptions.DryRun = []string{metav1.DryRunAll}
	}
	// Restore revision
	if _, err = r.c.AppsV1().Deployments(namespace).Patch(context.TODO(), name, patchType, patch, patchOptions); err != nil {
		return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
	}
	return rollbackSuccess, nil
}


equalIgnoreHash

// equalIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because:
//  1. The hash result would be different upon podTemplateSpec API changes
//     (e.g. the addition of a new field will cause the hash code to change)
//  2. The deployment template won't have hash labels
func equalIgnoreHash(template1, template2 *corev1.PodTemplateSpec) bool {
	t1Copy := template1.DeepCopy()
	t2Copy := template2.DeepCopy()
	// Remove hash labels from template.Labels before comparing
	delete(t1Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
	delete(t2Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
	return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
}


getDeploymentPatch

// getPatch returns a patch that can be applied to restore a Deployment to a
// previous version. If the returned error is nil the patch is valid.
func getDeploymentPatch(podTemplate *corev1.PodTemplateSpec, annotations map[string]string) (types.PatchType, []byte, error) {
	// Create a patch of the Deployment that replaces spec.template
	patch, err := json.Marshal([]interface{}{
		map[string]interface{}{
			"op":    "replace",
			"path":  "/spec/template",
			"value": podTemplate,
		},
		map[string]interface{}{
			"op":    "replace",
			"path":  "/metadata/annotations",
			"value": annotations,
		},
	})
	return types.JSONPatchType, patch, err
}


deploymentRevision

func deploymentRevision(deployment *appsv1.Deployment, c kubernetes.Interface, toRevision int64) (revision *appsv1.ReplicaSet, err error) {

	_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, c.AppsV1())
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err)
	}
	allRSs := allOldRSs
	if newRS != nil {
		allRSs = append(allRSs, newRS)
	}

	var (
		latestReplicaSet   *appsv1.ReplicaSet
		latestRevision     = int64(-1)
		previousReplicaSet *appsv1.ReplicaSet
		previousRevision   = int64(-1)
	)
	for _, rs := range allRSs {
		if v, err := deploymentutil.Revision(rs); err == nil {
			if toRevision == 0 {
				if latestRevision < v {
					// newest one we've seen so far
					previousRevision = latestRevision
					previousReplicaSet = latestReplicaSet
					latestRevision = v
					latestReplicaSet = rs
				} else if previousRevision < v {
					// second newest one we've seen so far
					previousRevision = v
					previousReplicaSet = rs
				}
			} else if toRevision == v {
				return rs, nil
			}
		}
	}

	if toRevision > 0 {
		return nil, revisionNotFoundErr(toRevision)
	}

	if previousReplicaSet == nil {
		return nil, fmt.Errorf("no rollout history found for deployment %q", deployment.Name)
	}
	return previousReplicaSet, nil
}


Rollback

func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRunStrategy cmdutil.DryRunStrategy) (string, error) {
	if toRevision < 0 {
		return "", revisionNotFoundErr(toRevision)
	}
	accessor, err := meta.Accessor(obj)
	if err != nil {
		return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
	}
	ds, history, err := daemonSetHistory(r.c.AppsV1(), accessor.GetNamespace(), accessor.GetName())
	if err != nil {
		return "", err
	}
	if toRevision == 0 && len(history) <= 1 {
		return "", fmt.Errorf("no last revision to roll back to")
	}

	toHistory := findHistory(toRevision, history)
	if toHistory == nil {
		return "", revisionNotFoundErr(toRevision)
	}

	if dryRunStrategy == cmdutil.DryRunClient {
		appliedDS, err := applyDaemonSetHistory(ds, toHistory)
		if err != nil {
			return "", err
		}
		return printPodTemplate(&appliedDS.Spec.Template)
	}

	// Skip if the revision already matches current DaemonSet
	done, err := daemonSetMatch(ds, toHistory)
	if err != nil {
		return "", err
	}
	if done {
		return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
	}

	patchOptions := metav1.PatchOptions{}
	if dryRunStrategy == cmdutil.DryRunServer {
		patchOptions.DryRun = []string{metav1.DryRunAll}
	}
	// Restore revision
	if _, err = r.c.AppsV1().DaemonSets(accessor.GetNamespace()).Patch(context.TODO(), accessor.GetName(), types.StrategicMergePatchType, toHistory.Data.Raw, patchOptions); err != nil {
		return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
	}

	return rollbackSuccess, nil
}


daemonSetMatch

// daemonMatch check if the given DaemonSet's template matches the template stored in the given history.
func daemonSetMatch(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (bool, error) {
	patch, err := getDaemonSetPatch(ds)
	if err != nil {
		return false, err
	}
	return bytes.Equal(patch, history.Data.Raw), nil
}


getDaemonSetPatch

// getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
// previous version. If the returned error is nil the patch is valid. The current state that we save is just the
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
// recorded patches.
func getDaemonSetPatch(ds *appsv1.DaemonSet) ([]byte, error) {
	dsBytes, err := json.Marshal(ds)
	if err != nil {
		return nil, err
	}
	var raw map[string]interface{}
	err = json.Unmarshal(dsBytes, &raw)
	if err != nil {
		return nil, err
	}
	objCopy := make(map[string]interface{})
	specCopy := make(map[string]interface{})

	// Create a patch of the DaemonSet that replaces spec.template
	spec := raw["spec"].(map[string]interface{})
	template := spec["template"].(map[string]interface{})
	specCopy["template"] = template
	template["$patch"] = "replace"
	objCopy["spec"] = specCopy
	patch, err := json.Marshal(objCopy)
	return patch, err
}