Skip to content
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

Add new ephemeral package for registering and applying PodOptions changes #2874

Merged
merged 8 commits into from
May 10, 2024
21 changes: 18 additions & 3 deletions pkg/controllers/repositoryserver/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/client-go/kubernetes"

"github.com/kanisterio/kanister/pkg/consts"
"github.com/kanisterio/kanister/pkg/ephemeral"
"github.com/kanisterio/kanister/pkg/format"
"github.com/kanisterio/kanister/pkg/kopia/command/storage"
"github.com/kanisterio/kanister/pkg/kube"
Expand Down Expand Up @@ -138,6 +139,10 @@ func volumeMountSpecForName(podSpec corev1.PodSpec, podOverride map[string]inter
Name: "container",
VolumeMounts: mountList,
}

// Apply the registered ephemeral pod changes.
ephemeral.Container.Apply(ctr)

podOverride["containers"] = []corev1.Container{*ctr}
return mount.Name, true
}
Expand Down Expand Up @@ -177,9 +182,14 @@ func addTLSCertConfigurationInPodOverride(podOverride *map[string]interface{}, t
})

if len(podOverrideSpec.Containers) == 0 {
podOverrideSpec.Containers = append(podOverrideSpec.Containers, corev1.Container{
container := corev1.Container{
Name: kube.ContainerNameFromPodOptsOrDefault(po),
})
}

// Apply the registered ephemeral pod changes.
ephemeral.Container.Apply(&container)

podOverrideSpec.Containers = append(podOverrideSpec.Containers, container)
}

