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

Pre-create common config directories #135

Merged
merged 1 commit into from
Nov 6, 2023

Conversation

AObuchow
Copy link
Contributor

@AObuchow AObuchow commented Oct 27, 2023

This PR aims to fix eclipse-che/che#22029, with the scope limited to pre-creating config directories for the following common tools:

  • Maven: /home/user/.m2/
  • Gradle: /home/user/.gradle/
  • Pip: /home/user/.config/pip/
  • Cargo: /home/user/.cargo/
  • Sbt: /home/user/.sbt/1.0/
  • PHP: /home/user/.composer/
  • .NET: /home/user/.nuget

As well as a /home/user/certs/ directory.

Yarn & npm were excluded since they are supposed to be placed in the $HOME directory (~/.yarnrc , ~.npmrc)

There might be other tools which support configuration files that I've missed in this PR, please feel free to suggest any.

I've pushed quay.io/aobuchow/universal-developer-image:m2 for testing this PR, I also used the following devfile for testing in Che:

schemaVersion: 2.1.0
metadata:
  name: java-backend
projects:
  - name: springbootproject
    git:
      remotes:
        origin: https://github.com/odo-devfiles/springboot-ex.git
components:
  - name: tools
    container:
      image: quay.io/aobuchow/universal-developer-image:m2
      memoryLimit: 3Gi
