Skip to content

Add ReleaseChannel CRD #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
golang 1.22.2
golang 1.22.5
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY controllers/ controllers/
COPY internal/ internal/
COPY pkg/ pkg/

# Build
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Image URL to use all building/pushing image targets
IMG ?= ghcr.io/nais/unleasherator:main
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.28.3
ENVTEST_K8S_VERSION = 1.30.0

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
Expand Down Expand Up @@ -48,7 +48,7 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and

.PHONY: proto
proto: protoc protoc-gen-go ## Generate protobuf files.
$(PROTOC) --go_opt=paths=source_relative --plugin $(LOCALBIN)/protoc-gen-go --go_out=. pkg/pb/unleasherator.proto
$(PROTOC) --go_opt=paths=source_relative --plugin $(LOCALBIN)/protoc-gen-go --go_out=. internal/pb/unleasherator.proto

.PHONY: fmt
fmt: ## Run go fmt against code.
Expand Down
15 changes: 14 additions & 1 deletion PROJECT
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Code generated by tool. DO NOT EDIT.
# This file is used to track the info used to scaffold your project
# and allow the plugins properly work.
# More info: https://book.kubebuilder.io/reference/project-config.html
componentConfig: true
domain: nais.io
layout:
- go.kubebuilder.io/v4-alpha
- go.kubebuilder.io/v4
projectName: unleasherator
repo: github.com/nais/unleasherator
resources:
Expand Down Expand Up @@ -32,4 +36,13 @@ resources:
kind: RemoteUnleash
path: github.com/nais/unleasherator/api/v1
version: v1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: nais.io
group: unleash
kind: ReleaseChannel
path: github.com/nais/unleasherator/api/v1
version: v1
version: "3"
4 changes: 2 additions & 2 deletions api/v1/apitoken_types.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package unleash_nais_io_v1
package v1

