ralsh --host works now, and is using REST.
A node running puppetd --listen will allow ralsh to find, search, and
modify live resources, via REST.

Signed-off-by: Jesse Wolfe <[email protected]>
---
 lib/puppet/application/ralsh.rb        |   56 ++++----------
 lib/puppet/indirector/resource/ral.rb  |   48 ++++++++++++
 lib/puppet/indirector/resource/rest.rb |    5 +
 lib/puppet/indirector/rest.rb          |    2 +-
 lib/puppet/network/rest_authconfig.rb  |    1 +
 lib/puppet/resource.rb                 |   17 ++++-
 lib/puppet/resource/catalog.rb         |    3 +-
 lib/puppet/type.rb                     |    6 ++
 lib/puppet/util/settings.rb            |    1 -
 spec/unit/application/ralsh.rb         |   86 +++++++++++-----------
 spec/unit/indirector/resource/ral.rb   |  129 ++++++++++++++++++++++++++++++++
 spec/unit/indirector/resource/rest.rb  |   11 +++
 spec/unit/indirector/rest.rb           |   15 +++-
 spec/unit/network/rest_authconfig.rb   |    1 +
 spec/unit/resource.rb                  |   19 +++++
 15 files changed, 309 insertions(+), 91 deletions(-)
 create mode 100644 lib/puppet/indirector/resource/ral.rb
 create mode 100644 lib/puppet/indirector/resource/rest.rb
 create mode 100644 spec/unit/indirector/resource/ral.rb
 create mode 100755 spec/unit/indirector/resource/rest.rb

diff --git a/lib/puppet/application/ralsh.rb b/lib/puppet/application/ralsh.rb
index b9f7a58..87cb68d 100644
--- a/lib/puppet/application/ralsh.rb
+++ b/lib/puppet/application/ralsh.rb
@@ -71,52 +71,24 @@ Puppet::Application.new(:ralsh) do
             trans.to_manifest
         }
 
-        text = if @host
-            client = Puppet::Network::Client.resource.new(:Server => @host, 
:Port => Puppet[:puppetport])
-            unless client.read_cert
-                raise "client.read_cert failed"
-            end
-            begin
-                # They asked for a single resource.
-                if name
-                    transbucket = [client.describe(type, name)]
-                else
-                    # Else, list the whole thing out.
-                    transbucket = client.instances(type)
-                end
-            rescue Puppet::Network::XMLRPCClientError => exc
-                raise "client.list(#{type}) failed: #{exc.message}"
-            end
-            transbucket.sort { |a,b| a.name <=> b.name }.collect(&format)
+        if @host
+            Puppet::Resource.indirection.terminus_class = :rest
+            port = Puppet[:puppetport]
+            key = ["https://#{host}:#{port}";, "production", "resources", type, 
name].join('/')
         else
-            if name
-                obj = typeobj.instances.find { |o| o.name == name } || 
typeobj.new(:name => name, :check => properties)
-                vals = obj.retrieve
-
-                unless params.empty?
-                    params.each do |param, value|
-                        obj[param] = value
-                    end
-                    catalog = Puppet::Resource::Catalog.new
-                    catalog.add_resource obj
-                    begin
-                        catalog.apply
-                    rescue => detail
-                        if Puppet[:trace]
-                            puts detail.backtrace
-                        end
-                    end
+            key = [type, name].join('/')
+        end
 
-                end
-                [format.call(obj.to_trans(true))]
+        text = if name
+            if params.empty?
+                [ Puppet::Resource.find( key ) ]
             else
-                typeobj.instances.collect do |obj|
-                    next if ARGV.length > 0 and ! ARGV.include? obj.name
-                    trans = obj.to_trans(true)
-                    format.call(trans)
-                end
+                request = Puppet::Indirector::Request.new(:resource, :save, 
key) # Yuck.
+                [ Puppet::Resource.new( type, name, params ).save( request ) ]
             end
-        end.compact.join("\n")
+        else
+            Puppet::Resource.search( key, {} )
+        end.map(&format).join("\n")
 
         if options[:edit]
             file = "/tmp/x2puppet-#{Process.pid}.pp"
