Skip to content

Commit 38f2020

Browse files
authored
Merge pull request #65 from carlydf/img-prefix
Prefix Build ID with image tag of first container, suffix with short pod spec hash
2 parents 070afa5 + 91d0a3f commit 38f2020

14 files changed

Lines changed: 672 additions & 66 deletions

File tree

api/v1alpha1/make.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package v1alpha1
2+
3+
import (
4+
v1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"k8s.io/apimachinery/pkg/types"
7+
)
8+
9+
func MakeTWD(
10+
replicas int32,
11+
podSpec v1.PodTemplateSpec,
12+
rolloutStrategy *RolloutStrategy,
13+
sunsetStrategy *SunsetStrategy,
14+
workerOpts *WorkerOptions,
15+
) *TemporalWorkerDeployment {
16+
r := RolloutStrategy{}
17+
s := SunsetStrategy{}
18+
w := WorkerOptions{}
19+
if rolloutStrategy != nil {
20+
r = *rolloutStrategy
21+
}
22+
if sunsetStrategy != nil {
23+
s = *sunsetStrategy
24+
}
25+
if workerOpts != nil {
26+
w = *workerOpts
27+
}
28+
29+
twd := &TemporalWorkerDeployment{
30+
TypeMeta: metav1.TypeMeta{
31+
APIVersion: "temporal.io/v1alpha1",
32+
Kind: "TemporalWorkerDeployment",
33+
},
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: "test-worker",
36+
Namespace: "default",
37+
UID: types.UID("test-owner-uid"),
38+
},
39+
Spec: TemporalWorkerDeploymentSpec{
40+
Replicas: &replicas,
41+
Template: podSpec,
42+
RolloutStrategy: r,
43+
SunsetStrategy: s,
44+
WorkerOptions: w,
45+
},
46+
}
47+
twd.Name = twd.ObjectMeta.Name
48+
return twd
49+
}
50+
51+
// MakePodSpec creates a pod spec. Feel free to add parameters as needed.
52+
func MakePodSpec(containers []v1.Container, labels map[string]string) v1.PodTemplateSpec {
53+
return v1.PodTemplateSpec{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Labels: labels,
56+
},
57+
Spec: v1.PodSpec{
58+
Containers: containers,
59+
},
60+
}
61+
}
62+
63+
func MakeTWDWithImage(imageName string) *TemporalWorkerDeployment {
64+
return MakeTWD(1, MakePodSpec([]v1.Container{{Image: imageName}}, nil), nil, nil, nil)
65+
}
66+
67+
func MakeTWDWithName(name string) *TemporalWorkerDeployment {
68+
twd := MakeTWD(1, MakePodSpec(nil, nil), nil, nil, nil)
69+
twd.ObjectMeta.Name = name
70+
twd.Name = name
71+
return twd
72+
}

api/v1alpha1/temporalworker_webhook.go

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,23 @@
55
package v1alpha1
66

77
import (
8+
"context"
9+
"fmt"
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
11+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
"k8s.io/apimachinery/pkg/util/validation/field"
815
ctrl "sigs.k8s.io/controller-runtime"
916
"sigs.k8s.io/controller-runtime/pkg/webhook"
17+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
18+
"time"
19+
)
20+
21+
const (
22+
defaultScaledownDelay = 1 * time.Hour
23+
defaultDeleteDelay = 24 * time.Hour
24+
maxTemporalWorkerDeploymentNameLen = 63
1025
)
1126

