Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Commit

Permalink
feat(source): Added support for HTTP
Browse files Browse the repository at this point in the history
  • Loading branch information
ajgon committed Mar 16, 2018
1 parent 2bc89df commit e1a3348
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .kitchen.yml
Expand Up @@ -61,8 +61,8 @@ suites:
queues:
- default
- mailers
- name: unicorn_apache_hanami_resque
data_bags_path: "test/integration/data_bags/unicorn_apache_hanami_resque"
- name: http_unicorn_apache_hanami_resque
data_bags_path: "test/integration/data_bags/http_unicorn_apache_hanami_resque"
run_list:
- recipe[opsworks_ruby::setup]
- recipe[opsworks_ruby::deploy]
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -24,7 +24,7 @@ env:
matrix:
- INSTANCE=default-ubuntu-1604
- INSTANCE=all-options-ubuntu-1604
- INSTANCE=unicorn-apache-hanami-resque-ubuntu-1604
- INSTANCE=http-unicorn-apache-hanami-resque-ubuntu-1604
- INSTANCE=s3-thin-nginx-padrino-delayed-job-ubuntu-1604
- INSTANCE=nullified-ubuntu-1604
- INSTANCE=maximum-override-ubuntu-1604
Expand Down
17 changes: 16 additions & 1 deletion docs/source/attributes.rst
Expand Up @@ -198,7 +198,7 @@ source
- ``app['source']['adapter']``

- **Supported values:** ``git``, ``s3``
- **Supported values:** ``git``, ``http``, ``s3``
- **Default:** ``git``
- Source used by the cookbook to fetch the application codebase.

Expand Down Expand Up @@ -261,6 +261,21 @@ s3

- ``AWS_SECRET_ACCESS_KEY`` for given ``AWS_ACCESS_KEY_ID``.

http
^^^^

| This source expects a packed project in one of the following formats:
| ``bzip2``, ``compress``, ``gzip``, ``tar``, ``xz`` or ``zip``.
| If you are using ubuntu, ``7zip`` is also supported.
- ``app['source']['user']``

- If file is hidden behind HTTP BASIC AUTH, this field should contain username.

- ``app['source']['password']``

- If file is hidden behind HTTP BASIC AUTH, this field should contain password.

framework
~~~~~~~~~

Expand Down
133 changes: 133 additions & 0 deletions libraries/drivers_source_http.rb
@@ -0,0 +1,133 @@
# frozen_string_literal: true

module Drivers
module Source
class Http < Drivers::Source::Base
adapter :http
allowed_engines :archive, :http
packages debian: %w[bzip2 git gzip p7zip tar unzip xz-utils],
rhel: %w[bzip2 git gzip tar unzip xz]
output filter: %i[user password url]

def initialize(context, app, options = {})
super
@file_name = File.basename(URI.parse(out[:url]).path)
end

def before_deploy
prepare_archive_directories
fetch_archive_from_http
prepare_dummy_git_repository
end

def deploy_before_restart
remove_dot_git
end

def fetch(deploy_context)
deploy_context.repository(dummy_repository_dir)
end

private

def prepare_archive_directories
context.directory(archive_file_dir) do
mode '0700'
end

context.directory(dummy_repository_dir) do
mode '0700'
end
end

def fetch_archive_from_http
uri = URI.parse(out[:url])
uri.userinfo = "#{out[:user]}:#{out[:password]}"

context.remote_file File.join(archive_file_dir, @file_name) do
source uri.to_s
owner node['deployer']['user'] || 'root'
group www_group
mode '0600'
action :create
end
end

def prepare_dummy_git_repository
chef_archive_file_dir = archive_file_dir
chef_dummy_repository_dir = dummy_repository_dir
file_name = @file_name

context.ruby_block 'extract' do
block do
OpsworksRuby::Archive.new(File.join(chef_archive_file_dir, file_name)).uncompress(chef_dummy_repository_dir)
end
end

context.execute dummy_git_command
end

def remove_dot_git
context.directory File.join(deploy_dir(app), 'current', '.git') do
recursive true
action :delete
end
end

def dummy_git_command
"cd #{dummy_repository_dir} && git init && git config user.name 'Chef' && " \
'git config user.email \'chef@localhost\' && git add -A && ' \
'git commit --author=\'Chef <>\' -m \'dummy repo\' -an'
end

def archive_file_dir
@archive_file_dir ||= File.join(tmpdir, 'archive')
end

def dummy_repository_dir
@dummy_repository_dir ||= File.join(tmpdir, 'archive.d')
end

def tmpdir
return @tmpdir if @tmpdir.present?

@tmpdir = Dir.mktmpdir('opsworks_ruby')

context.directory(@tmpdir) do
mode '0755'
end

@tmpdir
end

# taken from https://github.com/aws/opsworks-cookbooks/blob/release-chef-11.10/scm_helper/libraries/s3.rb#L6
def parse_uri(uri) # rubocop:disable Metrics/MethodLength
# base_uri | remote_path
#----------------------------------------+------------------------------
# scheme, userinfo, host, port, registry | path, opaque, query, fragment

components = URI.split(uri)
base_uri = URI::HTTP.new(*(components.take(5) + [nil] * 4))
remote_path = URI::HTTP.new(*([nil] * 5 + components.drop(5)))

virtual_host_match =
base_uri.host.match(/\A(.+)\.s3(?:[-.](?:ap|eu|sa|us)-(?:.+-)\d|-external-1)?\.amazonaws\.com/i)

