From fd9f65017c088a220e4c459bd50387ca06831d62 Mon Sep 17 00:00:00 2001 From: Jeff Ohrstrom Date: Wed, 4 Aug 2021 14:09:03 -0400 Subject: [PATCH] Improve dev tooling (#1305) improve dev container experience This adds a `dev` namespace to our rake tasks and documentation around those tasks. Running `dev:start` will build and start a development container with your UID & GID with password that you can set. --- .github/workflows/tests.yml | 19 ++--- DEVELOPMENT.md | 64 ++++++++++++++++ Dockerfile | 3 + Dockerfile.dev | 7 +- Gemfile | 1 + Gemfile.lock | 22 +++--- Rakefile | 1 + docker/dev-entrypoint.sh | 5 +- lib/.rubocop.yml | 1 + lib/tasks/build_utils.rb | 6 ++ lib/tasks/development.rb | 142 ++++++++++++++++++++++++++++++++++++ lib/tasks/packaging.rb | 15 ++-- lib/tasks/test.rb | 14 ++-- 13 files changed, 254 insertions(+), 46 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 lib/.rubocop.yml create mode 100644 lib/tasks/development.rb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9aac294da..9df3648f19 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,17 +30,18 @@ jobs: uses: actions/cache@v2 with: path: ~/vendor/bundle - key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('apps/*/Gemfile.lock', 'Gemfile.lock') }} + key: ${{ runner.os }}-${{ matrix.ruby }}-unit-gems-${{ hashFiles('apps/*/Gemfile.lock', 'Gemfile.lock') }} - name: Setup Bundler run: | bundle config path ~/vendor/bundle + bundle install - name: Run ShellCheck - run: rake test:shellcheck + run: bundle exec rake test:shellcheck - name: Run unit tests - run: rake test:unit + run: bundle exec rake test:unit - name: Run System Dashboard tests run: cd apps/dashboard; bin/rake test:system @@ -64,17 +65,7 @@ jobs: with: ruby-version: "2.7.1" bundler: "2.1.4" - - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/vendor/bundle - key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('apps/*/Gemfile.lock', 'Gemfile.lock') }} - - - name: Setup Bundler - run: | - bundle config path ~/vendor/bundle - bundle install + bundler-cache: true - name: Run E2E tests run: bundle exec rake test:e2e diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000000..bdb1eec288 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,64 @@ +# Developing Open-OnDemand + +## Getting Started + +These are instructions to build and interact with a full stack +development container. This container will create a duplicate user +with the same group and user id. Starting the container will prompt +you to set a password. This is only credentials for web access to the +container. + +Pull down this source code and start the container. + +```text +mkdir -p ~/ondemand +git clone https://github.com/OSC/ondemand.git ~/ondemand/src +cd ~/ondemand/src +rake dev:start +``` + +See `rake --tasks` for all the `dev:` related tasks. + +``` +rake dev:exec # Bash exec into the development container +rake dev:restart # Restart development container +rake dev:start # Start development container +rake dev:stop # Stop development container +``` + +### Login to the container + +Here's the important bit about user mapping with containers. Let's use the +example of `jessie` with `id` below. In creating the development container, +we added a user with the same. The password is for `dex` the IDP, and the +web only. + +``` +uid=1000(jessie) gid=1000(jessie) groups=1000(jessie) +``` + +Now you'll be able to access `http://localhost:8080/` where it'll redirect +you to `dex` the OpenID Connect provider within the container. Use the email +`@localhost`. + + +### Configuring the container + +In starting the container, you may see the mount +`~/.config/ondemand/container:/etc/ood`. This mount allows us to +completely configure this Open-OnDemand container. + +Create and edit files in the host's home directory and to mount in +new configurations. + +Remove `~/.config/ondemand/container/static_user.yml` to reset your +container's password. + +### Rebuilding the image + +All the development tasks will use the `ood-dev:latest` image. If +you want to rebuild to a newer version use the rebuild task. + +```text +rake dev:rebuild +``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 05e92dae85..c6bcde494d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,6 +45,9 @@ COPY ood_auth_map /opt/ood/ood_auth_map COPY apps /opt/ood/apps COPY Rakefile /opt/ood/Rakefile COPY lib /opt/ood/lib +COPY Gemfile /opt/ood/Gemfile + +RUN cd /opt/ood; bundle install RUN source /opt/rh/ondemand/enable && \ rake -f /opt/ood/Rakefile -mj$CONCURRENCY build && \ diff --git a/Dockerfile.dev b/Dockerfile.dev index e64a9dfd22..fdca23305b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -9,9 +9,10 @@ RUN dnf reinstall -y httpd-tools && \ strace lua python3 && \ dnf clean all && rm -rf /var/cache/dnf/* -RUN groupadd -g $GID $USER && \ - useradd -u $UID --create-home --gid $USER $USER && \ - echo "$USER ALL=(ALL) NOPASSWD:ALL" >>/etc/sudoers.d/$USER +RUN sed -i 's#^CREATE_MAIL_SPOOL=yes#CREATE_MAIL_SPOOL=no#' /etc/default/useradd; \ + grep $GID /etc/group >/dev/null || groupadd -g $GID $USER; \ + useradd -l -u $UID --create-home --gid $GID $USER && \ + echo "$USER ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/$USER COPY docker/dev-entrypoint.sh /entrypoint.sh diff --git a/Gemfile b/Gemfile index 605d02fadf..8f6cbb63a3 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rake" +gem "bcrypt" group :test do gem "rspec" diff --git a/Gemfile.lock b/Gemfile.lock index 033678b665..2c6c9a1f3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,35 +1,37 @@ GEM remote: https://rubygems.org/ specs: + bcrypt (3.1.16) childprocess (3.0.0) diff-lcs (1.4.4) - rake (13.0.3) - regexp_parser (1.8.2) + rake (13.0.6) + regexp_parser (2.1.1) rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) rspec-mocks (~> 3.10.0) - rspec-core (3.10.0) + rspec-core (3.10.1) rspec-support (~> 3.10.0) - rspec-expectations (3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-mocks (3.10.0) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-support (3.10.0) - rubyzip (2.3.0) + rspec-support (3.10.2) + rubyzip (2.3.2) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - watir (6.17.0) - regexp_parser (~> 1.2) - selenium-webdriver (~> 3.6) + watir (6.19.1) + regexp_parser (>= 1.2, < 3) + selenium-webdriver (>= 3.142.7) PLATFORMS ruby DEPENDENCIES + bcrypt rake rspec watir diff --git a/Rakefile b/Rakefile index cbb85a2c6c..bd57daec31 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,7 @@ PASSENGER_APP_ENV = ENV["PASSENGER_APP_ENV"] || "production" require "#{TASK_DIR}/packaging" require "#{TASK_DIR}/test" require "#{TASK_DIR}/docker" +require "#{TASK_DIR}/development" def infrastructure [ diff --git a/docker/dev-entrypoint.sh b/docker/dev-entrypoint.sh index ffee82e8ac..d1cd1c0ce7 100755 --- a/docker/dev-entrypoint.sh +++ b/docker/dev-entrypoint.sh @@ -9,12 +9,11 @@ APP_DEV_DIR="/home/$USER/ondemand/dev" OOD_DEV_DIR="/var/www/ood/apps/dev/$USER" sudo su root <> /etc/ood/dex/config.yaml diff --git a/lib/.rubocop.yml b/lib/.rubocop.yml new file mode 100644 index 0000000000..f834a9b413 --- /dev/null +++ b/lib/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: '../apps/dashboard/.rubocop.yml' diff --git a/lib/tasks/build_utils.rb b/lib/tasks/build_utils.rb index 1030707f7e..399d215137 100644 --- a/lib/tasks/build_utils.rb +++ b/lib/tasks/build_utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BuildUtils def image_tag tag? ? numeric_tag : "#{numeric_tag}-#{git_hash}" @@ -42,4 +44,8 @@ def dev_image_name def image_name "ood" end + + def user + @user ||= Etc.getpwnam(Etc.getlogin) + end end \ No newline at end of file diff --git a/lib/tasks/development.rb b/lib/tasks/development.rb new file mode 100644 index 0000000000..d0e0100cfc --- /dev/null +++ b/lib/tasks/development.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +namespace :dev do + require_relative 'build_utils' + require 'yaml' + require 'bcrypt' + include BuildUtils + + def dev_container_name + 'ood-dev' || ENV['OOD_DEV_CONTAINER_NAME'].to_s + end + + def init_ood_portal + file = "#{config_directory}/ood_portal.yml" + return if File.exist?(file) + + File.open(file, File::WRONLY|File::CREAT|File::EXCL) do |f| + f.write({ + 'servername': 'localhost', + 'port': 8080, + 'listen_addr_port': 8080, + 'oidc_remote_user_claim': 'email', + 'dex': { + 'connectors': [{ + 'type': 'mockCallback', + 'id': 'mock', + 'name': 'Mock' + }] + } + }.to_yaml) + end + end + + def init_ctr_user + file = "#{config_directory}/static_user.yml" + return if File.exist?(file) + + require 'io/console' + puts 'Enter password:' + plain_password = $stdin.noecho(&:gets).chomp + bcrypted = BCrypt::Password.create(plain_password) + + content = <<~CONTENT + enablePasswordDB: true + staticPasswords: + - email: "#{user.name}@localhost" + hash: "#{bcrypted}" + username: "#{user.name}" + userID: "71e63e31-7af3-41d7-add2-575568f4525f" + CONTENT + + File.open(file, File::WRONLY | File::CREAT | File::EXCL) do |f| + f.write(content) + end + end + + def container_rt_args + podman_runtime? ? podman_rt_args : docker_rt_args + end + + def docker_rt_args + [ + '--user', "#{user.uid}:#{user.gid}" + ].freeze + end + + def podman_rt_args + [ + '--userns', 'keep-id', + '--cap-add', 'sys_ptrace', + '--security-opt', 'label=disable' + ].freeze + end + + def config_directory + @config_directory ||= begin + base_dir = "#{user.dir}/.config/ondemand/container/config".tap { |dir| FileUtils.mkdir_p(dir) } + base_dir + end + end + + def dev_mounts + [ + '-v', "#{config_directory}:/etc/ood/config", + '-v', "#{user.dir}/ondemand:#{user.dir}/ondemand" + ] + end + + desc 'Start development container' + task :start => ['ensure_dev_files'] do + Rake::Task['package:dev_container'].invoke unless image_exists?("#{dev_image_name}:latest") + + ctr_args = [container_runtime, 'run', '-p 8080:8080', '-p 5556:5556'] + ctr_args.concat ["--name #{dev_container_name}"] + ctr_args.concat ['--rm', '--detach'] + ctr_args.concat ['-e', 'OOD_STATIC_USER=/etc/ood/config/static_user.yml'] + ctr_args.concat dev_mounts + ctr_args.concat container_rt_args + + ctr_args.concat ["#{dev_image_name}:latest"] + sh ctr_args.join(' ') + end + + desc 'Stop development container' + task :stop do + sh "#{container_runtime} stop #{dev_container_name}" + end + + desc 'See the development container\'s logs' + task :logs do + sh "#{container_runtime} logs #{dev_container_name}" + end + + desc 'Restart development container' + task :restart => [:stop, :start] + + desc 'Rebuild the ood-dev:latest container' + task :rebuild => ['package:dev_container'] + + desc 'Bash exec into the development container' + task :exec do + ctr_args = [container_runtime, 'exec', '-it'] + # home is set to /root? could be bug for me + ctr_args.concat ['-e', "HOME=#{user.dir}"] + ctr_args.concat ['--workdir', user.dir.to_s] + ctr_args.concat [dev_container_name, '/bin/bash'] + + sh ctr_args.join(' ') + end + + task :bash => [:exec] + + # let advanced users know this, not --tasks + task :ensure_dev_files do + [ + :init_ood_portal, + :init_ctr_user + ].each do |initer| + send(initer) + end + end +end diff --git a/lib/tasks/packaging.rb b/lib/tasks/packaging.rb index ec718682f8..d7c0dc66e5 100644 --- a/lib/tasks/packaging.rb +++ b/lib/tasks/packaging.rb @@ -57,8 +57,8 @@ def tag_latest_container_cmd(image_name) sh "git ls-files | #{tar} -c --transform 's,^,ondemand-#{version}/,' -T - | gzip > packaging/v#{version}.tar.gz" end - desc "Build clean docker container from ood image" - task container: [:clean] do + desc "Build the ood image" + task :container do sh build_cmd("Dockerfile", image_name) unless image_exists?("#{image_name}:#{image_tag}") end @@ -75,14 +75,9 @@ def tag_latest_container_cmd(image_name) desc "Build container with Dockerfile.dev" task dev_container: [:latest_container] do - if ENV['OOD_KEEP_USERNS'].to_s.empty? - extra = [] - else - username = Etc.getlogin - extra = ["--build-arg", "USER=#{username}"] - extra.concat ["--build-arg", "UID=#{Etc.getpwnam(username).uid}"] - extra.concat ["--build-arg", "GID=#{Etc.getpwnam(username).uid}"] - end + extra = ["--build-arg", "USER=#{user.name}"] + extra.concat ["--build-arg", "UID=#{user.uid}"] + extra.concat ["--build-arg", "GID=#{user.gid}"] sh build_cmd("Dockerfile.dev", dev_image_name, extra_args: extra) unless image_exists?("#{dev_image_name}:#{image_tag}") sh tag_latest_container_cmd(dev_image_name) diff --git a/lib/tasks/test.rb b/lib/tasks/test.rb index 393692014a..20009fad46 100644 --- a/lib/tasks/test.rb +++ b/lib/tasks/test.rb @@ -2,8 +2,7 @@ task :test => 'test:all' def yarn_app?(path) - @path = Pathname.new(path) - @path.join('yarn.lock').exist? + Pathname.new(path).join('yarn.lock').exist? end namespace :test do @@ -22,12 +21,13 @@ def yarn_app?(path) testing.each_pair do |app, _task| chdir PROJ_DIR.join(app.to_s) do - @path = PROJ_DIR.join(app.to_s) - if yarn_app?(@path) + if yarn_app?(Dir.pwd) sh 'bin/yarn install' end - sh "bundle install" + Bundler.with_unbundled_env do + sh "bundle install" + end end end end @@ -36,7 +36,9 @@ def yarn_app?(path) task :unit => [:setup] do testing.each_pair do |app, task| chdir PROJ_DIR.join(app.to_s) do - sh "bundle exec rake #{task}" + Bundler.with_unbundled_env do + sh "bundle exec rake #{task}" + end end end end