// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package framework_test

import (
	"bytes"
	"fmt"
	"log"
	"path/filepath"
	"strings"

	validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
	"k8s.io/kube-openapi/pkg/validation/spec"
	"sigs.k8s.io/kustomize/kyaml/errors"
	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
	"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
	"sigs.k8s.io/kustomize/kyaml/kio"
	"sigs.k8s.io/kustomize/kyaml/resid"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

const service = "Service"

// ExampleSimpleProcessor_modify implements a function that sets an annotation on each resource.
func ExampleSimpleProcessor_modify() {
	input := bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: foo
functionConfig:
  apiVersion: v1
  kind: ConfigMap
  data:
    value: baz
`)
	config := new(struct {
		Data map[string]string `yaml:"data" json:"data"`
	})
	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
		for i := range items {
			// set the annotation on each resource item
			if err := items[i].PipeE(yaml.SetAnnotation("value", config.Data["value"])); err != nil {
				return nil, err
			}
		}
		return items, nil
	}

	err := framework.Execute(framework.SimpleProcessor{Config: config, Filter: kio.FilterFunc(fn)}, &kio.ByteReadWriter{Reader: input})
	if err != nil {
		panic(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: foo
	//     annotations:
	//       value: 'baz'
	// - apiVersion: v1
	//   kind: Service
	//   metadata:
	//     name: foo
	//     annotations:
	//       value: 'baz'
	// functionConfig:
	//   apiVersion: v1
	//   kind: ConfigMap
	//   data:
	//     value: baz
}

// ExampleSimpleProcessor_generateReplace generates a resource from a FunctionConfig.
// If the resource already exists, it replaces the resource with a new copy.
func ExampleSimpleProcessor_generateReplace() {
	input := bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
`)

	// function API definition which will be parsed from the ResourceList.FunctionConfig
	// read from stdin
	type Spec struct {
		Name string `yaml:"name,omitempty"`
	}
	type ExampleServiceGenerator struct {
		Spec Spec `yaml:"spec,omitempty"`
	}

	functionConfig := &ExampleServiceGenerator{}

	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
		// remove the last generated resource
		var newNodes []*yaml.RNode
		for i := range items {
			meta, err := items[i].GetMeta()
			if err != nil {
				return nil, err
			}
			// something we already generated, remove it from the list so we regenerate it
			if meta.Name == functionConfig.Spec.Name &&
				meta.Kind == service &&
				meta.APIVersion == "v1" {
				continue
			}
			newNodes = append(newNodes, items[i])
		}
		items = newNodes

		// generate the resource again
		n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
 name: %s
`, functionConfig.Spec.Name))
		if err != nil {
			return nil, err
		}
		items = append(items, n)
		return items, nil
	}

	p := framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
	err := framework.Execute(p, &kio.ByteReadWriter{Reader: input})
	if err != nil {
		panic(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: foo
	// - apiVersion: v1
	//   kind: Service
	//   metadata:
	//     name: bar
	// functionConfig:
	//   apiVersion: example.com/v1alpha1
	//   kind: ExampleServiceGenerator
	//   spec:
	//     name: bar
}

// ExampleTemplateProcessor provides an example for using the TemplateProcessor to add resources
// from templates defined inline
func ExampleTemplateProcessor_generate_inline() {
	api := new(struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
	})
	// create the template
	fn := framework.TemplateProcessor{
		// Templates input
		TemplateData: api,
		// Templates
		ResourceTemplates: []framework.ResourceTemplate{{
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
 name: foo
 namespace: default
 annotations:
   {{ .Key }}: {{ .Value }}
`)}},
	}
	cmd := command.Build(fn, command.StandaloneEnabled, false)

	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
	cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	//   namespace: default
	//   annotations:
	//     a: b
}