1227
func (r *TemporalWorkerDeployment) SetupWebhookWithManager(mgr ctrl.Manager) error {
@@ -17,8 +32,62 @@ func (r *TemporalWorkerDeployment) SetupWebhookWithManager(mgr ctrl.Manager) err
1732

1833
//+kubebuilder:webhook:path=/mutate-temporal-io-temporal-io-v1alpha1-temporalworkerdeployment,mutating=true,failurePolicy=fail,sideEffects=None,groups=temporal.io.temporal.io,resources=temporalworkers,verbs=create;update,versions=v1alpha1,name=mtemporalworker.kb.io,admissionReviewVersions=v1
1934

20-
var _ webhook.Defaulter = &TemporalWorkerDeployment{}
35+
var _ webhook.CustomDefaulter = &TemporalWorkerDeployment{}
36+
var _ webhook.CustomValidator = &TemporalWorkerDeployment{}
37+
38+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
39+
func (r *TemporalWorkerDeployment) Default(ctx context.Context, obj runtime.Object) error {
40+
dep, ok := obj.(*TemporalWorkerDeployment)
41+
if !ok {
42+
return apierrors.NewBadRequest("expected a TemporalWorkerDeployment")
43+
}
44+
45+
if dep.Spec.SunsetStrategy.ScaledownDelay == nil {
46+
dep.Spec.SunsetStrategy.ScaledownDelay = &v1.Duration{Duration: defaultScaledownDelay}
47+
}
48+
49+
if dep.Spec.SunsetStrategy.DeleteDelay == nil {
50+
dep.Spec.SunsetStrategy.DeleteDelay = &v1.Duration{Duration: defaultDeleteDelay}
51+
}
52+
return nil
53+
}
54+
55+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
56+
func (r *TemporalWorkerDeployment) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
57+
return r.validateForUpdateOrCreate(ctx, obj)
58+
}
59+
60+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
61+
func (r *TemporalWorkerDeployment) ValidateUpdate(ctx context.Context, oldObj runtime.Object, newObj runtime.Object) (admission.Warnings, error) {
62+
return r.validateForUpdateOrCreate(ctx, newObj)
63+
}
64+
65+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
66+
func (r *TemporalWorkerDeployment) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
67+
return nil, nil
68+
}
69+
70+
func (r *TemporalWorkerDeployment) validateForUpdateOrCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
71+
dep, ok := obj.(*TemporalWorkerDeployment)
72+
if !ok {
73+
return nil, apierrors.NewBadRequest("expected a TemporalWorkerDeployment")
74+
}
75+
76+
var allErrs field.ErrorList
77+
78+
if len(dep.Name) > maxTemporalWorkerDeploymentNameLen {
79+
allErrs = append(allErrs,
80+
field.Invalid(field.NewPath("name"), dep.Name, fmt.Sprintf("cannot be more than %d characters", maxTemporalWorkerDeploymentNameLen)),
81+
)
82+
}
83+
84+
if len(allErrs) > 0 {
85+
return nil, apierrors.NewInvalid(
86+
schema.GroupKind{Group: "your.group", Kind: "TemporalWorkerDeployment"},
87+
dep.Name,
88+
allErrs,
89+
)
90+
}
2191

22-
// Default implements webhook.Defaulter so a webhook will be registered for the type
23-
func (r *TemporalWorkerDeployment) Default() {
92+
return nil, nil
2493
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
//
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2024 Datadog, Inc.
4+
5+
package v1alpha1
6+
7+
import (
8+
"context"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
v1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/runtime"
16+
)
17+
18+
func TestTemporalWorkerDeployment_ValidateCreate(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
obj runtime.Object
22+
expectError bool
23+
errorMsg string
24+
}{
25+
{
26+
name: "valid temporal worker deployment",
27+
obj: MakeTWDWithName("valid-worker"),
28+
expectError: false,
29+
},
30+
{
31+
name: "temporal worker deployment with name too long",
32+
obj: MakeTWDWithName("this-is-a-very-long-temporal-worker-deployment-name-that-exceeds-the-maximum-allowed-length-of-sixty-three-characters"),
33+
expectError: true,
34+
errorMsg: "cannot be more than 63 characters",
35+
},
36+
{
37+
name: "invalid object type",
38+
obj: &v1.Pod{
39+
ObjectMeta: metav1.ObjectMeta{
40+
Name: "test",
41+
},
42+
},
43+
expectError: true,
44+
errorMsg: "expected a TemporalWorkerDeployment",
45+
},
46+
}
47+
48+
for _, tt := range tests {
49+
t.Run(tt.name, func(t *testing.T) {
50+
ctx := context.Background()
51+
webhook := &TemporalWorkerDeployment{}
52+
53+
warnings, err := webhook.ValidateCreate(ctx, tt.obj)
54+
55+
if tt.expectError {
56+
require.Error(t, err)
57+
assert.Contains(t, err.Error(), tt.errorMsg)
58+
} else {
59+
require.NoError(t, err)
60+
}
61+
62+
// Warnings should always be nil for this implementation
63+
assert.Nil(t, warnings)
64+
})
65+
}
66+
}
67+
68+
func TestTemporalWorkerDeployment_ValidateUpdate(t *testing.T) {
69+
tests := []struct {
70+
name string
71+
oldObj runtime.Object
72+
newObj runtime.Object
73+
expectError bool
74+
errorMsg string
75+
}{
76+
{
77+
name: "valid update",
78+
oldObj: nil,
79+
newObj: MakeTWDWithName("valid-worker"),
80+
expectError: false,
81+
},
82+
{
83+
name: "update with name too long",
84+
oldObj: nil,
85+
newObj: MakeTWDWithName("this-is-a-very-long-temporal-worker-deployment-name-that-exceeds-the-maximum-allowed-length-of-sixty-three-characters"),
86+
expectError: true,
87+
errorMsg: "cannot be more than 63 characters",
88+
},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
ctx := context.Background()
94+
webhook := &TemporalWorkerDeployment{}
95+
96+
warnings, err := webhook.ValidateUpdate(ctx, tt.oldObj, tt.newObj)
97+
98+
if tt.expectError {
99+
require.Error(t, err)
100+
assert.Contains(t, err.Error(), tt.errorMsg)
101+
} else {
102+
require.NoError(t, err)
103+
}
104+
105+
// Warnings should always be nil for this implementation
106+
assert.Nil(t, warnings)
107+
})
108+
}
109+
}
110+
111+
func TestTemporalWorkerDeployment_ValidateDelete(t *testing.T) {
112+
ctx := context.Background()
113+
webhook := &TemporalWorkerDeployment{}
114+
115+
obj := &TemporalWorkerDeployment{
116+
ObjectMeta: metav1.ObjectMeta{
117+
Name: "worker",
118+
},
119+
}
120+
121+
warnings, err := webhook.ValidateDelete(ctx, obj)
122+
123+
// ValidateDelete should always return nil, nil
124+
assert.NoError(t, err)
125+
assert.Nil(t, warnings)
126+
}

