diff --git a/cmd/provisioner-localpv/app/env.go b/cmd/provisioner-localpv/app/env.go index aff437bc4d..e8c69d4f0d 100644 --- a/cmd/provisioner-localpv/app/env.go +++ b/cmd/provisioner-localpv/app/env.go @@ -25,6 +25,7 @@ import ( // provisioner also uses the following: // OPENEBS_NAMESPACE // NODE_NAME +// OPENEBS_SERVICE_ACCOUNT // OPENEBS_IO_K8S_MASTER // OPENEBS_IO_KUBE_CONFIG @@ -54,3 +55,7 @@ func getDefaultHelperImage() string { func getDefaultBasePath() string { return menv.GetOrDefault(ProvisionerBasePath, string(defaultBasePath)) } + +func getOpenEBSServiceAccountName() string { + return menv.Get(menv.OpenEBSServiceAccount) +} diff --git a/cmd/provisioner-localpv/app/env_test.go b/cmd/provisioner-localpv/app/env_test.go index 7e46b0b860..98ff521f3b 100644 --- a/cmd/provisioner-localpv/app/env_test.go +++ b/cmd/provisioner-localpv/app/env_test.go @@ -124,3 +124,36 @@ func TestGetDefaultBasePath(t *testing.T) { }) } } + +func TestGetOpenEBSServiceAccountName(t *testing.T) { + testCases := map[string]struct { + value string + expectedValue string + }{ + "Missing env variable": { + value: "", + expectedValue: "", + }, + "Present env variable with value": { + value: "value1", + expectedValue: "value1", + }, + "Present env variable with whitespaces": { + value: " ", + expectedValue: "", + }, + } + for k, v := range testCases { + v := v + t.Run(k, func(t *testing.T) { + if len(v.value) != 0 { + os.Setenv(string(menv.OpenEBSServiceAccount), v.value) + } + actualValue := getOpenEBSServiceAccountName() + if !reflect.DeepEqual(actualValue, v.expectedValue) { + t.Errorf("expected %s got %s", v.expectedValue, actualValue) + } + os.Unsetenv(string(menv.OpenEBSServiceAccount)) + }) + } +} diff --git a/cmd/provisioner-localpv/app/helper_hostpath.go b/cmd/provisioner-localpv/app/helper_hostpath.go index 2431e19bfb..f39e271590 100644 --- a/cmd/provisioner-localpv/app/helper_hostpath.go +++ b/cmd/provisioner-localpv/app/helper_hostpath.go @@ -63,6 +63,9 @@ type HelperPodOptions struct { //path is the volume hostpath directory path string + + // serviceAccountName is the service account with which the pod should be launched + serviceAccountName string } // validate checks that the required fields to launch @@ -70,8 +73,11 @@ type HelperPodOptions struct { // create or delete a directory (path) on a given node hostname (nodeHostname). // name refers to the volume being created or deleted. func (pOpts *HelperPodOptions) validate() error { - if pOpts.name == "" || pOpts.path == "" || pOpts.nodeHostname == "" { - return errors.Errorf("invalid empty name or hostpath or hostname") + if pOpts.name == "" || + pOpts.path == "" || + pOpts.nodeHostname == "" || + pOpts.serviceAccountName == "" { + return errors.Errorf("invalid empty name or hostpath or hostname or service account name") } return nil } @@ -146,10 +152,16 @@ func (p *Provisioner) createCleanupPod(pOpts *HelperPodOptions) error { } func (p *Provisioner) launchPod(config podConfig) (*corev1.Pod, error) { + // the helper pod need to be launched in privileged mode. This is because in CoreOS + // nodes, pods without privileged access cannot write to the host directory. + // Helper pods need to create and delete directories on the host. + privileged := true + helperPod, _ := pod.NewBuilder(). WithName(config.podName + "-" + config.pOpts.name). WithRestartPolicy(corev1.RestartPolicyNever). WithNodeSelectorHostnameNew(config.pOpts.nodeHostname). + WithServiceAccountName(config.pOpts.serviceAccountName). WithContainerBuilder( container.NewBuilder(). WithName("local-path-" + config.podName). @@ -161,7 +173,8 @@ func (p *Provisioner) launchPod(config podConfig) (*corev1.Pod, error) { ReadOnly: false, MountPath: "/data/", }, - }), + }). + WithPrivilegedSecurityContext(&privileged), ). WithVolumeBuilder( volume.NewBuilder(). diff --git a/cmd/provisioner-localpv/app/provisioner_hostpath.go b/cmd/provisioner-localpv/app/provisioner_hostpath.go index 0a2524dcc1..4385a5753f 100644 --- a/cmd/provisioner-localpv/app/provisioner_hostpath.go +++ b/cmd/provisioner-localpv/app/provisioner_hostpath.go @@ -35,6 +35,7 @@ func (p *Provisioner) ProvisionHostPath(opts pvController.VolumeOptions, volumeC nodeHostname := GetNodeHostname(opts.SelectedNode) name := opts.PVName stgType := volumeConfig.GetStorageType() + saName := getOpenEBSServiceAccountName() path, err := volumeConfig.GetPath() if err != nil { @@ -53,10 +54,11 @@ func (p *Provisioner) ProvisionHostPath(opts pvController.VolumeOptions, volumeC //Before using the path for local PV, make sure it is created. initCmdsForPath := []string{"mkdir", "-m", "0777", "-p"} podOpts := &HelperPodOptions{ - cmdsForPath: initCmdsForPath, - name: name, - path: path, - nodeHostname: nodeHostname, + cmdsForPath: initCmdsForPath, + name: name, + path: path, + nodeHostname: nodeHostname, + serviceAccountName: saName, } iErr := p.createInitPod(podOpts) diff --git a/pkg/kubernetes/pod/v1alpha1/build.go b/pkg/kubernetes/pod/v1alpha1/build.go index 80a1142fa9..793c751131 100644 --- a/pkg/kubernetes/pod/v1alpha1/build.go +++ b/pkg/kubernetes/pod/v1alpha1/build.go @@ -175,6 +175,20 @@ func (b *Builder) WithVolume(volume corev1.Volume) *Builder { return b.WithVolumes([]corev1.Volume{volume}) } +// WithServiceAccountName sets the ServiceAccountName of Pod spec with +// the provided value +func (b *Builder) WithServiceAccountName(serviceAccountName string) *Builder { + if len(serviceAccountName) == 0 { + b.errs = append( + b.errs, + errors.New("failed to build Pod object: missing Pod service account name"), + ) + return b + } + b.pod.object.Spec.ServiceAccountName = serviceAccountName + return b +} + // Build returns the Pod API instance func (b *Builder) Build() (*corev1.Pod, error) { if len(b.errs) > 0 {