mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
220 lines
7.2 KiB
Go
220 lines
7.2 KiB
Go
// Copyright 2023 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package v1alpha1
|
|
|
|
import (
|
|
"bytes"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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/parser"
|
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
//go:embed templates/*
|
|
var templateFS embed.FS
|
|
|
|
func (a *ExampleApp) Schema() (*spec.Schema, error) {
|
|
schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk(Group, Version, Kind), CRDString)
|
|
return schema, errors.WrapPrefixf(err, "parsing %s schema", Kind)
|
|
}
|
|
|
|
func (a *ExampleApp) Default() error {
|
|
if a.AppImage == "" {
|
|
a.AppImage = fmt.Sprintf("registry.example.com/path/to/%s", a.ObjectMeta.Name)
|
|
}
|
|
for i := range a.Workloads.WebWorkers {
|
|
if a.Workloads.WebWorkers[i].Replicas == nil {
|
|
a.Workloads.WebWorkers[i].Replicas = func() *int { three := 3; return &three }()
|
|
}
|
|
if a.Workloads.WebWorkers[i].Resources == "" {
|
|
a.Workloads.WebWorkers[i].Resources = ResourceBinSizeSmall
|
|
}
|
|
}
|
|
for i := range a.Workloads.JobWorkers {
|
|
if a.Workloads.JobWorkers[i].Replicas == nil {
|
|
a.Workloads.JobWorkers[i].Replicas = func() *int { one := 1; return &one }()
|
|
}
|
|
if a.Workloads.JobWorkers[i].Resources == "" {
|
|
a.Workloads.JobWorkers[i].Resources = ResourceBinSizeSmall
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *ExampleApp) Validate() error {
|
|
seenDomains := make(map[string]bool)
|
|
for i, worker := range a.Workloads.WebWorkers {
|
|
for _, domain := range worker.Domains {
|
|
if seenDomains[domain] {
|
|
return errors.Errorf("duplicate domain %q in worker %d", domain, i)
|
|
}
|
|
seenDomains[domain] = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a ExampleApp) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
templates := make([]framework.ResourceTemplate, 0)
|
|
for _, worker := range a.Workloads.JobWorkers {
|
|
templates = append(templates, framework.ResourceTemplate{
|
|
Templates: parser.TemplateFiles("templates/job_worker.template.yaml").FromFS(templateFS),
|
|
TemplateData: a.jobWorkerTemplateData(worker),
|
|
})
|
|
}
|
|
for _, worker := range a.Workloads.WebWorkers {
|
|
templates = append(templates, framework.ResourceTemplate{
|
|
Templates: parser.TemplateFiles("templates/web_worker.template.yaml").FromFS(templateFS),
|
|
TemplateData: a.webWorkerTemplateData(worker),
|
|
})
|
|
}
|
|
|
|
var patches []framework.PatchTemplate
|
|
if a.Datastores.PostgresInstance != "" {
|
|
templates = append(templates, framework.ResourceTemplate{
|
|
TemplateData: map[string]interface{}{"Name": a.Datastores.PostgresInstance},
|
|
Templates: parser.TemplateStrings(`apiVersion: apps.example.com/v1
|
|
kind: PostgresSecretRequest
|
|
metadata:
|
|
name: {{ .Name }}
|
|
`)})
|
|
patches = append(patches, framework.PatchTemplate(&framework.ContainerPatchTemplate{
|
|
Templates: parser.TemplateFiles("templates/postgres_secret_env_patch.template.yaml").FromFS(templateFS),
|
|
ContainerMatcher: framework.ContainerNameMatcher("app"),
|
|
}))
|
|
}
|
|
|
|
if len(a.Overrides.AdditionalResources) > 0 {
|
|
templates = append(templates, framework.ResourceTemplate{
|
|
Templates: parser.TemplateFiles(a.Overrides.AdditionalResources...).WithExtensions(".yaml", ".template.yaml"),
|
|
TemplateData: a,
|
|
})
|
|
}
|
|
|
|
for i, resource := range a.Overrides.ResourcePatches {
|
|
overridePatches, err := a.resourceSMPsFromOverrides(resource, i, patches)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
patches = append(patches, overridePatches...)
|
|
}
|
|
|
|
if len(a.Overrides.ContainerPatches) > 0 {
|
|
patches = append(patches, framework.PatchTemplate(&framework.ContainerPatchTemplate{
|
|
Templates: parser.TemplateFiles(a.Overrides.ContainerPatches...).WithExtensions(".yaml", ".template.yaml"),
|
|
TemplateData: a,
|
|
}))
|
|
}
|
|
|
|
items, err := framework.TemplateProcessor{
|
|
ResourceTemplates: templates,
|
|
PatchTemplates: patches,
|
|
}.Filter(items)
|
|
if err != nil {
|
|
return nil, errors.WrapPrefixf(err, "processing templates")
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// resourceSMPsFromOverrides parses the resource template and returns a patch that
|
|
// is targeted to match resources with the same GVKNN the patch itself contains.
|
|
// TODO: This is standard SMP semantics, so the framework should make this easier.
|
|
func (a ExampleApp) resourceSMPsFromOverrides(resource string, i int, patches []framework.PatchTemplate) ([]framework.PatchTemplate, error) {
|
|
tpl, err := parser.TemplateFiles(resource).WithExtensions(".yaml", ".template.yaml").Parse()
|
|
if err != nil {
|
|
return nil, errors.WrapPrefixf(err, "parsing resource template %d", i)
|
|
}
|
|
for _, template := range tpl {
|
|
var b bytes.Buffer
|
|
if err := template.Execute(&b, a); err != nil {
|
|
return nil, errors.WrapPrefixf(err, "failed to render patch template %v", template.DefinedTemplates())
|
|
}
|
|
var id yaml.ResourceMeta
|
|
err := yaml.Unmarshal(b.Bytes(), &id)
|
|
if err != nil {
|
|
return nil, errors.WrapPrefixf(err, "failed to unmarshal resource identifier from %v", template.DefinedTemplates())
|
|
}
|
|
selector := framework.MatchAll(
|
|
framework.GVKMatcher(strings.Join([]string{id.APIVersion, id.Kind}, "/")), framework.NameMatcher(id.Name),
|
|
framework.NamespaceMatcher(id.Namespace))
|
|
selector.FailOnEmptyMatch = true
|
|
patches = append(patches, framework.PatchTemplate(&framework.ResourcePatchTemplate{
|
|
Templates: parser.TemplateFiles(a.Overrides.ResourcePatches...).WithExtensions(".yaml", ".template.yaml"),
|
|
Selector: selector,
|
|
}))
|
|
}
|
|
return patches, nil
|
|
}
|
|
|
|
type resourceBucket struct {
|
|
Requests resourceAllocation `yaml:"requests" json:"requests"`
|
|
Limits resourceAllocation `yaml:"limits" json:"limits"`
|
|
}
|
|
|
|
type resourceAllocation struct {
|
|
CPU string `yaml:"cpu" json:"cpu"`
|
|
Memory string `yaml:"memory" json:"memory"`
|
|
}
|
|
|
|
//nolint:gochecknoglobals
|
|
var resourceBucketConversion = map[ResourceBinSize]resourceBucket{
|
|
"small": {
|
|
Requests: resourceAllocation{CPU: "100m", Memory: "128Mi"},
|
|
Limits: resourceAllocation{CPU: "500m", Memory: "512Mi"},
|
|
},
|
|
"medium": {
|
|
Requests: resourceAllocation{CPU: "1", Memory: "1Gi"},
|
|
Limits: resourceAllocation{CPU: "2", Memory: "2Gi"},
|
|
},
|
|
"large": {
|
|
Requests: resourceAllocation{CPU: "8", Memory: "8Gi"},
|
|
Limits: resourceAllocation{CPU: "8", Memory: "8Gi"},
|
|
},
|
|
}
|
|
|
|
const anArbitraryMultiplier = 2
|
|
|
|
func (a ExampleApp) jobWorkerTemplateData(w JobWorker) map[string]interface{} {
|
|
resourcesJson, err := json.Marshal(resourceBucketConversion[w.Resources])
|
|
if err != nil {
|
|
panic("failed to marshal resources for job worker" + err.Error())
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"Name": w.Name,
|
|
"AppImage": a.AppImage,
|
|
"QueueList": strings.Join(w.Queues, ","),
|
|
"ProcessPoolSize": len(w.Queues) * anArbitraryMultiplier,
|
|
"Resources": string(resourcesJson),
|
|
"Replicas": w.Replicas,
|
|
"Environment": a.Env,
|
|
}
|
|
}
|
|
|
|
const containerPort = 8080
|
|
|
|
func (a ExampleApp) webWorkerTemplateData(w WebWorker) map[string]interface{} {
|
|
resourcesJson, err := json.Marshal(resourceBucketConversion[w.Resources])
|
|
if err != nil {
|
|
panic("failed to marshal resources for web worker" + err.Error())
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"Name": w.Name,
|
|
"AppImage": a.AppImage,
|
|
"Resources": string(resourcesJson),
|
|
"Replicas": w.Replicas,
|
|
"Port": containerPort,
|
|
"Environment": a.Env,
|
|
}
|
|
}
|