podOverrideSpec.Containers[0].VolumeMounts = append(podOverrideSpec.Containers[0].VolumeMounts, corev1.VolumeMount{
Expand All @@ -202,7 +212,7 @@ func addTLSCertConfigurationInPodOverride(podOverride *map[string]interface{}, t
func getPodOptions(namespace string, svc *corev1.Service, vols map[string]kube.VolumeMountOptions) *kube.PodOptions {
uidguid := int64(0)
nonRootBool := false
return &kube.PodOptions{
options := &kube.PodOptions{
Namespace: namespace,
GenerateName: fmt.Sprintf("%s-", repoServerPod),
Image: consts.GetKanisterToolsImage(),
Expand All @@ -215,6 +225,11 @@ func getPodOptions(namespace string, svc *corev1.Service, vols map[string]kube.V
},
Volumes: vols,
}

// Apply the registered ephemeral pod changes.
ephemeral.PodOptions.Apply(options)

return options
}

func getPodAddress(ctx context.Context, cli kubernetes.Interface, namespace, podName string) (string, error) {
Expand Down
61 changes: 61 additions & 0 deletions pkg/ephemeral/envvar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ephemeral
kidgilson marked this conversation as resolved.
Show resolved Hide resolved

import (
"os"

"github.com/kanisterio/kanister/pkg/kube"
corev1 "k8s.io/api/core/v1"
kidgilson marked this conversation as resolved.
Show resolved Hide resolved
)

// OSEnvVar creates an ApplierSet to set an environment variable if its present
kidgilson marked this conversation as resolved.
Show resolved Hide resolved
// in the current environment.
func OSEnvVar(name string) ApplierSet {
return ApplierSet{
Container: ApplierFunc[corev1.Container](func(container *corev1.Container) {
if val, present := os.LookupEnv(name); present {
container.Env = append(
container.Env,
corev1.EnvVar{
Name: name,
Value: val,
},
)
}
}),
PodOptions: ApplierFunc[kube.PodOptions](func(options *kube.PodOptions) {
if val, present := os.LookupEnv(name); present {
options.EnvironmentVariables = append(
options.EnvironmentVariables,
corev1.EnvVar{
Name: name,
Value: val,
},
)
}
}),
}
}

// StaticEnvVar creates an ApplierSet to set a static environment variable.
func StaticEnvVar(name, value string) ApplierSet {
return ApplierSet{
Container: ApplierFunc[corev1.Container](func(container *corev1.Container) {
container.Env = append(
container.Env,
corev1.EnvVar{
Name: name,
Value: value,
},
)
}),
PodOptions: ApplierFunc[kube.PodOptions](func(options *kube.PodOptions) {
options.EnvironmentVariables = append(
options.EnvironmentVariables,
corev1.EnvVar{
Name: name,
Value: value,
},
)
}),
}
}
99 changes: 99 additions & 0 deletions pkg/ephemeral/envvar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package ephemeral_test

import (
"os"

. "gopkg.in/check.v1"
corev1 "k8s.io/api/core/v1"

"github.com/kanisterio/kanister/pkg/ephemeral"
"github.com/kanisterio/kanister/pkg/kube"
)

type EnvVarSuite struct{}

var _ = Suite(&EnvVarSuite{})

func (s *EnvVarSuite) TestOSEnvVarKubePodOptions(c *C) {
expected := []corev1.EnvVar{
{
Name: "KANISTER_REGISTERED_OS_ENVVAR",
Value: "1",
},
}

// OS environment variable not set
var registeredAppliers ephemeral.ApplierList[kube.PodOptions]
set := ephemeral.OSEnvVar(expected[0].Name)
registeredAppliers.Register(set.PodOptions)

var options kube.PodOptions
registeredAppliers.Apply(&options)
c.Assert(options.EnvironmentVariables, DeepEquals, []corev1.EnvVar(nil))

// OS environment variable set
os.Setenv(expected[0].Name, expected[0].Value)
defer os.Unsetenv(expected[0].Name)

registeredAppliers.Apply(&options)
c.Assert(options.EnvironmentVariables, DeepEquals, expected)
}

func (s *EnvVarSuite) TestOSEnvVarCoreV1Container(c *C) {
expected := []corev1.EnvVar{
{
Name: "KANISTER_REGISTERED_OS_ENVVAR",
Value: "1",
},
}

// OS environment variable not set
var registeredAppliers ephemeral.ApplierList[corev1.Container]
set := ephemeral.OSEnvVar(expected[0].Name)
registeredAppliers.Register(set.Container)

var options corev1.Container
registeredAppliers.Apply(&options)
c.Assert(options.Env, DeepEquals, []corev1.EnvVar(nil))

// OS environment variable set
os.Setenv(expected[0].Name, expected[0].Value)
defer os.Unsetenv(expected[0].Name)

registeredAppliers.Apply(&options)
c.Assert(options.Env, DeepEquals, expected)
}

func (s *EnvVarSuite) TestStaticEnvVarKubePodOptions(c *C) {
expected := []corev1.EnvVar{
{
Name: "KANISTER_REGISTERED_STATIC_ENVVAR",
Value: "1",
},
}

var registeredAppliers ephemeral.ApplierList[kube.PodOptions]
set := ephemeral.StaticEnvVar(expected[0].Name, expected[0].Value)
registeredAppliers.Register(set.PodOptions)

var options kube.PodOptions
registeredAppliers.Apply(&options)
c.Assert(options.EnvironmentVariables, DeepEquals, expected)
}

func (s *EnvVarSuite) TestRegisteringStaticEnvVarCoreV1Container(c *C) {
expected := []corev1.EnvVar{
{
Name: "KANISTER_REGISTERED_STATIC_ENVVAR",
Value: "1",
},
}

var registeredAppliers ephemeral.ApplierList[corev1.Container]
set := ephemeral.StaticEnvVar(expected[0].Name, expected[0].Value)
registeredAppliers.Register(set.Container)

var options corev1.Container
registeredAppliers.Apply(&options)
c.Assert(options.Env, DeepEquals, expected)
}
115 changes: 115 additions & 0 deletions pkg/ephemeral/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package ephemeral

import (
"github.com/kanisterio/kanister/pkg/kube"
corev1 "k8s.io/api/core/v1"
)

var (
Container ApplierList[corev1.Container]
PodOptions ApplierList[kube.PodOptions]
)

// ApplierSet is a group of Appliers, typically returned by an constructor.
kidgilson marked this conversation as resolved.
Show resolved Hide resolved
type ApplierSet struct {
Container Applier[corev1.Container]
PodOptions Applier[kube.PodOptions]
}

// Register generically registers an Applier.
func Register[T Constraint](applier Applier[T]) {
switch applier := any(applier).(type) {
case Applier[corev1.Container]:
Container.Register(applier)
case Applier[kube.PodOptions]:
PodOptions.Register(applier)
default:
panic("Unknown applier type")
}
}

// RegisterSet registers each of the Appliers contained in the set.
func RegisterSet(set ApplierSet) {
if set.Container != nil {
Container.Register(set.Container)
}

if set.PodOptions != nil {
PodOptions.Register(set.PodOptions)
}
}

// Constraint provides the set of types allowed for appliers and filterers.
type Constraint interface {
kube.PodOptions | corev1.Container
}

// Applier is the interface which applies a manipulation to the PodOption to be
// used to run ephemeral pdos.
type Applier[T Constraint] interface {
Apply(*T)
}

// ApplierFunc is a function which implements the Applier interface and can be
// used to generically manipulate the PodOptions.
type ApplierFunc[T Constraint] func(*T)

func (f ApplierFunc[T]) Apply(options *T) { f(options) }

// ApplierList is an array of registered Appliers which will be applied on
// a PodOption.
type ApplierList[T Constraint] []Applier[T]

// Apply calls the Applier::Apply method on all registered appliers.
func (l ApplierList[T]) Apply(options *T) {
for _, applier := range l {
applier.Apply(options)
}
}

// Register adds the applier to the list of Appliers to be used when
// manipulating the PodOptions.
func (l *ApplierList[T]) Register(applier Applier[T]) {
*l = append(*l, applier)
}

// Filterer is the interface which filters the use of registered appliers to
// only those PodOptions that match the filter criteria.
type Filterer[T Constraint] interface {
Filter(*T) bool
}

// FiltererFunc is a function which implements the Filterer interface and can be
// used to generically filter PodOptions to manipulate using the ApplierList.
type FiltererFunc[T Constraint] func(*T) bool

func (f FiltererFunc[T]) Filter(options *T) bool {
return f(options)
}

// Filter applies the Applier's if the Filterer criterion is met.
kidgilson marked this conversation as resolved.
Show resolved Hide resolved
func Filter[T Constraint](filterer Filterer[T], appliers ...Applier[T]) Applier[T] {
return ApplierFunc[T](func(options *T) {
if !filterer.Filter(options) {
return
}

for _, applier := range appliers {
applier.Apply(options)
}
})
}

// PodOptionsNameFilter is a Filterer that filters based on the PodOptions.Name.
pavannd1 marked this conversation as resolved.
Show resolved Hide resolved
type PodOptionsNameFilter string

func (n PodOptionsNameFilter) Filter(options *kube.PodOptions) bool {
return string(n) == options.Name
}

// ContainerNameFilter is a Filterer that filters based on the Container.Name.
type ContainerNameFilter string

func (n ContainerNameFilter) Filter(container *corev1.Container) bool {
return string(n) == container.Name
}
Loading
Loading