commands:
  - id: build
    exec:
      component: tools
      workingDir: ${PROJECT_SOURCE}
      commandLine: mvn clean -Dmaven.repo.local=/home/user/.m2/repository package -Dmaven.test.skip=true
      group:
        kind: build
        isDefault: true
  - id: run
    exec:
      component: tools
      workingDir: ${PROJECT_SOURCE}
      commandLine: mvn -Dmaven.repo.local=/home/user/.m2/repository spring-boot:run
      group:
        kind: run
        isDefault: true
  - id: debug
    exec:
      component: tools
      workingDir: ${PROJECT_SOURCE}
      commandLine: java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=${DEBUG_PORT},suspend=n -jar target/*.jar
      group:
        kind: debug
        isDefault: true

Testing

While logged into a cluster with Che installed, apply the following configmap list to your Che user namespace, it will create auto-mount configmaps (using the subPath mount method) for maven, gradle, sbt, pip and cargo:

apiVersion: v1
kind: ConfigMap
metadata:
  name: workspace-sbt-config
  labels:
    controller.devfile.io/mount-to-devworkspace: "true"
    controller.devfile.io/watch-configmap: "true"
  annotations:
    controller.devfile.io/mount-as: subpath
    controller.devfile.io/mount-path: /home/user/.sbt/1.0
data:
 global.sbt: |
    shellPrompt := { state =>
    s"\033[1;33m\033[43m[sbt ${name.value}]\033[0m >> "
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: workspace-pip-config
  labels:
    controller.devfile.io/mount-to-devworkspace: "true"
    controller.devfile.io/watch-configmap: "true"
  annotations:
    controller.devfile.io/mount-as: subpath
    controller.devfile.io/mount-path: /home/user/.config/pip
data:
 pip.conf: |
    [global]
    require-virtualenv = True
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: workspace-maven-config
  labels:
    controller.devfile.io/mount-to-devworkspace: "true"
    controller.devfile.io/watch-configmap: "true"
  annotations:
    controller.devfile.io/mount-as: subpath
    controller.devfile.io/mount-path: /home/user/.m2
data:
 settings.xml: |
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <localRepository>${user.home}/.m2/</localRepository>
    <interactiveMode>true</interactiveMode>
    <offline>true</offline>
    </settings>
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: workspace-gradle-config
  labels:
    controller.devfile.io/mount-to-devworkspace: "true"
    controller.devfile.io/watch-configmap: "true"
  annotations:
    controller.devfile.io/mount-as: subpath
    controller.devfile.io/mount-path: /home/user/.gradle
data:
 gradle.properties: |
    org.gradle.java.home=/projects
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: workspace-cargo-config
  labels:
    controller.devfile.io/mount-to-devworkspace: "true"
    controller.devfile.io/watch-configmap: "true"
  annotations:
    controller.devfile.io/mount-as: subpath
    controller.devfile.io/mount-path: /home/user/.cargo
data:
 config.toml: |
    [alias]     # command aliases
    myalias = "test"

Though it's a bit overkill, I made these configmaps to ensure the tool configurations could be read by the respective tools.

Maven (settings.xml)

Run the mvn command. Since offline is set to true, the command will fail with the following error message:

[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for com.example:demo:0.0.1-SNAPSHOT: The following artifacts could not be resolved: org.springframework.boot:spring-boot-starter-parent:pom:2.3.5.RELEASE (absent): Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.springframework.boot:spring-boot-starter-parent:pom:2.3.5.RELEASE has not been downloaded from it before. and 'parent.relativePath' points at no local POM @ line 5, column 10
 @ 
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project com.example:demo:0.0.1-SNAPSHOT (/projects/springbootproject/pom.xml) has 1 error
[ERROR]     Non-resolvable parent POM for com.example:demo:0.0.1-SNAPSHOT: The following artifacts could not be resolved: org.springframework.boot:spring-boot-starter-parent:pom:2.3.5.RELEASE (absent): Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.springframework.boot:spring-boot-starter-parent:pom:2.3.5.RELEASE has not been downloaded from it before. and 'parent.relativePath' points at no local POM @ line 5, column 10 -> [Help 2]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException

Pip (pip.conf)

Run pip install test. Since we don't have a virtual environment created, and we're using require-virtualenv = True, you should get the following error:

ERROR: Could not find an activated virtualenv (required).

SBT

Run sbt -sbt-create. Once the command completes (you'll see a few errors but it should still complete), the sbt prompt will be our custom prompt: [sbt springbootproject] >>

Cargo (config.toml)

Run cargo init && cargo myalias. myalias is a custom alias for test, defined in our config.toml. You should see the following output:

    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/main.rs (target/debug/deps/springbootproject-9d170e653f80aebf)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Copy link
Collaborator

@svor svor left a comment

Choose a reason for hiding this comment

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

LGTM
@AObuchow maybe we can also create:

  • for NuGet, it's the package manager for .NET: /home/user/.nuget
  • for composer, it's the package manager for PHP : /home/user/.composer

Copy link
Contributor

@amisevsk amisevsk left a comment

Choose a reason for hiding this comment

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

I'm split on whether it'd be more readable to put all the mkdir in one spot (and have one chgrp/chmod covering it all) rather than spreading it throughout the dockerfile, but approving this PR anyways

Copy link
Contributor

@l0rd l0rd left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@openshift-ci
Copy link

openshift-ci bot commented Oct 31, 2023

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: amisevsk, AObuchow, l0rd, svor

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot removed the lgtm label Nov 1, 2023
Copy link

openshift-ci bot commented Nov 1, 2023

New changes are detected. LGTM label has been removed.

Fix eclipse-che/che#22029

Pre-creates the following config directories:

- ~/.m2 (Maven)
- ~/.gradle (Gradle)
- ~/.config/pip (Pip)
- ~/.sbt/1.0 (SBT)
- ~/.cargo (Rust)
- ~/certs (SSL)
- ~/.composer (PHP)
- ~/.nuget (.NET)

Signed-off-by: Andrew Obuchowicz <[email protected]>
@AObuchow
Copy link
Contributor Author

AObuchow commented Nov 1, 2023

@svor @amisevsk @l0rd Thank you all for the reviews.

I rebased this PR after merging #115, added the .NET and PHP package manager config directories (~/.nuget and ~/.composer).

I also followed @amisevsk's suggestion to have a dedicated section for the config directory creation: it's easier to see which directories we're creating when they're all in one spot, rather than spread out throughout the Dockerfile.

@codekow
Copy link

codekow commented Nov 1, 2023

Coming into this discussion late...

But what if you focus on leveraging the structure of /etc/skel to stage a configuration for a new environment, similar to on a linux system vs putting the configs into an area that is ephemeral or a pvc?

Raw Example

if [ ! -e ~/.bashrc ]; then
  rsync -a /etc/skel /home/user
fi

This way you help initialize a home on first run and ignore where someone has configured things another way.

Use the same pattern we already use to setup user env on Linux systems?

@amisevsk
Copy link
Contributor

amisevsk commented Nov 2, 2023

@codekow Could you elaborate further? I'm not entirely clear on how /etc/skel helps us here, since the home directory is created (manually) at image build time. We can rsync files over to /home/user but in the context of a container build, I feel like this would just move the mkdir steps to /etc/skel and then the rsync would reproduce the result of mkdir in /home/user.

The issue we're trying to solve here is that configuration files (e.g. maven's settings.xml) are user provided at run time. To avoid requiring users build custom images with their configuration files built-in, we allow them to mount those files from Kubernetes configmaps into the container automatically. However, when Kubernetes does this, it creates non-leaf path elements with non-user permissions, meaning the directory created to mount your settings.xml is not writeable by you. In the case of maven (for example) it means that dependencies cannot be downloaded.

@codekow
Copy link

codekow commented Nov 3, 2023

@amisevsk I'm probably looking more at a structural view and maintaining patterns that already exist. /etc/skel already contains the initial files for a user - continue using that pattern. Regardless if /home/user is a persistent volume or ephemeral you can initialize from /etc/skel. The value of the pattern being Linux admins will already recognize. The use case being the one time setup of the user, if the env doesn't exist.

The second use case is initializing configs during each runtime. Ex: I need a settings.xml from a k8s secret or configmap. Generally I would put that logic in one of two places: /home/user/.bashrc (that came from /etc/skel) or the container entrypoint.sh. I generally favor letting the user override any of my opinionated solutions. That's why I would create a .bashrc I think a user will want, including maybe a cp /config/settings.xml /home/user/...

But the user can override .bashrc and do whatever they want. Because I don't want them fighting with my limited perspective and use cases.

I want the user to have the ability to easily override my solution for user config I didn't consider. My concern with the pattern I see on this thread is a lot of moving parts that can not be disabled if the user doesn't want it. .bashrc is where all the user things happen generally. I'd keep your logic in there to be overwritten by a user. Avoid putting user config logic in the Dockerfile that requires more effort for a user to undo.

Since words are sometimes hard. Let me know if this makes any sense... if not I'll try to give better examples. 😄

In short - move your logic for user settings initialization into .bashrc that comes from /etc/skel and avoid doing user config in the Dockerfile. Use your entrypoint.sh to do the one time user setup.

@amisevsk
Copy link
Contributor

amisevsk commented Nov 3, 2023

Ah I think the piece we're missing is that .bashrc cannot be used to e.g. create these directories, since they are created in the container before it is run by Kubernetes. Putting it into analogy with Linux system/user set up, we're effectively working with a system where before Linux starts, some magic entity will go into our filesystem and run

mkdir -m 755 -p /home/user/.m2/ # as the *root* user

If the directory already exists, this command is a no-op, but if it does not, you'll end up with an .m2 directory that is not writable by your user. Any attempts to update ownership/permissions after the container has been started will fail because you can't write to it.

As for setting up things like .bashrc, I don't think there's much difference between /etc/skel + copy over versus just having the files in /home/user. In these containers, we expect:

  • There is one user, and
  • Their home directory is /home/user

in this context, providing machinery for user setup (to me at least) seems like an unnecessary indirection. Any user setup you do at container run-time will be lost when the container is restarted, so it's actively discouraged to do things like "new user setup" outside the Dockerfile.

@codekow
Copy link

codekow commented Nov 6, 2023

I did a deeper dive into how che sets up the user env and see the primary volumes are:

  • /checode
  • /projects

Everything else is essentially read-only or ephemeral.

I assumed /home/user was also read/write and similar to the two above. I understand why dirs in /home/user are being pre-created based on the current design.

@AObuchow AObuchow merged commit 94ec27a into devfile:main Nov 6, 2023
2 checks passed
@AObuchow AObuchow deleted the pre-create-m2-dir branch November 6, 2023 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pre-create common tooling config directories for installed tools in UDI image
5 participants