docs/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Temporal Worker Controller Documentation
2+
3+
This directory contains extended documentation for the Temporal Worker Controller project. The documentation is organized into different categories to help you find the information you need.
4+
5+
6+
This documentation structure is designed to support various types of technical documentation, such as:
7+
8+
- **Limits**: Technical constraints and limitations of the system
9+
- **Runbooks**: Operational procedures and troubleshooting guides (planned)
10+
- **Conceptual Guides**: High-level explanations of concepts and architecture (planned)
11+
12+
## Index
13+
14+
### [Limits](limits.md)
15+
Technical constraints and limitations of the Temporal Worker Controller system, including maximum field lengths and other operational boundaries.
16+
17+
---
18+
19+
*Note: This documentation structure is designed to grow with the project.*

docs/limits.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Limits
2+
3+
- Max `TemporalWorkerDeployment.Name` length: 63 characters
4+
- Max `BuildID` length: 63 characters

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
require (
2323
github.com/beorn7/perks v1.0.1 // indirect
2424
github.com/cespare/xxhash/v2 v2.3.0 // indirect
25+
github.com/distribution/reference v0.6.0 // indirect
2526
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
2627
github.com/emirpasic/gods v1.12.0 // indirect
2728
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
@@ -59,6 +60,7 @@ require (
5960
github.com/modern-go/reflect2 v1.0.2 // indirect
6061
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
6162
github.com/nexus-rpc/sdk-go v0.3.0 // indirect
63+
github.com/opencontainers/go-digest v1.0.0 // indirect
6264
github.com/pborman/uuid v1.2.1 // indirect
6365
github.com/pkg/errors v0.9.1 // indirect
6466
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
2525
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2626
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
2727
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28+
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
29+
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
2830
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
2931
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
3032
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
@@ -154,6 +156,8 @@ github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo
154156
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
155157
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
156158
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
159+
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
160+
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
157161
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
158162
github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
159163
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=

internal/controller/genstatus.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ func (r *TemporalWorkerDeploymentReconciler) generateStatus(
2626
req ctrl.Request,
2727
workerDeploy *temporaliov1alpha1.TemporalWorkerDeployment,
2828
) (*temporaliov1alpha1.TemporalWorkerDeploymentStatus, error) {
29-
workerDeploymentName := computeWorkerDeploymentName(workerDeploy)
30-
targetVersionID := computeVersionID(workerDeploy)
29+
workerDeploymentName := k8s.ComputeWorkerDeploymentName(workerDeploy)
30+
targetVersionID := k8s.ComputeVersionID(workerDeploy)
3131

3232
// Fetch Kubernetes deployment state
3333
k8sState, err := k8s.GetDeploymentState(

internal/controller/k8s.io/utils/utils.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ func deepHashObject(hasher hash.Hash, objectToWrite interface{}) {
5757
// ComputeHash returns a hash value calculated from pod template and
5858
// a collisionCount to avoid hash collision. The hash will be safe encoded to
5959
// avoid bad words.
60+
// If `short` is true, the hash is truncated to 4 digits
6061
//
6162
// Copied from https://github.com/kubernetes/kubernetes/blob/86fec81606b579cc478a30656c29ddb400a72dc6/pkg/controller/controller_utils.go#L1174
62-
func ComputeHash(template *v1.PodTemplateSpec, collisionCount *int32) string {
63+
func ComputeHash(template *v1.PodTemplateSpec, collisionCount *int32, short bool) string {
6364
podTemplateSpecHasher := fnv.New32a()
6465
deepHashObject(podTemplateSpecHasher, *template)
6566

@@ -70,5 +71,8 @@ func ComputeHash(template *v1.PodTemplateSpec, collisionCount *int32) string {
7071
podTemplateSpecHasher.Write(collisionCountBytes)
7172
}
7273

74+
if short {
75+
return rand.SafeEncodeString(fmt.Sprintf("%04d", podTemplateSpecHasher.Sum32()%10000))
76+
}
7377
return rand.SafeEncodeString(fmt.Sprint(podTemplateSpecHasher.Sum32()))
7478
}

0 commit comments

Comments
 (0)