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
76 changes: 76 additions & 0 deletions pkg/ephemeral/envvar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ephemeral
kidgilson marked this conversation as resolved.
Show resolved Hide resolved

import (
"os"

corev1 "k8s.io/api/core/v1"

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

// OSEnvVar creates an ApplierSet to set an environment variable if it's present
// 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,
},
)
}),
}
}
113 changes: 113 additions & 0 deletions pkg/ephemeral/envvar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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)
}
131 changes: 131 additions & 0 deletions pkg/ephemeral/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ephemeral

import (
corev1 "k8s.io/api/core/v1"

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

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

// ApplierSet is a group of Appliers, typically returned by a constructor.
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 Appliers if the Filterer criterion is met.
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
// which is the Pod name.
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