// ExampleTemplateProcessor_files provides an example for using the TemplateProcessor to add
// resources from templates defined in files.
func ExampleTemplateProcessor_generate_files() {
	api := new(struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
	})
	// create the template
	templateFn := framework.TemplateProcessor{
		// Templates input
		TemplateData: api,
		// Templates
		ResourceTemplates: []framework.ResourceTemplate{{
			Templates: parser.TemplateFiles("testdata/example/templatefiles/deployment.template.yaml"),
		}},
	}
	cmd := command.Build(templateFn, command.StandaloneEnabled, false)
	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
	cmd.SetArgs([]string{filepath.Join("testdata", "example", "templatefiles", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

	// Output:
	// # Copyright 2021 The Kubernetes Authors.
	// # SPDX-License-Identifier: Apache-2.0
	//
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	//   namespace: default
	//   annotations:
	//     a: b
}

// ExampleTemplateProcessor_preprocess provides an example for using the TemplateProcessor
// with PreProcess to configure the template based on the input resources observed.
func ExampleTemplateProcessor_preprocess() {
	config := new(struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
		Short bool
	})

	// create the template
	fn := framework.TemplateProcessor{
		// Templates input
		TemplateData: config,
		PreProcessFilters: []kio.Filter{
			kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
				config.Short = len(items) < 3
				return items, nil
			}),
		},
		// Templates
		ResourceTemplates: []framework.ResourceTemplate{{
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
 name: foo
 namespace: default
 annotations:
   {{ .Key }}: {{ .Value }}
{{- if .Short }}
   short: 'true'
{{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: bar
 namespace: default
 annotations:
   {{ .Key }}: {{ .Value }}
{{- if .Short }}
   short: 'true'
{{- end }}
`),
		}},
	}

	cmd := command.Build(fn, command.StandaloneEnabled, false)
	// mimic standalone mode: testdata/template/config.yaml will be parsed into `api`
	cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	//   namespace: default
	//   annotations:
	//     a: b
	//     short: 'true'
	// ---
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: bar
	//   namespace: default
	//   annotations:
	//     a: b
	//     short: 'true'
}

// ExampleTemplateProcessor_postprocess provides an example for using the TemplateProcessor
// with PostProcess to modify the results.
func ExampleTemplateProcessor_postprocess() {
	config := new(struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
	})

	// create the template
	fn := framework.TemplateProcessor{
		// Templates input
		TemplateData: config,
		ResourceTemplates: []framework.ResourceTemplate{{
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
`),
		}},
		PostProcessFilters: []kio.Filter{
			kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
				items = items[1:]
				return items, nil
			}),
		},
	}
	cmd := command.Build(fn, command.StandaloneEnabled, false)

	cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: bar
	//   namespace: default
	//   annotations:
	//     a: b
}

// ExampleTemplateProcessor_patch provides an example for using the TemplateProcessor to
// create a function that patches resources.
func ExampleTemplateProcessor_patch() {
	fn := framework.TemplateProcessor{
		TemplateData: new(struct {
			Key   string `json:"key" yaml:"key"`
			Value string `json:"value" yaml:"value"`
		}),
		ResourceTemplates: []framework.ResourceTemplate{{
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
`),
		}},
		// PatchTemplates are applied to BOTH ResourceList input resources AND templated resources
		PatchTemplates: []framework.PatchTemplate{
			&framework.ResourcePatchTemplate{
				// patch the foo resource only
				Selector: &framework.Selector{Names: []string{"foo"}},
				Templates: parser.TemplateStrings(`
metadata:
  annotations:
    patched: 'true'
`),
			}},
	}
	cmd := command.Build(fn, command.StandaloneEnabled, false)

	cmd.SetArgs([]string{filepath.Join("testdata", "example", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	//   namespace: default
	//   annotations:
	//     a: b
	//     patched: 'true'
	// ---
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: bar
	//   namespace: default
	//   annotations:
	//     a: b
}

// ExampleTemplateProcessor_MergeResources provides an example for using the TemplateProcessor to
// create a function that treats incoming resources as potential patches
// for the resources the function generates itself.
func ExampleTemplateProcessor_MergeResources() {
	p := framework.TemplateProcessor{
		TemplateData: new(struct {
			Name string `json:"name" yaml:"name"`
		}),
		ResourceTemplates: []framework.ResourceTemplate{{
			// This is the generated resource the input will patch
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Name }}
spec:
  replicas: 1
  selector:
    app: foo
  template:
    spec:
      containers:
      - name: app
        image: example.io/team/app
`),
		}},
		MergeResources: true,
	}

	// The second resource will be treated as a patch since its metadata matches the resource
	// generated by ResourceTemplates and MergeResources is true.
	rw := kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
