Skip to content

Commit

Permalink
Add new ephemeral package for registering and applying PodOptions cha…
Browse files Browse the repository at this point in the history
…nges (#2874)

* Added ephemeral any registration scheme

* Added convenience functions for registering environment variables

* Added ephemeral Apply to PodOptions usage

* Added ephemeral Apply to corev1.Container usage

* Added test for registering env vars

* Switch to generics for increased type safety

* After Pavan's review

---------

Co-authored-by: Mark Severson <mark@kasten.io>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored May 10, 2024
1 parent bd3fd99 commit 7f0c993
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 3 deletions.
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

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

0 comments on commit 7f0c993

Please sign in to comment.