diff --git a/lib/puppet/indirector/resource/ral.rb 
b/lib/puppet/indirector/resource/ral.rb
new file mode 100644
index 0000000..f2c3f84
--- /dev/null
+++ b/lib/puppet/indirector/resource/ral.rb
@@ -0,0 +1,48 @@
+class Puppet::Resource::Ral < Puppet::Indirector::Code
+    def find( request )
+        # find by name
+        res   = type(request).instances.find { |o| o.name == 
resource_name(request) }
+        res ||= type(request).new(:name => resource_name(request), :check => 
type(request).properties.collect { |s| s.name })
+
+        return res.to_resource
+    end
+
+    def search( request )
+        conditions = request.options.dup
+        conditions[:name] = resource_name(request) if resource_name(request)
+
+        type(request).instances.map do |res|
+            res.to_resource
+        end.find_all do |res|
+            conditions.all? {|property, value| res.to_resource[property].to_s 
== value.to_s}
+        end.sort do |a,b|
+            a.title <=> b.title
+        end
+    end
+
+    def save( request )
+        # In RAL-land, to "save" means to actually try to change machine state
+        res = request.instance
+        ral_res = res.to_ral
+
+        catalog = Puppet::Resource::Catalog.new
+        catalog.add_resource ral_res
+        catalog.apply
+
+        return ral_res.to_resource
+    end
+
+    private
+
+    def type_name( request )
+        request.key.split('/')[0]
+    end
+
+    def resource_name( request )
+        request.key.split('/')[1]
+    end
+
+    def type( request )
+        Puppet::Type.type(type_name(request)) or raise Puppet::Error "Could 
not find type #{type}"
+    end
+end
diff --git a/lib/puppet/indirector/resource/rest.rb 
b/lib/puppet/indirector/resource/rest.rb
new file mode 100644
index 0000000..7848ae6
--- /dev/null
+++ b/lib/puppet/indirector/resource/rest.rb
@@ -0,0 +1,5 @@
+require 'puppet/indirector/status'
+require 'puppet/indirector/rest'
+
+class Puppet::Resource::Rest < Puppet::Indirector::REST
+end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index a89e986..4fd3859 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -67,7 +67,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
 
     def find(request)
         return nil unless result = 
deserialize(network(request).get(indirection2uri(request), headers))
-        result.name = request.key
+        result.name = request.key if result.respond_to?(:name=)
         result
     end
 
diff --git a/lib/puppet/network/rest_authconfig.rb 
b/lib/puppet/network/rest_authconfig.rb
index 01ed412..7c0ef9c 100644
--- a/lib/puppet/network/rest_authconfig.rb
+++ b/lib/puppet/network/rest_authconfig.rb
@@ -16,6 +16,7 @@ module Puppet
             { :acl => "/certificate/", :method => :find, :authenticated => 
false },
             { :acl => "/certificate_request", :method => [:find, :save], 
:authenticated => false },
             { :acl => "/status", :method => [:find], :authenticated => true },
+            { :acl => "/resource", :method => [:find, :save, :search], 
:authenticated => true },
         ]
 
         def self.main
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 91dd547..cbb6528 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,6 +1,6 @@
 require 'puppet'
 require 'puppet/util/tagging'
-require 'puppet/resource/reference'
+#require 'puppet/resource/reference'
 require 'puppet/util/pson'
 
 # The simplest resource class.  Eventually it will function as the
@@ -12,6 +12,10 @@ class Puppet::Resource
     attr_accessor :file, :line, :catalog, :exported, :virtual
     attr_writer :type, :title
 
+    require 'puppet/indirector'
+    extend Puppet::Indirector
+    indirects :resource, :terminus_class => :ral
+
     ATTRIBUTES = [:file, :line, :exported]
 
     def self.from_pson(pson)
@@ -221,6 +225,17 @@ class Puppet::Resource
         return result
     end
 
+    def name
+        # this is potential namespace conflict
+        # between the notion of an "indirector name"
+        # and a "resource name"
+        [ type, title ].join('/')
+    end
+
+    def to_resource
+        self
+    end
+
     private
 
     # Produce a canonical method name.
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index cb61285..ff967b3 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -1,3 +1,4 @@
+require 'puppet/resource'
 require 'puppet/node'
 require 'puppet/indirector'
 require 'puppet/simple_graph'
@@ -79,7 +80,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
 
             # If the name and title differ, set up an alias
 
-            if resource.respond_to?(:name) and resource.respond_to?(:title) 
and resource.name != resource.title
+            if resource.respond_to?(:name) and resource.respond_to?(:title) 
and resource.respond_to?(:isomorphic?) and resource.name != resource.title
                 self.alias(resource, resource.name) if resource.isomorphic?
             end
 
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 2f7b57a..f377eb6 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -2079,6 +2079,12 @@ class Type
         return trans
     end
 