items:
- kind: Deployment
  apiVersion: apps/v1
  metadata:
    name: custom
  spec:
    replicas: 6
  selector:
    app: custom
  template:
    spec:
      containers:
      - name: app
        image: example.io/team/custom
- kind: Deployment
  apiVersion: apps/v1
  metadata:
    name: mergeTest
  spec:
    replicas: 6
functionConfig:
  name: mergeTest
`)}
	if err := framework.Execute(p, &rw); err != nil {
		panic(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: mergeTest
	//   spec:
	//     replicas: 6
	//     selector:
	//       app: foo
	//     template:
	//       spec:
	//         containers:
	//         - name: app
	//           image: example.io/team/app
	// - kind: Deployment
	//   apiVersion: apps/v1
	//   metadata:
	//     name: custom
	//   spec:
	//     replicas: 6
	//   selector:
	//     app: custom
	//   template:
	//     spec:
	//       containers:
	//       - name: app
	//         image: example.io/team/custom
	// functionConfig:
	//   name: mergeTest
}

// ExampleSelector_templatizeKinds provides an example of using a template as a selector value,
// to dynamically match resources based on the functionConfig input. It also shows how Selector
// can be used with SimpleProcessor to implement a ResourceListProcessor the filters the input.
func ExampleSelector_templatizeKinds() {
	type api struct {
		KindName string `yaml:"kindName"`
	}
	rw := &kio.ByteReadWriter{
		Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
functionConfig:
  kindName: Deployment
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
    namespace: default
- apiVersion: apps/v1
  kind: StatefulSet
  metadata:
    name: bar
    namespace: default
`),
	}
	config := &api{}
	p := framework.SimpleProcessor{
		Config: config,
		Filter: &framework.Selector{
			TemplateData: config,
			Kinds:        []string{"{{ .KindName }}"},
		},
	}

	err := framework.Execute(p, rw)
	if err != nil {
		panic(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: foo
	//     namespace: default
	// functionConfig:
	//   kindName: Deployment
}

// ExampleSelector_templatizeKinds provides an example of using a template as a selector value,
// to dynamically match resources based on the functionConfig input. It also shows how Selector
// can be used with SimpleProcessor to implement a ResourceListProcessor the filters the input.
func ExampleSelector_templatizeAnnotations() {
	type api struct {
		Value string `yaml:"value"`
	}
	rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
functionConfig:
  value: bar
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
    namespace: default
    annotations:
      key: foo
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: bar
    namespace: default
    annotations:
      key: bar
`)}
	config := &api{}
	p := framework.SimpleProcessor{
		Config: config,
		Filter: &framework.Selector{
			TemplateData: config,
			Annotations:  map[string]string{"key": "{{ .Value }}"},
		},
	}

	if err := framework.Execute(p, rw); err != nil {
		panic(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: bar
	//     namespace: default
	//     annotations:
	//       key: bar
	// functionConfig:
	//   value: bar
}

// ExampleTemplateProcessor_container_patch provides an example for using TemplateProcessor to
// patch all of the containers in the input.
func ExampleTemplateProcessor_container_patch() {
	input := `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: bar
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: baz
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
`
	p := framework.TemplateProcessor{
		PatchTemplates: []framework.PatchTemplate{
			&framework.ContainerPatchTemplate{
				Templates: parser.TemplateStrings(`
env:
- name: KEY
  value: {{ .Value }}
`),
				TemplateData: struct{ Value string }{Value: "new-value"},
			}},
	}
	err := framework.Execute(p, &kio.ByteReadWriter{Reader: bytes.NewBufferString(input)})
	if err != nil {
		log.Fatal(err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	// spec:
	//   template:
	//     spec:
	//       containers:
	//       - name: foo
	//         image: a
	//         env:
	//         - name: KEY
	//           value: new-value
	//       - name: bar
	//         image: b
	//         env:
	//         - name: KEY
	//           value: new-value
	// ---
	// apiVersion: v1
	// kind: Service
	// metadata:
	//   name: foo
	// spec:
	//   selector:
	//     foo: bar
	// ---
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: bar
	// spec:
	//   template:
	//     spec:
	//       containers:
	//       - name: foo
	//         image: a
	//         env:
	//         - name: KEY
	//           value: new-value
	//       - name: baz
	//         image: b
	//         env:
	//         - name: KEY
	//           value: new-value
	// ---
	// apiVersion: v1
	// kind: Service
	// metadata:
	//   name: bar
	// spec:
	//   selector:
	//     foo: bar
}

// PatchTemplateContainersWithString patches containers matching
// a specific name.
func ExampleTemplateProcessor_container_patch_by_name() {
	input := `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
        env:
        - name: EXISTING
          value: variable
      - name: bar
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: baz
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
`
	p := framework.TemplateProcessor{
		TemplateData: struct{ Value string }{Value: "new-value"},
		PatchTemplates: []framework.PatchTemplate{
			&framework.ContainerPatchTemplate{
				// Only patch containers named "foo"
				ContainerMatcher: framework.ContainerNameMatcher("foo"),
				Templates: parser.TemplateStrings(`
env:
- name: KEY
  value: {{ .Value }}
`),
			}},
	}

	err := framework.Execute(p, &kio.ByteReadWriter{Reader: bytes.NewBufferString(input)})
	if err != nil {
		log.Fatal(err)
	}

	// Output:
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: foo
	// spec:
	//   template:
	//     spec:
	//       containers:
	//       - name: foo
	//         image: a
	//         env:
	//         - name: EXISTING
	//           value: variable
	//         - name: KEY
	//           value: new-value
	//       - name: bar
	//         image: b
	// ---
	// apiVersion: v1
	// kind: Service
	// metadata:
	//   name: foo
	// spec:
	//   selector:
	//     foo: bar
	// ---
	// apiVersion: apps/v1
	// kind: Deployment
	// metadata:
	//   name: bar
	// spec:
	//   template:
	//     spec:
	//       containers:
	//       - name: foo
	//         image: a
	//         env:
	//         - name: KEY
	//           value: new-value
	//       - name: baz
	//         image: b
	// ---
	// apiVersion: v1
	// kind: Service
	// metadata:
	//   name: bar
	// spec:
	//   selector:
	//     foo: bar
}

type v1alpha1JavaSpringBoot struct {
	Metadata Metadata                   `yaml:"metadata" json:"metadata"`
	Spec     v1alpha1JavaSpringBootSpec `yaml:"spec" json:"spec"`
}

type Metadata struct {
	Name string `yaml:"name" json:"name"`
}

type v1alpha1JavaSpringBootSpec struct {
	Replicas int    `yaml:"replicas" json:"replicas"`
	Domain   string `yaml:"domain" json:"domain"`
	Image    string `yaml:"image" json:"image"`
}

func (a v1alpha1JavaSpringBoot) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
	filter := framework.TemplateProcessor{
		ResourceTemplates: []framework.ResourceTemplate{{
			TemplateData: &a,
			Templates: parser.TemplateStrings(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Metadata.Name }}
  selector:
    app: {{ .Metadata.Name }}
spec:
  replicas: {{ .Spec.Replicas }}
  template:
    spec:
      containers:
      - name: app
        image: {{ .Spec.Image }}
        {{ if .Spec.Domain }}
        ports:
          - containerPort: 80
            name: http
        {{ end }}

{{ if .Spec.Domain }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Metadata.Name }}-svc
spec:
  selector:
    app:  {{ .Metadata.Name }}
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name:  {{ .Metadata.Name }}-ingress
spec:
  tls:
  - hosts:
      - {{ .Spec.Domain }}
    secretName: secret-tls
  defaultBackend:
    service:
      name: {{ .Metadata.Name }}
      port:
        number: 80
{{ end }}
`),
		}},
	}
	return filter.Filter(items)
}

