revamp 1.0.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.
@@ -0,0 +1,79 @@
1
+ require 'revamp'
2
+ require 'revamp/mapper/puppet-name-slugger'
3
+
4
+ # This class describes a puppet dependency
5
+ class Revamp::Model::PuppetDependency
6
+ attr_accessor :name, :version
7
+
8
+ def initialize(hash)
9
+ @name = hash['name']
10
+ versions = normalize(hash['version_requirement'])
11
+ @version = Gem::Requirement.new(versions)
12
+ end
13
+
14
+ private
15
+
16
+ def normalize(versionreq)
17
+ re = /^(([><=~]+)?\s*([\dx]+(?:\.[\dx]+(?:\.[\dx]+)?)?))(.*)/
18
+ m = re.match(versionreq.strip)
19
+ first = m[1].strip
20
+ second = m[4].strip
21
+ reqs = [first]
22
+ reqs << second unless second.empty?
23
+ reqs
24
+ end
25
+ end
26
+
27
+ # This class describes a puppet module
28
+ class Revamp::Model::PuppetModule
29
+ def self.puppet_accessor(*vars)
30
+ @attributes ||= []
31
+ @attributes.concat vars
32
+ attr_accessor(*vars)
33
+ end
34
+
35
+ class << self
36
+ attr_reader :attributes
37
+ end
38
+
39
+ attr_accessor :files
40
+ attr_accessor :metadata
41
+ attr_accessor :name
42
+
43
+ puppet_accessor :dependencies
44
+ puppet_accessor :source
45
+ puppet_accessor :author
46
+ puppet_accessor :version
47
+ puppet_accessor :license
48
+ puppet_accessor :summary
49
+ puppet_accessor :project_page
50
+
51
+ def initialize
52
+ @files = {}
53
+ @metadata = nil
54
+ @dependencies = []
55
+ end
56
+
57
+ def add_file(path, content)
58
+ @files[path] = content
59
+ end
60
+
61
+ def attributes
62
+ self.class.attributes
63
+ end
64
+
65
+ def dependencies=(deps)
66
+ @dependencies = []
67
+ deps.each do |dep|
68
+ @dependencies << Revamp::Model::PuppetDependency.new(dep)
69
+ end
70
+ end
71
+
72
+ def slugname
73
+ Revamp::Mapper::PuppetNameSlugger.new.map(name)
74
+ end
75
+
76
+ def rawname
77
+ name.split('/')[-1]
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ require 'revamp'
2
+ require 'zlib'
3
+ require 'json'
4
+ require 'rubygems/package'
5
+ require 'revamp/model/puppet-module'
6
+
7
+ # This class is a parser for Puppet's tarballs format
8
+ class Revamp::Parser::PuppetTarball
9
+ def initialize(tarball_file)
10
+ @tarball = tarball_file
11
+ end
12
+
13
+ def parse
14
+ model = nil
15
+ File.open(@tarball, 'rb') do |file|
16
+ Zlib::GzipReader.wrap(file) do |gz|
17
+ Gem::Package::TarReader.new(gz) do |tar|
18
+ model = Revamp::Model::PuppetModule.new
19
+ tar.each do |tarfile|
20
+ entry = Entry.new(tarfile)
21
+ parse_metadata(model, entry) if entry.metadata?
22
+ model.add_file(entry.name, entry.content) if entry.file?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ normalize(model)
28
+ end
29
+
30
+ private
31
+
32
+ def normalize(model)
33
+ strip = "#{model.slugname}-#{model.version}/"
34
+ model.files = Hash[model.files.map { |file, content| [file.gsub(strip, ''), content] }]
35
+ model
36
+ end
37
+
38
+ def parse_metadata(model, entry)
39
+ data = JSON.parse(entry.content)
40
+ model.metadata = data
41
+ model.name = data['name'].tr('-', '/') if data['name']
42
+ model.attributes.each do |attr|
43
+ value = data[attr.to_s]
44
+ fail ArgumentError, "No #{attr} module metadata provided for #{name}" unless value
45
+ parse_dependencies(attr, value)
46
+ model.send(attr.to_s + '=', value)
47
+ end
48
+ end
49
+
50
+ def parse_dependencies(attr, value)
51
+ return unless attr == :dependencies
52
+ value.tap do |dependencies|
53
+ dependencies.each do |dep|
54
+ dep['version_requirement'] ||= dep['versionRequirement'] || '>= 0.0.0'
55
+ end
56
+ end
57
+ end
58
+
59
+ # An entry from tarball
60
+ class Entry
61
+ attr_accessor :content
62
+ attr_accessor :name
63
+ def initialize(tarfile)
64
+ @tarfile = tarfile
65
+ @name = tarfile.full_name
66
+ @content = nil
67
+ @content = tarfile.read unless @tarfile.directory?
68
+ end
69
+
70
+ def metadata?
71
+ !@tarfile.directory? && name.end_with?('metadata.json')
72
+ end
73
+
74
+ def file?
75
+ !content.nil?
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,28 @@
1
+ Name: <%= name %>
2
+ Version: <%= model.version %>
3
+ Release: <%= release %>
4
+ Summary: <%= model.summary %>
5
+ Source0: <%= rpm %>.tar.gz
6
+ License: <%= model.license %>
7
+ Group: Development
8
+ BuildArch: noarch
9
+ Provides: puppetmodule(<%= model.slugname %>)
10
+ <% if has_requirements %>Requires: <%= requirements %><% end %>
11
+ %description
12
+ <%= model.summary %>
13
+ %prep
14
+ %setup
15
+ %build
16
+ %install
17
+ rm -rf $RPM_BUILD_ROOT
18
+ mkdir -p ${RPM_BUILD_ROOT}/etc/puppet/modules
19
+ cp -r %{_builddir}/<%= rpm %> ${RPM_BUILD_ROOT}%{_sysconfdir}/puppet/modules
20
+ mv ${RPM_BUILD_ROOT}%{_sysconfdir}/puppet/modules/<%= rpm %> ${RPM_BUILD_ROOT}%{_sysconfdir}/puppet/modules/<%= model.rawname %>
21
+ %clean
22
+ %post
23
+ %files
24
+ %dir %{_sysconfdir}/puppet
25
+ %dir %{_sysconfdir}/puppet/modules
26
+ %dir %{_sysconfdir}/puppet/modules/<%= model.rawname %>
27
+ <% filenames.each do |file| %>%{_sysconfdir}/puppet/modules/<%= model.rawname %>/<%= file %>
28
+ <% end %>
@@ -0,0 +1,156 @@
1
+ require 'revamp'
2
+ require 'tmpdir'
3
+ require 'erb'
4
+ require 'ostruct'
5
+ require 'rbconfig'
6
+ require 'revamp/filter/puppetver2rpmreq'
7
+
8
+ # A main RPM persister
9
+ class Revamp::Persister::Rpm
10
+ def initialize
11
+ @options = nil
12
+ end
13
+
14
+ attr_accessor :options
15
+
16
+ def persist(model)
17
+ dir = File.expand_path('~')
18
+ workdir = Pathname.new(dir).join('rpmbuild')
19
+ builder = Builder.new(model, workdir, options)
20
+ builder.make_structure
21
+ builder.write_spec
22
+ builder.write_sources
23
+ produced = builder.produce
24
+ builder.cleanup if options[:cleanup]
25
+ produced
26
+ end
27
+
28
+ # A command line executor for command line
29
+ class CommandLine
30
+ class << self
31
+ def execute(command, directory, verbose)
32
+ Revamp.logger.debug("Executing: '#{command}' in directory: '#{directory}'")
33
+ out = '/dev/null'
34
+ out = $stdout if verbose
35
+ pid = Process.spawn(command, chdir: directory, out: out, err: out)
36
+ Process.wait pid
37
+ $? # rubocop:disable SpecialGlobalVars
38
+ end
39
+ end
40
+ end
41
+
42
+ # A builder for RPM's packages
43
+ class Builder
44
+ SOURCES = 'SOURCES'
45
+ SPECS = 'SPECS'
46
+ RPMS = 'RPMS'
47
+ SELFDIR = Pathname.new(__FILE__).dirname
48
+ ATTRS = [
49
+ :name, :version, :filename, :rpm,
50
+ :model, :tmpdir, :release, :filenames,
51
+ :specfile, :has_requirements,
52
+ :requirements, :options
53
+ ]
54
+ ATTRS.each { |attr| attr_accessor(attr) }
55
+
56
+ def initialize(model, dir, options)
57
+ @options = options
58
+ @name = "puppetmodule_#{model.slugname}"
59
+ @release = options[:release]
60
+ @version = model.version
61
+ @filename = "#{name}-#{version}-#{release}.noarch.rpm"
62
+ @rpm = "#{name}-#{version}"
63
+ @specfile = rpm + '.spec'
64
+ @model = model
65
+ @tmpdir = dir
66
+ @filenames = model.files.keys
67
+ @has_requirements = model.dependencies.any?
68
+ @requirements = configure_rpm_requirements_line
69
+ end
70
+
71
+ def configure_rpm_requirements_line
72
+ req = []
73
+ model.dependencies.each do |dep|
74
+ req += Revamp::Filter::PuppetVerToRpmReq.new.filter(dep)
75
+ end
76
+ req.join(', ')
77
+ end
78
+
79
+ def make_structure
80
+ FileUtils.mkdir_p(tmpdir.join(SOURCES))
81
+ FileUtils.mkdir_p(tmpdir.join(SPECS))
82
+ end
83
+
84
+ def erbize(template, vars)
85
+ values = OpenStruct.new(vars).instance_eval { binding }
86
+ ERB.new(template).result(values)
87
+ end
88
+
89
+ def write_spec
90
+ tpl = SELFDIR.join('rpm-spec.erb')
91
+ values = Hash[ATTRS.map { |key| [key, send(key)] }]
92
+ spec = erbize(tpl.read, values)
93
+ File.open(specfile_path, 'w') { |file| file.write(spec) }
94
+ end
95
+
96
+ def write_sources
97
+ pathsufix = Pathname.new(rpm)
98
+ File.open(sources_tgz_path, 'wb') do |tgz|
99
+ Zlib::GzipWriter.wrap(tgz) do |gz|
100
+ Gem::Package::TarWriter.new(gz) do |tar|
101
+ model.files.each do |file, content|
102
+ full = pathsufix.join(file).to_s
103
+ tar.add_file_simple(full, 0644, content.length) { |io| io.write(content) }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def sources_tgz_path
111
+ sources = tmpdir.join(SOURCES)
112
+ sources.join("#{rpm}.tar.gz")
113
+ end
114
+
115
+ def specfile_path
116
+ tmpdir.join(SPECS).join(specfile)
117
+ end
118
+
119
+ def target
120
+ outdir = options[:outdir]
121
+ Pathname.new(outdir).join(filename)
122
+ end
123
+
124
+ def produce
125
+ Revamp.logger.info("Converting to RPM package #{target}...")
126
+ cmd = "rpmbuild -ba #{SPECS}/#{specfile}"
127
+ verbose = options[:verbose]
128
+ ret = Revamp::Persister::Rpm::CommandLine.execute(cmd, tmpdir, verbose)
129
+ fail "RPM Build failed with retcode = #{ret.exitstatus}" unless ret.success?
130
+ move_produced
131
+ target
132
+ end
133
+
134
+ def move_produced
135
+ produced = tmpdir.join(RPMS).join('noarch').join(filename)
136
+ Revamp.logger.debug("Produced RPM in build dir: #{produced}")
137
+ FileUtils.mv(produced, target)
138
+ end
139
+
140
+ def cleanup
141
+ arch = RbConfig::CONFIG['arch'].gsub('-linux', '')
142
+ cleanup_files [
143
+ sources_tgz_path, specfile_path, tmpdir.join('BUILD').join(rpm),
144
+ tmpdir.join('BUILDROOT').join("#{rpm}-#{release}.#{arch}")
145
+ ]
146
+ end
147
+
148
+ def cleanup_files(files)
149
+ Revamp.logger.debug("Files to be cleaned up: #{files}")
150
+ readable = files.reject { |path| !path.readable? }
151
+ readable.each do |path|
152
+ path.directory? ? FileUtils.rm_r(path) : path.unlink
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,35 @@
1
+ # Top level module for Revamp
2
+ module Revamp
3
+ # Prepare version
4
+ #
5
+ # @param desired [String] a desired version
6
+ # @return [String] a prepared version
7
+ def self.version_prep(desired)
8
+ version = desired
9
+ if desired.match(/[^0-9\.]+/)
10
+ git = `git describe --tags --dirty --always`
11
+ version += '.' + git.tr('-', '.')
12
+ end
13
+ version.strip
14
+ end
15
+
16
+ # Version for Herald
17
+ VERSION = version_prep '1.0.0'
18
+ # Lincense for Herald
19
+ LICENSE = 'Apache-2.0'
20
+ # Project name
21
+ NAME = 'Revamp'
22
+ # Package (gem) for Herald
23
+ PACKAGE = 'revamp'
24
+ # A summary info
25
+ SUMMARY = 'Converts puppet module file to valid RPM or DEB package'
26
+ # A homepage for Herald
27
+ HOMEPAGE = 'https://github.com/coi-gov-pl/gem-revamp'
28
+ # A description info
29
+ DESCRIPTION = <<-eos
30
+ This module can convert standard puppet module file in form of tarball to
31
+ valid RPM or DEB package with all dependencies as references to other
32
+ system packages. The dependencies can be packaged inside the final system
33
+ package or just referenced as dependencies.
34
+ eos
35
+ end
data/lib/revamp.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'logger'
2
+
3
+ begin
4
+ require 'pry'
5
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
6
+ # Do nothing here
7
+ end
8
+
9
+ # Top level module for Revamp
10
+ module Revamp
11
+ @logger = Logger.new($stdout)
12
+ @logger.formatter = proc { |severity, _datetime, _progname, msg| "#{severity}: #{msg}\n" }
13
+ class << self
14
+ # Logger for CLI interface
15
+ # @return [Logger] logger for CLI
16
+ attr_reader :logger
17
+
18
+ # Reports a bug in desired format
19
+ #
20
+ # @param ex [Exception] an exception that was thrown
21
+ # @return [Hash] a hash with info about bug to be displayed to user
22
+ def bug(ex)
23
+ file = Tempfile.new(['revamp-bug', '.log'])
24
+ filepath = file.path
25
+ file.close
26
+ file.unlink
27
+ message = "v#{Revamp::VERSION}-#{ex.class}: #{ex.message}"
28
+ contents = message + "\n\n" + ex.backtrace.join("\n") + "\n"
29
+ File.write(filepath, contents)
30
+ bugo = {
31
+ message: message,
32
+ homepage: Revamp::HOMEPAGE,
33
+ bugfile: filepath,
34
+ help: "Please report this bug to #{Revamp::HOMEPAGE} by passing contents of bug file: #{filepath}"
35
+ }
36
+ bugo
37
+ end
38
+ end
39
+ end
40
+
41
+ # A module for modeles
42
+ module Revamp::Model
43
+ end
44
+
45
+ # A module for mapper
46
+ module Revamp::Mapper
47
+ end
48
+
49
+ # A module for persister
50
+ module Revamp::Persister
51
+ end
52
+
53
+ # Parser module
54
+ module Revamp::Parser
55
+ end
56
+
57
+ # A module for filters of persiters
58
+ module Revamp::Filter
59
+ end
60
+
61
+ require 'revamp/version'
data/revamp.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'revamp/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = Revamp::PACKAGE
8
+ spec.version = Revamp::VERSION
9
+ spec.authors = [
10
+ 'Centralny Ośrodek Informatyki',
11
+ 'Suszyński Krzysztof'
12
+ ]
13
+ spec.email = ['krzysztof.suszynski@coi.gov.pl']
14
+ spec.summary = Revamp::SUMMARY
15
+ spec.description = Revamp::DESCRIPTION
16
+ spec.homepage = Revamp::HOMEPAGE
17
+ spec.license = Revamp::LICENSE
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_runtime_dependency 'micro-optparse', '~> 1.2.0'
27
+
28
+ spec.required_ruby_version = '>= 1.9'
29
+ end
@@ -0,0 +1,5 @@
1
+ RSpec::Matchers.define :be_readable_file do
2
+ match do |actual|
3
+ File.readable?(actual)
4
+ end
5
+ end
@@ -0,0 +1,49 @@
1
+ require 'rspec/its'
2
+
3
+ begin
4
+ gem 'simplecov'
5
+ require 'simplecov'
6
+ formatters = []
7
+ formatters << SimpleCov::Formatter::HTMLFormatter
8
+
9
+ begin
10
+ gem 'coveralls'
11
+ require 'coveralls'
12
+ formatters << Coveralls::SimpleCov::Formatter if ENV['TRAVIS']
13
+ rescue Gem::LoadError
14
+ # do nothing
15
+ end
16
+
17
+ begin
18
+ gem 'codeclimate-test-reporter'
19
+ require 'codeclimate-test-reporter'
20
+ formatters << CodeClimate::TestReporter::Formatter if (ENV['TRAVIS'] and ENV['CODECLIMATE_REPO_TOKEN'])
21
+ rescue Gem::LoadError
22
+ # do nothing
23
+ end
24
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[*formatters]
25
+ SimpleCov.start do
26
+ add_filter "/spec/"
27
+ add_filter "/.vendor/"
28
+ add_filter "/vendor/"
29
+ add_filter "/gems/"
30
+ minimum_coverage 95
31
+ refuse_coverage_drop
32
+ end
33
+ rescue Gem::LoadError
34
+ # do nothing
35
+ end
36
+
37
+ begin
38
+ gem 'pry'
39
+ require 'pry'
40
+ rescue Gem::LoadError
41
+ # do nothing
42
+ end
43
+
44
+ RSpec.configure do |c|
45
+ c.tty = true unless ENV['JENKINS_URL'].nil?
46
+ c.mock_with :rspec do |mock|
47
+ mock.syntax = [:expect]
48
+ end
49
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+ require 'revamp/cli'
3
+ require 'revamp/application'
4
+ require 'stringio'
5
+
6
+ describe Revamp::CLI do
7
+ let(:instance) { described_class.new }
8
+ describe '.run!' do
9
+ before :each do
10
+ @level = Revamp.logger.level
11
+ Revamp.logger.level = 100
12
+ end
13
+ after :each do
14
+ Revamp.logger.level = @level
15
+ end
16
+ context 'while ARGV is equal to' do
17
+ let (:argv) { self.class.description.split(/\s+/) }
18
+ subject { instance.run!(argv) }
19
+ describe '--invalid-option' do
20
+ it do
21
+ expect { subject }.to raise_error(SystemExit, 'exit') { |error|
22
+ expect(error).not_to be_success
23
+ }
24
+ end
25
+ end
26
+ context 'while catching STDOUT' do
27
+ before :each do
28
+ @stringio = StringIO.new
29
+ $stdout = @stringio
30
+ end
31
+ after :each do
32
+ $stdout = STDOUT
33
+ end
34
+ describe '--help' do
35
+ it do
36
+ begin
37
+ subject
38
+ rescue SystemExit
39
+ end
40
+ expect(@stringio.string).to include('Revamp v', 'Usage:', '-h, --help')
41
+ end
42
+ it do
43
+ expect { subject }.to raise_error(SystemExit, 'exit') { |error|
44
+ expect(error).to be_success
45
+ }
46
+ end
47
+ end
48
+ describe '--version' do
49
+ it do
50
+ begin
51
+ subject
52
+ rescue SystemExit
53
+ end
54
+ expect(@stringio.string).to match(/^\d+\.\d+\.\d+/)
55
+ end
56
+ it do
57
+ expect { subject }.to raise_error(SystemExit, 'exit') { |error|
58
+ expect(error).to be_success
59
+ }
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'mocking to return' do
65
+ before :each do
66
+ app = Revamp::Application.new({})
67
+ expect(app).to receive(:run!).and_return(:mock_return)
68
+ expect(Revamp::Application).to receive(:new).and_return(app)
69
+ end
70
+ describe '-f spec/fixtures/coi-sample-0.1.1.tar.gz' do
71
+ it { expect(subject).to eq(0) }
72
+ end
73
+ end
74
+ context 'mocking to raise error' do
75
+ before :each do
76
+ app = Revamp::Application.new({})
77
+ expect(app).to receive(:run!).and_raise(StandardError, 'mock-ex')
78
+ expect(Revamp::Application).to receive(:new).and_return(app)
79
+ end
80
+ describe '-f spec/fixtures/coi-sample-0.1.1.tar.gz' do
81
+ it { expect(subject).to eq(1) }
82
+ end
83
+ end
84
+ context 'performing real operation' do
85
+ before :each do
86
+ require 'revamp/persister/rpm'
87
+ ret = double(success?: true)
88
+ expect(Revamp::Persister::Rpm::CommandLine).to receive(:execute).and_return(ret)
89
+ expect(FileUtils).to receive(:mv)
90
+ end
91
+ describe '-f spec/fixtures/coi-sample-0.1.1.tar.gz -o /tmp' do
92
+ it { expect(subject).to eq(0) }
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '.parse' do
99
+ let (:argv) { eval(self.class.description) }
100
+ subject { instance.send(:parse, argv) }
101
+ describe([].inspect) do
102
+ it { expect { subject }.to raise_error(ArgumentError, /You must pass filenames with `-f`/) }
103
+ end
104
+ describe(['-f', 'abc-lib-1.0.0.tar.gz'].inspect) do
105
+ it { expect { subject }.to raise_error(ArgumentError, 'Can\'t read file given: abc-lib-1.0.0.tar.gz') }
106
+ end
107
+ describe(['-f', 'spec/fixtures/coi-sample-0.1.1.tar.gz', '-o', Dir.tmpdir].inspect) do
108
+ it do
109
+ expect(subject).to eq({
110
+ release: "1",
111
+ epoch: "6",
112
+ outdir: "/tmp",
113
+ filenames: ["spec/fixtures/coi-sample-0.1.1.tar.gz"],
114
+ verbose: false,
115
+ cleanup: true
116
+ })
117
+ end
118
+ end
119
+ end
120
+ end