puppetfile_editor 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3364e07c1a49414ed25fecced5c31afd13a15be
4
+ data.tar.gz: 3377a602b97e701d1f21a8dae430d9da65e418e4
5
+ SHA512:
6
+ metadata.gz: 4768b06d6ea7345a02cdd938716fc61a44cbb156dd2fcf056eaff6bb79adc98670adb09c85b5090bfc86911642e941df02a4a447a47815cff93cdc2481b8f14d
7
+ data.tar.gz: a3f6515c3d4ae5cd647e6fe8b25311d49e06de5b63abff8d44418e578cdc1769a90eb8e76a3d44353c17c3acf88438b6a5d4f7098767707b5dacd4b23a7c726a
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # PuppetfileEditor
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/puppetfile_editor.svg)](https://badge.fury.io/rb/puppetfile_editor)
4
+ [![Build Status](https://travis-ci.org/pegasd/puppetfile_editor.svg?branch=master)](https://travis-ci.org/pegasd/puppetfile_editor)
5
+ [![Coverage Status](https://coveralls.io/repos/github/pegasd/puppetfile_editor/badge.svg)](https://coveralls.io/github/pegasd/puppetfile_editor)
6
+
7
+ This gem provides CLI and Rake interfaces for maintaining Puppetfile.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'puppetfile_editor'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ $ gem install puppetfile_editor
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ TODO
32
+
33
+ ## Development
34
+
35
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`.
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/pegasd/puppetfile_editor).
42
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
43
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
48
+
49
+ ## Code of Conduct
50
+
51
+ Everyone interacting in the PuppetfileEditor project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the
52
+ [code of conduct](https://github.com/pegasd/puppetfile_editor/blob/master/CODE_OF_CONDUCT.md).
data/bin/pfile ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'puppetfile_editor/cli'
4
+ require 'optparse'
5
+
6
+ filename = File.basename(__FILE__)
7
+
8
+ # Display help if no arguments are specified
9
+ ARGV.push('-h') if ARGV.empty?
10
+
11
+ options = { puppetfile: 'Puppetfile' }
12
+ OptionParser.new do |parser|
13
+ parser.banner = "Usage: #{filename} [options] [command] [command options]"
14
+ parser.on('-v', '--[no-]verbose', 'Run verbosely') { |v| options[:verbose] = v }
15
+ parser.on('-f', '--filename PUPPETFILE', 'Path to your Puppetfile.') do |puppetfile|
16
+ options[:puppetfile] = puppetfile
17
+ end
18
+ end.order!
19
+
20
+ subcommands = {
21
+ 'edit' => OptionParser.new do |parser|
22
+ parser.banner = "Usage: #{filename} edit [options]"
23
+ parser.on('-m', '--module-name NAME', 'Module name') do |setting|
24
+ options[:name] = setting
25
+ end
26
+ parser.on('-u', '--update PARAM=VALUE', 'What to update') do |setting|
27
+ if (match = setting.match(/^(\w+)=([^=]+)$/))
28
+ options[:param], options[:value] = match[1], match[2]
29
+ else
30
+ warn 'Version must match PARAM=VALUE pattern'
31
+ exit 1
32
+ end
33
+ end
34
+ end,
35
+ 'format' => OptionParser.new do |parser|
36
+ parser.banner = "Usage: #{filename} format"
37
+ end,
38
+ 'add' => OptionParser.new do |parser|
39
+ parser.banner = "Usage: #{filename} add [options]"
40
+ parser.on('-m', '--module-name NAME', 'Module name') do |setting|
41
+ options[:name] = setting
42
+ end
43
+ parser.on('-t', '--type TYPE', [:hg, :forge, :local, :git], 'Type of module to add') do |setting|
44
+ options[:type] = setting
45
+ end
46
+ parser.on('-l', '--url [URL]', 'URL of module (applicable for hg and git modules)') do |setting|
47
+ options[:url] = setting
48
+ end
49
+ parser.on('-u', '--version [PARAM=VALUE]', 'Version of module to add in the form of PARAM=VALUE') do |setting|
50
+ if (match = setting.match(/^(\w+)=([^=]+)$/))
51
+ options[:param], options[:value] = match[1], match[2]
52
+ elsif (match = setting.match(/^\d+(\.\d)*$/))
53
+ options[:version] = match[0]
54
+ else
55
+ warn 'Version must match PARAM=VALUE pattern or be a proper Forge version.'
56
+ exit 1
57
+ end
58
+ end
59
+ end,
60
+ 'merge' => OptionParser.new do |parser|
61
+ parser.banner = "Usage: #{filename} merge [options]"
62
+ parser.separator ' Merge existing Puppetfile with another one from STDIN.'
63
+ parser.separator ' Dump the result to STDOUT.'
64
+ parser.separator ''
65
+
66
+ parser.on('-f', '--force', 'Force merge module versions') do |setting|
67
+ options[:force] = setting
68
+ end
69
+ end,
70
+ }
71
+
72
+ command = ARGV.shift
73
+ subcommands[command].order!
74
+
75
+ pf = PuppetfileEditor::CLI.new(options[:puppetfile])
76
+ pf.public_send(command, options) if pf.respond_to?(command)
@@ -0,0 +1,114 @@
1
+ require 'puppetfile_editor'
2
+
3
+ module PuppetfileEditor
4
+ # CLI methods
5
+ class CLI
6
+ def initialize(pfile_path)
7
+ @pfile = PuppetfileEditor::Puppetfile.new(path: pfile_path)
8
+ @logger = PuppetfileEditor::Logging.new
9
+ begin
10
+ @pfile.load
11
+ rescue IOError, NoMethodError => e
12
+ warn_and_exit(e.message)
13
+ end
14
+ end
15
+
16
+ def edit(opts)
17
+ opts[:value] = :latest if opts[:value] == 'latest'
18
+ @pfile.update_module(
19
+ opts[:name],
20
+ opts[:param],
21
+ opts[:value],
22
+ opts[:verbose],
23
+ )
24
+ @pfile.dump
25
+ end
26
+
27
+ def format(_)
28
+ @pfile.dump
29
+ end
30
+
31
+ def add(opts)
32
+ warn_and_exit "Module #{opts[:name]} is already present in your Puppetfile." if @pfile.modules.key? opts[:name]
33
+
34
+ case opts[:type]
35
+ when :hg, :git
36
+ warn_and_exit 'URL must be provided for Git and Hg modules' unless opts.key? :url
37
+ warn_and_exit 'Version must be provided for Git and Hg modules' unless opts.key? :param
38
+ opts[:value] = :latest if opts[:value] == 'latest'
39
+ @pfile.add_module(opts[:name], opts[:type] => opts[:url], opts[:param] => opts[:value])
40
+ when :local
41
+ @pfile.add_module(opts[:name], :local)
42
+ when :forge
43
+ warn_and_exit 'Version must be provided for Forge modules' unless opts.key? :version
44
+ @pfile.add_module(opts[:name], opts[:version])
45
+ else
46
+ warn_and_exit 'Only hg, git, local, and forge modules are supported at the moment.'
47
+ end
48
+ @pfile.dump
49
+ end
50
+
51
+ def merge(opts)
52
+ @pfdata = PuppetfileEditor::Puppetfile.new(from_stdin: true)
53
+ @pfdata.load
54
+ new_mod_types = @pfdata.modules.values.group_by(&:type)
55
+ new_mod_types.each do |mod_type, mods|
56
+ puts "\n #{@pfile.module_sections[mod_type]}\n\n"
57
+ unless [:hg, :git, :forge].include? mod_type
58
+ puts " Skipping #{mod_type} section."
59
+ next
60
+ end
61
+ indent = mods.map(&:name).max_by(&:length).length
62
+ mods.each do |mod|
63
+ if @pfile.modules.key? mod.name
64
+ begin
65
+ @pfile.modules[mod.name].merge_with(mod, opts[:force])
66
+ @logger.module_log(mod.name, indent, @pfile.modules[mod.name].message, @pfile.modules[mod.name].status)
67
+ rescue StandardError => e
68
+ @logger.module_log(mod.name, indent, e.message, @pfile.modules[mod.name].status)
69
+ end
70
+ else
71
+ @logger.module_log(mod.name, indent, 'does not exist in source Puppetfile', :not_found)
72
+ end
73
+ end
74
+ end
75
+ if opts[:stdout]
76
+ $stdout.puts(@pfile.generate_puppetfile)
77
+ else
78
+ @pfile.dump
79
+ end
80
+ end
81
+
82
+ def warn_and_exit(message)
83
+ warn message
84
+ exit 1
85
+ end
86
+ end
87
+
88
+ class Logging
89
+ def initialize
90
+ @statuses = {
91
+ updated: "[ \e[32;1m+\e[0m ]",
92
+ matched: "[ \e[33;1m=\e[0m ]",
93
+ skipped: "[ \e[33;1m~\e[0m ]",
94
+ not_found: "[ \e[31;1mx\e[0m ]",
95
+ type_mismatched: "[ \e[31;1mx\e[0m ]",
96
+ wont_upgrade: "[ \e[33;1m!\e[0m ]",
97
+ undef: '',
98
+ }
99
+ end
100
+
101
+ def log(message, message_type = :undef)
102
+ status = if @statuses.key? message_type
103
+ @statuses[message_type]
104
+ else
105
+ @statuses[:undef]
106
+ end
107
+ puts "#{status} #{message}"
108
+ end
109
+
110
+ def module_log(mod_name, indent, message, message_type = :undef)
111
+ log("#{mod_name.ljust(indent)} => #{message}", message_type)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,155 @@
1
+ module PuppetfileEditor
2
+ class Module
3
+ attr_reader :type
4
+ attr_reader :params
5
+ attr_reader :name
6
+ attr_reader :message
7
+ attr_reader :status
8
+
9
+ def initialize(title, args = nil)
10
+ @type = :undef
11
+ @params = nil
12
+ @message = nil
13
+ @status = nil
14
+ if args == :local
15
+ @type = :local
16
+ elsif args.nil? || args.is_a?(String) || args.is_a?(Symbol)
17
+ @type = :forge
18
+ @params = { version: args } unless args.nil?
19
+ elsif args.is_a? Hash
20
+ if args.key? :hg
21
+ @type = :hg
22
+ elsif args.key? :git
23
+ @type = :git
24
+ end
25
+ @params = args
26
+ calculate_indent
27
+ end
28
+ @author, @name = parse_title title
29
+ end
30
+
31
+ def set(param, newvalue, force = false)
32
+ case @type
33
+ when :hg, :git
34
+ if %w[branch tag ref].include? param
35
+ @params.delete :branch
36
+ @params.delete :tag
37
+ @params.delete :ref
38
+ @params[param.to_sym] = newvalue
39
+ calculate_indent
40
+ else
41
+ raise StandardError, "Only 'branch', 'tag', and 'ref' are supported for '#{@type}' modules."
42
+ end
43
+ when :forge
44
+ if param == 'version'
45
+ @params[:version] = newvalue
46
+ else
47
+ raise StandardError, "Only 'version' is supported for forge modules."
48
+ end
49
+ else
50
+ raise StandardError, "Editing params for '#{@type}' modules is not supported."
51
+ end
52
+ end
53
+
54
+ def merge_with(mod, force = false)
55
+ unless mod.type == @type
56
+ @status = :type_mismatched
57
+ raise(StandardError, "type mismatch ('#{@type}' vs '#{mod.type}')")
58
+ end
59
+ case @type
60
+ when :hg, :git
61
+ new = mod.params.reject { |param, _| param.eql? @type }
62
+ if !force && new.keys == [:tag] && (@params.key?(:branch) || @params.key?(:ref))
63
+ raise(StandardError, "kept at #{full_version}")
64
+ end
65
+ if full_version == mod.full_version
66
+ @message = "versions match (#{full_version})"
67
+ @status = :matched
68
+ else
69
+ @message = "updated (#{full_version} to #{mod.full_version})"
70
+ @status = :updated
71
+ end
72
+ @params.delete_if { |param, _| [:branch, :tag, :ref].include? param }
73
+ @params.merge!(new)
74
+ calculate_indent
75
+ when :forge
76
+ unless force
77
+ if mod.params.nil? or mod.params.is_a? Symbol
78
+ @status = :wont_upgrade
79
+ raise(StandardError, "won't upgrade to #{mod.full_version}")
80
+ end
81
+ end
82
+ if full_version == mod.full_version
83
+ @message = "versions match (#{full_version})"
84
+ @status = :matched
85
+ else
86
+ @message = "updated (#{full_version} to #{mod.full_version})"
87
+ @status = :updated
88
+ end
89
+ @params = mod.params
90
+ else
91
+ @status = :skipped
92
+ raise(StandardError, 'only git, forge, and hg modules are supported for merging')
93
+ end
94
+ end
95
+
96
+ def dump(old_hashes = false)
97
+ output = []
98
+ case @type
99
+ when :hg, :git
100
+ output.push "mod '#{full_title}'"
101
+ @params.each do |param_name, param_value|
102
+ value = if param_value == :latest
103
+ ':latest'
104
+ else
105
+ "'#{param_value}'"
106
+ end
107
+ param = old_hashes ? ":#{param_name.to_s.ljust(@indent - 1)} =>" : "#{param_name}:".ljust(@indent)
108
+ output.push " #{param} #{value}"
109
+ end
110
+ when :local
111
+ output.push("mod '#{full_title}', :local")
112
+ else
113
+ if @params.nil?
114
+ output.push("mod '#{full_title}'")
115
+ else
116
+ output.push("mod '#{full_title}', '#{@params[:version]}'")
117
+ end
118
+ end
119
+ output.join(",\n")
120
+ end
121
+
122
+ def full_title
123
+ return "#{@author}/#{@name}" if @author
124
+ @name
125
+ end
126
+
127
+ def full_version
128
+ case @type
129
+ when :hg, :git
130
+ @params.reject { |param, _| param.eql? @type }.map { |param, value| "#{param}: #{value}" }.sort.join(', ')
131
+ when :forge
132
+ return @params[:version] if @params.key? :version
133
+ nil
134
+ else
135
+ nil
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def parse_title(title)
142
+ if (match = title.match(/^(\w+)$/))
143
+ [nil, match[1]]
144
+ elsif (match = title.match(%r{^(\w+)[/-](\w[\w-]*\w)$}))
145
+ [match[1], match[2]]
146
+ else
147
+ raise ArgumentError, "Module name (#{title}) must match either 'modulename' or 'owner/modulename'"
148
+ end
149
+ end
150
+
151
+ def calculate_indent
152
+ @indent = @params.keys.max_by(&:length).length + 1
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,119 @@
1
+ require 'puppetfile_editor/module'
2
+
3
+ module PuppetfileEditor
4
+ # Puppetfile implementation
5
+ class Puppetfile
6
+ # @!attribute [r] modules
7
+ # @return [Hash<String, PuppetfileEditor::Module>]
8
+ attr_reader :modules
9
+
10
+ # @!attribute [r] puppetfile_path
11
+ # @return [String] The path to the Puppetfile
12
+ attr_reader :puppetfile_path
13
+
14
+ attr_reader :module_sections
15
+
16
+ # @param [String] path path to Puppetfile
17
+ def initialize(path: 'Puppetfile', from_stdin: false, old_hashes: false)
18
+ @puppetfile_path = path
19
+ @from_stdin = from_stdin
20
+ @old_hashes = old_hashes
21
+ @modules = {}
22
+ @loaded = false
23
+ @forge = nil
24
+
25
+ @module_sections = {
26
+ local: 'Local modules',
27
+ forge: 'Modules from the Puppet Forge',
28
+ hg: 'Mercurial modules',
29
+ git: 'Git modules',
30
+ undef: 'Other modules',
31
+ }
32
+ end
33
+
34
+ def load
35
+ puppetfile_contents = if @from_stdin
36
+ $stdin.gets(nil).chomp
37
+ else
38
+ raise(IOError, "Puppetfile #{@puppetfile_path} missing or unreadable") unless File.readable?(@puppetfile_path)
39
+
40
+ File.read @puppetfile_path
41
+ end
42
+
43
+ dsl = PuppetfileEditor::DSL.new(self)
44
+ dsl.instance_eval(puppetfile_contents)
45
+ @loaded = true
46
+ end
47
+
48
+ def generate_puppetfile
49
+ raise StandardError, 'File is not loaded' unless @loaded
50
+
51
+ contents = []
52
+
53
+ contents.push "forge '#{@forge}'\n" if @forge
54
+
55
+ @module_sections.each do |module_type, module_comment|
56
+ module_list = modules.select { |_, mod| mod.type == module_type }
57
+ next unless module_list.any?
58
+ contents.push "# #{module_comment}"
59
+ module_list.values.sort_by(&:name).each do |mod|
60
+ contents.push mod.dump(@old_hashes)
61
+ end
62
+ contents.push ''
63
+ end
64
+
65
+ contents.join("\n")
66
+ end
67
+
68
+ def dump
69
+ File.write(@puppetfile_path, generate_puppetfile) if @loaded
70
+ end
71
+
72
+ def update_module(name, param, value, verbose = false)
73
+ if @modules.key? name
74
+ begin
75
+ @modules[name].set(param, value)
76
+ puts "Successfully set #{param} to #{value} for #{name}." if verbose
77
+ rescue StandardError => e
78
+ warn e.message
79
+ exit 1
80
+ end
81
+ else
82
+ warn "Module #{name} does not exist in your Puppetfile"
83
+ end
84
+ end
85
+
86
+ # @param [String] name Module name
87
+ # @param [String, Hash] args Module arguments
88
+ def add_module(name, args)
89
+ mod = PuppetfileEditor::Module.new(name, args)
90
+ @modules[mod.name] = mod
91
+ end
92
+
93
+ def update_forge_url(url)
94
+ raise StandardError, "Forge URL must be a String, but it is a #{url.class}" unless url.is_a? String
95
+ @forge = url
96
+ end
97
+ end
98
+
99
+ # A barebones implementation of the Puppetfile DSL
100
+ #
101
+ # @api private
102
+ class DSL
103
+ def initialize(librarian)
104
+ @librarian = librarian
105
+ end
106
+
107
+ def mod(name, args = nil)
108
+ @librarian.add_module(name, args)
109
+ end
110
+
111
+ def forge(url)
112
+ @librarian.update_forge_url(url)
113
+ end
114
+
115
+ def method_missing(method, *_)
116
+ raise NoMethodError, "Unrecognized declaration: '#{method}'"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,3 @@
1
+ module PuppetfileEditor
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'puppetfile_editor/version'
2
+ require 'puppetfile_editor/puppetfile'
3
+ require 'puppetfile_editor/module'
4
+ require 'puppetfile_editor/cli'
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppetfile_editor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Eugene Piven
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: |2
56
+ PuppetfileEditor provides an easy-to-use interface to check Puppetfile for validity,
57
+ update module versions, add, and remove modules.
58
+ email: epiven@gmail.com
59
+ executables:
60
+ - pfile
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - README.md
65
+ - bin/pfile
66
+ - lib/puppetfile_editor.rb
67
+ - lib/puppetfile_editor/cli.rb
68
+ - lib/puppetfile_editor/module.rb
69
+ - lib/puppetfile_editor/puppetfile.rb
70
+ - lib/puppetfile_editor/version.rb
71
+ homepage: https://github.com/pegasd/puppetfile_editor
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.6.12
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Parse and edit Puppetfile
95
+ test_files: []