func (a *v1alpha1JavaSpringBoot) Default() error {
	if a.Spec.Replicas == 0 {
		a.Spec.Replicas = 3
	}
	return nil
}

var javaSpringBootDefinition = `
apiVersion: config.kubernetes.io/v1alpha1
kind: KRMFunctionDefinition
metadata:
  name: javaspringboot.example.com
spec:
  group: example.com
  names:
    kind: JavaSpringBoot
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
            properties:
              name:
                type: string
                minLength: 1
            required:
            - name
          spec:
            properties:
              domain:
                pattern: example\.com$
                type: string
              image:
                type: string
              replicas:
                maximum: 9
                minimum: 0
                type: integer
            type: object
        type: object
`

func (a v1alpha1JavaSpringBoot) Schema() (*spec.Schema, error) {
	schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk("example.com", "v1alpha1", "JavaSpringBoot"), javaSpringBootDefinition)
	return schema, errors.WrapPrefixf(err, "parsing JavaSpringBoot schema")
}

func (a *v1alpha1JavaSpringBoot) Validate() error {
	var errs []error
	if strings.HasSuffix(a.Spec.Image, ":latest") {
		errs = append(errs, errors.Errorf("spec.image should not have latest tag"))
	}
	if len(errs) > 0 {
		return validationErrors.CompositeValidationError(errs...)
	}
	return nil
}

