Skip to content
Browse files

Initial commit.

This code is taken from:

    lak:prototype/master/puppet-shell

So the original author is Luke Kanies <luke@puppetlabs.com>.
  • Loading branch information...
0 parents commit 605fbcca46a58656a6031d05adff31a62dc50d1c @kbarber kbarber committed May 6, 2012
4 .gitignore
@@ -0,0 +1,4 @@
+pkg/
+doc/
+.*.swp
+.rvmrc
17 LICENSE
@@ -0,0 +1,17 @@
+Puppet Shell - A shell for traversing the Puppet resource abstraction layer
+
+Copyright (C) 2012 Puppet Labs Inc
+
+Puppet Labs can be contacted at: info@puppetlabs.com
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
8 Modulefile
@@ -0,0 +1,8 @@
+name 'puppetlabs-shell'
+version '0.0.1'
+source 'git://github.com/puppetlabs/puppetlabs-shell.git'
+author 'puppetlabs'
+license 'ASL 2.0'
+summary 'Puppet Shell'
+description 'A shell for traversing the Puppet resource abstraction layer'
+project_page 'https://forge.puppetlabs.com/puppetlabs/shell'
61 README.markdown
@@ -0,0 +1,61 @@
+# Puppet Shell
+
+### Overview
+
+This module provides a shell utility for traversing the Puppet Resource
+Abstraction Layer.
+
+### Disclaimer
+
+Warning! While this software is written in the best interest of quality it has
+not been formally tested by our QA teams. Use at your own risk, but feel free
+to enjoy and perhaps improve it while you do.
+
+Please see the included Apache Software License for more legal details
+regarding warranty.
+
+### Requirements
+
+So this module was predominantly tested on:
+
+* Puppet 2.7.12 or greater (faces supported is required)
+
+Other combinations may work, and we are happy to obviously take patches to
+support other stacks.
+
+# Installation
+
+As with most modules, its best to download this module from the forge:
+
+http://forge.puppetlabs.com/puppetlabs/shell
+
+If you want the bleeding edge (and potentially broken) version from github,
+download the module into your modulepath on your Puppetmaster. If you are not
+sure where your module path is try this command:
+
+ puppet --configprint modulepath
+
+For the shell to work on all the agents, you must enable pluginsync:
+
+ [agent]
+ pluginsync = true
+
+# Quick Start
+
+To fire up the shell, use it like so:
+
+ # puppet shell
+ / >
+
+At the prompt, the following commands can be used:
+
+* ls - list all types, or instances of a single type
+* cd - move around types and instances like a file hierarchy
+* cat - view individual resource as puppet code
+* cp - copy a resource to a new name
+* rm - remove a resource
+* edit - convert a resource to text, open it in your editor, and apply the results
+
+# Detailed Usage
+
+TBA
15 Rakefile
@@ -0,0 +1,15 @@
+require 'rake'
+require 'rspec/core/rake_task'
+
+task :test => [:spec]
+
+desc 'Run RSpec'
+RSpec::Core::RakeTask.new(:spec) do |t|
+ t.pattern = 'spec/{unit}/**/*.rb'
+end
+
+desc 'Generate code coverage'
+RSpec::Core::RakeTask.new(:coverage) do |t|
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec']
+end
4 lib/puppet/application/shell.rb
@@ -0,0 +1,4 @@
+require 'puppet/application/face_base'
+
+class Puppet::Application::Shell < Puppet::Application::FaceBase
+end
347 lib/puppet/face/shell.rb
@@ -0,0 +1,347 @@
+require 'puppet/face'
+
+Puppet::Face.define(:shell, '0.0.1') do
+ copyright "Puppet Labs", 2012
+ license "Apache 2 license; see COPYING"
+
+ summary "Interactive Puppet shell."
+
+ option "--context CONTEXT" do |arg|
+ summary "A ruby-only option used by the interactive shell."
+ end
+
+ def apply_code(text)
+ puts text
+ Puppet[:code] = text
+ node = Puppet::Node.new(Puppet[:certname])
+ catalog = Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node)
+
+ catalog = catalog.to_ral
+ catalog.finalize
+ catalog.apply
+ end
+
+ def get_context(options)
+ options[:context] || Puppet::Util::InteractiveShell::ShellContext.new
+ end
+
+ def ral_to_resource(ral)
+ resource = Puppet::Resource.new(ral.type, ral.name)
+ ral.parameters.each do |name, param|
+ resource[name] = param.value
+ end
+ resource
+ end
+
+ # Return an instance of Puppet::Resource, reflecting actual system state.
+ def retrieve_resource(type_name, name)
+ type = Puppet::Type.type(type_name) or raise("Could not find type #{type_name}")
+ unless resource = type.instances.find { |r| r.name == name }
+ raise "Could not find resource #{name} of type #{type_name}"
+ end
+ values = resource.retrieve
+ values.each do |param, value| resource[param.name] = value end
+ resource.to_resource
+ end
+
+ def list_types
+ types = []
+ Puppet::Type.loadall
+ Puppet::Type.eachtype do |type|
+ types << type.name.to_s
+ end
+ types.sort
+ end
+
+ def list_type(name, tail = nil)
+ type = Puppet::Type.type(name) or raise("Could not find type #{name.inspect}")
+
+ type.instances.collect do |instance|
+ instance.name.to_s
+ end
+ end
+
+ action(:interact) do
+ summary "Operate interactively."
+ returns <<-'EOT'
+ Nil.
+ EOT
+ examples <<-'EOT'
+ $ puppet shell interact
+ > ls
+ file
+ ...
+ > cd user
+ > ls
+ luke
+ root
+ ...
+
+ EOT
+ default
+
+ when_invoked do |*args|
+ options = args.pop
+ require 'puppet/util/interactive_shell'
+ shell = Puppet::Util::InteractiveShell.new
+ shell.interact
+ end
+ end
+
+ action(:edit) do
+ summary "Change at stuff."
+ returns <<-'EOT'
+ A string.
+ EOT
+ examples <<-'EOT'
+ Edit a resource:
+
+ $ puppet shell
+ > cd user
+ > edit luke
+ user { luke: ... }
+ EOT
+
+ when_invoked do |*args|
+ require 'tempfile'
+
+ options = args.pop
+ context = get_context(options)
+ # Yeah, this often won't work. We should almost treat types specially...
+ unless context.cwd.length > 0
+ raise "Can only cat individual resources"
+ end
+ type_name = context.cwd[0]
+ name = args.shift
+
+ resource = retrieve_resource(type_name, name)
+
+ file = Tempfile.new(resource.to_s)
+ File.open(file.path, "w") { |f| f.puts resource.to_manifest }
+ system(ENV['EDITOR'], file.path)
+ text = File.read(file.path)
+
+ apply_code(text)
+ nil
+ end
+ end
+
+ action(:cp) do
+ summary "Copy stuff."
+ returns <<-'EOT'
+ nil
+ EOT
+ examples <<-'EOT'
+ Copy a resource:
+
+ $ puppet shell
+ > cd user
+ > cp luke foo uid=505
+ > cat foo
+ > rm foo
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+ # Yeah, this often won't work. We should almost treat types specially...
+ unless context.cwd.length > 0
+ raise "Can only cat individual resources"
+ end
+ type_name = context.cwd[0]
+ old_name = args.shift
+ new_name = args.shift
+ resource = retrieve_resource(type_name, old_name)
+
+ new_resource = Puppet::Resource.new(type_name, new_name)
+
+ resource[:name] = new_name
+
+ resource.each do |param, value|
+ if value.to_s =~ /#{old_name}/i
+ new_resource[param] = value.to_s.gsub(old_name, new_name).gsub(old_name.capitalize, new_name.capitalize)
+ else
+ new_resource[param] = value
+ end
+ end
+
+ unless args.empty?
+ args.each do |str|
+ unless str.include?("=")
+ Puppet.warning "Must specify new parameters as 'param=value'"
+ next
+ end
+ param, value = str.split("=")
+ new_resource[param] = value
+ end
+ end
+ catalog = Puppet::Resource::Catalog.new
+ catalog.add_resource(new_resource.to_ral)
+ catalog.apply
+ nil
+ end
+ end
+
+ action(:rm) do
+ summary "Destroy stuff."
+ returns <<-'EOT'
+ nil
+ EOT
+ examples <<-'EOT'
+ Remove a resource:
+
+ $ puppet shell
+ > cd user
+ > rm root
+ > *boom* :)
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+ # Yeah, this often won't work. We should almost treat types specially...
+ unless context.cwd.length > 0
+ raise "Can only cat individual resources"
+ end
+ type_name = context.cwd[0]
+ name = args.shift
+
+ resource = Puppet::Resource.new(type_name, name)
+ resource[:ensure] = :absent
+ catalog = Puppet::Resource::Catalog.new
+ catalog.add_resource(resource.to_ral)
+ catalog.apply
+ nil
+ end
+ end
+
+ action(:cat) do
+ summary "Look at stuff."
+ returns <<-'EOT'
+ A string.
+ EOT
+ examples <<-'EOT'
+ List resource types:
+
+ $ puppet shell
+ > cd user
+ > cat luke
+ user { luke: ... }
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+ # Yeah, this often won't work. We should almost treat types specially...
+ unless context.cwd.length > 0
+ raise "Can only cat individual resources"
+ end
+ type_name = context.cwd[0]
+ retrieve_resource(type_name, args.shift).to_manifest
+ end
+ end
+
+ action(:cd) do
+ summary "Change working directory."
+ returns <<-'EOT'
+ Nil.
+ EOT
+ examples <<-'EOT'
+ List resource types:
+
+ $ puppet shell
+ > ls
+ file
+ ...
+ > cd user
+ > ls
+ luke
+ root
+ ...
+
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+
+ dir = args.shift
+ if dir == "/"
+ context.cwd.clear
+ elsif dir == ".."
+ context.cwd.pop
+ else
+ dirs = ls(options)
+ unless dirs.include?(dir)
+ raise "Cannot cd to '#{dir.inspect}' - no such dir"
+ end
+ context.cwd << dir
+ end
+ nil
+ end
+ end
+
+ action(:pwd) do
+ summary "Print working directory."
+ returns <<-'EOT'
+ Working directory as a string.
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+
+ context.pwd
+ end
+ end
+
+ action(:ls) do
+ summary "List stuff."
+ returns <<-'EOT'
+ An array of things.
+ EOT
+ examples <<-'EOT'
+ List resource types:
+
+ $ puppet shell
+ > ls
+ file
+ ...
+
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ context = get_context(options)
+
+ if context.cwd.empty?
+ list_types
+ else
+ list_type(*(context.cwd))
+ end
+ end
+
+ when_rendering :console do |list|
+ list.join("\n")
+ end
+ end
+
+ action(:quit) do
+ summary "Quit."
+ returns <<-'EOT'
+ Nil.
+ EOT
+ examples <<-'EOT'
+ List resource types:
+
+ $ puppet shell
+ > quit
+ $
+ EOT
+
+ when_invoked do |*args|
+ options = args.pop
+ exit(0)
+ end
+ end
+end
69 lib/puppet/util/interactive_shell.rb
@@ -0,0 +1,69 @@
+require 'puppet/util'
+
+class Puppet::Util::InteractiveShell
+ class ShellContext
+ attr_accessor :cwd
+ def initialize
+ @cwd = []
+ end
+
+ def pwd
+ "/" + @cwd.join("/")
+ end
+ end
+
+ attr_reader :context, :face
+
+ def initialize
+ @context = ShellContext.new
+ @face = Puppet::Face[:shell, "0.0.1"] or raise("Could not find 'shell' face")
+ end
+
+ def get_command
+ begin
+ $stdin.readline
+ rescue EOFError
+ # Hackish, but sufficient.
+ return "quit"
+ end
+ end
+
+ def interact
+ while true do
+ begin
+ prompt
+ command = get_command
+ execute_shell_action(command)
+ rescue => detail
+ puts detail.backtrace
+ $stderr.puts detail
+ end
+ end
+ end
+
+ def prompt
+ print "#{context.pwd} > "
+ end
+
+ def execute_shell_action(line)
+ rest_of_line = line.split(/\s+/)
+ action_name = rest_of_line.shift
+ unless action = Puppet::Face.find_action(:shell, action_name)
+ raise "Could not find #{action_name}"
+ end
+
+ # Add our context in an options hash that the actions can
+ # handle.
+ args = rest_of_line
+ args << {:context => context}
+
+ result = face.send(action.name, *args)
+
+ # For now I'm going to just print results to the screen, although the
+ # original code from lak required a render code that was refactored to
+ # be in the action class.
+ #puts action.render(:console, result) unless result.nil?
+ puts result unless result.nil?
+ status = true
+ end
+end
30 spec/spec_helper.rb
@@ -0,0 +1,30 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+# Don't want puppet getting the command line arguments for rake or autotest
+ARGV.clear
+
+require 'puppet'
+require 'facter'
+require 'mocha'
+gem 'rspec', '>=2.0.0'
+require 'rspec/expectations'
+
+
+# So everyone else doesn't have to include this base constant.
+module PuppetSpec
+ FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)),
+ "fixtures") unless defined?(FIXTURE_DIR)
+end
+
+require 'puppet_spec_helper'
+
+RSpec.configure do |config|
+ config.before :each do
+ GC.disable
+ end
+
+ config.after :each do
+ GC.enable
+ end
+end
10 spec/unit/puppet/application/shell_spec.rb
@@ -0,0 +1,10 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+require 'puppet/application/shell'
+
+describe "Puppet::Application::Shell" do
+ it "should be a subclass of Puppet::Application::FaceBase" do
+ Puppet::Application::Shell.superclass.should equal(Puppet::Application::FaceBase)
+ end
+end
11 spec/unit/puppet/face/shell_spec.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+require 'puppet/face'
+
+describe Puppet::Face[:shell, '0.0.1'] do
+ [:interact].each do |action|
+ it { should be_action action }
+ it { should respond_to action }
+ end
+end
8 spec/unit/puppet/util/interactive_shell_spec.rb
@@ -0,0 +1,8 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+require 'puppet/util/interactive_shell'
+
+describe Puppet::Util::InteractiveShell do
+ it 'should have some tests ...'
+end

0 comments on commit 605fbcc

Please sign in to comment.
Something went wrong with that request. Please try again.