Skip to content

Commit 2a78c57

Browse files
Merge pull request #139 from tesshuflower/viper_env_vars
Use viper to allow both cmd line and env vars for mover container imgs
2 parents 26b283d + 919b46f commit 2a78c57

16 files changed

+802
-167
lines changed

controllers/mover/rclone/builder.go

+49-12
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,67 @@ import (
2222
"fmt"
2323

2424
"github.com/go-logr/logr"
25+
"github.com/spf13/viper"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627

2728
volsyncv1alpha1 "github.com/backube/volsync/api/v1alpha1"
2829
"github.com/backube/volsync/controllers/mover"
2930
"github.com/backube/volsync/controllers/volumehandler"
3031
)
3132

32-
// defaultRcloneContainerImage is the default container image for the rclone
33-
// data mover
34-
const defaultRcloneContainerImage = "quay.io/backube/volsync-mover-rclone:latest"
35-
36-
// rcloneContainerImage is the container image name of the rclone data mover
37-
var rcloneContainerImage string
33+
const (
34+
// defaultRcloneContainerImage is the default container image for the rclone
35+
// data mover
36+
defaultRcloneContainerImage = "quay.io/backube/volsync-mover-rclone:latest"
37+
// Command line flag will be checked first
38+
// If command line flag not set, the RELATED_IMAGE_ env var will be used
39+
rcloneContainerImageFlag = "rclone-container-image"
40+
rcloneContainerImageEnvVar = "RELATED_IMAGE_RCLONE_CONTAINER"
41+
)
3842

39-
type Builder struct{}
43+
type Builder struct {
44+
viper *viper.Viper // For unit tests to be able to override - global viper will be used by default in Register()
45+
flags *flag.FlagSet // For unit tests to be able to override - global flags will be used by default in Register()
46+
}
4047

4148
var _ mover.Builder = &Builder{}
4249

