mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +00:00
Add a rich example of fn framework for abstraction
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
# This generates a useful starting point for a CRD that can be used for validation.
|
||||
echo " - Generating CRD"
|
||||
controller-gen crd paths=./... output:crd:dir=.
|
||||
|
||||
# controller-gen does not currently support "additionalProperties: false".
|
||||
# This hack adds it manually to all properties sections of the schema.
|
||||
echo " - Adding additionalProperties: false to all properties sections"
|
||||
if [[ "$OSTYPE" == linux* ]]; then
|
||||
# Linux (GNU sed) expression
|
||||
sed -i "s/\(^[[:space:]]*\)properties:/\1additionalProperties: false\n\1properties:/g" ./platform.example.com_exampleapps.yaml
|
||||
elif [[ "$OSTYPE" == darwin* ]]; then
|
||||
# macOS (BSD sed) expression
|
||||
sed -i "" "s/\(^[[:space:]]*\)properties:/\1additionalProperties: false\n\1properties:/g" ./platform.example.com_exampleapps.yaml
|
||||
else
|
||||
echo "Unsupported OS: $OSTYPE"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
package v1alpha1_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/functions/examples/fn-framework-application/pkg/dispatcher"
|
||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
|
||||
)
|
||||
|
||||
func TestExampleApp_GoldenFiles(t *testing.T) {
|
||||
c := frameworktestutil.CommandResultsChecker{
|
||||
Command: dispatcher.NewCommand,
|
||||
// TestDataDirectory: "testdata/success",
|
||||
// UpdateExpectedFromActual: true,
|
||||
}
|
||||
c.Assert(t)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.3
|
||||
creationTimestamp: null
|
||||
name: exampleapps.platform.example.com
|
||||
spec:
|
||||
group: platform.example.com
|
||||
names:
|
||||
kind: ExampleApp
|
||||
listKind: ExampleAppList
|
||||
plural: exampleapps
|
||||
singular: exampleapp
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
appImage:
|
||||
type: string
|
||||
datastores:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
postgresInstance:
|
||||
type: string
|
||||
type: object
|
||||
env:
|
||||
enum:
|
||||
- production
|
||||
- staging
|
||||
- development
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
overrides:
|
||||
minProperties: 1
|
||||
additionalProperties: false
|
||||
properties:
|
||||
additionalResources:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
containerPatches:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
resourcePatches:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
workloads:
|
||||
minProperties: 1
|
||||
additionalProperties: false
|
||||
properties:
|
||||
jobWorkers:
|
||||
items:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
name:
|
||||
pattern: ^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
queues:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
uniqueItems: true
|
||||
replicas:
|
||||
minimum: 0
|
||||
type: integer
|
||||
resources:
|
||||
enum:
|
||||
- small
|
||||
- medium
|
||||
- large
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- queues
|
||||
type: object
|
||||
type: array
|
||||
webWorkers:
|
||||
items:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
domains:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
uniqueItems: true
|
||||
name:
|
||||
pattern: ^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
replicas:
|
||||
minimum: 0
|
||||
type: integer
|
||||
resources:
|
||||
enum:
|
||||
- small
|
||||
- medium
|
||||
- large
|
||||
type: string
|
||||
required:
|
||||
- domains
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- env
|
||||
- metadata
|
||||
- workloads
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
@@ -0,0 +1,216 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
type: jobs
|
||||
name: {{ .Name }}
|
||||
spec:
|
||||
replicas: {{ .Replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
type: jobs
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
type: jobs
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: app
|
||||
image: {{ .AppImage }}
|
||||
args: [
|
||||
"job-worker",
|
||||
"--queues",
|
||||
"{{ .QueueList }}",
|
||||
"--workers", "{{ .ProcessPoolSize }}",
|
||||
]
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- "bin/job-worker-readiness-probe"
|
||||
resources: {{ .Resources }}
|
||||
env:
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: ENV
|
||||
value: {{ .Environment }}
|
||||
@@ -0,0 +1,6 @@
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-url
|
||||
key: DATABASE_URL
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Name }}
|
||||
labels:
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
type: web
|
||||
spec:
|
||||
replicas: {{ .Replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
type: web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: {{ .Name }}
|
||||
env: {{ .Environment }}
|
||||
type: web
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: {{ .AppImage }}
|
||||
args:
|
||||
- web
|
||||
ports:
|
||||
- containerPort: {{ .Port }}
|
||||
name: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: "/ping"
|
||||
port: {{ .Port }}
|
||||
httpHeaders:
|
||||
- name: "X-Forwarded-Proto"
|
||||
value: "https"
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 3
|
||||
env:
|
||||
- name: ENV
|
||||
value: {{ .Environment }}
|
||||
- name: PORT
|
||||
value: "{{ .Port }}"
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
resources: {{ .Resources }}
|
||||
21
functions/examples/fn-framework-application/pkg/exampleapp/v1alpha1/testdata/error/config.yaml
vendored
Normal file
21
functions/examples/fn-framework-application/pkg/exampleapp/v1alpha1/testdata/error/config.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: platform.example.com/v1alpha1
|
||||
kind: ExampleApp
|
||||
metadata:
|
||||
name: simple-app-sample
|
||||
env: invalid
|
||||
workloads:
|
||||
webWorkers:
|
||||
- name: web-worker
|
||||
replicas: -1
|
||||
resources: small
|
||||
domains:
|
||||
- example.com
|
||||
- name: web-worker-2
|
||||
domains:
|
||||
- example.com
|
||||
jobWorkers:
|
||||
- name: job-worker-$$
|
||||
queues: []
|
||||
datastores:
|
||||
mongoDB: simple-app-sample-mongo
|
||||
extraProperty: true
|
||||
@@ -0,0 +1,8 @@
|
||||
validation failure list:
|
||||
.extraProperty in body is a forbidden property
|
||||
workloads.jobWorkers\[0\].name in body should match
|
||||
workloads.jobWorkers\[0\].queues in body should have at least 1 items
|
||||
workloads.webWorkers\[0\].replicas in body should be greater than or equal to 0
|
||||
datastores.mongoDB in body is a forbidden property
|
||||
env in body should be one of \[production staging development\]
|
||||
duplicate domain \"example.com\" in worker 1
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: platform.example.com/v1alpha1
|
||||
kind: ExampleApp
|
||||
metadata:
|
||||
name: simple-app-sample
|
||||
env: production
|
||||
workloads:
|
||||
webWorkers:
|
||||
- name: web-worker
|
||||
domains:
|
||||
- example.com
|
||||
jobWorkers:
|
||||
- name: job-worker
|
||||
replicas: 10
|
||||
resources: medium
|
||||
queues:
|
||||
- high
|
||||
- medium
|
||||
- low
|
||||
- name: job-worker-2
|
||||
replicas: 5
|
||||
queues:
|
||||
- bg2
|
||||
datastores:
|
||||
postgresInstance: simple-app-sample-postgres
|
||||
@@ -0,0 +1,149 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: job-worker
|
||||
labels:
|
||||
name: job-worker
|
||||
type: jobs
|
||||
env: production
|
||||
spec:
|
||||
replicas: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
name: job-worker
|
||||
type: jobs
|
||||
env: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: job-worker
|
||||
type: jobs
|
||||
env: production
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: app
|
||||
image: registry.example.com/path/to/simple-app-sample
|
||||
args: ["job-worker", "--queues", "high,medium,low", "--workers", "6"]
|
||||
env:
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-url
|
||||
key: DATABASE_URL
|
||||
resources: {"limits": {"cpu": "2", "memory": "2Gi"}, "requests": {"cpu": "1", "memory": "1Gi"}}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- "bin/job-worker-readiness-probe"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: job-worker-2
|
||||
labels:
|
||||
name: job-worker-2
|
||||
type: jobs
|
||||
env: production
|
||||
spec:
|
||||
replicas: 5
|
||||
selector:
|
||||
matchLabels:
|
||||
name: job-worker-2
|
||||
type: jobs
|
||||
env: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: job-worker-2
|
||||
type: jobs
|
||||
env: production
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: app
|
||||
image: registry.example.com/path/to/simple-app-sample
|
||||
args: ["job-worker", "--queues", "bg2", "--workers", "2"]
|
||||
env:
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-url
|
||||
key: DATABASE_URL
|
||||
resources: {"limits": {"cpu": "500m", "memory": "512Mi"}, "requests": {"cpu": "100m", "memory": "128Mi"}}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- "bin/job-worker-readiness-probe"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web-worker
|
||||
labels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: registry.example.com/path/to/simple-app-sample
|
||||
args:
|
||||
- web
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
env:
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: PORT
|
||||
value: "8080"
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-url
|
||||
key: DATABASE_URL
|
||||
resources: {"limits": {"cpu": "500m", "memory": "512Mi"}, "requests": {"cpu": "100m", "memory": "128Mi"}}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
httpHeaders:
|
||||
- name: "X-Forwarded-Proto"
|
||||
value: "https"
|
||||
path: "/ping"
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 3
|
||||
---
|
||||
apiVersion: apps.example.com/v1
|
||||
kind: PostgresSecretRequest
|
||||
metadata:
|
||||
name: simple-app-sample-postgres
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: platform.example.com/v1alpha1
|
||||
kind: ExampleApp
|
||||
metadata:
|
||||
name: simple-app-sample
|
||||
env: production
|
||||
workloads:
|
||||
webWorkers:
|
||||
- name: web-worker
|
||||
domains:
|
||||
- first.example.com
|
||||
- name: web-worker-no-sidecar
|
||||
domains:
|
||||
- second.example.com
|
||||
|
||||
overrides:
|
||||
additionalResources:
|
||||
- custom-configmap.yaml
|
||||
resourcePatches:
|
||||
- web-worker-sidecar.yaml
|
||||
containerPatches:
|
||||
- custom-app-env.yaml
|
||||
@@ -0,0 +1,6 @@
|
||||
env:
|
||||
- name: MY_NEW_VAR
|
||||
value: "new value"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: custom-configmap
|
||||
@@ -0,0 +1,6 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: custom-configmap
|
||||
data:
|
||||
LOADED: "true"
|
||||
@@ -0,0 +1,126 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web-worker
|
||||
labels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: web-worker
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: registry.example.com/path/to/simple-app-sample
|
||||
args:
|
||||
- web
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: custom-configmap
|
||||
env:
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: PORT
|
||||
value: "8080"
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: MY_NEW_VAR
|
||||
value: "new value"
|
||||
resources: {"limits": {"cpu": "500m", "memory": "512Mi"}, "requests": {"cpu": "100m", "memory": "128Mi"}}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
httpHeaders:
|
||||
- name: "X-Forwarded-Proto"
|
||||
value: "https"
|
||||
path: "/ping"
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 3
|
||||
- name: sidecar
|
||||
image: registry.example.com/path/to/custom-sidecar
|
||||
args:
|
||||
- run
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: custom-configmap
|
||||
env:
|
||||
- name: MY_NEW_VAR
|
||||
value: "new value"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web-worker-no-sidecar
|
||||
labels:
|
||||
name: web-worker-no-sidecar
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
name: web-worker-no-sidecar
|
||||
type: web
|
||||
env: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: web-worker-no-sidecar
|
||||
type: web
|
||||
env: production
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: registry.example.com/path/to/simple-app-sample
|
||||
args:
|
||||
- web
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: custom-configmap
|
||||
env:
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: PORT
|
||||
value: "8080"
|
||||
- name: KUBE_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: MY_NEW_VAR
|
||||
value: "new value"
|
||||
resources: {"limits": {"cpu": "500m", "memory": "512Mi"}, "requests": {"cpu": "100m", "memory": "128Mi"}}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
httpHeaders:
|
||||
- name: "X-Forwarded-Proto"
|
||||
value: "https"
|
||||
path: "/ping"
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 3
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: custom-configmap
|
||||
data:
|
||||
LOADED: "true"
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web-worker
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: sidecar
|
||||
image: registry.example.com/path/to/custom-sidecar
|
||||
args:
|
||||
- run
|
||||
@@ -0,0 +1,108 @@
|
||||
// +groupName=platform.example.com
|
||||
// +versionName=v1alpha1
|
||||
// +kubebuilder:validation:Required
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
//go:generate ./crd_gen.sh
|
||||
|
||||
//go:embed platform.example.com_exampleapps.yaml
|
||||
var CRDString string
|
||||
|
||||
const Group = "platform.example.com"
|
||||
const Version = "v1alpha1"
|
||||
const Kind = "ExampleApp"
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var GroupVersion = strings.Join([]string{Group, Version}, "/")
|
||||
|
||||
type ExampleApp struct {
|
||||
// Embedding these structs is required to use controller-gen to produce the CRD
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
// +kubebuilder:validation:Enum=production;staging;development
|
||||
Env string `json:"env" yaml:"env"`
|
||||
|
||||
// +optional
|
||||
AppImage string `json:"appImage" yaml:"appImage"`
|
||||
|
||||
Workloads Workloads `json:"workloads" yaml:"workloads"`
|
||||
|
||||
// +optional
|
||||
Datastores Datastores `json:"datastores,omitempty" yaml:"datastores,omitempty"`
|
||||
|
||||
// +optional
|
||||
Overrides Overrides `json:"overrides,omitempty" yaml:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinProperties=1
|
||||
type Overrides struct {
|
||||
// +optional
|
||||
AdditionalResources []string `json:"additionalResources,omitempty" yaml:"additionalResources,omitempty"`
|
||||
|
||||
// +optional
|
||||
ResourcePatches []string `json:"resourcePatches,omitempty" yaml:"resourcePatches,omitempty"`
|
||||
|
||||
// +optional
|
||||
ContainerPatches []string `json:"containerPatches,omitempty" yaml:"containerPatches,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinProperties=1
|
||||
type Workloads struct {
|
||||
// +optional
|
||||
JobWorkers []JobWorker `json:"jobWorkers,omitempty" yaml:"jobWorkers,omitempty"`
|
||||
|
||||
// +optional
|
||||
WebWorkers []WebWorker `json:"webWorkers,omitempty" yaml:"webWorkers,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Pattern="^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*$"
|
||||
type KubernetesMetaName string
|
||||
|
||||
type JobWorker struct {
|
||||
Name KubernetesMetaName `json:"name" yaml:"name"`
|
||||
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +optional
|
||||
Replicas *int `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||
|
||||
// +optional
|
||||
Resources ResourceBinSize `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:UniqueItems=true
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Queues []string `json:"queues" yaml:"queues"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=small;medium;large
|
||||
type ResourceBinSize string
|
||||
|
||||
const ResourceBinSizeSmall ResourceBinSize = "small"
|
||||
|
||||
type WebWorker struct {
|
||||
Name KubernetesMetaName `json:"name" yaml:"name"`
|
||||
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +optional
|
||||
Replicas *int `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||
|
||||
// +optional
|
||||
Resources ResourceBinSize `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:UniqueItems=true
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Domains []string `json:"domains" yaml:"domains"`
|
||||
}
|
||||
|
||||
type Datastores struct {
|
||||
// +optional
|
||||
PostgresInstance string `json:"postgresInstance,omitempty" yaml:"postgresInstance,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user