if virtual_host_match
# virtual-hosted-style: http://bucket.s3.amazonaws.com or http://bucket.s3-aws-region.amazonaws.com
bucket = virtual_host_match[1]
else
# path-style: http://s3.amazonaws.com/bucket or http://s3-aws-region.amazonaws.com/bucket
uri_path_components = remote_path.path.split('/').reject(&:empty?)
bucket = uri_path_components.shift # cut first element
base_uri.path = "/#{bucket}" # append bucket to base_uri
remote_path.path = uri_path_components.join('/') # delete bucket from remote_path
end

# remote_path don't allow a "/" at the beginning
# base_url don't allow a "/" at the end
[bucket, remote_path.to_s.to_s.sub(%r{^/}, ''), base_uri.to_s.chomp('/')]
end
end
end
end
18 changes: 18 additions & 0 deletions spec/unit/libraries/drivers_source_factory_spec.rb
Expand Up @@ -34,4 +34,22 @@
source = described_class.build(dummy_context(node_data), aws_opsworks_app(app_source: nil))
expect(source).to be_instance_of(Drivers::Source::S3)
end

it 'returns a Http class from app source' do
node_data = node
node_data['deploy']['dummy_project']['source'] = {}

source = described_class.build(
dummy_context(node_data), aws_opsworks_app(app_source: { type: 'archive', url: 'http://example.com' })
)
expect(source).to be_instance_of(Drivers::Source::Http)
end

it 'returns a Http class from node source' do
node_data = node
node_data['deploy']['dummy_project']['source'] = { 'adapter' => 'http', 'url' => 'http://example.com' }

source = described_class.build(dummy_context(node_data), aws_opsworks_app(app_source: nil))
expect(source).to be_instance_of(Drivers::Source::Http)
end
end
136 changes: 136 additions & 0 deletions spec/unit/libraries/drivers_source_http_spec.rb
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require 'spec_helper'

describe Drivers::Source::Http do
let(:http_aws_opsworks_app) do
aws_opsworks_app(
app_source: {
password: 'password',
type: 'archive',
url: 'https://example.com/path/file.tar.gz',
user: 'user'
}
)
end
let(:driver) { described_class.new(dummy_context(node), http_aws_opsworks_app) }

it 'receives and exposes app and node' do
expect(driver.app).to eq http_aws_opsworks_app
expect(driver.send(:node)).to eq node
expect(driver.options).to eq({})
end

it 'has the correct driver_type' do
expect(driver.driver_type).to eq('source')
end

context 'validate adapter and engine' do
it 'adapter = missing, engine = missing' do
expect do
described_class.new(dummy_context(node(deploy: { dummy_project: {} })), aws_opsworks_app(app_source: nil)).out
end.to raise_error ArgumentError,
"Missing :app or :node engine, expected #{described_class.allowed_engines.inspect}."
end

it 'adapter = missing, engine = wrong' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: {} })),
aws_opsworks_app(app_source: { type: 'svn' })
).out
end.to raise_error ArgumentError,
"Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'svn'."
end

it 'adapter = missing, engine = correct' do
expect do
described_class.new(dummy_context(node(deploy: { dummy_project: {} })), http_aws_opsworks_app).out
end.not_to raise_error
end

it 'adapter = wrong, engine = missing' do
expect do
described_class.new(
dummy_context(
node(deploy: { dummy_project: { source: { adapter: 'svn' } } })
),
aws_opsworks_app(app_source: nil)
).out
end.to raise_error ArgumentError,
"Incorrect :node engine, expected #{described_class.allowed_engines.inspect}, got 'svn'."
end

it 'adapter = wrong, engine = wrong' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: { source: { adapter: 'svn' } } })),
aws_opsworks_app(app_source: { type: 'svn' })
).out
end.to raise_error ArgumentError,
"Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'svn'."
end

it 'adapter = wrong, engine = correct' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: { source: { adapter: 'svn' } } })), http_aws_opsworks_app
).out
end.not_to raise_error
end

it 'adapter = correct, engine = missing' do
expect do
described_class.new(
dummy_context(
node(deploy: { dummy_project: { source: { adapter: 'http', url: 'http://example.com' } } })
),
aws_opsworks_app(app_source: nil)
).out
end.not_to raise_error
end

it 'adapter = correct, engine = wrong' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: { source: { adapter: 'http' } } })),
aws_opsworks_app(app_source: { type: 'svn' })
).out
end.to raise_error ArgumentError,
"Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'svn'."
end

it 'adapter = correct, engine = correct' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: { source: { adapter: 'http' } } })), http_aws_opsworks_app
).out
end.not_to raise_error
end
end

context 'connection data' do
after(:each) do
expect(@item.out).to eq(
password: 'password',
url: 'https://example.com/path/file.tar.gz',
user: 'user'
)
end

it 'taken from engine' do
@item = described_class.new(dummy_context(node), http_aws_opsworks_app)
end

it 'taken from adapter' do
node_data = node
node_data['deploy']['dummy_project']['source'] = {
'adapter' => 'http',
'password' => 'password',
'url' => 'https://example.com/path/file.tar.gz',
'user' => 'user'
}
@item = described_class.new(dummy_context(node_data), aws_opsworks_app(app_source: nil))
end
end
end
2 changes: 1 addition & 1 deletion spec/unit/libraries/drivers_source_s3_spec.rb
Expand Up @@ -103,7 +103,7 @@
it 'adapter = correct, engine = correct' do
expect do
described_class.new(
dummy_context(node(deploy: { dummy_project: { source: { type: 's3' } } })), s3_aws_opsworks_app
dummy_context(node(deploy: { dummy_project: { source: { adapter: 's3' } } })), s3_aws_opsworks_app
).out
end.not_to raise_error
end
Expand Down

0 comments on commit e1a3348

Please sign in to comment.