#!/usr/bin/python # Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import unittest from gae_ext_runtime import testutil RUNTIME_DEF_ROOT = os.path.dirname(os.path.dirname(__file__)) DOCKERFILE_TEXT = '''\ # This Dockerfile for a Ruby application was generated by gcloud. # The base Dockerfile installs: # * A number of packages needed by the Ruby runtime and by gems # commonly used in Ruby web apps (such as libsqlite3) # * A recent version of NodeJS # * A recent version of the standard Ruby runtime to use by default # * The bundler gem FROM gcr.io/google-appengine/ruby:{base_image_tag} # If your application requires a specific ruby version (compatible with rbenv), # set it here. Leave blank to use the currently recommended default. ARG REQUESTED_RUBY_VERSION="{ruby_version}" # Install any requested ruby if not already preinstalled by the base image. # Tries installing a prebuilt package first, then falls back to a source build. RUN if test -n "$REQUESTED_RUBY_VERSION" -a \\ ! -x /rbenv/versions/$REQUESTED_RUBY_VERSION/bin/ruby; then \\ (apt-get update -y \\ && apt-get install -y -q gcp-ruby-$REQUESTED_RUBY_VERSION) \\ || (cd /rbenv/plugins/ruby-build \\ && git pull \\ && rbenv install -s $REQUESTED_RUBY_VERSION) \\ && rbenv global $REQUESTED_RUBY_VERSION \\ && gem install -q --no-rdoc --no-ri bundler --version $BUNDLER_VERSION \\ && apt-get clean \\ && rm -f /var/lib/apt/lists/*_*; \\ fi ENV RBENV_VERSION=${{REQUESTED_RUBY_VERSION:-$RBENV_VERSION}} # Copy the application files. COPY . /app/ # Install required gems if Gemfile.lock is present. RUN if test -f Gemfile.lock; then \\ bundle install --deployment --without="development test" \\ && rbenv rehash; \\ fi # Temporary. Will be moved to base image later. ENV RACK_ENV=production \\ RAILS_ENV=production \\ RAILS_SERVE_STATIC_FILES=true # Run asset pipeline if we're in a Rails app. RUN if test -d app/assets -a -f config/application.rb; then \\ bundle exec rake assets:precompile || true; \\ fi # BUG: Reset entrypoint to override base image. ENTRYPOINT [] # Start application on port $PORT. CMD {entrypoint} ''' class RuntimeTestCase(testutil.TestBase): """Tests for the Ruby external runtime fingerprinter.""" def file_contents(self, filename): """Reads the contents of the file from the tempdir. Args: filename: (str) filename to be joined with tempdir prefix. Returns: File contents. """ with open(self.full_path(filename)) as f: return f.read() def stub_response(self, response): """Stubs the console response from the user. Args: response: (str) stubbed response. Returns: A function to reset the stubbed functions to their original implementations. """ can_prompt = self.exec_env.CanPrompt prompt_response = self.exec_env.PromptResponse def unstub(): self.exec_env.CanPrompt = can_prompt self.exec_env.PromptResponse = prompt_response self.exec_env.CanPrompt = lambda: True self.exec_env.PromptResponse = lambda prompt: response return unstub def setUp(self): self.runtime_def_root = RUNTIME_DEF_ROOT super(RuntimeTestCase, self).setUp() def test_generate_without_ruby_files(self): self.write_file('index.html', 'index') self.generate_configs() self.assertFalse(os.path.exists(self.full_path('app.yaml'))) self.assertFalse(os.path.exists(self.full_path('Dockerfile'))) self.assertFalse(os.path.exists(self.full_path('.dockerignore'))) def test_generate_without_ruby_files_no_write(self): """Tests generate_config_data does nothing if no ruby files.""" self.write_file('index.html', 'index') self.assertIsNone(self.generate_config_data()) self.assertFalse(os.path.exists(self.full_path('app.yaml'))) def test_generate_with_ruby_files(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') self.generate_configs() unstub() app_yaml = self.file_contents('app.yaml') self.assertIn('runtime: ruby\n', app_yaml) self.assertIn('env: flex\n', app_yaml) self.assertIn('entrypoint: bundle exec rackup -p $PORT -E deployment\n', app_yaml) self.assertFalse(os.path.exists(self.full_path('Dockerfile'))) self.assertFalse(os.path.exists(self.full_path('.dockerignore'))) def test_generate_with_ruby_files_no_write(self): """Tests generate_config_data with basic Ruby files. Tests that app.yaml is written with correct contents given entrypoint response, and that Dockerfile and .dockerignore not written to disk. """ self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') cfg_files = self.generate_config_data() unstub() app_yaml = self.file_contents('app.yaml') self.assertIn('runtime: ruby\n', app_yaml) self.assertIn('env: flex\n', app_yaml) self.assertIn('entrypoint: bundle exec rackup -p $PORT -E deployment\n', app_yaml) self.assertNotIn('Dockerfile', [f.filename for f in cfg_files]) self.assertNotIn('.dockerignore', [f.filename for f in cfg_files]) def test_generate_with_deploy(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') self.write_file('.ruby-version', 'rbx-3.9') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') self.generate_configs(deploy=True) unstub() dockerfile = self.file_contents('Dockerfile') self.assertEqual( dockerfile, DOCKERFILE_TEXT.format( ruby_version='rbx-3.9', entrypoint='bundle exec rackup -p $PORT -E deployment')) dockerignore = self.file_contents('.dockerignore') self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_deploy_no_write(self): """Tests generate_config_data with deploy=True. Tests that .dockerignore and Dockerfile contents are correct based on contents of app. """ self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') self.write_file('.ruby-version', 'rbx-3.9') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') cfg_files = self.generate_config_data(deploy=True) unstub() self.assert_genfile_exists_with_contents( cfg_files, 'Dockerfile', DOCKERFILE_TEXT.format( ruby_version='rbx-3.9', entrypoint='bundle exec rackup -p $PORT -E deployment')) self.assertIn('.dockerignore', [f.filename for f in cfg_files]) dockerignore = [f.contents for f in cfg_files if f.filename == '.dockerignore'][0] self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_custom(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') self.generate_configs(custom=True) unstub() app_yaml = self.file_contents('app.yaml') self.assertIn('runtime: custom\n', app_yaml) self.assertIn('env: flex\n', app_yaml) self.assertIn('entrypoint: bundle exec rackup -p $PORT -E deployment\n', app_yaml) dockerfile = self.file_contents('Dockerfile') self.assertEqual( dockerfile, DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec rackup -p $PORT -E deployment')) dockerignore = self.file_contents('.dockerignore') self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_custom_no_write(self): """Tests generate_config_data with custom=True. Tests that app.yaml is written with correct parameters and Dockerfile, .dockerignore contents are correctly returned by method. """ self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') unstub = self.stub_response('bundle exec rackup -p $PORT -E deployment') cfg_files = self.generate_config_data(custom=True) unstub() app_yaml = self.file_contents('app.yaml') self.assertIn('runtime: custom\n', app_yaml) self.assertIn('env: flex\n', app_yaml) self.assertIn('entrypoint: bundle exec rackup -p $PORT -E deployment\n', app_yaml) self.assert_genfile_exists_with_contents( cfg_files, 'Dockerfile', DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec rackup -p $PORT -E deployment')) self.assertIn('.dockerignore', [f.filename for f in cfg_files]) dockerignore = [f.contents for f in cfg_files if f.filename == '.dockerignore'][0] self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_existing_appinfo(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') appinfo = testutil.AppInfoFake( entrypoint='bundle exec ruby index.rb $PORT', runtime='ruby', vm=True) self.generate_configs(appinfo=appinfo, deploy=True) self.assertFalse(os.path.exists(self.full_path('app.yaml'))) dockerfile = self.file_contents('Dockerfile') self.assertEqual( dockerfile, DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec ruby index.rb $PORT')) dockerignore = self.file_contents('.dockerignore') self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_existing_appinfo_no_write(self): """Tests generate_config_data with passed appinfo.""" self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') appinfo = testutil.AppInfoFake( entrypoint='bundle exec ruby index.rb $PORT', runtime='ruby', vm=True) cfg_files = self.generate_config_data(appinfo=appinfo, deploy=True) self.assertFalse(os.path.exists(self.full_path('app.yaml'))) self.assert_genfile_exists_with_contents( cfg_files, 'Dockerfile', DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec ruby index.rb $PORT')) self.assertIn('.dockerignore', [f.filename for f in cfg_files]) dockerignore = [f.contents for f in cfg_files if f.filename == '.dockerignore'][0] self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_ruby_version(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') self.write_file('.ruby-version', '2.3.1\n') appinfo = testutil.AppInfoFake( entrypoint='bundle exec ruby index.rb $PORT', runtime='ruby', vm=True) self.generate_configs(appinfo=appinfo, deploy=True) self.assertFalse(os.path.exists(self.full_path('app.yaml'))) dockerfile = self.file_contents('Dockerfile') self.assertEqual( dockerfile, DOCKERFILE_TEXT.format( ruby_version='2.3.1', entrypoint='bundle exec ruby index.rb $PORT')) dockerignore = self.file_contents('.dockerignore') self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_ruby_version_no_write(self): """Tests generate_config_data with .ruby-version file.""" self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') self.write_file('config.ru', 'run Index.app') self.write_file('.ruby-version', '2.3.1\n') appinfo = testutil.AppInfoFake( entrypoint='bundle exec ruby index.rb $PORT', runtime='ruby', vm=True) cfg_files = self.generate_config_data(appinfo=appinfo, deploy=True) self.assertFalse(os.path.exists(self.full_path('app.yaml'))) self.assert_genfile_exists_with_contents( cfg_files, 'Dockerfile', DOCKERFILE_TEXT.format( ruby_version='2.3.1', entrypoint='bundle exec ruby index.rb $PORT')) self.assertIn('.dockerignore', [f.filename for f in cfg_files]) dockerignore = [f.contents for f in cfg_files if f.filename == '.dockerignore'][0] self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_prompt(self): self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') unstub = self.stub_response('bundle exec ruby index.rb $PORT') self.generate_configs(deploy=True) unstub() dockerfile = self.file_contents('Dockerfile') self.assertEqual( dockerfile, DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec ruby index.rb $PORT')) dockerignore = self.file_contents('.dockerignore') self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) def test_generate_with_prompt_no_write(self): """Tests generate_config_data with entrypoint given by prompt.""" self.write_file('index.rb', 'class Index; end') self.write_file('Gemfile', 'source "https://rubygems.org"') unstub = self.stub_response('bundle exec ruby index.rb $PORT') cfg_files = self.generate_config_data(deploy=True) unstub() self.assert_genfile_exists_with_contents( cfg_files, 'Dockerfile', DOCKERFILE_TEXT.format( ruby_version='', entrypoint='bundle exec ruby index.rb $PORT')) self.assertIn('.dockerignore', [f.filename for f in cfg_files]) dockerignore = [f.contents for f in cfg_files if f.filename == '.dockerignore'][0] self.assertIn('.dockerignore\n', dockerignore) self.assertIn('Dockerfile\n', dockerignore) self.assertIn('.git\n', dockerignore) self.assertIn('.hg\n', dockerignore) self.assertIn('.svn\n', dockerignore) if __name__ == '__main__': unittest.main()