+    def to_resource
+        # this 'type instance' versus 'resource' distinction seems artificial
+        # I'd like to see it collapsed someday ~JW
+        self.to_trans.to_resource
+    end
+
     %w{exported virtual}.each do |m|
         define_method(m+"?") do
             self.send(m)
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index e6e1333..cf63e1a 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -1,6 +1,5 @@
 require 'puppet'
 require 'sync'
-require 'puppet/transportable'
 require 'getoptlong'
 
 require 'puppet/external/event-loop'
diff --git a/spec/unit/application/ralsh.rb b/spec/unit/application/ralsh.rb
index b0fa3bd..37e5a24 100755
--- a/spec/unit/application/ralsh.rb
+++ b/spec/unit/application/ralsh.rb
@@ -179,76 +179,76 @@ describe "ralsh" do
             before :each do
                 @ralsh.stubs(:puts)
                 @ralsh.host = 'host'
-                @client = stub_everything 'client'
-                @client.stubs(:read_cert).returns(true)
-                @client.stubs(:instances).returns([])
-                Puppet::Network::Client.resource.stubs(:new).returns(@client)
+
+                Puppet::Resource.stubs(:find  ).never
+                Puppet::Resource.stubs(:search).never
+                Puppet::Resource.stubs(:save  ).never
             end
 
-            it "should connect to it" do
-                Puppet::Network::Client.resource.expects(:new).with { |h| 
h[:Server] == 'host' }.returns(@client)
+            it "should search for resources" do
+                
Puppet::Resource.expects(:search).with('https://host:8139/production/resources/type/',
 {}).returns([])
                 @ralsh.main
             end
 
-            it "should raise an error if there are no certs" do
-                @client.stubs(:read_cert).returns(nil)
-
-                lambda { @ralsh.main }.should raise_error
+            it "should describe the given resource" do
+                push_args('type','name')
+                x = stub_everything 'resource'
+                
Puppet::Resource.expects(:find).with('https://host:8139/production/resources/type/name').returns(x)
+                @ralsh.main
+                pop_args
             end
 
-            it "should retrieve all the instances if there is no name" do
-                @client.expects(:instances).returns([])
+            it "should add given parameters to the object" do
+                push_args('type','name','param=temp')
 
-                @ralsh.main
-            end
+                res = stub "resource"
+                res.expects(:save).with{|x| x.uri == 
'https://host:8139/production/resources/type/name'}.returns(res)
+                res.expects(:collect)
+                res.expects(:to_manifest)
+                Puppet::Resource.expects(:new).with('type', 'name', {'param' 
=> 'temp'}).returns(res)
 
-            it "should describe the given resource" do
-                push_args('type','name')
-                @client.expects(:describe).returns(stub_everything)
                 @ralsh.main
                 pop_args
             end
+
         end
 
         describe "without a host" do
             before :each do
                 @ralsh.stubs(:puts)
                 @ralsh.host = nil
-            end
 
-            it "should retrieve all the instances if there is no name" do
-                @type.expects(:instances).returns([])
+                Puppet::Resource.stubs(:find  ).never
+                Puppet::Resource.stubs(:search).never
+                Puppet::Resource.stubs(:save  ).never
+            end
 
+            it "should search for resources" do
+                Puppet::Resource.expects(:search).with('type/', {}).returns([])
                 @ralsh.main
             end
 
-            describe 'but with a given name' do
-                before :each do
-                    push_args('type','name')
-                    @type.stubs(:new).returns(:bob)
-                end
-
-                after :each do
-                    pop_args
-                end
-
-                it "should retrieve a specific instance if it exists" do
-                    pending
-                end
+            it "should describe the given resource" do
+                push_args('type','name')
+                x = stub_everything 'resource'
+                Puppet::Resource.expects(:find).with('type/name').returns(x)
+                @ralsh.main
+                pop_args
+            end
 
+            it "should add given parameters to the object" do
+                push_args('type','name','param=temp')
 
-                it "should create a stub instance if it doesn't exist" do
-                    pending
-                end
+                res = stub "resource"
+                res.expects(:save).with{|x| x.uri == nil}.returns(res)
+                res.expects(:collect)
+                res.expects(:to_manifest)
+                Puppet::Resource.expects(:new).with('type', 'name', {'param' 
=> 'temp'}).returns(res)
 
