puppet-moddeps 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,100 +1,75 @@
1
- # Puppet::Moddeps
2
-
3
- [![Gem Version][gem-v-img]][gem-version]
4
-
5
- #### Master branch:
6
- [![Build Status][travis-img-master]][travis-ci]
7
- [![Code Climate][cc-img]][code-climate]
8
- [![Coverage Status][coveralls-img-master]][coveralls-master]
9
- [![Dependency Status][gemnasium-img]][gemnasium]
10
-
11
- #### Devlop branch:
12
- [![Build Status][travis-img-develop]][travis-ci]
13
- [![Coverage Status][coveralls-img-develop]][coveralls-develop]
14
-
15
- ## Description
16
-
17
- This gem will allow you to pull in all missing dependencies for a given Puppet
18
- module. This is targeted specifically at private modules or modules cloned from
19
- GitHub that have a populated `metadata.json` file.
20
-
21
- puppet-moddeps has been extensively tested on CentOS 6 using the system ruby
22
- (v1.8.7). Due to dependency requirements all development is done on ruby 1.9.3
23
- or higher. Automated testing of v1.8.7 cannot be done on Travis-CI due to the
24
- development dependencies.
25
-
26
- ## Installation
27
-
28
- Add this line to your application's Gemfile:
29
-
30
- ```ruby
31
- gem 'puppet-moddeps'
32
- ```
33
-
34
- And then execute:
35
-
36
- $ bundle
37
-
38
- Or install it yourself as:
39
-
40
- $ gem install puppet-moddeps
41
-
42
- ## Usage
43
-
44
- To put this gem to use just install it and run `puppet-moddeps some_module`,
45
- replacing `some_module` with the name of any module installed in
46
- `/etc/puppet/modules`. It will parse the module's `metadata.json` file pull down
47
- it's listed dependencies.
48
-
49
- ## Contributing
50
-
51
- The development environment requires at least Ruby 1.9.2 due to the use of Guard
52
- and Coveralls. The gem is designed to run on Ruby 1.8.7 and higher as that is
53
- the version available in Red Hat / CentOS 6.
54
-
55
- 1. Fork it ( https://github.com/genebean/puppet-moddeps/fork )
56
- 2. Create your feature branch based off of the develop branch
57
- (`git checkout develop; git checkout -b my-new-feature`)
58
- 3. Install development gems (`bundle install`)
59
- 4. Open a second terminal and use guard to make sure your code doesn't break anything
60
- (`bundle exec guard`)
61
- 5. Commit your changes and associated tests (`git commit -am 'Add some feature'`)
62
- 6. Push to the branch (`git push origin my-new-feature`)
63
- 7. Create a new Pull Request
64
-
65
- ### Vagrant
66
-
67
- Development and testing can also be done utilizing the included `Vagrantfile`.
68
- To do so, install [Vagrant][vagrant] and [VirtualBox][vbox], fork and clone the
69
- project, cd into the project, and run `vagrant up` followed by `vagrant ssh`.
70
- The setup process will take care of running `bundle install` for you. You
71
- can find the code symlinked into the vagrant user's home directory. Also, RVM
72
- is installed system-wide in the referenced Vagrant box.
73
-
74
- ### Tests
75
-
76
- Please try and write tests using Rspec's expect syntax for any code you add or change.
77
- Code must have tests before it will be merged.
78
-
79
- ## License
80
-
81
- This code is released under the New BSD license (aka 3-clause BSD). A copy is
82
- in the LICENSE file in this repo.
83
-
84
-
85
- [code-climate]: https://codeclimate.com/github/genebean/puppet-moddeps
86
- [cc-img]: https://img.shields.io/codeclimate/github/genebean/puppet-moddeps.svg
87
- [coveralls-master]: https://coveralls.io/r/genebean/puppet-moddeps?branch=master
88
- [coveralls-develop]: https://coveralls.io/r/genebean/puppet-moddeps?branch=develop
89
- [coveralls-img-master]: https://img.shields.io/coveralls/genebean/puppet-moddeps/master.svg
90
- [coveralls-img-develop]: https://img.shields.io/coveralls/genebean/puppet-moddeps/develop.svg
91
- [gem-v-img]: https://badge.fury.io/rb/puppet-moddeps.svg
92
- [gem-version]: http://badge.fury.io/rb/puppet-moddeps
93
- [gemnasium-img]: https://img.shields.io/gemnasium/genebean/puppet-moddeps.svg
94
- [gemnasium]: https://gemnasium.com/genebean/puppet-moddeps
95
- [rvm]: http://rvm.io
96
- [travis-ci]: https://travis-ci.org/genebean/puppet-moddeps
97
- [travis-img-master]: https://img.shields.io/travis/genebean/puppet-moddeps/master.svg
98
- [travis-img-develop]: https://img.shields.io/travis/genebean/puppet-moddeps/develop.svg
99
- [vbox]: https://www.virtualbox.org
100
- [vagrant]: https://www.vagrantup.com
1
+ # Puppet::Moddeps
2
+
3
+ [![Gem](https://img.shields.io/gem/v/puppet-moddeps)](https://rubygems.org/gems/puppet-moddeps)
4
+ [![Actions Status](https://github.com/genebean/puppet-moddeps/workflows/Rspec%20Tests/badge.svg)](https://github.com/genebean/puppet-moddeps/actions)
5
+ [![codecov](https://codecov.io/gh/genebean/puppet-moddeps/branch/master/graph/badge.svg?token=9sn4KbY0yu)](https://app.codecov.io/gh/genebean/puppet-moddeps)
6
+ ![GitHub](https://img.shields.io/github/license/genebean/puppet-moddeps)
7
+
8
+ ## Description
9
+
10
+ This gem will allow you to pull in all missing dependencies for a given Puppet module by running `puppet-moddeps some_module`. This is targeted specifically at private modules or modules cloned from GitHub that have a populated `metadata.json` file. It really shines as a tool to install all the dependencies of a module that you have just created or when you have just updated the list of dependencies in a module's `metadata.json` file.
11
+
12
+ ## Installation
13
+
14
+ puppet-moddeps has been extensively tested using the ruby bundled with the Puppet agent. The recommended way to use this gem is to first install puppet and then open an elevated command prompt and run this:
15
+
16
+ ```bash
17
+ puppet resource package puppet-moddeps provider=puppet_gem ensure=latest
18
+ ```
19
+
20
+ Alternatively, you can also install it using another instance of Ruby so long as it is at least version 2.5.7.
21
+
22
+ ## Usage
23
+
24
+ After installation, you can run `puppet-moddeps some_module`, replacing `some_module` with the name of any module installed in `/etc/puppetlabs/code/environments/production/modules/`. It will resolve the module's dependencies based on its `metadata.json` file and install each of them.
25
+
26
+ ### Using in a module's Vagrantfile
27
+
28
+ If your module includes a [`Vagrantfile`](https://www.vagrantup.com/docs/vagrantfile) you can utilize this gem as part of your provisioning step by adding this to it:
29
+
30
+ ```ruby
31
+ MODULE_NAME='some_module'
32
+ Vagrant.configure('2') do |config|
33
+ config.vm.box = 'genebean/centos-7-puppet-latest' # Any box that includes Puppet will work here
34
+ config.vm.provision 'shell', inline: <<-SCRIPT
35
+ puppet resource file /etc/puppetlabs/code/environments/production/modules/#{MODULE_NAME} ensure=link target=/vagrant force=true
36
+ /opt/puppetlabs/puppet/bin/gem install puppet-moddeps
37
+ /opt/puppetlabs/puppet/bin/puppet-moddeps #{MODULE_NAME}
38
+ SCRIPT
39
+ end
40
+ ```
41
+
42
+ ## Contributing
43
+
44
+ Pull requests are welcome!
45
+
46
+ 1. Fork it ( https://github.com/genebean/puppet-moddeps/fork )
47
+ 2. Create a feature branch
48
+ 3. Install bundler (`gem install bundler`)
49
+ 4. Install development gems (`bundle install`)
50
+ 5. Open a second terminal and use guard to make sure your code doesn't break anything (`bundle exec guard -p`)
51
+ 6. Commit your changes and associated tests (`git commit -am 'Add some feature'`)
52
+ 7. Push to the branch (`git push origin my-new-feature`)
53
+ 8. Create a new pull request
54
+
55
+ ### Vagrant
56
+
57
+ Development and testing can also be done utilizing the included `Vagrantfile`.
58
+ To do so, install [Vagrant][vagrant] and [VirtualBox][vbox], fork and clone the
59
+ project, cd into the project, and run `vagrant up` followed by `vagrant ssh`.
60
+ The setup process will take care of running `bundle install` for you. You
61
+ can find the code symlinked into the vagrant user's home directory. Also, RVM
62
+ is installed system-wide in the referenced Vagrant box.
63
+
64
+ ### Tests
65
+
66
+ Please try and write tests using Rspec's expect syntax for any code you add or change.
67
+ Code must have tests before it will be merged.
68
+
69
+ ## License
70
+
71
+ This code is released under the Apache 2.0 license. A copy is in the LICENSE file in this repo.
72
+
73
+ [rvm]: http://rvm.io
74
+ [vbox]: https://www.virtualbox.org
75
+ [vagrant]: https://www.vagrantup.com
data/Rakefile CHANGED
@@ -1,11 +1,14 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
-
4
- # Default directory to look in is `/specs`
5
- # Run with `rake spec`
6
- RSpec::Core::RakeTask.new(:spec) do |task|
7
- task.rspec_opts = ['--color', '--format', 'documentation']
8
- end
9
-
10
- task :default => :spec
11
-
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ # Default directory to look in is `/specs`
6
+ # Run with `rake spec`
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.rspec_opts = ['--color', '--format', 'documentation']
9
+ end
10
+
11
+ YARD::Rake::YardocTask.new
12
+
13
+ task :default => :spec
14
+
@@ -1,16 +1,16 @@
1
- # -*- mode: ruby -*-
2
- # vi: set ft=ruby :
3
-
4
- # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
- VAGRANTFILE_API_VERSION = "2"
6
-
7
- Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
- config.vm.box = "genebean/centos6-rvm193-64bit"
9
-
10
- config.vm.synced_folder "./", "/home/vagrant/puppet-moddeps"
11
-
12
- config.vm.provision "shell", inline: "yum -y install dos2unix git nano ruby-devel tree vim-enhanced"
13
- config.vm.provision "shell", inline: "dos2unix /vagrant/scripts/*.sh"
14
- config.vm.provision "shell", inline: "su - vagrant -c 'export PUPPET_VERSION=3.7.3; /vagrant/scripts/vagrant-user.sh'"
15
-
16
- end
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+ VAGRANTFILE_API_VERSION = "2"
6
+
7
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+ config.vm.box = "genebean/centos-7-rvm-multi"
9
+
10
+ config.vm.network "forwarded_port", guest: 8808, host: 8808 # used for Yard docs
11
+
12
+ config.vm.synced_folder "./", "/home/vagrant/puppet-moddeps"
13
+
14
+ config.vm.provision "shell", inline: "yum -y install dos2unix git nano ruby-devel tree vim-enhanced"
15
+ config.vm.provision "shell", inline: "echo 'install bundler after selecting a ruby with RVM'"
16
+ end
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'puppet/moddeps'
4
-
5
- depinstaller = Puppet::Moddeps::InstallDeps.new
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'puppet/moddeps'
4
+
5
+ depinstaller = Puppet::Moddeps::Installer.new
6
6
  depinstaller.install(ARGV)
@@ -1,8 +1,8 @@
1
- require 'puppet/moddeps/install_deps'
2
- require 'puppet/moddeps/version'
3
-
4
- module Puppet
5
- module Moddeps
6
-
7
- end
8
- end
1
+ require 'puppet/moddeps/installer'
2
+ require 'puppet/moddeps/version'
3
+
4
+ module Puppet
5
+ module Moddeps
6
+
7
+ end
8
+ end
@@ -0,0 +1,244 @@
1
+ require 'json'
2
+ require 'puppetfile-resolver'
3
+ require 'puppet/moddeps/module'
4
+
5
+ module Puppet
6
+ module Moddeps
7
+ class Installer
8
+
9
+ # The path to which modules will be installed
10
+ #
11
+ # @return [String]
12
+ # The path to which modules will be installed
13
+ attr_reader :module_path
14
+
15
+ # The version of Puppet that will be used when resolving dependencies.
16
+ # If none is provided then a Puppet version constraint is not applied
17
+ # during resolution.
18
+ #
19
+ # @return [String]
20
+ # The version of Puppet that will be used when resolving dependencies
21
+ # if one was specified.
22
+ attr_reader :puppet_version
23
+
24
+ # Creates an instance of Puppet::Moddeps::Installer
25
+ #
26
+ # @param module_path [String]
27
+ # The path to which modules will be installed
28
+ # @param puppet_version [String]
29
+ # The verion of Puppet to use when resolving dependencies
30
+ #
31
+ # @example No parameters
32
+ # depinstaller = Puppet::Moddeps::Installer.new
33
+ #
34
+ # @example Specify a module path
35
+ # depinstaller = Puppet::Moddeps::Installer.new('/tmp')
36
+ #
37
+ # @example Specify a module path and a Puppet version
38
+ # depinstaller = Puppet::Moddeps::Installer.new('/tmp', '6.18.0')
39
+ #
40
+ def initialize(module_path=nil, puppet_version=nil)
41
+ if module_path
42
+ abort('The provided module path was not a string.') unless module_path.is_a?(String)
43
+ @module_path = module_path
44
+ else
45
+ separator = self.path_separator(RbConfig::CONFIG['host_os'])
46
+ @module_path = %x(puppet config print modulepath).split(separator)[0].strip
47
+ end
48
+
49
+ if puppet_version
50
+ abort('The provided puppet version was not a string.') unless puppet_version.is_a?(String)
51
+ end
52
+
53
+ @puppet_version = puppet_version
54
+
55
+ @usage = <<~ENDUSAGE
56
+ Usage: puppet-moddeps module_one [module_two] [...]
57
+ Call puppet-moddeps with the name of one or more installed modules'
58
+ ENDUSAGE
59
+ end
60
+
61
+ # Installs the dependencies for one or more local modules. This is the
62
+ # primary entry point for this class.
63
+ #
64
+ # @param puppet_module [Array[String]]
65
+ # An array of strings representig the names of one or more locally
66
+ # installed puppet modules.
67
+ #
68
+ # @example Install dependencies for one module
69
+ # depinstaller.install(['apache'])
70
+ #
71
+ # @example Install dependencies for multiple module
72
+ # depinstaller.install(['apache', 'nginx'])
73
+ def install(*puppet_module)
74
+ # puppet_module will always be an array.
75
+ # The elements of the array are the values passed in.
76
+ if puppet_module.nil? || puppet_module.size == 0 || puppet_module[0].nil? || puppet_module[0].size == 0
77
+ puts 'input problem'
78
+ abort(@usage)
79
+ end
80
+
81
+ module_array = puppet_module[0]
82
+
83
+ # The intent is to only accept strings as arguments.
84
+ # It is also expected that all arguments are installed modules
85
+ module_array.each do |mod|
86
+ abort(@usage) unless mod.is_a?(String)
87
+ abort("Can't find #{mod} in #{@module_path}") unless self.installed?(mod)
88
+ end
89
+
90
+ module_objects = resolve_local_module_deps(module_array)
91
+
92
+ # Remove the local modules from the list of modules to install
93
+ # so that the installation process does not overwrite whatever
94
+ # initiated running puppet-moddeps.
95
+ module_objects.each do |obj|
96
+ if module_array.include?(obj.name)
97
+ module_objects.delete(obj)
98
+ end
99
+ end
100
+
101
+ # install the needed modules
102
+ self.install_modules(module_objects)
103
+ end
104
+
105
+ # Test to see if a module's folder is present within the module path.
106
+ #
107
+ # @param module_name [String]
108
+ # The name of a module
109
+ #
110
+ # @return [Boolean]
111
+ # Returns true if found, false otherwise
112
+ def installed?(module_name)
113
+ File.directory?("#{@module_path}/#{module_name}")
114
+ end
115
+
116
+ # Determine the character used to separate entries in an operating
117
+ # system's path environment variable.
118
+ #
119
+ # @param os_string [String]
120
+ # A string representing a host's operating system as returned
121
+ # by RbConfig::CONFIG['host_os']
122
+ #
123
+ # @return [String]
124
+ # Returns ";" on Windows and ":" on everything else
125
+ def path_separator(os_string)
126
+ if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ os_string) != nil
127
+ separator = ';'
128
+ else
129
+ separator = ':'
130
+ end
131
+ end
132
+
133
+ # Resolves the dependencies of one or more locally installed modules
134
+ #
135
+ # @param modules [Array[String]]
136
+ # An array of module names
137
+ #
138
+ # @return [Array[Puppet::Moddeps::Module]]
139
+ # An array of module objects representing the modules that need
140
+ # to be installed to satisfiy the dependencies of the local module(s)
141
+ def resolve_local_module_deps(modules)
142
+ # Build the document model from the modules.
143
+ model = PuppetfileResolver::Puppetfile::Document.new('')
144
+
145
+ modules.each do |mod|
146
+ model.add_module(
147
+ PuppetfileResolver::Puppetfile::LocalModule.new(mod)
148
+ )
149
+ end
150
+
151
+ # Make sure the Puppetfile model is valid.
152
+ unless model.valid?
153
+ raise "Unable to resolve dependencies for modules: #{modules.map(&:title).join(', ')}"
154
+ end
155
+
156
+ resolver = PuppetfileResolver::Resolver.new(model, @puppet_version)
157
+
158
+ # Configure and resolve the dependency graph, catching any errors
159
+ # raised by puppetfile-resolver and re-raising them as Bolt errors.
160
+ begin
161
+ result = resolver.resolve(
162
+ cache: nil,
163
+ ui: nil,
164
+ module_paths: [@module_path],
165
+ allow_missing_modules: false
166
+ )
167
+ rescue StandardError => e
168
+ raise e.message
169
+ end
170
+
171
+ # Turn specifications into module objects. This will skip over anything that is not
172
+ # a module specification (i.e. a Puppet version specification).
173
+ module_objects = result.specifications.each_with_object(Set.new) do |(_name, spec), acc|
174
+ next unless spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
175
+ acc << Puppet::Moddeps::Module.new(spec.owner, spec.name, spec.version.to_s)
176
+ end
177
+ end
178
+
179
+ # Installs requested modules if they are not already installed.
180
+ #
181
+ # @param module_objects [Array[Puppet::Moddeps::Module]]
182
+ # An array of module objects representing the modules that need
183
+ # to be installed
184
+ def install_modules(module_objects)
185
+ if Array(module_objects).size > 0
186
+ puts "Modules will be installed into #{@module_path}"
187
+
188
+ Array(module_objects).each do |mod|
189
+ if self.installed?(mod.name) && self.module_versions_match?(mod.owner, mod.name, mod.version)
190
+ puts "#{mod.owner}-#{mod.name} #{mod.version} is already installed, skipping"
191
+ else
192
+ self.call_puppet(mod)
193
+ end
194
+ end
195
+ else
196
+ puts 'No dependencies were marked for installation.'
197
+ end
198
+ end
199
+
200
+ # Parses the metadata.json file of a module and returns it as Hash
201
+ #
202
+ # @param module_name [String]
203
+ # The name of a module
204
+ #
205
+ # @return [Hash]
206
+ # A hash representing the JSON metadata
207
+ def parse_metadata(module_name)
208
+ metadata = File.read("#{@module_path}/#{module_name}/metadata.json")
209
+ data = JSON.parse(metadata)
210
+ end
211
+
212
+ # Compare the owner, name, and version of an installed module to a
213
+ # provided set of information.
214
+ #
215
+ # @param module_owner [String]
216
+ # The owner / author of a module. Ex. puppetlabs
217
+ # @param module_name [String]
218
+ # The name of a module. Ex. apache
219
+ # @param module_version [String]
220
+ # The semantic version of a module. Ex. 5.6.0
221
+ #
222
+ # @return [Boolean]
223
+ # Retruns true if the information matches, false otherwise
224
+ def module_versions_match?(module_owner, module_name, module_version)
225
+ metadata = self.parse_metadata(module_name)
226
+ if metadata['author'].eql?(module_owner) && metadata['version'].eql?(module_version)
227
+ return true
228
+ else
229
+ return false
230
+ end
231
+ end
232
+
233
+ # Shell out to the puppet binary to install a module without dependencies.
234
+ #
235
+ # @param module_object [Puppet::Moddeps::Module]
236
+ # A Module object that represents the module to install
237
+ def call_puppet(module_object)
238
+ cmd = "puppet module install --force --ignore-dependencies #{module_object.owner}-#{module_object.name} --version #{module_object.version}"
239
+ puts "Running \"#{cmd}\""
240
+ %x(#{cmd})
241
+ end
242
+ end
243
+ end
244
+ end