43-
func Register() {
44-
flag.StringVar(&rcloneContainerImage, "rclone-container-image",
45-
defaultRcloneContainerImage, "The container image for the rclone data mover")
46-
mover.Register(&Builder{})
50+
func Register() error {
51+
// Use global viper & command line flags
52+
b, err := newBuilder(viper.GetViper(), flag.CommandLine)
53+
if err != nil {
54+
return err
55+
}
56+
57+
mover.Register(b)
58+
return nil
59+
}
60+
61+
func newBuilder(viper *viper.Viper, flags *flag.FlagSet) (*Builder, error) {
62+
b := &Builder{
63+
viper: viper,
64+
flags: flags,
65+
}
66+
67+
// Set default rclone container image - will be used if both command line flag and env var are not set
68+
b.viper.SetDefault(rcloneContainerImageFlag, defaultRcloneContainerImage)
69+
70+
// Setup command line flag for the rclone container image
71+
b.flags.String(rcloneContainerImageFlag, defaultRcloneContainerImage,
72+
"The container image for the rclone data mover")
73+
// Viper will check for command line flag first, then fallback to the env var
74+
err := b.viper.BindEnv(rcloneContainerImageFlag, rcloneContainerImageEnvVar)
75+
76+
return b, err
4777
}
4878

4979
func (rb *Builder) VersionInfo() string {
50-
return fmt.Sprintf("Rclone container: %s", rcloneContainerImage)
80+
return fmt.Sprintf("Rclone container: %s", rb.getRcloneContainerImage())
81+
}
82+
83+
// rcloneContainerImage is the container image name of the rclone data mover
84+
func (rb *Builder) getRcloneContainerImage() string {
85+
return rb.viper.GetString(rcloneContainerImageFlag)
5186
}
5287

5388
func (rb *Builder) FromSource(client client.Client, logger logr.Logger,
@@ -71,6 +106,7 @@ func (rb *Builder) FromSource(client client.Client, logger logr.Logger,
71106
logger: logger.WithValues("method", "Rclone"),
72107
owner: source,
73108
vh: vh,
109+
containerImage: rb.getRcloneContainerImage(),
74110
rcloneConfigSection: source.Spec.Rclone.RcloneConfigSection,
75111
rcloneDestPath: source.Spec.Rclone.RcloneDestPath,
76112
rcloneConfig: source.Spec.Rclone.RcloneConfig,
@@ -101,6 +137,7 @@ func (rb *Builder) FromDestination(client client.Client, logger logr.Logger,
101137
logger: logger.WithValues("method", "Rclone"),
102138
owner: destination,
103139
vh: vh,
140+
containerImage: rb.getRcloneContainerImage(),
104141
rcloneConfigSection: destination.Spec.Rclone.RcloneConfigSection,
105142
rcloneDestPath: destination.Spec.Rclone.RcloneDestPath,
106143
rcloneConfig: destination.Spec.Rclone.RcloneConfig,

controllers/mover/rclone/mover.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type Mover struct {
4747
logger logr.Logger
4848
owner metav1.Object
4949
vh *volumehandler.VolumeHandler
50+
containerImage string
5051
rcloneConfigSection *string
5152
rcloneDestPath *string
5253
rcloneConfig *string
@@ -236,7 +237,7 @@ func (m *Mover) ensureJob(ctx context.Context, dataPVC *corev1.PersistentVolumeC
236237
{Name: "RCLONE_CONFIG_SECTION", Value: *m.rcloneConfigSection},
237238
},
238239
Command: []string{"/bin/bash", "-c", "./active.sh"},
239-
Image: rcloneContainerImage,
240+
Image: m.containerImage,
240241
SecurityContext: &corev1.SecurityContext{
241242
RunAsUser: &runAsUser,
242243
},

controllers/mover/rclone/rclone_test.go

+121-14
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
package rclone
1919

2020
import (
21+
"flag"
22+
"os"
23+
2124
snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1beta1"
2225
. "github.com/onsi/ginkgo"
2326
. "github.com/onsi/gomega"
27+
"github.com/spf13/pflag"
28+
"github.com/spf13/viper"
2429
batchv1 "k8s.io/api/batch/v1"
2530
corev1 "k8s.io/api/core/v1"
2631
rbacv1 "k8s.io/api/rbac/v1"
@@ -49,8 +54,9 @@ var emptyString = ""
4954
var _ = Describe("Rclone properly registers", func() {
5055
When("Rclone's registration function is called", func() {
5156
BeforeEach(func() {
52-
Register()
57+
Expect(Register()).To(Succeed())
5358
})
59+
5460
It("is added to the mover catalog", func() {
5561
found := false
5662
for _, v := range mover.Catalog {
@@ -63,6 +69,115 @@ var _ = Describe("Rclone properly registers", func() {
6369
})
6470
})
6571

72+
var _ = Describe("Rclone init flags and env vars", func() {
73+
logger := zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))
74+
When("Rclone builder inits flags", func() {
75+
var builderForInitTests *Builder
76+
var testPflagSet *pflag.FlagSet
77+
BeforeEach(func() {
78+
os.Unsetenv(rcloneContainerImageEnvVar)
79+
80+
// Instantiate new viper instance and flagset instance just for this test
81+
testViper := viper.New()
82+
testFlagSet := flag.NewFlagSet("testflagsetrclone", flag.ExitOnError)
83+
84+
// New Builder for this test - use testViper and testFlagSet so we can modify
85+
// flags for these tests without modifying global flags and potentially affecting other tests
86+
var err error
87+
builderForInitTests, err = newBuilder(testViper, testFlagSet)
88+
Expect(err).NotTo(HaveOccurred())
89+
Expect(builderForInitTests).NotTo(BeNil())
90+
91+
// code here (see main.go) for viper to bind cmd line flags (including those
92+
// defined in the mover Register() func)
93+
// Bind viper to a new set of flags so each of these tests can get their own
94+
testPflagSet = pflag.NewFlagSet("testpflagsetrclone", pflag.ExitOnError)
95+
testPflagSet.AddGoFlagSet(testFlagSet)
96+
Expect(testViper.BindPFlags(testPflagSet)).To(Succeed())
97+
})
98+
99+
AfterEach(func() {
100+
os.Unsetenv(rcloneContainerImageEnvVar)
101+
})
102+
103+
JustBeforeEach(func() {
104+
// Common checks - make sure if we instantiate a source/dest mover, it uses the container image that
105+
// was picked up by flags/command line etc from the builder
106+
var err error
107+
108+
rs := &volsyncv1alpha1.ReplicationSource{
109+
ObjectMeta: metav1.ObjectMeta{
110+
Name: "testrscr",
111+
Namespace: "testing",
112+
},
113+
Spec: volsyncv1alpha1.ReplicationSourceSpec{
114+
Rclone: &volsyncv1alpha1.ReplicationSourceRcloneSpec{},
115+
},
116+
Status: &volsyncv1alpha1.ReplicationSourceStatus{}, // Controller sets status to non-nil
117+
}
118+
sourceMover, err := builderForInitTests.FromSource(k8sClient, logger, rs)
119+
Expect(err).NotTo(HaveOccurred())
120+
Expect(sourceMover).NotTo(BeNil())
121+
sourceRcloneMover, _ := sourceMover.(*Mover)
122+
Expect(sourceRcloneMover.containerImage).To(Equal(builderForInitTests.getRcloneContainerImage()))
123+
124+
rd := &volsyncv1alpha1.ReplicationDestination{
125+
ObjectMeta: metav1.ObjectMeta{
126+
Name: "rd",
127+
Namespace: "testing",
128+
},
129+
Spec: volsyncv1alpha1.ReplicationDestinationSpec{
130+
Trigger: &volsyncv1alpha1.ReplicationDestinationTriggerSpec{},
131+
Rclone: &volsyncv1alpha1.ReplicationDestinationRcloneSpec{},
132+
},
133+
Status: &volsyncv1alpha1.ReplicationDestinationStatus{}, // Controller sets status to non-nil
134+
}
135+
destMover, err := builderForInitTests.FromDestination(k8sClient, logger, rd)
136+
Expect(err).NotTo(HaveOccurred())
137+
Expect(destMover).NotTo(BeNil())
138+
destRcloneMover, _ := destMover.(*Mover)
139+
Expect(destRcloneMover.containerImage).To(Equal(builderForInitTests.getRcloneContainerImage()))
140+
})
141+
142+
Context("When no command line flag or ENV var is specified", func() {
143+
It("Should use the default rclone container image", func() {
144+
Expect(builderForInitTests.getRcloneContainerImage()).To(Equal(defaultRcloneContainerImage))
145+
})
146+
})
147+
148+
Context("When rclone container image command line flag is specified", func() {
149+
const cmdLineOverrideImageName = "test-rclone-image-name:cmdlineoverride"
150+
BeforeEach(func() {
151+
// Manually set the value of the command line flag
152+
Expect(testPflagSet.Set("rclone-container-image", cmdLineOverrideImageName)).To(Succeed())
153+
})
154+
It("Should use the rclone container image set by the cmd line flag", func() {
155+
Expect(builderForInitTests.getRcloneContainerImage()).To(Equal(cmdLineOverrideImageName))
156+
})
157+
158+
Context("And env var is set", func() {
159+
const envVarOverrideShouldBeIgnored = "test-rclone-image-name:donotuseme"
160+
BeforeEach(func() {
161+
os.Setenv(rcloneContainerImageEnvVar, envVarOverrideShouldBeIgnored)
162+
})
163+
It("Should still use the cmd line flag instead of the env var", func() {
164+
Expect(builderForInitTests.getRcloneContainerImage()).To(Equal(cmdLineOverrideImageName))
165+
})
166+
})
167+
})
168+
169+
Context("When rclone container image cmd line flag is not set and env var is", func() {
170+
const envVarOverrideImageName = "test-rclone-image-name:setbyenvvar"
171+
BeforeEach(func() {
172+
os.Setenv(rcloneContainerImageEnvVar, envVarOverrideImageName)
173+
})
174+
It("Should use the value from the env var", func() {
175+
Expect(builderForInitTests.getRcloneContainerImage()).To(Equal(envVarOverrideImageName))
176+
})
177+
})
178+
})
179+
})
180+
66181
var _ = Describe("Rclone ignores other movers", func() {
67182
logger := zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))
68183
When("An RS isn't for rclone", func() {
@@ -76,8 +191,7 @@ var _ = Describe("Rclone ignores other movers", func() {
76191
Rclone: nil,
77192
},
78193
}
79-
builder := Builder{}
80-
m, e := builder.FromSource(k8sClient, logger, rs)
194+
m, e := commonBuilderForTestSuite.FromSource(k8sClient, logger, rs)
81195
Expect(m).To(BeNil())
82196
Expect(e).NotTo(HaveOccurred())
83197
})
@@ -93,8 +207,7 @@ var _ = Describe("Rclone ignores other movers", func() {
93207
Rclone: nil,
94208
},
95209
}
96-
builder := Builder{}
97-
m, e := builder.FromDestination(k8sClient, logger, rd)
210+
m, e := commonBuilderForTestSuite.FromDestination(k8sClient, logger, rd)
98211
Expect(m).To(BeNil())
99212
Expect(e).NotTo(HaveOccurred())
100213
})
@@ -168,9 +281,7 @@ var _ = Describe("Rclone as a source", func() {
168281
// Controller sets status to non-nil
169282
rs.Status = &volsyncv1alpha1.ReplicationSourceStatus{}
170283
// Instantiate a rclone mover for the tests
171-
b := Builder{}
172-
var err error
173-
m, err := b.FromSource(k8sClient, logger, rs)
284+
m, err := commonBuilderForTestSuite.FromSource(k8sClient, logger, rs)
174285
Expect(err).ToNot(HaveOccurred())
175286
Expect(m).NotTo(BeNil())
176287
mover, _ = m.(*Mover)
@@ -401,7 +512,6 @@ var _ = Describe("Rclone as a source", func() {
401512
}
402513
})
403514
JustBeforeEach(func() {
404-
rcloneContainerImage = "therclonecontainerimage"
405515
Expect(k8sClient.Create(ctx, sa)).To(Succeed())
406516
Expect(k8sClient.Create(ctx, rcloneConfigSecret)).To(Succeed())
407517
})
@@ -431,7 +541,7 @@ var _ = Describe("Rclone as a source", func() {
431541
return err
432542
}).Should(Succeed())
433543
Expect(len(job.Spec.Template.Spec.Containers)).To(BeNumerically(">", 0))
434-
Expect(job.Spec.Template.Spec.Containers[0].Image).To(Equal(rcloneContainerImage))
544+
Expect(job.Spec.Template.Spec.Containers[0].Image).To(Equal(defaultRcloneContainerImage))
435545
})
436546

437547
It("should use the specified service account", func() {
@@ -640,9 +750,7 @@ var _ = Describe("Rclone as a destination", func() {
640750
// Controller sets status to non-nil
641751
rd.Status = &volsyncv1alpha1.ReplicationDestinationStatus{}
642752
// Instantiate a restic mover for the tests
643-
b := Builder{}
644-
var err error
645-
m, err := b.FromDestination(k8sClient, logger, rd)
753+
m, err := commonBuilderForTestSuite.FromDestination(k8sClient, logger, rd)
646754
Expect(err).ToNot(HaveOccurred())
647755
Expect(m).NotTo(BeNil())
648756
mover, _ = m.(*Mover)
@@ -769,7 +877,6 @@ var _ = Describe("Rclone as a destination", func() {
769877
}
770878
})
771879
JustBeforeEach(func() {
772-
rcloneContainerImage = "therclonecontainerimage"
773880
Expect(k8sClient.Create(ctx, dPVC)).To(Succeed())
774881
Expect(k8sClient.Create(ctx, sa)).To(Succeed())
775882
Expect(k8sClient.Create(ctx, rcloneConfigSecret)).To(Succeed())

controllers/mover/rclone/suite_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ package rclone
1919

2020
import (
2121
"context"
22+
"flag"
2223
"path/filepath"
2324
"testing"
2425

2526
snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1beta1"
2627
. "github.com/onsi/ginkgo"
2728
. "github.com/onsi/gomega"
29+
"github.com/spf13/viper"
2830
"k8s.io/client-go/kubernetes/scheme"
2931
"k8s.io/client-go/rest"
3032
ctrl "sigs.k8s.io/controller-runtime"
@@ -44,6 +46,7 @@ import (
4446
var cfg *rest.Config
4547
var k8sClient client.Client
4648
var testEnv *envtest.Environment
49+
var commonBuilderForTestSuite *Builder
4750
var cancel context.CancelFunc
4851
var ctx context.Context
4952

@@ -114,6 +117,12 @@ var _ = BeforeSuite(func() {
114117
k8sClient = k8sManager.GetClient()
115118
return k8sClient
116119
}, "60s", "1s").Should(Not(BeNil()))
120+
121+
// Instantiate common rsync builder to use for tests in this test suite
122+
commonBuilderForTestSuite, err = newBuilder(viper.New(), flag.NewFlagSet("testfsetrclonetests", flag.ExitOnError))
123+
Expect(err).NotTo(HaveOccurred())
124+
Expect(commonBuilderForTestSuite).NotTo(BeNil())
125+
117126
})
118127

119128
var _ = AfterSuite(func() {

0 commit comments

Comments
 (0)