-                it "should add given parameters to the object" do
-                    push_args('type','name','param=temp')
-                    pending
-                    @object.expects(:[]=).with('param','temp')
-                    @ralsh.main
-                    pop_args
-                end
+                @ralsh.main
+                pop_args
             end
+
         end
     end
 end
diff --git a/spec/unit/indirector/resource/ral.rb 
b/spec/unit/indirector/resource/ral.rb
new file mode 100644
index 0000000..f74bf3d
--- /dev/null
+++ b/spec/unit/indirector/resource/ral.rb
@@ -0,0 +1,129 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe "Puppet::Resource::Ral" do
+    describe "find" do
+        before do
+            @request = stub 'request', :key => "user/root"
+        end
+
+        it "should find an existing instance" do
+            my_resource    = stub "my user resource"
+
+            wrong_instance = stub "wrong user", :name => "bob"
+            my_instance    = stub "my user",    :name => "root", :to_resource 
=> my_resource
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ wrong_instance, 
my_instance, wrong_instance ])
+            Puppet::Resource::Ral.new.find(@request).should == my_resource
+        end
+
+        it "if there is no instance, it should create one" do
+            wrong_instance = stub "wrong user", :name => "bob"
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ wrong_instance, 
wrong_instance ])
+            result = Puppet::Resource::Ral.new.find(@request)
+            result.should be_is_a Puppet::Resource
+            result.title.should == "root"
+        end
+    end
+
+    describe "search" do
+        before do
+            @request = stub 'request', :key => "user/", :options => {}
+        end
+
+        it "should convert ral resources into regular resources" do
+            my_resource = stub "my user resource"
+            my_instance = stub "my user", :name => "root", :to_resource => 
my_resource
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ my_instance ])
+            Puppet::Resource::Ral.new.search(@request).should == [my_resource]
+        end
+
+        it "should filter results by name if there's a name in the key" do
+            my_resource    = stub "my user resource"
+            my_resource.stubs(:to_resource).returns(my_resource)
+            my_resource.stubs(:[]).with(:name).returns("root")
+
+            wrong_resource = stub "wrong resource"
+            wrong_resource.stubs(:to_resource).returns(wrong_resource)
+            wrong_resource.stubs(:[]).with(:name).returns("bad")
+
+            my_instance    = stub "my user",    :to_resource => my_resource
+            wrong_instance = stub "wrong user", :to_resource => wrong_resource
+
+            @request = stub 'request', :key => "user/root", :options => {}
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ my_instance, 
wrong_instance ])
+            Puppet::Resource::Ral.new.search(@request).should == [my_resource]
+        end
+
+        it "should filter results by query parameters" do
+            wrong_resource = stub "my user resource"
+            wrong_resource.stubs(:to_resource).returns(wrong_resource)
+            wrong_resource.stubs(:[]).with(:name).returns("root")
+
+            my_resource = stub "wrong resource"
+            my_resource.stubs(:to_resource).returns(my_resource)
+            my_resource.stubs(:[]).with(:name).returns("bob")
+
+            my_instance    = stub "my user",    :to_resource => my_resource
+            wrong_instance = stub "wrong user", :to_resource => wrong_resource
+
+            @request = stub 'request', :key => "user/", :options => {:name => 
"bob"}
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ my_instance, 
wrong_instance ])
+            Puppet::Resource::Ral.new.search(@request).should == [my_resource]
+        end
+
+        it "should return sorted results" do
+            a_resource = stub "alice resource"
+            a_resource.stubs(:to_resource).returns(a_resource)
+            a_resource.stubs(:title).returns("alice")
+
+            b_resource = stub "bob resource"
+            b_resource.stubs(:to_resource).returns(b_resource)
+            b_resource.stubs(:title).returns("bob")
+
+            a_instance = stub "alice user", :to_resource => a_resource
+            b_instance = stub "bob user",   :to_resource => b_resource
+
+            @request = stub 'request', :key => "user/", :options => {}
+
+            require 'puppet/type/user'
+            Puppet::Type::User.expects(:instances).returns([ b_instance, 
a_instance ])
+            Puppet::Resource::Ral.new.search(@request).should == [a_resource, 
b_resource]
+        end
+    end
+
+    describe "save" do
+        before do
+            @rebuilt_res = stub 'rebuilt instance'
+            @ral_res     = stub 'ral resource', :to_resource => @rebuilt_res
+            @instance    = stub 'instance', :to_ral => @ral_res
+            @request     = stub 'request',  :key => "user/", :instance => 
@instance
+            @catalog     = stub 'catalog'
+
+            Puppet::Resource::Catalog.stubs(:new).returns(@catalog)
+            @catalog.stubs(:apply)
+            @catalog.stubs(:add_resource)
+        end
+
+        it "should apply a new catalog with a ral object in it" do
+            Puppet::Resource::Catalog.expects(:new).returns(@catalog)
+            @catalog.expects(:add_resource).with(@ral_res)
+            @catalog.expects(:apply)
+            Puppet::Resource::Ral.new.save(@request)
+        end
+
+        it "should return a regular resource that used to be the ral resource" 
do
+            Puppet::Resource::Ral.new.save(@request).should == @rebuilt_res
+        end
+    end
+end
diff --git a/spec/unit/indirector/resource/rest.rb 
b/spec/unit/indirector/resource/rest.rb
new file mode 100755
index 0000000..d5f2a9d
--- /dev/null
+++ b/spec/unit/indirector/resource/rest.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? 
require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+require 'puppet/indirector/resource/rest'
+
+describe Puppet::Resource::Rest do
+    it "should be a sublcass of Puppet::Indirector::REST" do
+        Puppet::Resource::Rest.superclass.should 
equal(Puppet::Indirector::REST)
+    end
+end
diff --git a/spec/unit/indirector/rest.rb b/spec/unit/indirector/rest.rb
index d12e3c6..1cb34c4 100755
--- a/spec/unit/indirector/rest.rb
+++ b/spec/unit/indirector/rest.rb
@@ -199,15 +199,26 @@ describe Puppet::Indirector::REST do
             @searcher.find(@request)
         end
 
