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

Create an initcontainer for persistent home setup #1251

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ type PersistentHomeConfig struct {
// Must be used with the 'per-user'/'common' or 'per-workspace' storage class in order to take effect.
// Disabled by default.
Enabled *bool `json:"enabled,omitempty"`
// Determines whether the init container that initializes the persistent home directory should be disabled.
// When the `/home/user` directory is persisted, the init container is used to initialize the directory before
// the workspace starts. If set to true, the init container will not be created.
// This field is not used if the `workspace.persistUserHome.enabled` field is set to false.
// Enabled by default.
DisableInitContainer *bool `json:"disableInitContainer,omitempty"`
}

type Proxy struct {
Expand Down
5 changes: 5 additions & 0 deletions apis/controller/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
if home.NeedsPersistentHomeDirectory(workspace) {
workspaceWithHomeVolume, err := home.AddPersistentHomeVolume(workspace)
if err != nil {
reconcileStatus.addWarning(fmt.Sprintf("Info: default persistentHome volume is not being used: %s", err.Error()))
reconcileStatus.addWarning(fmt.Sprintf("Info: unable to setup home persistence: %s", err.Error()))
} else {
workspace.Spec.Template = *workspaceWithHomeVolume
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions deploy/deployment/kubernetes/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions deploy/deployment/openshift/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ var defaultConfig = &v1alpha1.OperatorConfiguration{
PerWorkspace: &perWorkspaceStorageSize,
},
PersistUserHome: &v1alpha1.PersistentHomeConfig{
Enabled: pointer.Bool(false),
Enabled: pointer.Bool(false),
DisableInitContainer: pointer.Bool(false),
},
IdleTimeout: "15m",
ProgressTimeout: "5m",
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
if from.Workspace.PersistUserHome.Enabled != nil {
to.Workspace.PersistUserHome.Enabled = from.Workspace.PersistUserHome.Enabled
}
if from.Workspace.PersistUserHome.DisableInitContainer != nil {
to.Workspace.PersistUserHome.DisableInitContainer = from.Workspace.PersistUserHome.DisableInitContainer
}
}
if from.Workspace.DefaultTemplate != nil {
templateSpecContentCopy := from.Workspace.DefaultTemplate.DeepCopy()
Expand Down Expand Up @@ -522,6 +525,9 @@ func GetCurrentConfigString(currConfig *controller.OperatorConfiguration) string
if workspace.PersistUserHome.Enabled != nil && *workspace.PersistUserHome.Enabled != *defaultConfig.Workspace.PersistUserHome.Enabled {
config = append(config, fmt.Sprintf("workspace.persistUserHome.enabled=%t", *workspace.PersistUserHome.Enabled))
}
if workspace.PersistUserHome.DisableInitContainer != nil && *workspace.PersistUserHome.DisableInitContainer != *defaultConfig.Workspace.PersistUserHome.DisableInitContainer {
config = append(config, fmt.Sprintf("workspace.persistUserHome.disableInitContainer=%t", *workspace.PersistUserHome.DisableInitContainer))
}
}
if !reflect.DeepEqual(workspace.PodSecurityContext, defaultConfig.Workspace.PodSecurityContext) {
config = append(config, "workspace.podSecurityContext is set")
Expand Down
4 changes: 4 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const (

HomeVolumeName = "persistent-home"

HomeInitComponentName = "init-persistent-home"

HomeInitEventId = "init-persistent-home"

ServiceAccount = "devworkspace"

PVCStorageSize = "10Gi"
Expand Down
144 changes: 144 additions & 0 deletions pkg/library/home/persistentHome.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ import (
"github.com/devfile/devworkspace-operator/pkg/constants"
)

const initScript = `(echo "Checking for stow command"
STOW_COMPLETE=/home/user/.stow_completed
if command -v stow &> /dev/null; then
if [ ! -f $STOW_COMPLETE ]; then
echo "Running stow command"
stow . -t /home/user/ -d /home/tooling/ --no-folding -v 2 > /home/user/.stow.log 2>&1
cp -n /home/tooling/.viminfo /home/user/.viminfo
cp -n /home/tooling/.bashrc /home/user/.bashrc
cp -n /home/tooling/.bash_profile /home/user/.bash_profile
touch $STOW_COMPLETE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to log here that the init script completed here, but that can be added in a future PR. We could also check whether stow completed successfully and report that here.

else
echo "Stow command already run. If you wish to re-run it, delete $STOW_COMPLETE from the persistent volume and restart the workspace."
fi
else
echo "Stow command not found"
fi) || true
`

// Returns a modified copy of the given DevWorkspace's Template Spec which contains an additional
// Devfile volume 'persistentHome' that is mounted to `/home/user/` for every container component defined in the DevWorkspace.
// An error is returned if the addition of the 'persistentHome' volume would result
Expand All @@ -44,6 +62,13 @@ func AddPersistentHomeVolume(workspace *common.DevWorkspaceWithConfig) (*v1alpha
Path: constants.HomeUserDirectory,
}

if workspace.Config.Workspace.PersistUserHome.DisableInitContainer == nil || !*workspace.Config.Workspace.PersistUserHome.DisableInitContainer {
err := addInitContainer(dwTemplateSpecCopy)
if err != nil {
return nil, fmt.Errorf("failed to add init container for home persistence setup: %w", err)
}
}

dwTemplateSpecCopy.Components = append(dwTemplateSpecCopy.Components, homeVolume)
for _, component := range dwTemplateSpecCopy.Components {
if component.Container == nil {
Expand Down Expand Up @@ -95,3 +120,122 @@ func storageStrategySupportsPersistentHome(workspace *common.DevWorkspaceWithCon
storageClass := workspace.Spec.Template.Attributes.GetString(constants.DevWorkspaceStorageTypeAttribute, nil)
return storageClass != constants.EphemeralStorageClassType
}

func addInitContainer(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error {

if initComponentExists(dwTemplateSpec) {
return fmt.Errorf("component named %s already exists in the devworkspace", constants.HomeInitComponentName)
}

if initCommandExists(dwTemplateSpec) {
return fmt.Errorf("command with id %s already exists in the devworkspace", constants.HomeInitEventId)
}

if initEventExists(dwTemplateSpec) {
return fmt.Errorf("event with id %s already exists in the devworkspace", constants.HomeInitEventId)
}

initContainer := inferInitContainer(dwTemplateSpec)
if initContainer == nil {
return fmt.Errorf("cannot infer initcontainer for home persistence setup")
}

addInitContainerComponent(dwTemplateSpec, initContainer)

if dwTemplateSpec.Commands == nil {
dwTemplateSpec.Commands = []v1alpha2.Command{}
}

if dwTemplateSpec.Events == nil {
dwTemplateSpec.Events = &v1alpha2.Events{}
}

dwTemplateSpec.Commands = append(dwTemplateSpec.Commands, v1alpha2.Command{
Id: constants.HomeInitEventId,
CommandUnion: v1alpha2.CommandUnion{
Apply: &v1alpha2.ApplyCommand{
Component: constants.HomeInitComponentName,
},
},
})

dwTemplateSpec.Events.PreStart = append(dwTemplateSpec.Events.PreStart, constants.HomeInitEventId)

return nil
}

func initComponentExists(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) bool {
if dwTemplateSpec.Components == nil {
return false
}
for _, component := range dwTemplateSpec.Components {
if component.Name == constants.HomeInitComponentName {
return true
}
}
return false

}

func initCommandExists(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) bool {
if dwTemplateSpec.Commands == nil {
return false
}
for _, command := range dwTemplateSpec.Commands {
if command.Id == constants.HomeInitEventId {
return true
}
}
return false
}

func initEventExists(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) bool {
if dwTemplateSpec.Events == nil {
return false
}
for _, event := range dwTemplateSpec.Events.PreStart {
if event == constants.HomeInitEventId {
return true
}
}
return false

}

func addInitContainerComponent(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec, initContainer *v1alpha2.Container) {
initComponent := v1alpha2.Component{
Name: constants.HomeInitComponentName,
ComponentUnion: v1alpha2.ComponentUnion{
Container: &v1alpha2.ContainerComponent{
Container: *initContainer,
},
},
}
dwTemplateSpec.Components = append(dwTemplateSpec.Components, initComponent)
}

func inferInitContainer(dwTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) *v1alpha2.Container {
var nonImportedComponent v1alpha2.Component
for _, component := range dwTemplateSpec.Components {
if component.Container == nil {
continue
}

pluginSource := component.Attributes.GetString(constants.PluginSourceAttribute, nil)
if pluginSource == "" || pluginSource == "parent" {
// First non-imported container component is selected
nonImportedComponent = component
break
}
}

if nonImportedComponent.Name != "" {
image := nonImportedComponent.Container.Image
return &v1alpha2.Container{
Image: image,
Command: []string{"/bin/sh", "-c"},
Args: []string{initScript},
}
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ input:
workspace:
persistUserHome:
enabled: true
disableInitContainer: true
workspace:
components:
- name: testing-container-1
Expand Down
Loading
Loading