boshify 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 301facaa8c05504dbec23d0896f196a1714a7551
4
+ data.tar.gz: c37f62978214b529975c01c8c1ef3a84549ed5a0
5
+ SHA512:
6
+ metadata.gz: 3fe9bb319022d84106081a18ad7e341c90cb256d3e670b5c9eb9f70d4c56ee7c2012d35eb2e8eb68c8fc702e31ef22b3021c44b764d3ab4f8ed91fa156749cb7
7
+ data.tar.gz: 813e29e859b7945191cc061298038923ac9f76153610f886b6303a1c684748e2a463237ae001350d6191f957b8ba656f5a955f9a63678897bc9ec7a9fd9af4ed
data/bin/boshify ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ require 'boshify'
3
+ require 'bosh_lite_helpers'
4
+
5
+ # Boshify generates BOSH releases
6
+ module Boshify
7
+ cr = BoshLiteHelpers::CommandRunner.new
8
+ fs = Filesystem.new
9
+ dl = Downloader.new(filesystem: fs)
10
+ release_creator = ReleaseCreator.new(filesystem: fs,
11
+ release_dir: Dir.pwd,
12
+ cmd_runner: cr)
13
+ pkg_source = UbuntuPackages.new(downloader: dl, cmd_runner: cr)
14
+ converter = PackageConverter.new(package_source: pkg_source,
15
+ downloader: dl,
16
+ release_creator: release_creator)
17
+ cmd_line = CommandLine.new(program_name: $PROGRAM_NAME,
18
+ package_converter: converter)
19
+ result = cmd_line.run(ARGV)
20
+ puts result[:stdout] unless result[:stdout].empty?
21
+ STDERR.puts result[:stderr] unless result[:stderr].empty?
22
+ exit result[:exit_code]
23
+ end
@@ -0,0 +1,81 @@
1
+ require 'optparse'
2
+ require 'pathname'
3
+
4
+ module Boshify
5
+ # Command line argument processing
6
+ class CommandLine
7
+ def initialize(options)
8
+ unless options[:program_name]
9
+ fail ArgumentError, 'Program name must be specified'
10
+ end
11
+ unless options[:package_converter]
12
+ fail ArgumentError, 'Package converter must be specified'
13
+ end
14
+ @program_name = Pathname.new(options[:program_name]).basename
15
+ @package_converter = options[:package_converter]
16
+ end
17
+
18
+ def run(args)
19
+ with_options(args) do |options|
20
+ begin
21
+ use_mirror_if_specified(options[:mirror])
22
+ @package_converter.create_release_for(name: options[:package])
23
+ { exit_code: 0, stdout: "Package #{options[:package]} converted",
24
+ stderr: '' }
25
+ rescue => e
26
+ { exit_code: 1, stdout: '', stderr: e.message }
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def with_options(args)
34
+ options = parse(args)
35
+ if options[:package]
36
+ yield options
37
+ else
38
+ { exit_code: 0, stdout: @parser.help, stderr: '' }
39
+ end
40
+ rescue OptionParser::MissingArgument
41
+ { exit_code: 1, stdout: @parser.help, stderr: '' }
42
+ end
43
+
44
+ def use_mirror_if_specified(mirror_url)
45
+ return unless mirror_url
46
+ @package_converter.package_source.mirror_url = mirror_url
47
+ end
48
+
49
+ def parse(args)
50
+ options = {}
51
+ @parser = OptionParser.new do |cmd_opts|
52
+ cmd_opts.banner = "#{@program_name} [options]"
53
+ add_package_option(cmd_opts, options)
54
+ add_mirror_option(cmd_opts, options)
55
+ add_help_option(cmd_opts, options)
56
+ end
57
+ @parser.parse!(args)
58
+ options
59
+ end
60
+
61
+ def add_package_option(cmd_opts, options)
62
+ cmd_opts.on('-p', '--package PACKAGE', 'Ubuntu source package') do |p|
63
+ options[:package] = p
64
+ end
65
+ end
66
+
67
+ def add_mirror_option(cmd_opts, options)
68
+ cmd_opts.on('-m',
69
+ '--mirror [MIRROR]',
70
+ 'Alternate Ubuntu mirror URL') do |m|
71
+ options[:mirror] = m
72
+ end
73
+ end
74
+
75
+ def add_help_option(cmd_opts, options)
76
+ cmd_opts.on('-h', '--help', 'Print help') do
77
+ options[:help] = true
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,22 @@
1
+ require 'httparty'
2
+ require 'uri'
3
+
4
+ module Boshify
5
+ class DownloadError < StandardError; end
6
+
7
+ # Downloads remote resources to disk
8
+ class Downloader
9
+ def initialize(options = {})
10
+ @filesystem = options[:filesystem]
11
+ end
12
+
13
+ def get(url)
14
+ bn = Pathname.new(URI.parse(url.to_s).path).basename
15
+ r = HTTParty.get(url)
16
+ unless r.ok?
17
+ fail DownloadError, "The resource could not be retrieved: #{url}"
18
+ end
19
+ @filesystem.write_file(basename: bn, content: r.body)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+ require 'pathname'
4
+
5
+ module Boshify
6
+ # Wrapper around filesystem operations
7
+ class Filesystem
8
+ def copy(from, to)
9
+ FileUtils.copy(from, to)
10
+ end
11
+
12
+ def mkdir_p(path)
13
+ path.mkpath
14
+ end
15
+
16
+ def write_file(options)
17
+ check_file_options!(options)
18
+ file = determine_file_path(options)
19
+ File.open(file.cleanpath, 'w') { |f| f.write(options[:content]) }
20
+ file
21
+ end
22
+
23
+ private
24
+
25
+ def check_file_options!(options)
26
+ unless options[:content]
27
+ fail ArgumentError, 'File content must be specified'
28
+ end
29
+
30
+ # rubocop:disable GuardClause
31
+ unless options[:path] || options[:basename]
32
+ fail ArgumentError, 'Either basename or path must be specified'
33
+ end
34
+ end
35
+
36
+ def determine_file_path(options)
37
+ if options[:path]
38
+ options[:path]
39
+ else
40
+ Pathname.new(Dir.mktmpdir) + options[:basename].basename
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Boshify
2
+ # Converts an operating system package to a BOSH release
3
+ class PackageConverter
4
+ attr_reader :package_source
5
+
6
+ def initialize(options)
7
+ @package_source = options[:package_source]
8
+ @downloader = options[:downloader]
9
+ @release_creator = options[:release_creator]
10
+ end
11
+
12
+ def create_release_for(package = {})
13
+ @package_source.refresh
14
+ local_path = @downloader.get(
15
+ @package_source.source_tarball_url(package[:name]))
16
+ @release_creator.create_release(name: package[:name], packages: [
17
+ name: package[:name],
18
+ source_tarball: local_path
19
+ ])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,154 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ module Boshify
5
+ # Responsible for generating BOSH releases
6
+ # rubocop:disable ClassLength
7
+ class ReleaseCreator
8
+ def initialize(options)
9
+ check_options!(options)
10
+ @fs = options[:filesystem]
11
+ @release_dir = Pathname.new(options[:release_dir])
12
+ @cmd_runner = options[:cmd_runner]
13
+ end
14
+
15
+ def create_release(release)
16
+ create_empty_release
17
+ create_placeholder_blobstore_config(release)
18
+ create_job(release)
19
+ create_packages(release)
20
+ end
21
+
22
+ private
23
+
24
+ def check_options!(options)
25
+ unless options[:filesystem]
26
+ fail ArgumentError, 'Filesystem must be provided'
27
+ end
28
+ unless options[:release_dir]
29
+ fail ArgumentError, 'Release directory must be provided'
30
+ end
31
+ # rubocop:disable GuardClause
32
+ unless options[:cmd_runner]
33
+ fail ArgumentError, 'Command runner must be provided'
34
+ end
35
+ end
36
+
37
+ def create_empty_release
38
+ create_release_dirs
39
+ generate_empty_blobs_yaml
40
+ end
41
+
42
+ def create_release_dirs
43
+ %w(blobs config jobs packages src).each do |dir|
44
+ @fs.mkdir_p(@release_dir + dir)
45
+ end
46
+ end
47
+
48
+ def generate_empty_blobs_yaml
49
+ @fs.write_file(path: @release_dir + 'config' + 'blobs.yml',
50
+ content: YAML.dump({}))
51
+ end
52
+
53
+ # rubocop:disable MethodLength
54
+ def create_placeholder_blobstore_config(release)
55
+ @fs.write_file(path: @release_dir + 'config' + 'final.yml',
56
+ content: YAML.dump(
57
+ 'blobstore' => {
58
+ 'provider' => 's3',
59
+ 'options' => {
60
+ 'bucket_name' => "#{release[:name]}-release",
61
+ 'access_key_id' => 'MY_ACCESS_KEY_ID',
62
+ 'secret_acces_key' => 'MY_SECRET_ACCESS_KEY',
63
+ 'encryption_key' => 'MY_ENCRYPTION_KEY'
64
+ }
65
+ },
66
+ 'final_name' => release[:name]
67
+ ))
68
+ end
69
+
70
+ def create_job(release)
71
+ job_dir = @release_dir + 'jobs' + release[:name]
72
+ @fs.mkdir_p(job_dir)
73
+ @fs.write_file(path: job_dir + 'monit', content: '')
74
+ @fs.write_file(path: job_dir + 'spec', content: job_spec(release))
75
+ end
76
+
77
+ def job_spec(release)
78
+ YAML.dump(
79
+ 'name' => release[:name],
80
+ 'packages' => release[:packages].map { |p| p[:name] },
81
+ 'templates' => {}
82
+ )
83
+ end
84
+
85
+ def build_path(source_tarball)
86
+ files = @cmd_runner.run("tar -ztf #{source_tarball}",
87
+ quiet: true)[:stdout].split("\n")
88
+ directory_with_configure(files.map { |p| Pathname.new(p) })
89
+ end
90
+
91
+ def directory_with_configure(paths)
92
+ paths.find { |p| p.basename == Pathname.new('configure') }.dirname
93
+ end
94
+
95
+ def create_packages(release)
96
+ release[:packages].each do |pkg|
97
+ pkg_dir = make_package_dir(pkg[:name])
98
+ bp = blob_path(pkg)
99
+
100
+ generate_package_spec(pkg, pkg_dir, bp)
101
+ copy_blob_into_place(pkg, bp)
102
+ generate_package_script(pkg_dir, bp, pkg[:source_tarball])
103
+ end
104
+ end
105
+
106
+ def generate_package_script(pkg_dir, blob_path, source_tarball)
107
+ pkg_script = packaging_script(blob_path, build_path(source_tarball))
108
+ @fs.write_file(path: pkg_dir + 'packaging', content: pkg_script)
109
+ end
110
+
111
+ def blob_path(pkg)
112
+ "#{pkg[:name]}/#{pkg[:source_tarball].basename}"
113
+ end
114
+
115
+ def make_package_dir(pkg_name)
116
+ pkg_dir = @release_dir + 'packages' + pkg_name
117
+ @fs.mkdir_p(pkg_dir)
118
+ pkg_dir
119
+ end
120
+
121
+ def generate_package_spec(pkg, pkg_dir, blob_path)
122
+ @fs.write_file(path: pkg_dir + 'spec',
123
+ content: package_spec(pkg, blob_path))
124
+ end
125
+
126
+ def copy_blob_into_place(pkg, blob_path)
127
+ @fs.mkdir_p(@release_dir + 'blobs' + pkg[:name])
128
+ @fs.copy(pkg[:source_tarball],
129
+ Pathname.new(@release_dir + 'blobs' + blob_path))
130
+ end
131
+
132
+ def packaging_script(blob_path, build_dir_path)
133
+ "#!/bin/bash
134
+ set -e
135
+ set -u
136
+
137
+ tar zxvf #{blob_path}
138
+ cd #{build_dir_path}
139
+
140
+ ./configure --prefix=${BOSH_INSTALL_TARGET}
141
+
142
+ make
143
+ make install"
144
+ end
145
+
146
+ def package_spec(pkg, blob_path)
147
+ YAML.dump(
148
+ 'name' => pkg[:name],
149
+ 'dependencies' => [],
150
+ 'files' => [blob_path]
151
+ )
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,111 @@
1
+ require 'uri'
2
+ module Boshify
3
+ class PackageNotFoundError < StandardError; end
4
+ class InvalidPackageMetadataError < StandardError; end
5
+
6
+ # Ubuntu package source
7
+ class UbuntuPackages
8
+ attr_reader :mirror_url
9
+
10
+ def initialize(options = {})
11
+ @downloader = options[:downloader]
12
+ @cmd_runner = options[:cmd_runner]
13
+ self.mirror_url = options[:mirror_url] || 'http://us.archive.ubuntu.com/ubuntu'
14
+ end
15
+
16
+ def mirror_url=(mirror)
17
+ @mirror_url = URI.parse("#{mirror}/")
18
+ end
19
+
20
+ def refresh
21
+ @all_packages = packages_hash(
22
+ parse(decompress(download_sources_metadata)))
23
+ end
24
+
25
+ def source_tarball_url(package_name)
26
+ unless @all_packages[package_name]
27
+ fail PackageNotFoundError, "Package #{package_name} was not found"
28
+ end
29
+ pkg = @all_packages[package_name]
30
+ mirror_url + "#{pkg['Directory']}/#{original_tarball(pkg)}"
31
+ end
32
+
33
+ def parse(input)
34
+ group_package_values(
35
+ as_a_hash(
36
+ parse_multiline_values(
37
+ group_by_whether_pairs(
38
+ split_key_value_pairs(input)))))
39
+ end
40
+
41
+ private
42
+
43
+ def original_tarball(pkg)
44
+ pkg['Files'].find { |f| f[:name].end_with?('orig.tar.gz') }[:name]
45
+ end
46
+
47
+ def download_sources_metadata
48
+ @downloader.get(mirror_url + 'dists/lucid/main/source/Sources.bz2')
49
+ end
50
+
51
+ def decompress(local_path)
52
+ result = @cmd_runner.run("bzcat #{local_path}", quiet: true)
53
+ if result[:exit_code] != 0
54
+ fail InvalidPackageMetadataError,
55
+ "Could not decompress: #{result[:stderr]}"
56
+ end
57
+ result[:stdout]
58
+ end
59
+
60
+ FILE_KEYS = %w(Files Checksums-Sha1 Checksums-Sha256)
61
+
62
+ def split_key_value_pairs(input)
63
+ input.lines.map { |line| line.split(':', 2).map { |f| f.strip } }
64
+ end
65
+
66
+ def group_by_whether_pairs(pairs)
67
+ pairs.chunk { |p| p.size == 2 }.to_a
68
+ end
69
+
70
+ def parse_multiline_values(pairs)
71
+ # rubocop:disable Next
72
+ pairs.each_with_index do |v, i|
73
+ if !v[0] && FILE_KEYS.include?(pairs[i - 1][1].last[0])
74
+ pairs[i - 1][1].last[1] = remove_empty(v[1]).map do |line|
75
+ file = line[0].split(' ')
76
+ { name: file[2], size_bytes: file[1].to_i, checksum: file[0] }
77
+ end
78
+ pairs[i] = nil
79
+ end
80
+ end
81
+ pairs.compact
82
+ end
83
+
84
+ def remove_empty(lines)
85
+ lines.reject { |line| line[0].empty? }
86
+ end
87
+
88
+ def as_a_hash(pairs)
89
+ pairs.map { |b, p| Hash[p] if b }.compact
90
+ end
91
+
92
+ def group_package_values(pkgs)
93
+ last_pkg_index = -1
94
+ pkgs.each_with_index do |pkg, i|
95
+ if pkg.key?('Package')
96
+ last_pkg_index = i
97
+ else
98
+ pkgs[last_pkg_index].merge!(pkg)
99
+ pkgs[i] = nil
100
+ end
101
+ end
102
+ pkgs.compact
103
+ end
104
+
105
+ def packages_hash(pkgs)
106
+ Hash[pkgs.map do |pkg|
107
+ [pkg['Package'], pkg]
108
+ end]
109
+ end
110
+ end
111
+ end
data/lib/boshify.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'boshify/command_line'
2
+ require_relative 'boshify/downloader'
3
+ require_relative 'boshify/filesystem'
4
+ require_relative 'boshify/package_converter'
5
+ require_relative 'boshify/release_creator'
6
+ require_relative 'boshify/ubuntu_packages'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boshify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Crump
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.1
27
+ description: Generates BOSH releases
28
+ email: andrew@cloudcredo.com
29
+ executables:
30
+ - boshify
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/boshify
35
+ - lib/boshify.rb
36
+ - lib/boshify/command_line.rb
37
+ - lib/boshify/downloader.rb
38
+ - lib/boshify/filesystem.rb
39
+ - lib/boshify/package_converter.rb
40
+ - lib/boshify/release_creator.rb
41
+ - lib/boshify/ubuntu_packages.rb
42
+ homepage: https://github.com/cloudcredo/boshify
43
+ licenses:
44
+ - Apache
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.2.2
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Generates BOSH releases
66
+ test_files: []