-        it "should deserialize and return the http response" do
+        it "should deserialize and return the http response, setting name" do
             @connection.expects(:get).returns @response
 
-            instance = stub 'object', :name= => nil
+            instance = stub 'object'
+            instance.expects(:name=)
             @searcher.expects(:deserialize).with(@response).returns instance
 
             @searcher.find(@request).should == instance
         end
 
+        it "should deserialize and return the http response, and not require 
name=" do
+            @connection.expects(:get).returns @response
+
+            instance = stub 'object'
+            @searcher.expects(:deserialize).with(@response).returns instance
+
+            @searcher.find(@request).should == instance
+        end
+
+
         it "should use the URI generated by the Handler module" do
             @searcher.expects(:indirection2uri).with(@request).returns 
"/my/uri"
             @connection.expects(:get).with { |path, args| path == "/my/uri" 
}.returns(@response)
diff --git a/spec/unit/network/rest_authconfig.rb 
b/spec/unit/network/rest_authconfig.rb
index 407fc43..fe17d56 100755
--- a/spec/unit/network/rest_authconfig.rb
+++ b/spec/unit/network/rest_authconfig.rb
@@ -17,6 +17,7 @@ describe Puppet::Network::RestAuthConfig do
         { :acl => "/certificate/", :method => :find, :authenticated => false },
         { :acl => "/certificate_request", :method => [:find, :save], 
:authenticated => false },
         { :acl => "/status", :method => [:find], :authenticated => true },
+        { :acl => "/resource", :method => [:find, :save, :search], 
:authenticated => true },
     ]
 
     before :each do
diff --git a/spec/unit/resource.rb b/spec/unit/resource.rb
index b26f4f9..2b1d49d 100755
--- a/spec/unit/resource.rb
+++ b/spec/unit/resource.rb
@@ -492,4 +492,23 @@ describe Puppet::Resource do
             resource['foo'].should == %w{one}
         end
     end
+
+    describe "it should implement to_resource" do
+        resource = Puppet::Resource.new("file", "/my/file")
+        resource.to_resource.should == resource
+    end
+
+    describe "because it is an indirector model" do
+        it "should include Puppet::Indirector" do
+            Puppet::Resource.should be_is_a(Puppet::Indirector)
+        end
+
+        it "should have a default terminus" do
+            Puppet::Resource.indirection.terminus_class.should == :ral
+        end
+
+        it "should have a name" do
+            Puppet::Resource.new("file", "/my/file").name.should == 
"File//my/file"
+        end
+    end
 end
-- 
1.6.3.3

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to