// ExampleVersionedAPIProcessor shows how to use the VersionedAPIProcessor and TemplateProcessor to
// build functions that implement complex multi-version APIs that require defaulting and validation.
func ExampleVersionedAPIProcessor() {
	p := &framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{
		"JavaSpringBoot": {
			"example.com/v1alpha1": &v1alpha1JavaSpringBoot{},
		}}}

	source := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: JavaSpringBoot
  metadata:
    name: my-app
  spec:
    image: example.docker.com/team/app:1.0
    domain: demo.example.com
`)}
	if err := framework.Execute(p, source); err != nil {
		log.Fatal(err)
	}

	// Output:
	// apiVersion: config.kubernetes.io/v1
	// kind: ResourceList
	// items:
	// - apiVersion: apps/v1
	//   kind: Deployment
	//   metadata:
	//     name: my-app
	//     selector:
	//       app: my-app
	//   spec:
	//     replicas: 3
	//     template:
	//       spec:
	//         containers:
	//         - name: app
	//           image: example.docker.com/team/app:1.0
	//           ports:
	//           - containerPort: 80
	//             name: http
	// - apiVersion: v1
	//   kind: Service
	//   metadata:
	//     name: my-app-svc
	//   spec:
	//     selector:
	//       app: my-app
	//     ports:
	//     - protocol: TCP
	//       port: 80
	//       targetPort: 80
	// - apiVersion: networking.k8s.io/v1
	//   kind: Ingress
	//   metadata:
	//     name: my-app-ingress
	//   spec:
	//     tls:
	//     - hosts:
	//       - demo.example.com
	//       secretName: secret-tls
	//     defaultBackend:
	//       service:
	//         name: my-app
	//         port:
	//           number: 80
	// functionConfig:
	//   apiVersion: example.com/v1alpha1
	//   kind: JavaSpringBoot
	//   metadata:
	//     name: my-app
	//   spec:
	//     image: example.docker.com/team/app:1.0
	//     domain: demo.example.com
}