import (
"fmt"
"strings"

"github.com/nais/unleasherator/internal/utils"
"github.com/nais/unleasherator/pkg/unleashclient"
"github.com/nais/unleasherator/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
Expand Down
2 changes: 1 addition & 1 deletion api/v1/apitoken_types_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package unleash_nais_io_v1
package v1

import (
"testing"
Expand Down
5 changes: 2 additions & 3 deletions api/v1/meta.go → api/v1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Package v1 contains API Schema definitions for the unleash.nais.io v1 API group
// Package v1 contains API Schema definitions for the unleash v1 API group
// +kubebuilder:object:generate=true
// +groupName=unleash.nais.io
// +versionName=v1
package unleash_nais_io_v1
package v1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down
114 changes: 114 additions & 0 deletions api/v1/releasechannel_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

// ReleaseChannelSpec defines the desired state of ReleaseChannel
type ReleaseChannelSpec struct {
// Image is the Docker image to deploy for the release channel.
// +kubebuilder:validation:Required
Image UnleashImage `json:"image,omitempty"`

// Strategy defines the deployment strategy.
Strategy ReleaseChannelStrategy `json:"strategy,omitempty"`
}

type ReleaseChannelStrategy struct {
// Canary defines the canary strategy.
Canary ReleaseChannelCanary `json:"canary,omitempty"`

// MaxParallel defines the maximum number of instances to deploy in parallel.
// +kubebuilder:default=1
MaxParallel int `json:"maxParallel,omitempty"`
}

type ReleaseChannelCanary struct {
// Enabled defines if canary is enabled.
// +kubebuilder:default=false
Enabled bool `json:"enabled,omitempty"`

// LabelSelector is the label selector for the canary instances.
// Unleash instances matching this selector will be considered canary instances.
LabelSelector metav1.LabelSelector `json:"podSelector,omitempty"`
}

// ReleaseChannelStatus defines the observed state of ReleaseChannel
type ReleaseChannelStatus struct {
// Conditions is a list of conditions for the release channel.
Conditions []metav1.Condition `json:"conditions,omitempty"`

// Version is the version of the release channel.
// +kubebuilder:default="unknown"
Version string `json:"version,omitempty"`

// Instances is the number of instances for the release channel.
// +kubebuilder:default=0
Instances int `json:"instances,omitempty"`

// CanaryInstances is the number of canary instances for the release channel.
// +kubebuilder:default=0
CanaryInstances int `json:"canaryInstances,omitempty"`

// InstancesUpToDate is the number of instances that are up to date.
// +kubebuilder:default=0
InstancesUpToDate int `json:"instancesUpToDate,omitempty"`

// CanaryInstancesUpToDate is the number of canary instances that are up to date.
// +kubebuilder:default=0
CanaryInstancesUpToDate int `json:"canaryInstancesUpToDate,omitempty"`
}

type ReleaseChannelCondition struct {
// Type is the type of the condition.
Type string `json:"type,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.status.version`
// +kubebuilder:printcolumn:name="Instances",type=integer,JSONPath=`.status.instances`
// +kubebuilder:printcolumn:name="InstancesUpToDate",type=integer,JSONPath=`.status.instancesUpToDate`
// +kubebuilder:printcolumn:name="Canaries",type=integer,JSONPath=`.status.canaryInstances`
// +kubebuilder:printcolumn:name="CanariesUpToDate",type=integer,JSONPath=`.status.canaryInstancesUpToDate`

// ReleaseChannel is the Schema for the releasechannels API
type ReleaseChannel struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ReleaseChannelSpec `json:"spec,omitempty"`
Status ReleaseChannelStatus `json:"status,omitempty"`
}

// IsCandidate checks if the release channel is a candidate for the given Unleash instance.
func (rc *ReleaseChannel) IsCandidate(u *Unleash) bool {
return rc.Name == u.Spec.ReleaseChannel.Name && rc.Namespace == u.Namespace
}

// ShouldUpdate checks if the release channel should update the given Unleash instance.
func (rc *ReleaseChannel) ShouldUpdate(u *Unleash) bool {
return rc.IsCandidate(u) && rc.Spec.Image != u.Spec.CustomImage
}

// NamespacedName returns the namespaced name of the release channel resource.
func (rc *ReleaseChannel) NamespacedName() types.NamespacedName {
return types.NamespacedName{
Namespace: rc.Namespace,
Name: rc.Name,
}
}

//+kubebuilder:object:root=true

// ReleaseChannelList contains a list of ReleaseChannel
type ReleaseChannelList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ReleaseChannel `json:"items"`
}

func init() {
SchemeBuilder.Register(&ReleaseChannel{}, &ReleaseChannelList{})
}
163 changes: 163 additions & 0 deletions api/v1/releasechannel_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package v1

import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestIsCandidate(t *testing.T) {
testCases := []struct {
name string
releaseChannel *ReleaseChannel
unleash *Unleash
expectedResult bool
}{
{
name: "Matching release channel",
releaseChannel: &ReleaseChannel{
ObjectMeta: metav1.ObjectMeta{
Name: "test-channel",
Namespace: "test-namespace",
},
},
unleash: &Unleash{
ObjectMeta: metav1.ObjectMeta{
Name: "test-unleash",
Namespace: "test-namespace",
},
Spec: UnleashSpec{
ReleaseChannel: UnleashReleaseChannelConfig{
Name: "test-channel",
},
},
},
expectedResult: true,
},
{
name: "Non-matching release channel",
releaseChannel: &ReleaseChannel{
ObjectMeta: metav1.ObjectMeta{
Name: "test-channel",
Namespace: "test-namespace",
},
},
unleash: &Unleash{
ObjectMeta: metav1.ObjectMeta{
Name: "test-unleash",
Namespace: "test-namespace",
},
Spec: UnleashSpec{
ReleaseChannel: UnleashReleaseChannelConfig{
Name: "other-channel",
},
},
},
expectedResult: false,
},
// Add more test cases here
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.releaseChannel.IsCandidate(tc.unleash)
if result != tc.expectedResult {
t.Errorf("Expected release channel to be a candidate: %v, but got: %v", tc.expectedResult, result)
}
})
}
}
func TestShouldUpdate(t *testing.T) {
testCases := []struct {
name string
releaseChannel *ReleaseChannel
unleash *Unleash
expectedResult bool
}{
{
name: "Matching release channel with different image",
releaseChannel: &ReleaseChannel{
ObjectMeta: metav1.ObjectMeta{
Name: "test-channel",
Namespace: "test-namespace",
},
Spec: ReleaseChannelSpec{
Image: "test-image",
},
},
unleash: &Unleash{
ObjectMeta: metav1.ObjectMeta{
Name: "test-unleash",
Namespace: "test-namespace",
},
Spec: UnleashSpec{
ReleaseChannel: UnleashReleaseChannelConfig{
Name: "test-channel",
},
CustomImage: "custom-image",
},
},
expectedResult: true,
},
{
name: "Matching release channel with same image",
releaseChannel: &ReleaseChannel{
ObjectMeta: metav1.ObjectMeta{
Name: "test-channel",
Namespace: "test-namespace",
},
Spec: ReleaseChannelSpec{
Image: "test-image",
},
},
unleash: &Unleash{
ObjectMeta: metav1.ObjectMeta{
Name: "test-unleash",
Namespace: "test-namespace",
},
Spec: UnleashSpec{
ReleaseChannel: UnleashReleaseChannelConfig{
Name: "test-channel",
},
CustomImage: "test-image",
},
},
expectedResult: false,
},
{
name: "Non-matching release channel",
releaseChannel: &ReleaseChannel{
ObjectMeta: metav1.ObjectMeta{
Name: "test-channel",
Namespace: "test-namespace",
},
Spec: ReleaseChannelSpec{
Image: "test-image",
},
},
unleash: &Unleash{
ObjectMeta: metav1.ObjectMeta{
Name: "test-unleash",
Namespace: "test-namespace",
},
Spec: UnleashSpec{
ReleaseChannel: UnleashReleaseChannelConfig{
Name: "other-channel",
},
CustomImage: "custom-image",
},
},
expectedResult: false,
},
// Add more test cases here
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.releaseChannel.ShouldUpdate(tc.unleash)
if result != tc.expectedResult {
t.Errorf("Expected release channel to require update: %v, but got: %v", tc.expectedResult, result)
}
})
}
}
2 changes: 1 addition & 1 deletion api/v1/remoteunleash_types.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package unleash_nais_io_v1
package v1

import (
"context"
Expand Down
2 changes: 1 addition & 1 deletion api/v1/remoteunleash_types_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package unleash_nais_io_v1
package v1

import (
"context"
Expand Down
2 changes: 1 addition & 1 deletion api/v1/status.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package unleash_nais_io_v1
package v1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

Expand Down
2 changes: 1 addition & 1 deletion api/v1/status_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package unleash_nais_io_v1
package v1

import (
"testing"
Expand Down
Loading
Loading