~ubuntu-branches/ubuntu/quantal/puppet/quantal

« back to all changes in this revision

Viewing changes to lib/puppet/face/module/list.rb

  • Committer: Package Import Robot
  • Author(s): Marc Deslauriers
  • Date: 2012-07-14 01:56:30 UTC
  • mfrom: (1.1.29) (3.1.43 sid)
  • Revision ID: package-import@ubuntu.com-20120714015630-ntj41rkvkq4zph4y
Tags: 2.7.18-1ubuntu1
* Resynchronise with Debian. (LP: #1023931) Remaining changes:
  - debian/puppetmaster-passenger.postinst: Make sure we error if puppet
    config print doesn't work
  - debian/puppetmaster-passenger.postinst: Ensure upgrades from
    <= 2.7.11-1 fixup passenger apache configuration.
* Dropped upstreamed patches:
  - debian/patches/CVE-2012-1906_CVE-2012-1986_to_CVE-2012-1989.patch
  - debian/patches/puppet-12844
  - debian/patches/2.7.17-Puppet-July-2012-CVE-fixes.patch
* Drop Build-Depends on ruby-rspec (in universe):
  - debian/control: remove ruby-rspec from Build-Depends
  - debian/patches/no-rspec.patch: make Rakefile work anyway if rspec
    isn't installed so we can use it in debian/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# encoding: UTF-8
 
2
 
1
3
Puppet::Face.define(:module, '1.0.0') do
2
4
  action(:list) do
3
5
    summary "List installed modules"
4
6
    description <<-HEREDOC
5
 
      List puppet modules from a specific environment, specified modulepath or
6
 
      default to listing modules in the default modulepath:
7
 
      #{Puppet.settings[:modulepath]}
 
7
      Lists the installed puppet modules. By default, this action scans the
 
8
      modulepath from puppet.conf's `[main]` block; use the --modulepath
 
9
      option to change which directories are scanned.
 
10
 
 
11
      The output of this action includes information from the module's
 
12
      metadata, including version numbers and unmet module dependencies.
8
13
    HEREDOC
9
14
    returns "hash of paths to module objects"
10
15
 
11
 
    option "--env ENVIRONMENT" do
 
16
    option "--environment NAME" do
 
17
      default_to { "production" }
12
18
      summary "Which environments' modules to list"
 
19
      description <<-EOT
 
20
        Which environments' modules to list.
 
21
      EOT
13
22
    end
14
23
 
15
24
    option "--modulepath MODULEPATH" do
16
25
      summary "Which directories to look for modules in"
 
26
      description <<-EOT
 
27
        Which directories to look for modules in; use the system path separator
 
28
        character (`:` on Unix-like systems and `;` on Windows) to specify
 
29
        multiple directories.
 
30
      EOT
 
31
    end
 
32
 
 
33
    option "--tree" do
 
34
      summary "Whether to show dependencies as a tree view"
17
35
    end
18
36
 
19
37
    examples <<-EOT
21
39
 
22
40
      $ puppet module list
23
41
        /etc/puppet/modules
24
 
          bacula (0.0.2)
25
 
        /usr/share/puppet/modules
26
 
          apache (0.0.3)
27
 
          bacula (0.0.1)
 
42
        ├── bodepd-create_resources (v0.0.1)
 
43
        ├── puppetlabs-bacula (v0.0.2)
 
44
        ├── puppetlabs-mysql (v0.0.1)
 
45
        ├── puppetlabs-sqlite (v0.0.1)
 
46
        └── puppetlabs-stdlib (v2.2.1)
 
47
        /usr/share/puppet/modules (no modules installed)
 
48
 
 
49
      List installed modules in a tree view:
 
50
 
 
51
      $ puppet module list --tree
 
52
        /etc/puppet/modules
 
53
        └─┬ puppetlabs-bacula (v0.0.2)
 
54
          ├── puppetlabs-stdlib (v2.2.1)
 
55
          ├─┬ puppetlabs-mysql (v0.0.1)
 
56
          │ └── bodepd-create_resources (v0.0.1)
 
57
          └── puppetlabs-sqlite (v0.0.1)
 
58
        /usr/share/puppet/modules (no modules installed)
28
59
 
29
60
      List installed modules from a specified environment:
30
61
 
31
 
      $ puppet module list --env 'test'
32
 
        /tmp/puppet/modules
33
 
          rrd (0.0.2)
 
62
      $ puppet module list --environment production
 
63
        /etc/puppet/modules
 
64
        ├── bodepd-create_resources (v0.0.1)
 
65
        ├── puppetlabs-bacula (v0.0.2)
 
66
        ├── puppetlabs-mysql (v0.0.1)
 
67
        ├── puppetlabs-sqlite (v0.0.1)
 
68
        └── puppetlabs-stdlib (v2.2.1)
 
69
        /usr/share/puppet/modules (no modules installed)
34
70
 
35
71
      List installed modules from a specified modulepath:
36
72
 
37
 
      $ puppet module list --modulepath /tmp/facts1:/tmp/facts2
38
 
        /tmp/facts1
39
 
          stdlib
40
 
        /tmp/facts2
41
 
          nginx (1.0.0)
 
73
      $ puppet module list --modulepath /usr/share/puppet/modules
 
74
        /usr/share/puppet/modules (no modules installed)
42
75
    EOT
43
76
 
44
77
    when_invoked do |options|
45
78
      Puppet[:modulepath] = options[:modulepath] if options[:modulepath]
46
 
      environment = Puppet::Node::Environment.new(options[:env])
 
79
      environment = Puppet::Node::Environment.new(options[:environment])
47
80
 
48
81
      environment.modules_by_path
49
82
    end
50
83
 
51
 
    when_rendering :console do |modules_by_path|
 
84
    when_rendering :console do |modules_by_path, options|
52
85
      output = ''
53
 
      modules_by_path.each do |path, modules|
54
 
        output << "#{path}\n"
55
 
        modules.each do |mod|
56
 
          version_string = mod.version ? "(#{mod.version})" : ''
57
 
          output << "  #{mod.name} #{version_string}\n"
58
 
        end
59
 
      end
 
86
 
 
87
      Puppet[:modulepath] = options[:modulepath] if options[:modulepath]
 
88
      environment = Puppet::Node::Environment.new(options[:production])
 
89
 
 
90
      error_types = {
 
91
        :non_semantic_version => {
 
92
          :title => "Non semantic version dependency"
 
93
        },
 
94
        :missing => {
 
95
          :title => "Missing dependency"
 
96
        },
 
97
        :version_mismatch => {
 
98
          :title => "Module '%s' (v%s) fails to meet some dependencies:"
 
99
        }
 
100
      }
 
101
 
 
102
      @unmet_deps = {}
 
103
      error_types.each_key do |type|
 
104
        @unmet_deps[type] = Hash.new do |hash, key|
 
105
          hash[key] = { :errors => [], :parent => nil }
 
106
        end
 
107
      end
 
108
 
 
109
      # Prepare the unmet dependencies for display on the console.
 
110
      environment.modules.sort_by {|mod| mod.name}.each do |mod|
 
111
        unmet_grouped = Hash.new { |h,k| h[k] = [] }
 
112
        unmet_grouped = mod.unmet_dependencies.inject(unmet_grouped) do |acc, dep|
 
113
          acc[dep[:reason]] << dep
 
114
          acc
 
115
        end
 
116
        unmet_grouped.each do |type, deps|
 
117
          unless deps.empty?
 
118
            unmet_grouped[type].sort_by { |dep| dep[:name] }.each do |dep|
 
119
              dep_name           = dep[:name].gsub('/', '-')
 
120
              installed_version  = dep[:mod_details][:installed_version]
 
121
              version_constraint = dep[:version_constraint]
 
122
              parent_name        = dep[:parent][:name].gsub('/', '-')
 
123
              parent_version     = dep[:parent][:version]
 
124
 
 
125
              msg = "'#{parent_name}' (#{parent_version})"
 
126
              msg << " requires '#{dep_name}' (#{version_constraint})"
 
127
              @unmet_deps[type][dep[:name]][:errors] << msg
 
128
              @unmet_deps[type][dep[:name]][:parent] = {
 
129
                :name    => dep[:parent][:name],
 
130
                :version => parent_version
 
131
              }
 
132
              @unmet_deps[type][dep[:name]][:version] = installed_version
 
133
            end
 
134
          end
 
135
        end
 
136
      end
 
137
 
 
138
      # Display unmet dependencies by category.
 
139
      error_display_order = [:non_semantic_version, :version_mismatch, :missing]
 
140
      error_display_order.each do |type|
 
141
        unless @unmet_deps[type].empty?
 
142
          @unmet_deps[type].keys.sort_by {|dep| dep }.each do |dep|
 
143
            name    = dep.gsub('/', '-')
 
144
            title   = error_types[type][:title]
 
145
            errors  = @unmet_deps[type][dep][:errors]
 
146
            version = @unmet_deps[type][dep][:version]
 
147
 
 
148
            msg = case type
 
149
                  when :version_mismatch
 
150
                    title % [name, version] + "\n"
 
151
                  when :non_semantic_version
 
152
                    title + " '#{name}' (v#{version}):\n"
 
153
                  else
 
154
                    title + " '#{name}':\n"
 
155
                  end
 
156
 
 
157
            errors.each { |error_string| msg << "  #{error_string}\n" }
 
158
            Puppet.warning msg.chomp
 
159
          end
 
160
        end
 
161
      end
 
162
 
 
163
      environment.modulepath.each do |path|
 
164
        modules = modules_by_path[path]
 
165
        no_mods = modules.empty? ? ' (no modules installed)' : ''
 
166
        output << "#{path}#{no_mods}\n"
 
167
 
 
168
        if options[:tree]
 
169
          # The modules with fewest things depending on them will be the
 
170
          # parent of the tree.  Can't assume to start with 0 dependencies
 
171
          # since dependencies may be cyclical.
 
172
          modules_by_num_requires = modules.sort_by {|m| m.required_by.size}
 
173
          @seen = {}
 
174
          tree = list_build_tree(modules_by_num_requires, [], nil,
 
175
            :label_unmet => true, :path => path, :label_invalid => false)
 
176
        else
 
177
          tree = []
 
178
          modules.sort_by { |mod| mod.forge_name or mod.name  }.each do |mod|
 
179
            tree << list_build_node(mod, path, :label_unmet => false,
 
180
                      :path => path, :label_invalid => true)
 
181
          end
 
182
        end
 
183
 
 
184
        output << Puppet::ModuleTool.format_tree(tree)
 
185
      end
 
186
 
60
187
      output
61
188
    end
62
 
 
 
189
  end
 
190
 
 
191
  # Prepare a list of module objects and their dependencies for print in a
 
192
  # tree view.
 
193
  #
 
194
  # Returns an Array of Hashes
 
195
  #
 
196
  # Example:
 
197
  #
 
198
  #   [
 
199
  #     {
 
200
  #       :text => "puppetlabs-bacula (v0.0.2)",
 
201
  #       :dependencies=> [
 
202
  #         { :text => "puppetlabs-stdlib (v2.2.1)", :dependencies => [] },
 
203
  #         {
 
204
  #           :text => "puppetlabs-mysql (v1.0.0)"
 
205
  #           :dependencies => [
 
206
  #             {
 
207
  #               :text => "bodepd-create_resources (v0.0.1)",
 
208
  #               :dependencies => []
 
209
  #             }
 
210
  #           ]
 
211
  #         },
 
212
  #         { :text => "puppetlabs-sqlite (v0.0.1)", :dependencies => [] },
 
213
  #       ]
 
214
  #     }
 
215
  #   ]
 
216
  #
 
217
  # When the above data structure is passed to Puppet::ModuleTool.build_tree
 
218
  # you end up with something like this:
 
219
  #
 
220
  #   /etc/puppet/modules
 
221
  #   └─┬ puppetlabs-bacula (v0.0.2)
 
222
  #     ├── puppetlabs-stdlib (v2.2.1)
 
223
  #     ├─┬ puppetlabs-mysql (v1.0.0)
 
224
  #     │ └── bodepd-create_resources (v0.0.1)
 
225
  #     └── puppetlabs-sqlite (v0.0.1)
 
226
  #
 
227
  def list_build_tree(list, ancestors=[], parent=nil, params={})
 
228
    list.map do |mod|
 
229
      next if @seen[(mod.forge_name or mod.name)]
 
230
      node = list_build_node(mod, parent, params)
 
231
      @seen[(mod.forge_name or mod.name)] = true
 
232
 
 
233
      unless ancestors.include?(mod)
 
234
        node[:dependencies] ||= []
 
235
        missing_deps = mod.unmet_dependencies.select do |dep|
 
236
          dep[:reason] == :missing
 
237
        end
 
238
        missing_deps.map do |mis_mod|
 
239
          str = "#{colorize(:bg_red, 'UNMET DEPENDENCY')} #{mis_mod[:name].gsub('/', '-')} "
 
240
          str << "(#{colorize(:cyan, mis_mod[:version_constraint])})"
 
241
          node[:dependencies] << { :text => str }
 
242
        end
 
243
        node[:dependencies] += list_build_tree(mod.dependencies_as_modules,
 
244
          ancestors + [mod], mod, params)
 
245
      end
 
246
 
 
247
      node
 
248
    end.compact
 
249
  end
 
250
 
 
251
  # Prepare a module object for print in a tree view.  Each node in the tree
 
252
  # must be a Hash in the following format:
 
253
  #
 
254
  #    { :text => "puppetlabs-mysql (v1.0.0)" }
 
255
  #
 
256
  # The value of a module's :text is affected by three (3) factors: the format
 
257
  # of the tree, it's dependency status, and the location in the modulepath
 
258
  # relative to it's parent.
 
259
  #
 
260
  # Returns a Hash
 
261
  #
 
262
  def list_build_node(mod, parent, params)
 
263
    str = ''
 
264
    str << (mod.forge_name ? mod.forge_name.gsub('/', '-') : mod.name)
 
265
    str << ' (' + colorize(:cyan, mod.version ? "v#{mod.version}" : '???') + ')'
 
266
 
 
267
    unless File.dirname(mod.path) == params[:path]
 
268
      str << " [#{File.dirname(mod.path)}]"
 
269
    end
 
270
 
 
271
    if @unmet_deps[:version_mismatch].include?(mod.forge_name)
 
272
      if params[:label_invalid]
 
273
        str << '  ' + colorize(:red, 'invalid')
 
274
      elsif parent.respond_to?(:forge_name)
 
275
        unmet_parent = @unmet_deps[:version_mismatch][mod.forge_name][:parent]
 
276
        if (unmet_parent[:name] == parent.forge_name &&
 
277
            unmet_parent[:version] == "v#{parent.version}")
 
278
          str << '  ' + colorize(:red, 'invalid')
 
279
        end
 
280
      end
 
281
    end
 
282
 
 
283
    { :text => str }
63
284
  end
64
285
end