From 8f95084cd854aef4e3493854e58cefd352cdc68d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 27 Jun 2005 21:44:46 +0000 Subject: renaming blink to puppet git-svn-id: https://reductivelabs.com/svn/puppet/library/trunk@302 980ebf18-57e1-0310-9a29-db15c13687c0 --- lib/blink.rb | 87 ---- lib/blink/client.rb | 120 ----- lib/blink/element.rb | 41 -- lib/blink/event.rb | 201 -------- lib/blink/fact.rb | 65 --- lib/blink/function.rb | 72 --- lib/blink/message.rb | 64 --- lib/blink/selector.rb | 82 ---- lib/blink/statechange.rb | 111 ----- lib/blink/storage.rb | 48 -- lib/blink/transaction.rb | 162 ------- lib/blink/transportable.rb | 200 -------- lib/blink/type.rb | 851 ---------------------------------- lib/blink/type/component.rb | 74 --- lib/blink/type/file.rb | 450 ------------------ lib/blink/type/package.rb | 366 --------------- lib/blink/type/process.rb | 83 ---- lib/blink/type/service.rb | 186 -------- lib/blink/type/state.rb | 135 ------ lib/blink/type/symlink.rb | 109 ----- lib/blink/type/typegen.rb | 146 ------ lib/blink/type/typegen/filerecord.rb | 243 ---------- lib/blink/type/typegen/filetype.rb | 316 ------------- lib/puppet.rb | 87 ++++ lib/puppet/client.rb | 120 +++++ lib/puppet/element.rb | 41 ++ lib/puppet/event.rb | 201 ++++++++ lib/puppet/fact.rb | 65 +++ lib/puppet/function.rb | 72 +++ lib/puppet/message.rb | 64 +++ lib/puppet/selector.rb | 82 ++++ lib/puppet/statechange.rb | 111 +++++ lib/puppet/storage.rb | 48 ++ lib/puppet/transaction.rb | 162 +++++++ lib/puppet/transportable.rb | 200 ++++++++ lib/puppet/type.rb | 851 ++++++++++++++++++++++++++++++++++ lib/puppet/type/component.rb | 74 +++ lib/puppet/type/file.rb | 450 ++++++++++++++++++ lib/puppet/type/package.rb | 366 +++++++++++++++ lib/puppet/type/process.rb | 83 ++++ lib/puppet/type/service.rb | 186 ++++++++ lib/puppet/type/state.rb | 135 ++++++ lib/puppet/type/symlink.rb | 109 +++++ lib/puppet/type/typegen.rb | 146 ++++++ lib/puppet/type/typegen/filerecord.rb | 243 ++++++++++ lib/puppet/type/typegen/filetype.rb | 316 +++++++++++++ 46 files changed, 4212 insertions(+), 4212 deletions(-) delete mode 100644 lib/blink.rb delete mode 100644 lib/blink/client.rb delete mode 100644 lib/blink/element.rb delete mode 100644 lib/blink/event.rb delete mode 100644 lib/blink/fact.rb delete mode 100644 lib/blink/function.rb delete mode 100644 lib/blink/message.rb delete mode 100644 lib/blink/selector.rb delete mode 100644 lib/blink/statechange.rb delete mode 100644 lib/blink/storage.rb delete mode 100644 lib/blink/transaction.rb delete mode 100644 lib/blink/transportable.rb delete mode 100644 lib/blink/type.rb delete mode 100644 lib/blink/type/component.rb delete mode 100644 lib/blink/type/file.rb delete mode 100644 lib/blink/type/package.rb delete mode 100644 lib/blink/type/process.rb delete mode 100644 lib/blink/type/service.rb delete mode 100644 lib/blink/type/state.rb delete mode 100644 lib/blink/type/symlink.rb delete mode 100644 lib/blink/type/typegen.rb delete mode 100644 lib/blink/type/typegen/filerecord.rb delete mode 100644 lib/blink/type/typegen/filetype.rb create mode 100644 lib/puppet.rb create mode 100644 lib/puppet/client.rb create mode 100644 lib/puppet/element.rb create mode 100644 lib/puppet/event.rb create mode 100644 lib/puppet/fact.rb create mode 100644 lib/puppet/function.rb create mode 100644 lib/puppet/message.rb create mode 100644 lib/puppet/selector.rb create mode 100644 lib/puppet/statechange.rb create mode 100644 lib/puppet/storage.rb create mode 100644 lib/puppet/transaction.rb create mode 100644 lib/puppet/transportable.rb create mode 100644 lib/puppet/type.rb create mode 100644 lib/puppet/type/component.rb create mode 100644 lib/puppet/type/file.rb create mode 100644 lib/puppet/type/package.rb create mode 100644 lib/puppet/type/process.rb create mode 100644 lib/puppet/type/service.rb create mode 100644 lib/puppet/type/state.rb create mode 100644 lib/puppet/type/symlink.rb create mode 100644 lib/puppet/type/typegen.rb create mode 100644 lib/puppet/type/typegen/filerecord.rb create mode 100644 lib/puppet/type/typegen/filetype.rb (limited to 'lib') diff --git a/lib/blink.rb b/lib/blink.rb deleted file mode 100644 index 17d3a8d6c..000000000 --- a/lib/blink.rb +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'singleton' - -# XXX see the bottom of the file for further inclusions - -PINK="" -GREEN="" -YELLOW="" -SLATE="" -ORANGE="" -BLUE="" -RESET="" - -#------------------------------------------------------------ -# the top-level module -# -# all this really does is dictate how the whole system behaves, through -# preferences for things like debugging -# -# it's also a place to find top-level commands like 'debug' -module Blink - # the hash that determines how our system behaves - @@config = Hash.new(false) - - @@config[:blinkroot] = "/var/blink" - @@config[:logdir] = "/var/blink/log" - @@config[:logfile] = "/var/blink/log/blink.log" - @@config[:statefile] = "/var/blink/log/state" - - - loglevels = [:debug,:verbose,:notice,:warning,:error] - - # handle the different message levels - # XXX this should be redone to treat log-levels like radio buttons - # pick one, and it and all above it will be logged - loglevels.each { |level| - define_method(level,proc { |args| - Blink.message(level,args) - }) - module_function level - # default to enabling all notice levels except debug - @@config[level] = true unless level == :debug - } - - def Blink.message(level,*ary) - msg = ary.join(" ") - - if @@config[level] - Blink::Message.new( - :level => level, - :source => "Blink", - :message => msg - ) - end - end - - # set up our configuration - def Blink.init(args) - args.each {|p,v| - @@config[p] = v - } - end - - # just print any messages we get - # we should later behave differently depending on the message - def Blink.newmessage(msg) - puts msg - end - - # configuration parameter access and stuff - def Blink.[](param) - return @@config[param] - end - - # configuration parameter access and stuff - def Blink.[]=(param,value) - @@config[param] = value - end - -end - -require 'blink/storage' -require 'blink/message' -require 'blink/type' diff --git a/lib/blink/client.rb b/lib/blink/client.rb deleted file mode 100644 index f266d2dfb..000000000 --- a/lib/blink/client.rb +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# the available clients - -require 'blink' -require 'blink/function' -require 'blink/type' -require 'blink/fact' -require 'blink/transaction' -require 'blink/transportable' -require 'http-access2' -require 'soap/rpc/driver' -require 'soap/rpc/httpserver' -#require 'webrick/https' -require 'logger' - -module Blink - class ClientError < RuntimeError; end - #--------------------------------------------------------------- - class Client < SOAP::RPC::HTTPServer - def initialize(hash) - # to whom do we connect? - @server = nil - @nil = nil - @url = hash[:Server] - if hash.include?(:Listen) and hash[:Listen] == false - Blink.notice "We're a local client" - @localonly = true - @driver = @url - else - Blink.notice "We're a networked client" - @localonly = false - @driver = SOAP::RPC::Driver.new(@url, 'urn:blink-server') - @driver.add_method("getconfig", "name") - end - unless @localonly - hash.delete(:Server) - - Blink.notice "Server is %s" % @url - - hash[:BindAddress] ||= "0.0.0.0" - hash[:Port] ||= 17444 - hash[:Debug] ||= true - hash[:AccessLog] ||= [] - - super(hash) - end - end - - def getconfig - Blink.debug "server is %s" % @url - #client.loadproperty('files/sslclient.properties') - Blink.notice("getting config") - objects = nil - if @localonly - objects = @driver.getconfig(self) - else - objects = @driver.getconfig(Blink::Fact["hostname"]) - end - self.config(objects) - end - - # this method is how the client receives the tree of Transportable - # objects - # for now, just descend into the tree and perform and necessary - # manipulations - def config(tree) - Blink.notice("Calling config") - container = Marshal::load(tree).to_type - - # this is a gross hack... but i don't see a good way around it - # set all of the variables to empty - Blink::Transaction.init - # for now we just evaluate the top-level container, but eventually - # there will be schedules and such associated with each object, - # and probably with the container itself - transaction = container.evaluate - #transaction = Blink::Transaction.new(objects) - transaction.toplevel = true - transaction.evaluate - self.shutdown - end - - def callfunc(name,args) - Blink.notice("Calling callfunc on %s" % name) - if function = Blink::Function[name] - #Blink.debug("calling function %s" % function) - value = function.call(args) - #Blink.debug("from %s got %s" % [name,value]) - return value - else - raise "Function '%s' not found" % name - end - end - - private - - def on_init - @default_namespace = 'urn:blink-client' - add_method(self, 'config', 'config') - add_method(self, 'callfunc', 'name', 'arguments') - end - - def cert(filename) - OpenSSL::X509::Certificate.new(File.open(File.join(@dir, filename)) { |f| - f.read - }) - end - - def key(filename) - OpenSSL::PKey::RSA.new(File.open(File.join(@dir, filename)) { |f| - f.read - }) - end - - end - #--------------------------------------------------------------- -end diff --git a/lib/blink/element.rb b/lib/blink/element.rb deleted file mode 100644 index dd187977a..000000000 --- a/lib/blink/element.rb +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# included so we can test object types -require 'blink' - - -#--------------------------------------------------------------- -# the base class for both types and states -# very little functionality; basically just defines the interface -# and provides a few simple across-the-board functions like 'noop' -class Blink::Element - attr_writer :noop - - #--------------------------------------------------------------- - # all of our subclasses must respond to each of these methods... - @@interface_methods = [ - :retrieve, :insync?, :sync, :fqpath, :evaluate - ] - - # so raise an error if a method that isn't overridden gets called - @@interface_methods.each { |method| - self.send(:define_method,method) { - raise "%s has not overridden %s" % [self.class,method] - } - } - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # for testing whether we should actually do anything - def noop - unless defined? @noop - @noop = false - end - return @noop || Blink[:noop] || false - end - #--------------------------------------------------------------- - -end -#--------------------------------------------------------------- diff --git a/lib/blink/event.rb b/lib/blink/event.rb deleted file mode 100644 index 2cdda9976..000000000 --- a/lib/blink/event.rb +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# included so we can test object types -require 'blink' -require 'blink/type' - -module Blink - # events are transient packets of information; they result in one or more (or none) - # subscriptions getting triggered, and then they get cleared - # eventually, these will be passed on to some central event system - class Event - # subscriptions are permanent associations determining how different - # objects react to an event - class Subscription - attr_accessor :source, :event, :target, :method - - def initialize(hash) - @triggered = false - - hash.each { |method,value| - # assign each value appropriately - # this is probably wicked-slow - self.send(method.to_s + "=",value) - } - Blink.warning "New Subscription: '%s' => '%s'" % - [@source,@event] - end - - # the transaction is passed in so that we can notify it if - # something fails - def trigger(transaction) - # this is potentially incomplete, because refreshing an object - # could theoretically kick off an event, which would not get run - # or, because we're executing the first subscription rather than - # the last, a later-refreshed object could somehow be connected - # to the "old" object rather than "new" - # but we're pretty far from that being a problem - if transaction.triggercount(self) > 0 - Blink.verbose "%s has already run" % self - else - Blink.verbose "'%s' matched '%s'; triggering '%s' on '%s'" % - [@source,@event,@method,@target] - begin - if @target.respond_to?(@method) - @target.send(@method) - else - Blink.verbose "'%s' of type '%s' does not respond to '%s'" % - [@target,@target.class,@method.inspect] - end - rescue => detail - # um, what the heck do i do when an object fails to refresh? - # shouldn't that result in the transaction rolling back? - # XXX yeah, it should - Blink.error "'%s' failed to %s: '%s'" % - [@target,@method,detail] - raise - #raise "We need to roll '%s' transaction back" % - #transaction - end - transaction.triggered(self) - end - end - end - - attr_accessor :event, :object, :transaction - - @@events = [] - - @@subscriptions = [] - - def Event.process - Blink.warning "Processing events" - @@events.each { |event| - @@subscriptions.find_all { |sub| - #Blink.warning "Sub source: '%s'; event object: '%s'" % - # [sub.source.inspect,event.object.inspect] - sub.source == event.object and - (sub.event == event.event or - sub.event == :ALL_EVENTS) - }.each { |sub| - Blink.notice "Found sub" - sub.trigger(event.transaction) - } - } - - @@events.clear - end - - def Event.subscribe(hash) - if hash[:event] == '*' - hash[:event] = :ALL_EVENTS - end - sub = Subscription.new(hash) - - # add to the correct area - @@subscriptions.push sub - end - - def initialize(args) - unless args.include?(:event) and args.include?(:object) - raise "Event.new called incorrectly" - end - - @event = args[:event] - @object = args[:object] - @transaction = args[:transaction] - - Blink.warning "New Event: '%s' => '%s'" % - [@object,@event] - - # initially, just stuff all instances into a central bucket - # to be handled as a batch - @@events.push self - end - end -end - - -#--------------------------------------------------------------- -# here i'm separating out the methods dealing with handling events -# currently not in use, so... - -class Blink::NotUsed - #--------------------------------------------------------------- - # return action array - # these are actions to use for responding to events - # no, this probably isn't the best way, because we're providing - # access to the actual hash, which is silly - def action - if not defined? @actions - puts "defining action hash" - @actions = Hash.new - end - @actions - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # call an event - # this is called on subscribers by the trigger method from the obj - # which sent the event - # event handling should probably be taking place in a central process, - # but.... - def event(event,obj) - Blink.debug "#{self} got event #{event} from #{obj}" - if @actions.key?(event) - Blink.debug "calling it" - @actions[event].call(self,obj,event) - else - p @actions - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # subscribe to an event or all events - # this entire event system is a hack job and needs to - # be replaced with a central event handler - def subscribe(args,&block) - obj = args[:object] - event = args[:event] || '*'.intern - if obj.nil? or event.nil? - raise "subscribe was called wrongly; #{obj} #{event}" - end - obj.action[event] = block - #events.each { |event| - unless @notify.key?(event) - @notify[event] = Array.new - end - unless @notify[event].include?(obj) - Blink.debug "pushing event '%s' for object '%s'" % [event,obj] - @notify[event].push(obj) - end - # } - #else - # @notify['*'.intern].push(obj) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # initiate a response to an event - def trigger(event) - subscribers = Array.new - if @notify.include?('*') and @notify['*'].length > 0 - @notify['*'].each { |obj| subscribers.push(obj) } - end - if (@notify.include?(event) and (! @notify[event].empty?) ) - @notify[event].each { |obj| subscribers.push(obj) } - end - Blink.debug "triggering #{event}" - subscribers.each { |obj| - Blink.debug "calling #{event} on #{obj}" - obj.event(event,self) - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- -end # Blink::Type diff --git a/lib/blink/fact.rb b/lib/blink/fact.rb deleted file mode 100644 index 2b46aabd8..000000000 --- a/lib/blink/fact.rb +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# an interface for registering and retrieving facts -# this is an abstract interface, and should just be used to interact -# with another library - -# currently a very thin veneer on 'facter' - -require 'facter' -require 'blink' -require 'blink/type' - -module Blink - class Fact - def Fact.[](name) - fact = Facter[name] - if fact.value.nil? - raise "Could not retrieve fact %s" % name - end - Blink.debug("fact: got %s from %s for %s" % [fact.value,fact,name]) - return fact.value - end - - # just pass the block to 'add' - # the block has to do things like set the interpreter, - # the code (which can be a ruby block), and maybe the - # os and osrelease - def Fact.add(name,&block) - Facter[name].add(&block) - end - - def Fact.name - return :fact - end - - def Fact.namevar - return :name - end - - #Blink::Type.newtype(self) - - # we're adding a new resolution mechanism here; this is just how - # types work - # we don't have any real interest in the returned object - def initialize(hash) - name = hash[:name] - hash.delete(:name) - Fact.add(name) { |fact| - method = nil - hash.each { |key,value| - if key.is_a?(String) - method = key + "=" - elsif key.is_a?(Symbol) - method = key.id2name + "=" - else - raise "Key must be either string or symbol" - end - fact.send(method,value) - } - } - end - end -end diff --git a/lib/blink/function.rb b/lib/blink/function.rb deleted file mode 100644 index d5cc11107..000000000 --- a/lib/blink/function.rb +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'blink' -require 'blink/fact' - -module Blink - class Function - @@functions = Hash.new(nil) - - #--------------------------------------------------------------- - def Function.[](name) - return @@functions[name] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def call(args) - @code.call(args) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # we want a 'proc' item instead of a block, so that we can return - # from it - def initialize(name,code) - @name = name - @code = code - - @@functions[name] = self - end - #--------------------------------------------------------------- - end - - Function.new("fact", proc { |fact| - require 'blink/fact' - - value = Fact[fact] - Blink.debug("retrieved %s as %s" % [fact,value]) - value - }) - - Function.new("addfact", proc { |args| - require 'blink/fact' - #Blink.debug("running addfact") - - hash = nil - if args.is_a?(Array) - hash = Hash[*args] - end - name = nil - if hash.has_key?("name") - name = hash["name"] - hash.delete("name") - elsif hash.has_key?(:name) - name = hash[:name] - hash.delete(:name) - else - raise "Functions must have names" - end - #Blink.debug("adding fact %s" % name) - newfact = Fact.add(name) { |fact| - hash.each { |key,value| - method = key + "=" - fact.send(method,value) - } - } - - #Blink.debug("got fact %s" % newfact) - }) -end diff --git a/lib/blink/message.rb b/lib/blink/message.rb deleted file mode 100644 index 31d5fa503..000000000 --- a/lib/blink/message.rb +++ /dev/null @@ -1,64 +0,0 @@ -# $Id$ - -module Blink - #------------------------------------------------------------ - # provide feedback of various types to the user - # modeled after syslog messages - # each level of message prints in a different color - class Message - @@messages = Array.new - @@levels = [ :debug, :verbose, :notice, :warning, :error ] - @@colors = { - :debug => SLATE, - :verbose => ORANGE, - :notice => PINK, - :warning => GREEN, - :error => YELLOW - } - - attr_accessor :level, :message, :source - - def Message.loglevels - return @@levels - end - - def initialize(args) - unless args.include?(:level) && args.include?(:message) && - args.include?(:source) - raise "Blink::Message called incorrectly" - end - - if args[:level].class == String - @level = args[:level].intern - elsif args[:level].class == Symbol - @level = args[:level] - else - raise "Level is not a string or symbol: #{args[:level].class}" - end - @message = args[:message] - @source = args[:source] - @time = Time.now - # this should include the host name, and probly lots of other - # stuff, at some point - unless @@levels.include?(level) - raise "Invalid message level #{level}" - end - - @@messages.push(self) - Blink.newmessage(self) - end - - def to_s - # this probably won't stay, but until this leaves the console, - # i'm going to use coloring... - #return "#{@time} #{@source} (#{@level}): #{@message}" - #return @@colors[@level] + "%s %s (%s): %s" % [ - # @time, @source, @level, @message - #] + RESET - return @@colors[@level] + "%s (%s): %s" % [ - @source, @level, @message - ] + RESET - end - end - #------------------------------------------------------------ -end diff --git a/lib/blink/selector.rb b/lib/blink/selector.rb deleted file mode 100644 index 51e82b09d..000000000 --- a/lib/blink/selector.rb +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'blink' -require 'blink/fact' - -module Blink - #--------------------------------------------------------------- - # this class will provide something like a 'select' statement, but it will - # return a value - # it will be used something like this: - # value = Selector.new( - # proc { test() } => value, - # proc { test2() } => value2, - # ) - - # each test gets evaluated in turn; the first one to return true has its - # value returned as the value of the statement - # this will be used to provide abstraction in objects, but it's currently - # unused - - class Selector < Array - attr_accessor :default - - def add(value,&block) - option = Option.new(value,&block) - @ohash[value] = option - @oarray.push(option) - end - - def evaluate - @oarray.each { |option| - if option.true? - return option.value - end - } - return nil - end - - # we have to support providing different values based on - # different criteria, e.g., default is X, SunOS gets Y, and - # host Yayness gets Z. - # thus, no invariant - def initialize - @oarray = [] - @ohash = {} - - if block_given? - yield self - end - end - - def to_s - return self.evaluate() - end - - class Option - attr_accessor :value, :test, :invariant - - def initialize(value,&block) - @value = value - @test = block - end - - def to_s - if self.evaluate - return value - end - end - - def true? - unless @test.is_a?(Proc) - raise "Cannot yet evaluate non-code tests" - end - - return @test.call() - end - end - #--------------------------------------------------------------- - end -end diff --git a/lib/blink/statechange.rb b/lib/blink/statechange.rb deleted file mode 100644 index f88108d8a..000000000 --- a/lib/blink/statechange.rb +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# the class responsible for actually doing any work - -# enables no-op and logging/rollback - -module Blink - class StateChange - attr_accessor :is, :should, :type, :path, :state, :transaction, :run - - #--------------------------------------------------------------- - def initialize(state) - @state = state - #@state.parent.newchange - @path = state.fqpath - @is = state.is - @should = state.should - - @run = false - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def go - if @state.noop - #Blink.notice "%s is noop" % @state - return nil - end - - begin - event = @state.sync - @run = true - - # default to a simple event type - if event.nil? - event = @state.parent.class.name.id2name + "_changed" - elsif ! event.is_a?(Symbol) - Blink.notice "State '%s' returned invalid event '%s'; resetting to default" % - [@state.class,event] - - event = @state.parent.class.name.id2name + "_changed" - end - - # i should maybe include object type, but the event type - # should basically point to that, right? - return Blink::Event.new( - :event => event, - :object => @state.parent, - :transaction => @transaction, - :message => self.to_s - ) - rescue => detail - Blink.error "%s failed: %s" % [self.to_s,detail] - raise - # there should be a way to ask the state what type of event - # it would have generated, but... - pname = @state.parent.class.name.id2name - #if pname.is_a?(Symbol) - # pname = pname.id2name - #end - return Blink::Event.new( - :event => pname + "_failed", - :object => @state.parent, - :transaction => @transaction, - :message => "Failed: " + self.to_s - ) - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def forward - #Blink.notice "moving change forward" - - unless defined? @transaction - raise "StateChange '%s' tried to be executed outside of transaction" % - self - end - - return self.go - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def backward - @state.should = @is - @state.retrieve - - Blink.notice "Rolling %s backward" % self - return self.go - - #raise "Moving statechanges backward is currently unsupported" - #@type.change(@path,@should,@is) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def noop - return @state.noop - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def to_s - return "%s: %s => %s" % [@state,@is,@should] - end - #--------------------------------------------------------------- - end -end diff --git a/lib/blink/storage.rb b/lib/blink/storage.rb deleted file mode 100644 index 9fc21d38b..000000000 --- a/lib/blink/storage.rb +++ /dev/null @@ -1,48 +0,0 @@ -# $Id$ - -module Blink - # a class for storing state - class Storage - include Singleton - @@state = Hash.new { |hash,key| - hash[key] = Hash.new(nil) - } - @@splitchar = "\t" - - def initialize - self.class.load - end - - def Storage.load - # XXX I should probably use a better default state dir - Blink[:statefile] ||= "/var/tmp/blinkstate" - return unless File.exists?(Blink[:statefile]) - File.open(Blink[:statefile]) { |file| - file.gets { |line| - myclass, key, value = line.split(@@splitchar) - - @@state[myclass][key] = Marshal::load(value) - } - } - end - - def Storage.state(myclass) - unless myclass.is_a? Class - myclass = myclass.class - end - result = @@state[myclass] - return result - end - - def Storage.store - File.open(Blink[:statefile], File::CREAT|File::WRONLY, 0600) { |file| - @@state.each { |klass, thash| - thash.each { |key,value| - mvalue = Marshal::dump(value) - file.puts([klass,key,mvalue].join(@@splitchar)) - } - } - } - end - end -end diff --git a/lib/blink/transaction.rb b/lib/blink/transaction.rb deleted file mode 100644 index 87b2f950c..000000000 --- a/lib/blink/transaction.rb +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# the class that actually walks our object/state tree, collects the changes, -# and performs them - -# there are two directions of walking: -# - first we recurse down the tree and collect changes -# - then we walk back up the tree through 'refresh' after the changes - -require 'blink' -require 'blink/statechange' - -#--------------------------------------------------------------- -module Blink -class Transaction - attr_accessor :toplevel, :component - - #--------------------------------------------------------------- - # a bit of a gross hack; a global list of objects that have failed to sync, - # so that we can verify during later syncs that our dependencies haven't - # failed - def Transaction.init - @@failures = Hash.new(0) - @@changed = [] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # for now, just store the changes for executing linearly - # later, we might execute them as we receive them - def change(change) - @changes.push change - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # i don't need to worry about ordering, because it's not possible to specify - # an object as a dependency unless it's already been mentioned within the language - # thus, an object gets defined, then mentioned as a dependency, and the objects - # are synced in that order automatically - def evaluate - Blink.notice "executing %s changes or transactions" % @changes.length - - return @changes.collect { |change| - if change.is_a?(Blink::StateChange) - change.transaction = self - events = nil - begin - events = [change.forward].flatten - #@@changed.push change.state.parent - rescue => detail - Blink.error("%s failed: %s" % [change,detail]) - raise - # at this point, we would normally do error handling - # but i haven't decided what to do for that yet - # so just record that a sync failed for a given object - #@@failures[change.state.parent] += 1 - # this still could get hairy; what if file contents changed, - # but a chmod failed? how would i handle that error? dern - end - - if events.nil? - Blink.verbose "No events returned?" - else - # first handle the subscriptions on individual objects - events.each { |event| - change.state.parent.subscribers?(event).each { |sub| - sub.trigger(self) - } - } - end - events - elsif change.is_a?(Blink::Transaction) - change.evaluate - else - raise "Transactions cannot handle objects of type %s" % child.class - end - }.flatten.reject { |event| - event.nil? - }.each { |event| - # this handles subscriptions on the components, rather than - # on idividual objects - self.component.subscribers?(event).each { |sub| - sub.trigger(self) - } - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this should only be called by a Blink::Container object now - # and it should only receive an array - def initialize(tree) - @tree = tree - @toplevel = false - - @triggered = Hash.new(0) - - # of course, this won't work on the second run - unless defined? @@failures - @toplevel = true - self.class.init - end - # change collection is in-band, and message generation is out-of-band - # of course, exception raising is also out-of-band - @changes = @tree.collect { |child| - # these children are all Blink::Type instances - # not all of the children will return a change, and Containers - # return transactions - child.evaluate - }.flatten.reject { |child| - child.nil? # remove empties - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def rollback - @changes.each { |change| - if change.is_a?(Blink::StateChange) - next unless change.run - #change.transaction = self - begin - change.backward - #@@changed.push change.state.parent - rescue => detail - Blink.error("%s rollback failed: %s" % [change,detail]) - # at this point, we would normally do error handling - # but i haven't decided what to do for that yet - # so just record that a sync failed for a given object - #@@failures[change.state.parent] += 1 - # this still could get hairy; what if file contents changed, - # but a chmod failed? how would i handle that error? dern - end - elsif change.is_a?(Blink::Transaction) - # yay, recursion - change.rollback - else - raise "Transactions cannot handle objects of type %s" % child.class - end - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def triggercount(sub) - Blink.notice "Triggercount on %s is %s" % [sub,@triggered[sub]] - return @triggered[sub] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def triggered(sub) - @triggered[sub] += 1 - Blink.notice "%s was triggered; count is %s" % [sub,@triggered[sub]] - end - #--------------------------------------------------------------- -end -end -#--------------------------------------------------------------- diff --git a/lib/blink/transportable.rb b/lib/blink/transportable.rb deleted file mode 100644 index 522a34d7c..000000000 --- a/lib/blink/transportable.rb +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'blink' - -module Blink - #------------------------------------------------------------ - class TransObject < Hash - attr_accessor :type - - @@ohash = {} - @@oarray = [] - - def TransObject.add(object) - @@oarray.push object - - # this is just so we can check, at parse time, whether a required - # object has already been mentioned when it is listed as required - # because we're ordered, as long as an object gets made before its - # dependent objects will get synced later - @@ohash[object.longname] = object - end - - def TransObject.clear - @@oarray.clear - end - - def TransObject.list - return @@oarray - end - - def initialize(name,type) - self[:name] = name - @type = type - self.class.add(self) - end - - def longname - return [self.type,self[:name]].join('--') - end - - def name - return self[:name] - end - - def to_s - return "%s(%s) => %s" % [@type,self[:name],super] - end - - def to_type - retobj = nil - if type = Blink::Type.type(self.type) - #begin - # this will fail if the type already exists - # which may or may not be a good thing... - retobj = type.new(self) - #rescue => detail - # Blink.error "Failed to create %s: %s" % [type.name,detail] - # puts self.class - # puts self.inspect - # exit - #end - else - raise "Could not find object type %s" % self.type - end - - return retobj - end - end - #------------------------------------------------------------ - - #------------------------------------------------------------ - class TransSetting - attr_accessor :type, :name, :args, :evalcount - - def initialize - @evalcount = 0 - end - - def evaluate - @evalcount += 0 - if type = Blink::Type.type(self.type) - # call the settings - name = self.name - unless name.is_a?(Symbol) - name = name.intern - end - if type.allowedmethod(name) - type.send(self.name,self.args) - else - Blink.error("%s does not respond to %s" % [self.type,self.name]) - end - else - raise "Could not find object type %s" % setting.type - end - end - end - #------------------------------------------------------------ - - #------------------------------------------------------------ - # just a linear container for objects - class TransBucket < Array - attr_accessor :name, :type - - def push(*args) - args.each { |arg| - case arg - when Blink::TransBucket, Blink::TransObject, Blink::TransSetting - # nada - else - raise "TransBuckets cannot handle objects of type %s" % - arg.class - end - } - super - end - - def to_type - # this container will contain the equivalent of all objects at - # this level - #container = Blink::Component.new(:name => @name, :type => @type) - unless defined? @name - raise "TransBuckets must have names" - end - unless defined? @type - Blink.verbose "TransBucket '%s' has no type" % @name - end - hash = { - :name => @name, - :type => @type - } - if defined? @parameters - @parameters.each { |param,value| - Blink.warning "Defining %s on %s of type %s" % - [param,@name,@type] - hash[param] = value - } - else - Blink.warning "%s has no parameters" % @name - end - container = Blink::Component.new(hash) - nametable = {} - - self.each { |child| - # the fact that we descend here means that we are - # always going to execute depth-first - # which is _probably_ a good thing, but one never knows... - if child.is_a?(Blink::TransBucket) - # just perform the same operation on any children - container.push(child.to_type) - elsif child.is_a?(Blink::TransSetting) - # XXX this is wrong, but for now just evaluate the settings - child.evaluate - elsif child.is_a?(Blink::TransObject) - # do a simple little naming hack to see if the object already - # exists in our scope - # this assumes that type/name combinations are globally - # unique - name = [child[:name],child.type].join("--") - - if nametable.include?(name) - object = nametable[name] - child.each { |var,value| - # don't rename; this shouldn't be possible anyway - next if var == :name - - Blink.notice "Adding %s to %s" % [var,name] - # override any existing values - object[var] = value - } - else # the object does not exist yet in our scope - # now we have the object instantiated, in our scope - object = child.to_type - nametable[name] = object - - # this sets the order of the object - container.push object - end - else - raise "TransBucket#to_type cannot handle objects of type %s" % - child.class - end - } - - # at this point, no objects at are level are still Transportable - # objects - return container - end - - def param(param,value) - unless defined? @parameters - @parameters = {} - end - @parameters[param] = value - end - - end - #------------------------------------------------------------ -end diff --git a/lib/blink/type.rb b/lib/blink/type.rb deleted file mode 100644 index 51cd904e7..000000000 --- a/lib/blink/type.rb +++ /dev/null @@ -1,851 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# included so we can test object types -require 'blink' -require 'blink/element' -require 'blink/event' -require 'blink/type/state' - - -# XXX see the bottom of the file for the rest of the inclusions - -#--------------------------------------------------------------- -# This class is the abstract base class for the mechanism for organizing -# work. No work is actually done by this class or its subclasses; rather, -# the subclasses include states which do the actual work. -# See state.rb for how work is actually done. - -# our duck type interface -- if your object doesn't match this interface, -# it won't work - -# all of our first-class objects (objects, states, and components) will -# respond to these methods -# although states don't inherit from Blink::Type -# although maybe Blink::State should... - -# the default behaviour that this class provides is to just call a given -# method on each contained object, e.g., in calling 'sync', we just run: -# object.each { |subobj| subobj.sync() } - -# to use this interface, just define an 'each' method and 'include Blink::Type' - -module Blink -class Type < Blink::Element - attr_accessor :children, :parameters, :parent - include Enumerable - - @@allobjects = Array.new # an array for all objects - @abstract = true - - @name = :blink # a little fakery, since Blink itself isn't a type - @namevar = :notused - - @states = [] - @parameters = [:notused] - - @allowedmethods = [:noop,:debug,:statefile] - - @@metaparams = [ - :onerror, - :schedule, - :check, - :require - ] - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # class methods dealing with Type management - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # these objects are used for mapping type names (e.g., 'file') - # to actual object classes; because Type.inherited is - # called before the .name method is defined, we need - # to store each class in an array, and then later actually iterate - # across that array and make a map - @@typeary = [self] # so that the allowedmethods stuff works - @@typehash = Hash.new { |hash,key| - if key.is_a?(String) - key = key.intern - end - if hash.include?(key) - hash[key] - else - raise "Object type %s not found" % key - end - } - - #--------------------------------------------------------------- - # a test for whether this type is allowed to have instances - # on clients - # subclasses can just set '@abstract = true' to mark themselves - # as abstract - def Type.abstract - if defined? @abstract - return @abstract - else - return false - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.allowedmethod(method) - if defined? @allowedmethods and @allowedmethods.include?(method) - return true - else - return false - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.statefile(file) - Blink[:statefile] = file - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # ill thought-out - # this needs to return @noop - #def noop(ary) - # Blink[:noop] = ary.shift - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def debug(ary) - # Blink[:debug] = ary.shift - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this is meant to be run multiple times, e.g., when a new - # type is defined at run-time - def Type.buildtypehash - @@typeary.each { |otype| - if @@typehash.include?(otype.name) - if @@typehash[otype.name] != otype - Blink.warning("Object type %s is already defined (%s vs %s)" % - [otype.name,@@typehash[otype.name],otype]) - end - else - @@typehash[otype.name] = otype - end - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.eachtype - @@typeary.each { |type| yield type } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this should make it so our subclasses don't have to worry about - # defining these class instance variables - def Type.inherited(sub) - sub.initvars - - #Blink.notice("subtype %s(%s) just created" % [sub,sub.superclass]) - # add it to the master list - # unfortunately we can't yet call sub.name, because the #inherited - # method gets called before any commands in the class definition - # get executed, which, um, sucks - @@typeary.push(sub) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this is so we don't have to eval this code - # init all of our class instance variables - def Type.initvars - @objects = Hash.new - @actions = Hash.new - #Blink.verbose "initing validstates for %s" % self - @validstates = {} - @validparameters = {} - - unless defined? @states - @states = {} - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.metaclass - if defined? @metaclass - return @metaclass - else - return false - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this is used for mapping object types (e.g., Blink::Type::File) - # to names (e.g., "file") - def Type.name - return @name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.newtype(type) - raise "Type.newtype called, but I don't know why" - @@typeary.push(type) - if @@typehash.has_key?(type.name) - Blink.notice("Redefining object type %s" % type.name) - end - @@typehash[type.name] = type - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.type(type) - unless @@typeary.length == @@typehash.length - Type.buildtypehash - end - @@typehash[type] - end - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # class methods dealing with type instance management - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # retrieve a named object - def Type.[](name) - if @objects.has_key?(name) - return @objects[name] - else - return nil - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.[]=(name,object) - newobj = nil - if object.is_a?(Blink::Type) - newobj = object - else - raise "must pass a Blink::Type object" - end - - if @objects.has_key?(newobj.name) - raise "Object '%s' of type '%s' already exists with id '%s' vs. '%s'" % - [newobj.name,newobj.class.name, - @objects[newobj.name].object_id,newobj.object_id] - else - #Blink.debug("adding %s of type %s to class list" % - # [object.name,object.class]) - @objects[newobj.name] = newobj - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # remove all type instances - def Type.allclear - @@typeary.each { |subtype| - Blink.notice "Clearing %s of objects" % subtype - subtype.clear - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # per-type clearance - def Type.clear - if defined? @objects - @objects.clear - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # all objects total - def Type.push(object) - @@allobjects.push object - #Blink.debug("adding %s of type %s to master list" % - # [object.name,object.class]) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # some simple stuff to make it easier to get a name from everyone - def Type.namevar - unless defined? @namevar and ! @namevar.nil? - raise "Class %s has no namevar defined" % self - end - return @namevar - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.has_key?(name) - return @objects.has_key?(name) - end - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # class and instance methods dealing with parameters and states - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.buildstatehash - unless defined? @validstates - @validstates = Hash.new(false) - end - @states.each { |stateklass| - name = stateklass.name - if @validstates.include?(name) - if @validstates[name] != stateklass - raise "Redefining state %s(%s) in %s" % [name,stateklass,self] - else - # it's already there, so don't bother - end - else - @validstates[name] = stateklass - end - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # Is the parameter in question a meta-parameter? - def Type.metaparam(param) - @@metaparams.include?(param) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this is probably only used by FileRecord objects - def Type.parameters=(params) - Blink.notice "setting parameters to [%s]" % params.join(" ") - @parameters = params.collect { |param| - if param.class == Symbol - param - else - param.intern - end - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.states - return @states - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.validstates - return @validstates - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.validstate(name) - unless @validstates.length == @states.length - self.buildstatehash - end - if @validstates.include?(name) - return @validstates[name] - else - return false - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.validparameter(name) - unless defined? @parameters - raise "Class %s has not defined parameters" % self - end - return @parameters.include?(name) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def Type.validparam(name) - self.validstate(name) or self.validparameter(name) or false - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this abstracts accessing parameters and states, and normalizes - # access to always be symbols, not strings - def [](name) - mname = name - if name.is_a?(String) - mname = name.intern - end - unless self.class.validparam(name) - raise "Invalid parameter %s" % [mname] - end - if @states.include?(mname) - # if they're using [], they don't know if we're a state or a string - # thus, return a string - # if they want the actual state object, they should use state() - return @states[mname].is - elsif @parameters.include?(mname) - return @parameters[mname] - else - return nil - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this abstracts setting parameters and states, and normalizes - # access to always be symbols, not strings - def []=(name,value) - mname = name - if name.is_a?(String) - mname = name.intern - end - - if Blink::Type.metaparam(mname) - # call the metaparam method - self.send(("meta" + mname.id2name),value) - elsif stateklass = self.class.validstate(mname) - if value.is_a?(Blink::State) - Blink.debug "'%s' got handed a state for '%s'" % [self,mname] - @states[mname] = value - else - if @states.include?(mname) - @states[mname].should = value - else - @states[mname] = stateklass.new( - :parent => self, - :should => value - ) - #Blink.notice "Adding parent to %s" % mname - #@states[mname].parent = self - end - end - elsif self.class.validparameter(mname) - @parameters[mname] = value - else - raise "Invalid parameter %s" % [mname] - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # removing states - def delete(attr) - if @states.has_key?(attr) - @states.delete(attr) - else - raise "Undefined state '#{attr}' in #{self}" - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def state(name) - return @states[name] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def parameter(name) - return @parameters[name] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # instance methods related to instance intrinsics - # e.g., initialize() and name() - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def initialize(hash) - @children = [] - @evalcount = 0 - - @subscriptions = [] - - # states and parameters are treated equivalently from the outside: - # as name-value pairs (using [] and []=) - # internally, however, parameters are merely a hash, while states - # point to State objects - # further, the lists of valid states and parameters are defined - # at the class level - @states = Hash.new(false) - @parameters = Hash.new(false) - - @noop = false - - # which objects to notify when we change - @notify = [] - - # keeping stats for the total number of changes, and how many were - # completely sync'ed - # this isn't really sufficient either, because it adds lots of special cases - # such as failed changes - # it also doesn't distinguish between changes from the current transaction - # vs. changes over the process lifetime - @totalchanges = 0 - @syncedchanges = 0 - @failedchanges = 0 - - hash.each { |var,value| - unless var.is_a? Symbol - hash[var.intern] = value - hash.delete(var) - end - } - - if hash.include?(:noop) - @noop = hash[:noop] - hash.delete(:noop) - end - - # we have to set the name of our object before anything else, - # because it might be used in creating the other states - namevar = self.class.namevar - - # if they're not using :name for the namevar but we got :name (probably - # from the parser) - if namevar != :name and hash.include?(:name) and ! hash[:name].nil? - self[namevar] = hash[:name] - hash.delete(:name) - # else if we got the namevar - elsif hash.has_key?(namevar) and ! hash[namevar].nil? - self[namevar] = hash[namevar] - hash.delete(namevar) - # else something's screwy - else - p hash - p namevar - raise TypeError.new("A name must be provided to %s at initialization time" % - self.class) - end - - hash.each { |param,value| - #Blink.debug("adding param '%s' with value '%s'" % - # [param,value]) - self[param] = value - } - - # add this object to the specific class's list of objects - #Blink.notice("Adding [%s] to %s" % [self.name,self.class]) - self.class[self.name] = self - - # and then add it to the master list - Blink::Type.push(self) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # return the full path to us, for logging and rollback - # some classes (e.g., FileTypeRecords) will have to override this - def fqpath - return self.class, self.name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this might result from a state or from a parameter - def name - return self[self.class.namevar] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def retrieve - # it's important to use the method here, so we always get - # them back in the right order - self.states.collect { |state| - state.retrieve - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def sync - self.collect { |child| - child.sync - }.reject { |event| - ! (event.is_a?(Symbol) or event.is_a?(String)) - }.flatten - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def to_s - self.name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # instance methods dealing with contained states - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def states - Blink.debug "%s has %s states" % [self,@states.length] - tmpstates = [] - self.class.states.each { |state| - if @states.include?(state.name) - tmpstates.push(@states[state.name]) - end - } - unless tmpstates.length == @states.length - raise "Something went very wrong with tmpstates creation" - end - return tmpstates - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def eachstate - self.states.each { |state| - yield state - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # iterate across all children, and then iterate across states - # we do children first so we're sure that all dependent objects - # are checked first - # we ignore parameters here, because they only modify how work gets - # done, they don't ever actually result in work specifically - def each - # we want to return the states in the order that each type - # specifies it, because it may (as in the case of File#create) - # be important - @children.each { |child| - yield child - } - self.eachstate { |state| - yield state - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def push(*child) - @children.push(*child) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # instance methods dealing with actually doing work - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def newchange - @totalchanges += 1 - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this method is responsible for collecting state changes - # we always descend into the children before we evaluate our current - # states - # this returns any changes resulting from testing, thus 'collect' - # rather than 'each' - def evaluate - unless defined? @evalcount - Blink.error "No evalcount defined on '%s' of type '%s'" % - [self.name,self.class] - end - # if we're a metaclass and we've already evaluated once... - if self.metaclass and @evalcount > 0 - return - end - @evalcount += 1 - - changes = @children.collect { |child| - child.evaluate - } - - # this only operates on states, not states + children - self.retrieve - unless self.insync? - changes << self.states.find_all { |state| - ! state.insync? - }.collect { |state| - Blink::StateChange.new(state) - } - end - # collect changes and return them - # these changes could be from child objects or from contained states - #self.collect { |child| - # child.evaluate - #} - return changes - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # if all contained objects are in sync, then we're in sync - def insync? - insync = true - - self.states.each { |state| - unless state.insync? - Blink.debug("%s is not in sync" % state) - insync = false - end - } - - Blink.debug("%s sync status is %s" % [self,insync]) - return insync - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # do we actually do work, or do we modify the system instead? - # instances of a metaclass only get executed once per client process, - # while instances of normal classes get run every time - def metaclass - return self.class.metaclass - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #--------------------------------------------------------------- - # Meta-parameter methods: These methods deal with the results - # of specifying metaparameters - #--------------------------------------------------------------- - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this just marks states that we definitely want to retrieve values - # on - def metacheck(args) - unless args.is_a?(Array) - args = [args] - end - - # these are states that we might not have values for but we want to retrieve - # values for anyway - args.each { |state| - unless state.is_a?(Symbol) - state = state.intern - end - next if @states.include?(state) - - stateklass = nil - unless stateklass = self.class.validstate(state) - raise "%s is not a valid state for %s" % [state,self.class] - end - - # XXX it's probably a bad idea to have code this important in - # two places - @states[state] = stateklass.new( - :parent => self - ) - #@states[state] = stateklass.new() - #@states[state].parent = self - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def subscribe(hash) - if hash[:event] == '*' - hash[:event] = :ALL_EVENTS - end - - hash[:source] = self - sub = Blink::Event::Subscription.new(hash) - - # add to the correct area - @subscriptions.push sub - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # return all of the subscriptions to a given event - def subscribers?(event) - @subscriptions.find_all { |sub| - sub.event == event.event or - sub.event == :ALL_EVENTS - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # for each object we require, subscribe to all events that it - # generates - # we might reduce the level of subscription eventually, but for now... - def metarequire(requires) - unless requires.is_a?(Array) - requires = [requires] - end - requires.each { |rname| - # we just have a name and a type, and we need to convert it - # to an object... - type = nil - object = nil - tname = rname[0] - unless type = Blink::Type.type(tname) - raise "Could not find type %s" % tname - end - name = rname[1] - unless object = type[name] - raise "Could not retrieve object '%s' of type '%s'" % - [name,type] - end - Blink.debug("%s requires %s" % [self.name,object]) - - # for now, we only support this one method, 'refresh' - object.subscribe( - :event => '*', - :target => self, - :method => :refresh - ) - #object.addnotify(self) - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def metaonerror(response) - Blink.debug("Would have called metaonerror") - @onerror = response - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def metaschedule(schedule) - @schedule = schedule - end - #--------------------------------------------------------------- -end # Blink::Type -end - -require 'blink/type/service' -require 'blink/type/file' -require 'blink/type/symlink' -require 'blink/type/package' -require 'blink/type/component' -require 'blink/statechange' -#require 'blink/type/typegen' -#require 'blink/type/typegen/filetype' -#require 'blink/type/typegen/filerecord' diff --git a/lib/blink/type/component.rb b/lib/blink/type/component.rb deleted file mode 100644 index 38bf3326d..000000000 --- a/lib/blink/type/component.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# the object allowing us to build complex structures -# this thing contains everything else, including itself - -require 'blink' -require 'blink/type' -require 'blink/transaction' - -module Blink - class Component < Blink::Type - include Enumerable - - @name = :component - @namevar = :name - - @states = [] - @parameters = [:name,:type] - - def each - @children.each { |child| yield child } - end - - def initialize(args) - @children = [] - super(args) - Blink.verbose "Made component with name %s" % self.name - end - - # now we decide whether a transaction is dumb, and just accepts - # changes from the container, or whether it collects them itself - # for now, because i've already got this implemented, let transactions - # collect the changes themselves - def evaluate - transaction = Blink::Transaction.new(@children) - transaction.component = self - return transaction - end - - def push(*ary) - ary.each { |child| - unless child.is_a?(Blink::Element) - Blink.notice "Got object of type %s" % child.class - raise "Containers can only contain Blink::Elements" - end - @children.push child - } - end - - def name - return "%s[%s]" % [@parameters[:type],@parameters[:name]] - end - - def refresh - @children.collect { |child| - if child.respond_to?(:refresh) - child.refresh - end - } - end - - def retrieve - self.collect { |child| - child.retrieve - } - end - - def to_s - return "component(%s)" % self.name - end - end -end diff --git a/lib/blink/type/file.rb b/lib/blink/type/file.rb deleted file mode 100644 index 9d0f98c61..000000000 --- a/lib/blink/type/file.rb +++ /dev/null @@ -1,450 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'digest/md5' -require 'etc' -require 'blink/type/state' - -module Blink - # we first define all of the state that our file will use - # because the objects must be defined for us to use them in our - # definition of the file object - class State - class FileCreate < Blink::State - require 'etc' - attr_accessor :file - @name = :create - @event = :file_created - - def should=(value) - # default to just about anything meaning 'true' - if value == false or value.nil? - @should = false - else - @should = true - end - end - - def retrieve - stat = nil - - self.is = FileTest.exist?(self.parent[:path]) - Blink.debug "'exists' state is %s" % self.is - end - - - def sync - begin - File.open(self.parent[:path],"w") { # just create an empty file - } - rescue => detail - raise detail - end - return :file_created - end - end - - class FileChecksum < Blink::State - @name = :checksum - @event = :file_modified - - def should=(value) - @checktype = value - state = Blink::Storage.state(self) - if hash = state[self.parent[:path]] - if hash.include?(@checktype) - @should = hash[@checktype] - else - Blink.verbose "Found checksum for %s but not of type %s" % - [self.parent[:path],@checktype] - @should = nil - end - else - Blink.debug "No checksum for %s" % self.parent[:path] - end - end - - def retrieve - unless defined? @checktype - @checktype = "md5" - end - - sum = "" - case @checktype - when "md5": - File.open(self.parent[:path]) { |file| - sum = Digest::MD5.hexdigest(file.read) - } - when "md5lite": - File.open(self.parent[:path]) { |file| - sum = Digest::MD5.hexdigest(file.read(512)) - } - when "timestamp","mtime": - sum = File.stat(self.parent[:path]).mtime - when "time": - sum = File.stat(self.parent[:path]).ctime - end - - self.is = sum - - Blink.debug "checksum state is %s" % self.is - end - - - # at this point, we don't actually modify the system, we just kick - # off an event if we detect a change - def sync - if self.updatesum - return :file_modified - else - return nil - end - end - - def updatesum - state = Blink::Storage.state(self) - unless state.include?(self.parent[:path]) - state[self.parent[:path]] = Hash.new - end - # if we're replacing, vs. updating - if state[self.parent[:path]].include?(@checktype) - Blink.debug "Replacing checksum %s with %s" % - [state[self.parent[:path]][@checktype],@is] - result = true - else - Blink.verbose "Creating checksum %s for %s of type %s" % - [@is,self.parent[:path],@checktype] - result = false - end - state[self.parent[:path]][@checktype] = @is - return result - end - end - - class FileUID < Blink::State - require 'etc' - attr_accessor :file - @name = :owner - @event = :inode_changed - - def retrieve - stat = self.parent.stat(true) - - self.is = stat.uid - if defined? @should - unless @should.is_a?(Integer) - begin - user = Etc.getpwnam(@should) - if user.gid == "" - raise "Could not retrieve uid for '%s'" % self.parent - end - Blink.debug "converting %s to integer '%d'" % - [@should,user.uid] - @should = user.uid - rescue - raise "Could not get any info on user '%s'" % @should - end - end - end - - Blink.debug "chown state is %d" % self.is - end - - def sync - if @is == -1 - self.parent.stat(true) - self.retrieve - Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] - end - - unless self.parent.stat - Blink.error "File '%s' does not exist; cannot chown" % - self.parent[:path] - end - - begin - File.chown(self.should,-1,self.parent[:path]) - rescue => detail - raise "failed to chown '%s' to '%s': %s" % - [self.parent[:path],self.should,detail] - end - - return :inode_changed - end - end - - # this state should actually somehow turn into many states, - # one for each bit in the mode - # I think MetaStates are the answer, but I'm not quite sure - class FileMode < Blink::State - require 'etc' - - @name = :mode - @event = :inode_changed - - def should=(should) - # this is pretty hackish, but i need to make sure the number is in - # octal, yet the number can only be specified as a string right now - unless should.is_a?(Integer) # i've already converted it correctly - unless should =~ /^0/ - should = "0" + should - end - should = Integer(should) - end - @should = should - end - - def retrieve - stat = self.parent.stat(true) - self.is = stat.mode & 007777 - - Blink.debug "chmod state is %o" % self.is - end - - def sync - if @is == -1 - self.parent.stat(true) - self.retrieve - Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] - end - - unless self.parent.stat - Blink.error "File '%s' does not exist; cannot chmod" % - self.parent[:path] - return - end - - begin - File.chmod(self.should,self.parent[:path]) - rescue - raise "failed to chmod #{self.parent[:path]}: #{$!}" - end - return :inode_changed - end - end - - # not used until I can figure out how to solve the problem with - # metastates - class FileSetUID < Blink::State - require 'etc' - - @parent = Blink::State::FileMode - - @name = :setuid - @event = :inode_changed - - def <=>(other) - self.is <=> @parent.value[11] - end - - # this just doesn't seem right... - def sync - unless defined? @is or @is == -1 - self.parent.stat(true) - self.retrieve - Blink.notice "%s: should is '%s'" % [self.class.name,self.should] - end - tmp = 0 - if self.is == true - tmp = 1 - end - @parent.value[11] = tmp - return :inode_changed - end - end - - class FileGroup < Blink::State - require 'etc' - - @name = :group - @event = :inode_changed - - def retrieve - stat = self.parent.stat(true) - - self.is = stat.gid - - # we probably shouldn't actually modify the 'should' value - # but i don't see a good way around it right now - # mmmm, should - if defined? @should - unless self.should.is_a?(Integer) - begin - require 'blink/fact' - group = Etc.getgrnam(self.should) - # apparently os x is six shades of weird - os = Blink::Fact["Operatingsystem"] - - gid = "" - case os - when "Darwin": - gid = group.passwd - else - gid = group.gid - end - if gid == "" - raise "Could not retrieve gid for %s" % self.parent - end - Blink.debug "converting %s to integer %d" % - [self.should,gid] - self.should = gid - rescue - #raise "Could not get any info on group %s" % self.should - raise - end - end - end - Blink.debug "chgrp state is %d" % self.is - end - - def sync - Blink.debug "setting chgrp state to %s" % self.should - if @is == -1 - self.parent.stat(true) - self.retrieve - Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] - end - - unless self.parent.stat - Blink.error "File '%s' does not exist; cannot chgrp" % - self.parent[:path] - return - end - - begin - # set owner to nil so it's ignored - File.chown(nil,self.should,self.parent[:path]) - rescue - raise "failed to chgrp %s to %s: %s" % - [self.parent[:path], self.should, $!] - end - return :inode_changed - end - end - end - class Type - class File < Type - attr_reader :params - # class instance variable - @states = [ - Blink::State::FileCreate, - Blink::State::FileUID, - Blink::State::FileGroup, - Blink::State::FileMode, - Blink::State::FileChecksum, - Blink::State::FileSetUID - ] - - @parameters = [ - :path, - :recurse - ] - - @name = :file - @namevar = :path - - # a wrapper method to make sure the file exists before doing anything - def retrieve - unless stat = self.stat(true) - Blink.verbose "File %s does not exist" % self[:path] - @states.each { |name,state| - state.is = -1 - } - return - end - super - end - - def stat(refresh = false) - if @stat.nil? or refresh == true - begin - @stat = ::File.stat(self[:path]) - rescue => error - Blink.debug "Failed to stat %s: %s" % - [self[:path],error] - @stat = nil - end - end - - return @stat - end - - def initialize(hash) - arghash = hash.dup - super - @stat = nil - - # if recursion is enabled and we're a directory... - if @parameters[:recurse] and self.stat.directory? - recurse = self[:recurse] - # we might have a string, rather than a number - if recurse.is_a?(String) - if recurse =~ /^[0-9]+$/ - recurse = Integer(recurse) - elsif recurse =~ /^inf/ # infinite recursion - recurse = true - end - end - - # unless we're at the end of the recursion - if recurse != 0 - arghash.delete("recurse") - if recurse.is_a?(Integer) - recurse -= 1 # reduce the level of recursion - end - - arghash[:recurse] = recurse - - # now make each contained file/dir a child - unless defined? @children - @children = [] - end - - Dir.foreach(self[:path]) { |file| - next if file =~ /^\.\.?/ # skip . and .. - - arghash[:path] = ::File.join(self[:path],file) - - child = nil - # if the file already exists... - if child = self.class[arghash[:path]] - arghash.each { |var,value| - next if var == :path - child[var] = value - } - else # create it anew - child = self.class.new(arghash) - end - @children.push child - } - end - end - end - end # Blink::Type::File - end # Blink::Type - - class FileSource - attr_accessor :name - - @sources = Hash.new(nil) - - def FileSource.[]=(name,sub) - @sources[name] = sub - end - - def FileSource.[](name) - return @sources[name] - end - - def initialize(name) - @name = name - - if block_given? - yield self - end - - FileSource[name] = self - end - end -end diff --git a/lib/blink/type/package.rb b/lib/blink/type/package.rb deleted file mode 100644 index 77bcd76b2..000000000 --- a/lib/blink/type/package.rb +++ /dev/null @@ -1,366 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'blink/type/state' -require 'blink/fact' - -module Blink - class State - class PackageInstalled < Blink::State - @name = :install - - def retrieve - #self.is = Blink::PackageTyping[@object.format][@object.name] - unless @parent.class.listed - @parent.class.getpkglist - end - Blink.debug "package install state is %s" % self.is - end - - def sync - #begin - raise "cannot sync package states yet" - #rescue - # raise "failed to sync #{@params[:file]}: #{$!}" - #end - - #return :package_installed - end - end - end - - class Type - # packages are complicated because each package format has completely - # different commands. We need some way to convert specific packages - # into the general package object... - class Package < Type - attr_reader :version, :format - @states = [ - Blink::State::PackageInstalled - ] - @parameters = [ - :format, - :name, - :status, - :version, - :category, - :platform, - :root, - :vendor, - :description - ] - - @name = :package - @namevar = :name - @listed = false - - @allowedmethods = [:types] - @@types = nil - - def Package.clear - @listed = false - super - end - - def Package.listed - return @listed - end - - def Package.types(array) - unless array.is_a?(Array) - array = [array] - end - @@types = array - Blink.warning "Types are %s" % array.join(" ") - end - - def Package.getpkglist - if @@types.nil? - case Blink::Fact["operatingsystem"] - when "SunOS": @@types = ["sunpkg"] - when "Linux": - case Blink::Fact["distro"] - when "Debian": @@types = ["dpkg"] - else - raise "No default type for " + Blink::Fact["distro"] - end - else - raise "No default type for " + Blink::Fact["operatingsystem"] - end - end - - list = @@types.collect { |type| - if typeobj = Blink::PackagingType[type] - # pull all of the objects - typeobj.list - else - raise "Could not find package type '%s'" % type - end - }.flatten - @listed = true - return list - end - - def Package.installedpkg(hash) - # this is from code, so we don't have to do as much checking - name = hash[:name] - - # if it already exists, modify the existing one - if object = Package[name] - states = {} - object.states.each { |state| - Blink.warning "Adding %s" % state.name.inspect - states[state.name] = state - } - hash.each { |var,value| - if states.include?(var) - Blink.verbose "%s is a set state" % var.inspect - states[var].is = value - else - Blink.verbose "%s is not a set state" % var.inspect - if object[var] and object[var] != value - Blink.warning "Overriding %s => %s on %s with %s" % - [var,object[var],name,value] - end - - object[var] = value - - # swap the values if we're a state - if states.include?(var) - Blink.verbose "Swapping %s because it's a state" % var - states[var].is = value - states[var].should = nil - else - Blink.verbose "%s is not a state" % var.inspect - Blink.verbose "States are %s" % states.keys.collect { |st| - st.inspect - }.join(" ") - end - end - } - return object - else # just create it - return self.new(hash) - end - end - - # okay, there are two ways that a package could be created... - # either through the language, in which case the hash's values should - # be set in 'should', or through comparing against the system, in which - # case the hash's values should be set in 'is' - def initialize(hash) - super - end - - end # Blink::Type::Package - end - - class PackagingType - attr_writer :list, :install, :remove, :check - - @@types = Hash.new(false) - - def PackagingType.[](name) - if @@types.include?(name) - return @@types[name] - else - Blink.warning name.inspect - Blink.warning @@types.keys.collect { |key| - key.inspect - }.join(" ") - return nil - end - end - - # whether a package is installed or not - def [](name) - return @packages[name] - end - - [:list, :install, :remove, :check].each { |method| - self.send(:define_method, method) { - # retrieve the variable - var = eval("@" + method.id2name) - if var.is_a?(Proc) - var.call() - else - raise "only blocks are supported right now" - end - } - } - - def initialize(name) - if block_given? - yield self - end - - @packages = Hash.new(false) - @@types[name] = self - end - - def retrieve - @packages.clear() - - @packages = self.list() - end - end - - PackagingType.new("dpkg") { |type| - type.list = proc { - packages = [] - - # dpkg only prints as many columns as you have available - # which means we don't get all of the info - # stupid stupid - oldcol = ENV["COLUMNS"] - ENV["COLUMNS"] = "500" - - # list out all of the packages - open("| dpkg -l") { |process| - # our regex for matching dpkg output - regex = %r{^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$} - fields = [:status, :name, :install, :description] - hash = {} - - 5.times { process.gets } # throw away the header - - # now turn each returned line into a package object - process.each { |line| - if match = regex.match(line) - hash.clear - - fields.zip(match.captures) { |field,value| - hash[field] = value - } - packages.push Blink::Type::Package.installedpkg(hash) - else - raise "failed to match dpkg line %s" % line - end - } - } - ENV["COLUMNS"] = oldcol - - return packages - } - - # we need package retrieval mechanisms before we can have package - # installation mechanisms... - #type.install = proc { |pkg| - # raise "installation not implemented yet" - #} - - type.remove = proc { |pkg| - cmd = "dpkg -r %s" % pkg.name - output = %x{#{cmd}} - if $? != 0 - raise output - end - } - } - - PackagingType.new("sunpkg") { |type| - type.list = proc { - packages = [] - hash = {} - names = { - "PKGINST" => :name, - "NAME" => nil, - "CATEGORY" => :category, - "ARCH" => :platform, - "VERSION" => :install, - "BASEDIR" => :root, - "HOTLINE" => nil, - "EMAIL" => nil, - "VENDOR" => :vendor, - "DESC" => :description, - "PSTAMP" => nil, - "INSTDATE" => nil, - "STATUS" => nil, - "FILES" => nil - } - - # list out all of the packages - open("| pkginfo -l") { |process| - # we're using the long listing, so each line is a separate piece - # of information - process.each { |line| - case line - when /^$/ then - packages.push Blink::Type::Package.installedpkg(hash) - hash.clear - when /\s*(\w+):\s+(.+)/ - name = $1 - value = $2 - if names.include?(name) - hash[names[name]] = value - else - raise "Could not find %s" % name - end - when /\s+\d+.+/ - # nothing; we're ignoring the FILES info - end - } - } - return packages - } - - # we need package retrieval mechanisms before we can have package - # installation mechanisms... - #type.install = proc { |pkg| - # raise "installation not implemented yet" - #} - - type.remove = proc { |pkg| - cmd = "pkgrm -n %s" % pkg.name - output = %x{#{cmd}} - if $? != 0 - raise output - end - } - } - - # this is how we retrieve packages - class PackageSource - attr_accessor :uri - attr_writer :retrieve - - @@sources = Hash.new(false) - - def PackageSource.get(file) - type = file.sub(%r{:.+},'') - source = nil - if source = @@sources[type] - return source.retrieve(file) - else - raise "Unknown package source: %s" % type - end - end - - def initialize(name) - if block_given? - yield self - end - - @@sources[name] = self - end - - def retrieve(path) - @retrieve.call(path) - end - - end - - PackageSource.new("file") { |obj| - obj.retrieve = proc { |path| - # this might not work for windows... - file = path.sub(%r{^file://},'') - - if FileTest.exists?(file) - return file - else - raise "File %s does not exist" % file - end - } - } -end diff --git a/lib/blink/type/process.rb b/lib/blink/type/process.rb deleted file mode 100644 index b42f75f26..000000000 --- a/lib/blink/type/process.rb +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/local/bin/ruby -w - -# DISABLED -# I'm only working on services, not processes, right now - -module Blink - class State - class ProcessRunning < State - def retrieve - running = 0 - regex = Regexp.new(@params[:pattern]) - begin - # this ps is only tested on Solaris - # XXX yeah, this definitely needs to be fixed... - %x{ps -ef -U #{@params[:user]}}.split("\n").each { |process| - if regex.match(process) - running += 1 - end - } - rescue - # this isn't correct, but what the hell - Blink::Message.new( - :level => :error, - :source => self.parent, - :message => "Failed to run ps" - ) - end - - self.state = running - Blink.debug "there are #{running} #{self.parent} processes for start" - end - - def <=>(other) - self.state < 1 - end - - def fix - require 'etc' - # ruby is really cool - uid = 0 - if @params[:user].is_a? Integer - uid = @params[:user] - else - uid = Etc.getpwnam(@params[:user]).uid - end - Kernel.fork { - Process.uid = uid - Process.euid = uid - string = @params[:binary] + (@params[:arguments] || "") - Blink::Message.new( - :level => :notice, - :source => self.parent, - :message => "starting" - ) - Kernel.exec(string) - } - end - end - end - class Type - class BProcess < Type - attr_reader :stat, :path - @parameters = [:start, :stop, :user, :pattern, :binary, :arguments] - @name = :process - - @namevar = :pattern - - Blink::Relation.new(self, Blink::Operation::Start, { - :user => :user, - :pattern => :pattern, - :binary => :binary, - :arguments => :arguments - }) - - Blink::Relation.new(self, Blink::Operation::Stop, { - :user => :user, - :pattern => :pattern - }) - - end # Blink::Type::BProcess - end # Blink::Type - -end diff --git a/lib/blink/type/service.rb b/lib/blink/type/service.rb deleted file mode 100644 index f17f17e3f..000000000 --- a/lib/blink/type/service.rb +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# this is our main way of managing processes right now -# -# a service is distinct from a process in that services -# can only be managed through the interface of an init script -# which is why they have a search path for initscripts and such - -module Blink - class State - class ServiceRunning < State - @name = :running - #@event = :file_created - - # this whole thing is annoying - # i should probably just be using booleans, but for now, i'm not... - def should=(should) - case should - when false,0,"0": - should = 0 - when true,1,"1": - should = 1 - else - Blink.warning "%s: interpreting '%s' as false" % - [self.class,should] - should = 0 - end - @should = should - end - - def retrieve - self.is = self.running() - Blink.debug "Running value for '%s' is '%s'" % - [self.parent.name,self.is] - end - - # should i cache this info? - def running - begin - status = self.parent.initcmd("status") - Blink.debug "initcmd status for '%s' is '%s'" % - [self.parent.name,status] - - if status # the command succeeded - return 1 - else - return 0 - end - rescue SystemCallError - raise "Could not execute %s" % initscript - end - - end - - def sync - if self.running > 0 - status = 1 - else - status = 0 - end - Blink.debug "'%s' status is '%s' and should be '%s'" % - [self,status,should] - event = nil - if self.should > 0 - if status < 1 - Blink.debug "Starting '%s'" % self - if self.parent.initcmd("start") - event = :service_started - else - raise "Failed to start '%s'" % self.parent.name - end - else - Blink.debug "'%s' is already running, yo" % self - #Blink.debug "Starting '%s'" % self - #unless self.parent.initcmd("start") - # raise "Failed to start %s" % self.name - #end - end - elsif status > 0 - Blink.debug "Stopping '%s'" % self - if self.parent.initcmd("stop") - event = :service_stopped - else - raise "Failed to stop %s" % self.name - end - else - Blink.debug "Not running '%s' and shouldn't be running" % self - end - - return event - end - end - end - class Type - class Service < Type - attr_reader :stat - @states = [ - Blink::State::ServiceRunning - ] - @parameters = [ - :name, - :pattern - ] - - @functions = [ - :setpath - ] - - @name = :service - @namevar = :name - - @searchpaths = Array.new - @allowedmethods = [:setpath] - - def Service.search(name) - @searchpaths.each { |path| - # must specify that we want the top-level File, not Blink::...::File - fqname = ::File.join(path,name) - begin - stat = ::File.stat(fqname) - rescue - # should probably rescue specific errors... - Blink.debug("Could not find %s in %s" % [name,path]) - next - end - - # if we've gotten this far, we found a valid script - return fqname - } - raise "Could not find init script for '%s'" % name - end - - def Service.setpath(ary) - # verify each of the paths exists - #ary.flatten! - @searchpaths = ary.find_all { |dir| - retvalue = false - begin - retvalue = ::File.stat(dir).directory? - rescue => detail - Blink.verbose("Directory %s does not exist: %s" % [dir,detail]) - # just ignore it - end - # disallow relative paths - #if dir !~ /^\// - # retvalue = false - #end - retvalue - } - end - - # it'd be nice if i didn't throw the output away... - # this command returns true if the exit code is 0, and returns - # false otherwise - def initcmd(cmd) - script = self.initscript - - Blink.debug "Executing '%s %s' as initcmd for '%s'" % - [script,cmd,self] - - rvalue = Kernel.system("%s %s" % - [script,cmd]) - - Blink.debug "'%s' ran with exit status '%s'" % - [cmd,rvalue] - - - rvalue - end - - def initscript - if defined? @initscript - return @initscript - else - @initscript = Service.search(self.name) - end - end - - def refresh - self.initcmd("restart") - end - end # Blink::Type::Service - end # Blink::Type -end diff --git a/lib/blink/type/state.rb b/lib/blink/type/state.rb deleted file mode 100644 index 4706b04b7..000000000 --- a/lib/blink/type/state.rb +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'blink' -require 'blink/element' -require 'blink/statechange' - -#--------------------------------------------------------------- -# this is a virtual base class for states -# states are self-contained building blocks for objects - -# States can currently only be used for comparing a virtual "should" value -# against the real state of the system. For instance, you could verify that -# a file's owner is what you want, but you could not create two file objects -# and use these methods to verify that they have the same owner -module Blink -class State < Blink::Element - attr_accessor :is, :should, :parent - - @virtual = true - - #--------------------------------------------------------------- - # every state class must tell us what its name will be (as a symbol) - # this determines how we will refer to the state during usage - # e.g., the Owner state for Files might say its name is :owner; - # this means that we can say "file[:owner] = 'yayness'" - def State.name - return @name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # which event gets generated if this state change happens - def State.generates - return @event - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # if we're not in sync, return a statechange capable of putting us - # in sync - def evaluate - Blink.verbose "evaluating %s" % self - self.retrieve - if self.insync? - Blink.verbose "%s is in sync" % self - return nil - else - return Blink::StateChange.new(self) - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # return the full path to us, for logging and rollback - def fqpath - return @parent.fqpath, self.name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # we aren't actually comparing the states themselves, we're only - # comparing the "should" value with the "is" value - def insync? - Blink.debug "%s value is '%s', should be '%s'" % - [self,self.is.inspect,self.should.inspect] - self.is == self.should - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def initialize(hash) - @is = nil - - unless hash.include?(:parent) - raise "State %s was not passed a parent" % self - end - @parent = hash[:parent] - - if hash.include?(:should) - self.should = hash[:should] - else # we got passed no argument - # leave @should undefined - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # for testing whether we should actually do anything - def noop - unless defined? @noop - @noop = false - end - tmp = @noop || self.parent.noop || Blink[:noop] || false - Blink.notice "noop is %s" % tmp - return tmp - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def refresh(transaction) - # self.retrieve - - # we definitely need some way to batch these refreshes, so a - # given object doesn't get refreshed multiple times in a single - # run - # @parent.refresh - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # each state class must define the name() method, and state instances - # do not change that name - # this implicitly means that a given object can only have one state - # instance of a given state class - def name - return self.class.name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # retrieve the current state from the running system - def retrieve - raise "'retrieve' method was not overridden by %s" % self.class - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def to_s - return "%s(%s)" % [@parent.name,self.name] - end - #--------------------------------------------------------------- -end -end diff --git a/lib/blink/type/symlink.rb b/lib/blink/type/symlink.rb deleted file mode 100644 index 772b5a831..000000000 --- a/lib/blink/type/symlink.rb +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -require 'etc' -require 'blink/type/state' -require 'blink/type/file' - -module Blink - # okay, how do we deal with parameters that don't have operations - # associated with them? - class State - class SymlinkTarget < Blink::State - require 'etc' - attr_accessor :file - - @name = :target - - def create - begin - Blink.debug("Creating symlink '%s' to '%s'" % - [self.parent[:path],self.should]) - unless File.symlink(self.should,self.parent[:path]) - raise TypeError.new("Could not create symlink '%s'" % - self.parent[:path]) - end - rescue => detail - raise TypeError.new("Cannot create symlink '%s': %s" % - [self.parent[:path],detail]) - end - end - - def remove - if FileTest.symlink?(self.parent[:path]) - Blink.debug("Removing symlink '%s'" % self.parent[:path]) - begin - File.unlink(self.parent[:path]) - rescue - raise TypeError.new("Failed to remove symlink '%s'" % - self.parent[:path]) - end - elsif FileTest.exists?(self.parent[:path]) - raise TypeError.new("Cannot remove normal file '%s'" % - self.parent[:path]) - else - Blink.debug("Symlink '%s' does not exist" % - self.parent[:path]) - end - end - - def retrieve - stat = nil - - if FileTest.symlink?(self.parent[:path]) - self.is = File.readlink(self.parent[:path]) - Blink.debug("link value is '%s'" % self.is) - return - else - self.is = nil - return - end - end - - # this is somewhat complicated, because it could exist and be - # a file - def sync - if self.should.nil? - self.remove() - else # it should exist and be a symlink - if FileTest.symlink?(self.parent[:path]) - path = File.readlink(self.parent[:path]) - if path != self.should - self.remove() - self.create() - end - elsif FileTest.exists?(self.parent[:path]) - raise TypeError.new("Cannot replace normal file '%s'" % - self.parent[:path]) - else - self.create() - end - end - - #self.parent.newevent(:event => :inode_changed) - end - end - end - - class Type - class Symlink < Type - attr_reader :stat, :path, :params - # class instance variable - @states = [ - Blink::State::FileUID, - Blink::State::FileGroup, - Blink::State::FileMode, - Blink::State::SymlinkTarget - ] - - @parameters = [ - :path - ] - - @name = :symlink - @namevar = :path - end # Blink::Type::Symlink - end # Blink::Type - -end diff --git a/lib/blink/type/typegen.rb b/lib/blink/type/typegen.rb deleted file mode 100644 index 85f04912c..000000000 --- a/lib/blink/type/typegen.rb +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# parse and write configuration files using objects with minimal parsing abilities - -require 'etc' -require 'blink/type' - -module Blink - class Type -class TypeGenerator < Blink::Type - include Enumerable - - @namevar = :name - @name = :typegen - @abstract = true - - @parameters = [:name] - @states = [] - - #--------------------------------------------------------------- - def TypeGenerator.[](name) - return @subclasses[name] - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.inherited(subclass) - #subclass.initvars - super(subclass) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # we don't need to 'super' here because type.rb already runs initvars - # in Type#inherited - def TypeGenerator.initvars - @subclasses = Hash.new(nil) - super - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.name - return @name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.name=(name) - @name = name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.namevar - return @namevar || :name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.namevar=(namevar) - Blink.debug "Setting namevar for %s to %s" % [self,namevar] - unless namevar.is_a? Symbol - namevar = namevar.intern - end - @namevar = namevar - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def TypeGenerator.newtype(arghash) - unless defined? @parameters - raise "Type %s is set up incorrectly" % self - end - - arghash.each { |key,value| - if key.class != Symbol - # convert to a symbol - arghash[key.intern] = value - arghash.delete key - key = key.intern - end - unless @parameters.include?(key) - raise "Invalid argument %s on class %s" % - [key,self] - end - - } - - # turn off automatically checking all arguments - #@parameters.each { |option| - # unless arghash.include?(option) - # p arghash - # raise "Must pass %s to class %s" % - # [option,self] - # end - #} - - if @subclasses.include?(arghash[:name]) - raise "File type %s already exists" % arghash[:name] - end - - klassname = arghash[:name].capitalize - - # create the file type - Blink::Type.module_eval " - class %s < %s - end" % [klassname,self] - klass = eval(klassname) - klass.name = arghash[:name] - - @subclasses[arghash[:name]] = klass - - arghash.each { |option,value| - method = option.id2name + "=" - if klass.respond_to?(method) - #Blink.debug "Setting %s on %s to '%s'" % [option,klass,arghash[option]] - klass.send(method,arghash[option]) - else - Blink.debug "%s does not respond to %s" % [klass,method] - end - } - - # i couldn't get the method definition stuff to work - # oh well - # probably wouldn't want it in the end anyway - #@parameters.each { |option| - # writer = option.id2name + "=" - # readproc = proc { eval("@" + option.id2name) } - # klass.send(:define_method,option,readproc) - # writeproc = proc { |value| module_eval("@" + option.id2name) = value } - # klass.send(:define_method,writer,writeproc) - # klass.send(writer,hash[option]) - #} - - #Blink::Type.inherited(klass) - Blink::Type.buildtypehash - return klass - end - #--------------------------------------------------------------- -end -#--------------------------------------------------------------- -end -end diff --git a/lib/blink/type/typegen/filerecord.rb b/lib/blink/type/typegen/filerecord.rb deleted file mode 100644 index fb9030c12..000000000 --- a/lib/blink/type/typegen/filerecord.rb +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# parse and write configuration files using objects with minimal parsing abilities - -require 'etc' -require 'blink/type' -require 'blink/type/typegen' - -#--------------------------------------------------------------- -class Blink::Type::FileRecord < Blink::Type::TypeGenerator - attr_accessor :fields, :namevar, :splitchar, :object - - @parameters = [:name, :splitchar, :fields, :namevar, :filetype, :regex, :joinchar] - @abstract = true - @metaclass = true - - @namevar = :name - @name = :filerecord - - #--------------------------------------------------------------- - def FileRecord.newtype(hash) - #shortname = hash[:name] - #hash[:name] = hash[:filetype].name.capitalize + hash[:name].capitalize - klass = super(hash) - #klass.name = shortname - klass.parameters = hash[:fields] - #klass.namevar = hash[:namevar] - klass.filetype = hash[:filetype] - hash.delete(:fields) - hash.delete(:namevar) - return klass - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.fields=(ary) - @fields = ary - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.fields - return @fields - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.filetype - @filetype - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.filetype=(filetype) - if filetype.is_a?(String) - @filetype = Blink::Type::FileType[filetype] - elsif filetype.is_a?(Blink::Type::FileType) - @filetype = filetype - else - raise "Cannot use objects of type %s as filetypes" % filetype - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.joinchar=(char) - @joinchar = char - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.joinchar - unless defined? @joinchar - @joinchar = nil - end - @joinchar - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.match(object,line) - matchobj = nil - begin - matchobj = self.regex.match(line) - rescue RegexpError => detail - raise - end - - if matchobj.nil? - return nil - else - child = self.new(object) - child.match = matchobj - return child - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.regex=(regex) - @regex = regex - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.regex - # the only time @regex is allowed to be nil is if @splitchar is defined - if @regex.nil? - if @splitchar.nil? - raise "%s defined incorrectly -- splitchar or regex must be specified" % - self - else - ary = [] - text = @fields.collect { |field| - "([^%s]*)" % @splitchar - }.join(@splitchar) - begin - @regex = Regexp.new(text) - rescue RegexpError => detail - raise "Could not create splitregex from %s" % @splitchar - end - Blink.debug("Created regexp %s" % @regex) - end - elsif @regex.is_a?(String) - begin - @regex = Regexp.new(@regex) - rescue RegexpError => detail - raise "Could not create splitregex from %s" % @regex - end - end - return @regex - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.splitchar=(char) - @splitchar = char - #@regex = %r{#{char}} - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileRecord.splitchar - return @splitchar - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def [](field) - # @parameters[field] - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def []=(field,value) - # @parameters[field] = value - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def ==(other) - unless self.class == other.class - return false - end - - unless self.name == other.name - return false - end - @parameters.keys { |field| - unless self[field] == other[field] - Blink.debug("%s -> %s has changed" % [self.name, field]) - return false - end - } - return true - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def initialize(hash) - if self.class == Blink::Type::FileRecord - self.class.newtype(hash) - return - end - @parameters = {} - #if block_given? - # yield self - #end - super(hash) - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def match=(matchobj) - @match = matchobj - #puts "captures are [%s]" % [matchobj.captures] - self.class.fields.zip(matchobj.captures) { |field,value| - @parameters[field] = value - #puts "%s => %s" % [field,@parameters[field]] - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def record=(record) - begin - ary = record.split(self.class.regex) - rescue RegexpError=> detail - raise RegexpError.new(detail) - end - self.class.fields.each { |field| - @parameters[field] = ary.shift - #puts "%s => %s" % [field,@parameters[field]] - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def name - if @parameters.include?(self.class.namevar) - return @parameters[self.class.namevar] - else - raise "No namevar '%s' for objects of type %s" % - [self.class.namevar,self.class.to_s] - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def to_s - ary = self.class.fields.collect { |field| - if ! @parameters.include?(field) - raise "Object %s is missing field %s" % [self.name,field] - else - @parameters[field] - end - }.join(self.class.joinchar || self.class.splitchar) - end - #--------------------------------------------------------------- -end -#--------------------------------------------------------------- diff --git a/lib/blink/type/typegen/filetype.rb b/lib/blink/type/typegen/filetype.rb deleted file mode 100644 index 4b3b89db8..000000000 --- a/lib/blink/type/typegen/filetype.rb +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# parse and write configuration files using objects with minimal parsing abilities - -require 'blink/type' -require 'blink/type/typegen' - -class Blink::Type::FileType < Blink::Type::TypeGenerator - attr_accessor :childtype - - @parameters = [:name, :linesplit, :escapednewlines] - #@abstract = true - @metaclass = true - - @namevar = :name - @name = :filetype - - @modsystem = true - - #--------------------------------------------------------------- - def FileType.newtype(hash) - unless hash.include?(:linesplit) - hash[:linesplit] = "\n" - end - - # i don't think there's any reason to 'super' this - #klass = Blink::Type::TypeGenerator.newtype(hash) - klass = super(hash) - - klass.escapednewlines = true - klass.namevar = :name - klass.parameters = [:name, :path, :complete] - - #klass.childtype = Blink::Type::FileRecord.newtype( - # :name => hash[:name] + "_record", - # :splitchar => hash[:recordsplit], - # :fields => hash[:fields], - # :namevar => hash[:namevar] - #) - #klass.addrecord( - # :name => hash[:name] + "_record", - # :splitchar => hash[:recordsplit], - # :fields => hash[:fields], - # :namevar => hash[:namevar] - #) - - return klass - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # currently not used - def FileType.addrecord(hash) - unless defined? @records - @records = {} - end - hash[:filetype] = self - - # default to the naming field being the first field provided - unless hash.include?(:namevar) - hash[:namevar] = hash[:fields][0] - end - - recordtype = Blink::Type::FileRecord.newtype(hash) - @records[recordtype.name] = recordtype - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.records - return @records - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.escapednewlines=(value) - @escnlines = value - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.escapednewlines - if ! defined? @escnlines or @escnlines.nil? - return false - else - return @escnlines - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.childtype - unless defined? @childtype - @childtype = nil - end - return @childtype - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.childtype=(childtype) - @childtype = childtype - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.regex - return @regex - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.linesplit=(linesplit) - @regex = %r{#{linesplit}} - @linesplit = linesplit - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def FileType.linesplit - return @linesplit - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def [](name) - # return @childhash[name] - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def []=(name,value) - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # we don't really have a 'less-than/greater-than' sense here - # so i'm sticking with 'equals' until those make sense - def ==(other) - unless self.children.length == other.children.length - Blink.debug("file has %s records instead of %s" % - [self.children.length, other.children.length]) - return self.children.length == other.children.length - end - equal = true - self.zip(other.children) { |schild,ochild| - unless schild == ochild - Blink.debug("%s has changed in %s" % - [schild.name,self.name]) - equal = false - break - end - } - - return equal - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # create a new record with a block - def add(type,&block) - obj = self.class.records[type].new(self,&block) - Blink.debug("adding %s" % obj.name) - @childary.push(obj) - @childhash[obj.name] = obj - - return obj - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def children - return @childary - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # remove a record - def delete(name) - if @childhash.has_key?(name) - child = @childhash[name] - - @childhash.delete(child) - @childary.delete(child) - else - raise "No such entry %s" % name - end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def each - @childary.each { |child| - yield child - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # create a new file - def initialize(hash) - # if we are the FileType object itself, we create a new type - # otherwise, we create an instance of an existing type - # yes, this should be more straightforward - if self.class == Blink::Type::FileType - self.class.newtype(hash) - return - end - Blink.debug "Creating new '%s' file with path '%s' and name '%s'" % - [self.class.name,hash["path"],hash[:name]] - Blink.debug hash.inspect - @file = hash["path"] - - @childary = [] - @childhash = {} - super - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # this is where we're pretty different from other objects - # we can choose to either reparse the existing file and compare - # the objects, or we can write our file out and do an - # text comparison - def insync? - tmp = self.class.new(@file) - tmp.retrieve - - return self == tmp - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - #def name - # return @file - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # read the whole file in and turn it into each of the appropriate - # objects - def retrieve - str = "" - ::File.open(@file) { |fname| - fname.each { |line| - str += line - } - } - - if self.class.escapednewlines - endreg = %r{\\\n\s*} - str.gsub!(endreg,'') - end - @childary = str.split(self.class.regex).collect { |line| - childobj = nil - self.class.records.each { |name,recordtype| - if childobj = recordtype.match(self,line) - break - end - } - if childobj.nil? - Blink.warning("%s: could not match %s" % [self.name,line]) - #Blink.warning("could not match %s" % line) - next - end - - begin - Blink.debug("got child: %s(%s)" % [childobj.class,childobj.to_s]) - rescue NoMethodError - Blink.warning "Failed: %s" % childobj - end - childobj - }.reject { |child| - child.nil? - } - - @childary.each { |child| - begin - @childhash[child.name] = child - rescue NoMethodError => detail - p child - p child.class - puts detail - exit - end - } - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def sync - #unless self.insync? - self.write - #end - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def to_s - return @childary.collect { |child| - child.to_s - }.join(self.class.linesplit) + self.class.linesplit - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - def write - ::File.open(@file, "w") { |file| - file.write(self.to_s) - } - end - #--------------------------------------------------------------- -end -#--------------------------------------------------------------- diff --git a/lib/puppet.rb b/lib/puppet.rb new file mode 100644 index 000000000..17d3a8d6c --- /dev/null +++ b/lib/puppet.rb @@ -0,0 +1,87 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'singleton' + +# XXX see the bottom of the file for further inclusions + +PINK="" +GREEN="" +YELLOW="" +SLATE="" +ORANGE="" +BLUE="" +RESET="" + +#------------------------------------------------------------ +# the top-level module +# +# all this really does is dictate how the whole system behaves, through +# preferences for things like debugging +# +# it's also a place to find top-level commands like 'debug' +module Blink + # the hash that determines how our system behaves + @@config = Hash.new(false) + + @@config[:blinkroot] = "/var/blink" + @@config[:logdir] = "/var/blink/log" + @@config[:logfile] = "/var/blink/log/blink.log" + @@config[:statefile] = "/var/blink/log/state" + + + loglevels = [:debug,:verbose,:notice,:warning,:error] + + # handle the different message levels + # XXX this should be redone to treat log-levels like radio buttons + # pick one, and it and all above it will be logged + loglevels.each { |level| + define_method(level,proc { |args| + Blink.message(level,args) + }) + module_function level + # default to enabling all notice levels except debug + @@config[level] = true unless level == :debug + } + + def Blink.message(level,*ary) + msg = ary.join(" ") + + if @@config[level] + Blink::Message.new( + :level => level, + :source => "Blink", + :message => msg + ) + end + end + + # set up our configuration + def Blink.init(args) + args.each {|p,v| + @@config[p] = v + } + end + + # just print any messages we get + # we should later behave differently depending on the message + def Blink.newmessage(msg) + puts msg + end + + # configuration parameter access and stuff + def Blink.[](param) + return @@config[param] + end + + # configuration parameter access and stuff + def Blink.[]=(param,value) + @@config[param] = value + end + +end + +require 'blink/storage' +require 'blink/message' +require 'blink/type' diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb new file mode 100644 index 000000000..f266d2dfb --- /dev/null +++ b/lib/puppet/client.rb @@ -0,0 +1,120 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the available clients + +require 'blink' +require 'blink/function' +require 'blink/type' +require 'blink/fact' +require 'blink/transaction' +require 'blink/transportable' +require 'http-access2' +require 'soap/rpc/driver' +require 'soap/rpc/httpserver' +#require 'webrick/https' +require 'logger' + +module Blink + class ClientError < RuntimeError; end + #--------------------------------------------------------------- + class Client < SOAP::RPC::HTTPServer + def initialize(hash) + # to whom do we connect? + @server = nil + @nil = nil + @url = hash[:Server] + if hash.include?(:Listen) and hash[:Listen] == false + Blink.notice "We're a local client" + @localonly = true + @driver = @url + else + Blink.notice "We're a networked client" + @localonly = false + @driver = SOAP::RPC::Driver.new(@url, 'urn:blink-server') + @driver.add_method("getconfig", "name") + end + unless @localonly + hash.delete(:Server) + + Blink.notice "Server is %s" % @url + + hash[:BindAddress] ||= "0.0.0.0" + hash[:Port] ||= 17444 + hash[:Debug] ||= true + hash[:AccessLog] ||= [] + + super(hash) + end + end + + def getconfig + Blink.debug "server is %s" % @url + #client.loadproperty('files/sslclient.properties') + Blink.notice("getting config") + objects = nil + if @localonly + objects = @driver.getconfig(self) + else + objects = @driver.getconfig(Blink::Fact["hostname"]) + end + self.config(objects) + end + + # this method is how the client receives the tree of Transportable + # objects + # for now, just descend into the tree and perform and necessary + # manipulations + def config(tree) + Blink.notice("Calling config") + container = Marshal::load(tree).to_type + + # this is a gross hack... but i don't see a good way around it + # set all of the variables to empty + Blink::Transaction.init + # for now we just evaluate the top-level container, but eventually + # there will be schedules and such associated with each object, + # and probably with the container itself + transaction = container.evaluate + #transaction = Blink::Transaction.new(objects) + transaction.toplevel = true + transaction.evaluate + self.shutdown + end + + def callfunc(name,args) + Blink.notice("Calling callfunc on %s" % name) + if function = Blink::Function[name] + #Blink.debug("calling function %s" % function) + value = function.call(args) + #Blink.debug("from %s got %s" % [name,value]) + return value + else + raise "Function '%s' not found" % name + end + end + + private + + def on_init + @default_namespace = 'urn:blink-client' + add_method(self, 'config', 'config') + add_method(self, 'callfunc', 'name', 'arguments') + end + + def cert(filename) + OpenSSL::X509::Certificate.new(File.open(File.join(@dir, filename)) { |f| + f.read + }) + end + + def key(filename) + OpenSSL::PKey::RSA.new(File.open(File.join(@dir, filename)) { |f| + f.read + }) + end + + end + #--------------------------------------------------------------- +end diff --git a/lib/puppet/element.rb b/lib/puppet/element.rb new file mode 100644 index 000000000..dd187977a --- /dev/null +++ b/lib/puppet/element.rb @@ -0,0 +1,41 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# included so we can test object types +require 'blink' + + +#--------------------------------------------------------------- +# the base class for both types and states +# very little functionality; basically just defines the interface +# and provides a few simple across-the-board functions like 'noop' +class Blink::Element + attr_writer :noop + + #--------------------------------------------------------------- + # all of our subclasses must respond to each of these methods... + @@interface_methods = [ + :retrieve, :insync?, :sync, :fqpath, :evaluate + ] + + # so raise an error if a method that isn't overridden gets called + @@interface_methods.each { |method| + self.send(:define_method,method) { + raise "%s has not overridden %s" % [self.class,method] + } + } + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # for testing whether we should actually do anything + def noop + unless defined? @noop + @noop = false + end + return @noop || Blink[:noop] || false + end + #--------------------------------------------------------------- + +end +#--------------------------------------------------------------- diff --git a/lib/puppet/event.rb b/lib/puppet/event.rb new file mode 100644 index 000000000..2cdda9976 --- /dev/null +++ b/lib/puppet/event.rb @@ -0,0 +1,201 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# included so we can test object types +require 'blink' +require 'blink/type' + +module Blink + # events are transient packets of information; they result in one or more (or none) + # subscriptions getting triggered, and then they get cleared + # eventually, these will be passed on to some central event system + class Event + # subscriptions are permanent associations determining how different + # objects react to an event + class Subscription + attr_accessor :source, :event, :target, :method + + def initialize(hash) + @triggered = false + + hash.each { |method,value| + # assign each value appropriately + # this is probably wicked-slow + self.send(method.to_s + "=",value) + } + Blink.warning "New Subscription: '%s' => '%s'" % + [@source,@event] + end + + # the transaction is passed in so that we can notify it if + # something fails + def trigger(transaction) + # this is potentially incomplete, because refreshing an object + # could theoretically kick off an event, which would not get run + # or, because we're executing the first subscription rather than + # the last, a later-refreshed object could somehow be connected + # to the "old" object rather than "new" + # but we're pretty far from that being a problem + if transaction.triggercount(self) > 0 + Blink.verbose "%s has already run" % self + else + Blink.verbose "'%s' matched '%s'; triggering '%s' on '%s'" % + [@source,@event,@method,@target] + begin + if @target.respond_to?(@method) + @target.send(@method) + else + Blink.verbose "'%s' of type '%s' does not respond to '%s'" % + [@target,@target.class,@method.inspect] + end + rescue => detail + # um, what the heck do i do when an object fails to refresh? + # shouldn't that result in the transaction rolling back? + # XXX yeah, it should + Blink.error "'%s' failed to %s: '%s'" % + [@target,@method,detail] + raise + #raise "We need to roll '%s' transaction back" % + #transaction + end + transaction.triggered(self) + end + end + end + + attr_accessor :event, :object, :transaction + + @@events = [] + + @@subscriptions = [] + + def Event.process + Blink.warning "Processing events" + @@events.each { |event| + @@subscriptions.find_all { |sub| + #Blink.warning "Sub source: '%s'; event object: '%s'" % + # [sub.source.inspect,event.object.inspect] + sub.source == event.object and + (sub.event == event.event or + sub.event == :ALL_EVENTS) + }.each { |sub| + Blink.notice "Found sub" + sub.trigger(event.transaction) + } + } + + @@events.clear + end + + def Event.subscribe(hash) + if hash[:event] == '*' + hash[:event] = :ALL_EVENTS + end + sub = Subscription.new(hash) + + # add to the correct area + @@subscriptions.push sub + end + + def initialize(args) + unless args.include?(:event) and args.include?(:object) + raise "Event.new called incorrectly" + end + + @event = args[:event] + @object = args[:object] + @transaction = args[:transaction] + + Blink.warning "New Event: '%s' => '%s'" % + [@object,@event] + + # initially, just stuff all instances into a central bucket + # to be handled as a batch + @@events.push self + end + end +end + + +#--------------------------------------------------------------- +# here i'm separating out the methods dealing with handling events +# currently not in use, so... + +class Blink::NotUsed + #--------------------------------------------------------------- + # return action array + # these are actions to use for responding to events + # no, this probably isn't the best way, because we're providing + # access to the actual hash, which is silly + def action + if not defined? @actions + puts "defining action hash" + @actions = Hash.new + end + @actions + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # call an event + # this is called on subscribers by the trigger method from the obj + # which sent the event + # event handling should probably be taking place in a central process, + # but.... + def event(event,obj) + Blink.debug "#{self} got event #{event} from #{obj}" + if @actions.key?(event) + Blink.debug "calling it" + @actions[event].call(self,obj,event) + else + p @actions + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # subscribe to an event or all events + # this entire event system is a hack job and needs to + # be replaced with a central event handler + def subscribe(args,&block) + obj = args[:object] + event = args[:event] || '*'.intern + if obj.nil? or event.nil? + raise "subscribe was called wrongly; #{obj} #{event}" + end + obj.action[event] = block + #events.each { |event| + unless @notify.key?(event) + @notify[event] = Array.new + end + unless @notify[event].include?(obj) + Blink.debug "pushing event '%s' for object '%s'" % [event,obj] + @notify[event].push(obj) + end + # } + #else + # @notify['*'.intern].push(obj) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # initiate a response to an event + def trigger(event) + subscribers = Array.new + if @notify.include?('*') and @notify['*'].length > 0 + @notify['*'].each { |obj| subscribers.push(obj) } + end + if (@notify.include?(event) and (! @notify[event].empty?) ) + @notify[event].each { |obj| subscribers.push(obj) } + end + Blink.debug "triggering #{event}" + subscribers.each { |obj| + Blink.debug "calling #{event} on #{obj}" + obj.event(event,self) + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- +end # Blink::Type diff --git a/lib/puppet/fact.rb b/lib/puppet/fact.rb new file mode 100644 index 000000000..2b46aabd8 --- /dev/null +++ b/lib/puppet/fact.rb @@ -0,0 +1,65 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# an interface for registering and retrieving facts +# this is an abstract interface, and should just be used to interact +# with another library + +# currently a very thin veneer on 'facter' + +require 'facter' +require 'blink' +require 'blink/type' + +module Blink + class Fact + def Fact.[](name) + fact = Facter[name] + if fact.value.nil? + raise "Could not retrieve fact %s" % name + end + Blink.debug("fact: got %s from %s for %s" % [fact.value,fact,name]) + return fact.value + end + + # just pass the block to 'add' + # the block has to do things like set the interpreter, + # the code (which can be a ruby block), and maybe the + # os and osrelease + def Fact.add(name,&block) + Facter[name].add(&block) + end + + def Fact.name + return :fact + end + + def Fact.namevar + return :name + end + + #Blink::Type.newtype(self) + + # we're adding a new resolution mechanism here; this is just how + # types work + # we don't have any real interest in the returned object + def initialize(hash) + name = hash[:name] + hash.delete(:name) + Fact.add(name) { |fact| + method = nil + hash.each { |key,value| + if key.is_a?(String) + method = key + "=" + elsif key.is_a?(Symbol) + method = key.id2name + "=" + else + raise "Key must be either string or symbol" + end + fact.send(method,value) + } + } + end + end +end diff --git a/lib/puppet/function.rb b/lib/puppet/function.rb new file mode 100644 index 000000000..d5cc11107 --- /dev/null +++ b/lib/puppet/function.rb @@ -0,0 +1,72 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'blink/fact' + +module Blink + class Function + @@functions = Hash.new(nil) + + #--------------------------------------------------------------- + def Function.[](name) + return @@functions[name] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def call(args) + @code.call(args) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # we want a 'proc' item instead of a block, so that we can return + # from it + def initialize(name,code) + @name = name + @code = code + + @@functions[name] = self + end + #--------------------------------------------------------------- + end + + Function.new("fact", proc { |fact| + require 'blink/fact' + + value = Fact[fact] + Blink.debug("retrieved %s as %s" % [fact,value]) + value + }) + + Function.new("addfact", proc { |args| + require 'blink/fact' + #Blink.debug("running addfact") + + hash = nil + if args.is_a?(Array) + hash = Hash[*args] + end + name = nil + if hash.has_key?("name") + name = hash["name"] + hash.delete("name") + elsif hash.has_key?(:name) + name = hash[:name] + hash.delete(:name) + else + raise "Functions must have names" + end + #Blink.debug("adding fact %s" % name) + newfact = Fact.add(name) { |fact| + hash.each { |key,value| + method = key + "=" + fact.send(method,value) + } + } + + #Blink.debug("got fact %s" % newfact) + }) +end diff --git a/lib/puppet/message.rb b/lib/puppet/message.rb new file mode 100644 index 000000000..31d5fa503 --- /dev/null +++ b/lib/puppet/message.rb @@ -0,0 +1,64 @@ +# $Id$ + +module Blink + #------------------------------------------------------------ + # provide feedback of various types to the user + # modeled after syslog messages + # each level of message prints in a different color + class Message + @@messages = Array.new + @@levels = [ :debug, :verbose, :notice, :warning, :error ] + @@colors = { + :debug => SLATE, + :verbose => ORANGE, + :notice => PINK, + :warning => GREEN, + :error => YELLOW + } + + attr_accessor :level, :message, :source + + def Message.loglevels + return @@levels + end + + def initialize(args) + unless args.include?(:level) && args.include?(:message) && + args.include?(:source) + raise "Blink::Message called incorrectly" + end + + if args[:level].class == String + @level = args[:level].intern + elsif args[:level].class == Symbol + @level = args[:level] + else + raise "Level is not a string or symbol: #{args[:level].class}" + end + @message = args[:message] + @source = args[:source] + @time = Time.now + # this should include the host name, and probly lots of other + # stuff, at some point + unless @@levels.include?(level) + raise "Invalid message level #{level}" + end + + @@messages.push(self) + Blink.newmessage(self) + end + + def to_s + # this probably won't stay, but until this leaves the console, + # i'm going to use coloring... + #return "#{@time} #{@source} (#{@level}): #{@message}" + #return @@colors[@level] + "%s %s (%s): %s" % [ + # @time, @source, @level, @message + #] + RESET + return @@colors[@level] + "%s (%s): %s" % [ + @source, @level, @message + ] + RESET + end + end + #------------------------------------------------------------ +end diff --git a/lib/puppet/selector.rb b/lib/puppet/selector.rb new file mode 100644 index 000000000..51e82b09d --- /dev/null +++ b/lib/puppet/selector.rb @@ -0,0 +1,82 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'blink/fact' + +module Blink + #--------------------------------------------------------------- + # this class will provide something like a 'select' statement, but it will + # return a value + # it will be used something like this: + # value = Selector.new( + # proc { test() } => value, + # proc { test2() } => value2, + # ) + + # each test gets evaluated in turn; the first one to return true has its + # value returned as the value of the statement + # this will be used to provide abstraction in objects, but it's currently + # unused + + class Selector < Array + attr_accessor :default + + def add(value,&block) + option = Option.new(value,&block) + @ohash[value] = option + @oarray.push(option) + end + + def evaluate + @oarray.each { |option| + if option.true? + return option.value + end + } + return nil + end + + # we have to support providing different values based on + # different criteria, e.g., default is X, SunOS gets Y, and + # host Yayness gets Z. + # thus, no invariant + def initialize + @oarray = [] + @ohash = {} + + if block_given? + yield self + end + end + + def to_s + return self.evaluate() + end + + class Option + attr_accessor :value, :test, :invariant + + def initialize(value,&block) + @value = value + @test = block + end + + def to_s + if self.evaluate + return value + end + end + + def true? + unless @test.is_a?(Proc) + raise "Cannot yet evaluate non-code tests" + end + + return @test.call() + end + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/statechange.rb b/lib/puppet/statechange.rb new file mode 100644 index 000000000..f88108d8a --- /dev/null +++ b/lib/puppet/statechange.rb @@ -0,0 +1,111 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the class responsible for actually doing any work + +# enables no-op and logging/rollback + +module Blink + class StateChange + attr_accessor :is, :should, :type, :path, :state, :transaction, :run + + #--------------------------------------------------------------- + def initialize(state) + @state = state + #@state.parent.newchange + @path = state.fqpath + @is = state.is + @should = state.should + + @run = false + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def go + if @state.noop + #Blink.notice "%s is noop" % @state + return nil + end + + begin + event = @state.sync + @run = true + + # default to a simple event type + if event.nil? + event = @state.parent.class.name.id2name + "_changed" + elsif ! event.is_a?(Symbol) + Blink.notice "State '%s' returned invalid event '%s'; resetting to default" % + [@state.class,event] + + event = @state.parent.class.name.id2name + "_changed" + end + + # i should maybe include object type, but the event type + # should basically point to that, right? + return Blink::Event.new( + :event => event, + :object => @state.parent, + :transaction => @transaction, + :message => self.to_s + ) + rescue => detail + Blink.error "%s failed: %s" % [self.to_s,detail] + raise + # there should be a way to ask the state what type of event + # it would have generated, but... + pname = @state.parent.class.name.id2name + #if pname.is_a?(Symbol) + # pname = pname.id2name + #end + return Blink::Event.new( + :event => pname + "_failed", + :object => @state.parent, + :transaction => @transaction, + :message => "Failed: " + self.to_s + ) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def forward + #Blink.notice "moving change forward" + + unless defined? @transaction + raise "StateChange '%s' tried to be executed outside of transaction" % + self + end + + return self.go + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def backward + @state.should = @is + @state.retrieve + + Blink.notice "Rolling %s backward" % self + return self.go + + #raise "Moving statechanges backward is currently unsupported" + #@type.change(@path,@should,@is) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def noop + return @state.noop + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + return "%s: %s => %s" % [@state,@is,@should] + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/storage.rb b/lib/puppet/storage.rb new file mode 100644 index 000000000..9fc21d38b --- /dev/null +++ b/lib/puppet/storage.rb @@ -0,0 +1,48 @@ +# $Id$ + +module Blink + # a class for storing state + class Storage + include Singleton + @@state = Hash.new { |hash,key| + hash[key] = Hash.new(nil) + } + @@splitchar = "\t" + + def initialize + self.class.load + end + + def Storage.load + # XXX I should probably use a better default state dir + Blink[:statefile] ||= "/var/tmp/blinkstate" + return unless File.exists?(Blink[:statefile]) + File.open(Blink[:statefile]) { |file| + file.gets { |line| + myclass, key, value = line.split(@@splitchar) + + @@state[myclass][key] = Marshal::load(value) + } + } + end + + def Storage.state(myclass) + unless myclass.is_a? Class + myclass = myclass.class + end + result = @@state[myclass] + return result + end + + def Storage.store + File.open(Blink[:statefile], File::CREAT|File::WRONLY, 0600) { |file| + @@state.each { |klass, thash| + thash.each { |key,value| + mvalue = Marshal::dump(value) + file.puts([klass,key,mvalue].join(@@splitchar)) + } + } + } + end + end +end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb new file mode 100644 index 000000000..87b2f950c --- /dev/null +++ b/lib/puppet/transaction.rb @@ -0,0 +1,162 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the class that actually walks our object/state tree, collects the changes, +# and performs them + +# there are two directions of walking: +# - first we recurse down the tree and collect changes +# - then we walk back up the tree through 'refresh' after the changes + +require 'blink' +require 'blink/statechange' + +#--------------------------------------------------------------- +module Blink +class Transaction + attr_accessor :toplevel, :component + + #--------------------------------------------------------------- + # a bit of a gross hack; a global list of objects that have failed to sync, + # so that we can verify during later syncs that our dependencies haven't + # failed + def Transaction.init + @@failures = Hash.new(0) + @@changed = [] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # for now, just store the changes for executing linearly + # later, we might execute them as we receive them + def change(change) + @changes.push change + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # i don't need to worry about ordering, because it's not possible to specify + # an object as a dependency unless it's already been mentioned within the language + # thus, an object gets defined, then mentioned as a dependency, and the objects + # are synced in that order automatically + def evaluate + Blink.notice "executing %s changes or transactions" % @changes.length + + return @changes.collect { |change| + if change.is_a?(Blink::StateChange) + change.transaction = self + events = nil + begin + events = [change.forward].flatten + #@@changed.push change.state.parent + rescue => detail + Blink.error("%s failed: %s" % [change,detail]) + raise + # at this point, we would normally do error handling + # but i haven't decided what to do for that yet + # so just record that a sync failed for a given object + #@@failures[change.state.parent] += 1 + # this still could get hairy; what if file contents changed, + # but a chmod failed? how would i handle that error? dern + end + + if events.nil? + Blink.verbose "No events returned?" + else + # first handle the subscriptions on individual objects + events.each { |event| + change.state.parent.subscribers?(event).each { |sub| + sub.trigger(self) + } + } + end + events + elsif change.is_a?(Blink::Transaction) + change.evaluate + else + raise "Transactions cannot handle objects of type %s" % child.class + end + }.flatten.reject { |event| + event.nil? + }.each { |event| + # this handles subscriptions on the components, rather than + # on idividual objects + self.component.subscribers?(event).each { |sub| + sub.trigger(self) + } + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this should only be called by a Blink::Container object now + # and it should only receive an array + def initialize(tree) + @tree = tree + @toplevel = false + + @triggered = Hash.new(0) + + # of course, this won't work on the second run + unless defined? @@failures + @toplevel = true + self.class.init + end + # change collection is in-band, and message generation is out-of-band + # of course, exception raising is also out-of-band + @changes = @tree.collect { |child| + # these children are all Blink::Type instances + # not all of the children will return a change, and Containers + # return transactions + child.evaluate + }.flatten.reject { |child| + child.nil? # remove empties + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def rollback + @changes.each { |change| + if change.is_a?(Blink::StateChange) + next unless change.run + #change.transaction = self + begin + change.backward + #@@changed.push change.state.parent + rescue => detail + Blink.error("%s rollback failed: %s" % [change,detail]) + # at this point, we would normally do error handling + # but i haven't decided what to do for that yet + # so just record that a sync failed for a given object + #@@failures[change.state.parent] += 1 + # this still could get hairy; what if file contents changed, + # but a chmod failed? how would i handle that error? dern + end + elsif change.is_a?(Blink::Transaction) + # yay, recursion + change.rollback + else + raise "Transactions cannot handle objects of type %s" % child.class + end + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def triggercount(sub) + Blink.notice "Triggercount on %s is %s" % [sub,@triggered[sub]] + return @triggered[sub] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def triggered(sub) + @triggered[sub] += 1 + Blink.notice "%s was triggered; count is %s" % [sub,@triggered[sub]] + end + #--------------------------------------------------------------- +end +end +#--------------------------------------------------------------- diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb new file mode 100644 index 000000000..522a34d7c --- /dev/null +++ b/lib/puppet/transportable.rb @@ -0,0 +1,200 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' + +module Blink + #------------------------------------------------------------ + class TransObject < Hash + attr_accessor :type + + @@ohash = {} + @@oarray = [] + + def TransObject.add(object) + @@oarray.push object + + # this is just so we can check, at parse time, whether a required + # object has already been mentioned when it is listed as required + # because we're ordered, as long as an object gets made before its + # dependent objects will get synced later + @@ohash[object.longname] = object + end + + def TransObject.clear + @@oarray.clear + end + + def TransObject.list + return @@oarray + end + + def initialize(name,type) + self[:name] = name + @type = type + self.class.add(self) + end + + def longname + return [self.type,self[:name]].join('--') + end + + def name + return self[:name] + end + + def to_s + return "%s(%s) => %s" % [@type,self[:name],super] + end + + def to_type + retobj = nil + if type = Blink::Type.type(self.type) + #begin + # this will fail if the type already exists + # which may or may not be a good thing... + retobj = type.new(self) + #rescue => detail + # Blink.error "Failed to create %s: %s" % [type.name,detail] + # puts self.class + # puts self.inspect + # exit + #end + else + raise "Could not find object type %s" % self.type + end + + return retobj + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + class TransSetting + attr_accessor :type, :name, :args, :evalcount + + def initialize + @evalcount = 0 + end + + def evaluate + @evalcount += 0 + if type = Blink::Type.type(self.type) + # call the settings + name = self.name + unless name.is_a?(Symbol) + name = name.intern + end + if type.allowedmethod(name) + type.send(self.name,self.args) + else + Blink.error("%s does not respond to %s" % [self.type,self.name]) + end + else + raise "Could not find object type %s" % setting.type + end + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # just a linear container for objects + class TransBucket < Array + attr_accessor :name, :type + + def push(*args) + args.each { |arg| + case arg + when Blink::TransBucket, Blink::TransObject, Blink::TransSetting + # nada + else + raise "TransBuckets cannot handle objects of type %s" % + arg.class + end + } + super + end + + def to_type + # this container will contain the equivalent of all objects at + # this level + #container = Blink::Component.new(:name => @name, :type => @type) + unless defined? @name + raise "TransBuckets must have names" + end + unless defined? @type + Blink.verbose "TransBucket '%s' has no type" % @name + end + hash = { + :name => @name, + :type => @type + } + if defined? @parameters + @parameters.each { |param,value| + Blink.warning "Defining %s on %s of type %s" % + [param,@name,@type] + hash[param] = value + } + else + Blink.warning "%s has no parameters" % @name + end + container = Blink::Component.new(hash) + nametable = {} + + self.each { |child| + # the fact that we descend here means that we are + # always going to execute depth-first + # which is _probably_ a good thing, but one never knows... + if child.is_a?(Blink::TransBucket) + # just perform the same operation on any children + container.push(child.to_type) + elsif child.is_a?(Blink::TransSetting) + # XXX this is wrong, but for now just evaluate the settings + child.evaluate + elsif child.is_a?(Blink::TransObject) + # do a simple little naming hack to see if the object already + # exists in our scope + # this assumes that type/name combinations are globally + # unique + name = [child[:name],child.type].join("--") + + if nametable.include?(name) + object = nametable[name] + child.each { |var,value| + # don't rename; this shouldn't be possible anyway + next if var == :name + + Blink.notice "Adding %s to %s" % [var,name] + # override any existing values + object[var] = value + } + else # the object does not exist yet in our scope + # now we have the object instantiated, in our scope + object = child.to_type + nametable[name] = object + + # this sets the order of the object + container.push object + end + else + raise "TransBucket#to_type cannot handle objects of type %s" % + child.class + end + } + + # at this point, no objects at are level are still Transportable + # objects + return container + end + + def param(param,value) + unless defined? @parameters + @parameters = {} + end + @parameters[param] = value + end + + end + #------------------------------------------------------------ +end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb new file mode 100644 index 000000000..51cd904e7 --- /dev/null +++ b/lib/puppet/type.rb @@ -0,0 +1,851 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# included so we can test object types +require 'blink' +require 'blink/element' +require 'blink/event' +require 'blink/type/state' + + +# XXX see the bottom of the file for the rest of the inclusions + +#--------------------------------------------------------------- +# This class is the abstract base class for the mechanism for organizing +# work. No work is actually done by this class or its subclasses; rather, +# the subclasses include states which do the actual work. +# See state.rb for how work is actually done. + +# our duck type interface -- if your object doesn't match this interface, +# it won't work + +# all of our first-class objects (objects, states, and components) will +# respond to these methods +# although states don't inherit from Blink::Type +# although maybe Blink::State should... + +# the default behaviour that this class provides is to just call a given +# method on each contained object, e.g., in calling 'sync', we just run: +# object.each { |subobj| subobj.sync() } + +# to use this interface, just define an 'each' method and 'include Blink::Type' + +module Blink +class Type < Blink::Element + attr_accessor :children, :parameters, :parent + include Enumerable + + @@allobjects = Array.new # an array for all objects + @abstract = true + + @name = :blink # a little fakery, since Blink itself isn't a type + @namevar = :notused + + @states = [] + @parameters = [:notused] + + @allowedmethods = [:noop,:debug,:statefile] + + @@metaparams = [ + :onerror, + :schedule, + :check, + :require + ] + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # class methods dealing with Type management + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # these objects are used for mapping type names (e.g., 'file') + # to actual object classes; because Type.inherited is + # called before the .name method is defined, we need + # to store each class in an array, and then later actually iterate + # across that array and make a map + @@typeary = [self] # so that the allowedmethods stuff works + @@typehash = Hash.new { |hash,key| + if key.is_a?(String) + key = key.intern + end + if hash.include?(key) + hash[key] + else + raise "Object type %s not found" % key + end + } + + #--------------------------------------------------------------- + # a test for whether this type is allowed to have instances + # on clients + # subclasses can just set '@abstract = true' to mark themselves + # as abstract + def Type.abstract + if defined? @abstract + return @abstract + else + return false + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.allowedmethod(method) + if defined? @allowedmethods and @allowedmethods.include?(method) + return true + else + return false + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.statefile(file) + Blink[:statefile] = file + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # ill thought-out + # this needs to return @noop + #def noop(ary) + # Blink[:noop] = ary.shift + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def debug(ary) + # Blink[:debug] = ary.shift + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is meant to be run multiple times, e.g., when a new + # type is defined at run-time + def Type.buildtypehash + @@typeary.each { |otype| + if @@typehash.include?(otype.name) + if @@typehash[otype.name] != otype + Blink.warning("Object type %s is already defined (%s vs %s)" % + [otype.name,@@typehash[otype.name],otype]) + end + else + @@typehash[otype.name] = otype + end + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.eachtype + @@typeary.each { |type| yield type } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this should make it so our subclasses don't have to worry about + # defining these class instance variables + def Type.inherited(sub) + sub.initvars + + #Blink.notice("subtype %s(%s) just created" % [sub,sub.superclass]) + # add it to the master list + # unfortunately we can't yet call sub.name, because the #inherited + # method gets called before any commands in the class definition + # get executed, which, um, sucks + @@typeary.push(sub) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is so we don't have to eval this code + # init all of our class instance variables + def Type.initvars + @objects = Hash.new + @actions = Hash.new + #Blink.verbose "initing validstates for %s" % self + @validstates = {} + @validparameters = {} + + unless defined? @states + @states = {} + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.metaclass + if defined? @metaclass + return @metaclass + else + return false + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is used for mapping object types (e.g., Blink::Type::File) + # to names (e.g., "file") + def Type.name + return @name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.newtype(type) + raise "Type.newtype called, but I don't know why" + @@typeary.push(type) + if @@typehash.has_key?(type.name) + Blink.notice("Redefining object type %s" % type.name) + end + @@typehash[type.name] = type + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.type(type) + unless @@typeary.length == @@typehash.length + Type.buildtypehash + end + @@typehash[type] + end + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # class methods dealing with type instance management + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # retrieve a named object + def Type.[](name) + if @objects.has_key?(name) + return @objects[name] + else + return nil + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.[]=(name,object) + newobj = nil + if object.is_a?(Blink::Type) + newobj = object + else + raise "must pass a Blink::Type object" + end + + if @objects.has_key?(newobj.name) + raise "Object '%s' of type '%s' already exists with id '%s' vs. '%s'" % + [newobj.name,newobj.class.name, + @objects[newobj.name].object_id,newobj.object_id] + else + #Blink.debug("adding %s of type %s to class list" % + # [object.name,object.class]) + @objects[newobj.name] = newobj + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # remove all type instances + def Type.allclear + @@typeary.each { |subtype| + Blink.notice "Clearing %s of objects" % subtype + subtype.clear + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # per-type clearance + def Type.clear + if defined? @objects + @objects.clear + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # all objects total + def Type.push(object) + @@allobjects.push object + #Blink.debug("adding %s of type %s to master list" % + # [object.name,object.class]) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # some simple stuff to make it easier to get a name from everyone + def Type.namevar + unless defined? @namevar and ! @namevar.nil? + raise "Class %s has no namevar defined" % self + end + return @namevar + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.has_key?(name) + return @objects.has_key?(name) + end + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # class and instance methods dealing with parameters and states + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.buildstatehash + unless defined? @validstates + @validstates = Hash.new(false) + end + @states.each { |stateklass| + name = stateklass.name + if @validstates.include?(name) + if @validstates[name] != stateklass + raise "Redefining state %s(%s) in %s" % [name,stateklass,self] + else + # it's already there, so don't bother + end + else + @validstates[name] = stateklass + end + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # Is the parameter in question a meta-parameter? + def Type.metaparam(param) + @@metaparams.include?(param) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is probably only used by FileRecord objects + def Type.parameters=(params) + Blink.notice "setting parameters to [%s]" % params.join(" ") + @parameters = params.collect { |param| + if param.class == Symbol + param + else + param.intern + end + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.states + return @states + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.validstates + return @validstates + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.validstate(name) + unless @validstates.length == @states.length + self.buildstatehash + end + if @validstates.include?(name) + return @validstates[name] + else + return false + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.validparameter(name) + unless defined? @parameters + raise "Class %s has not defined parameters" % self + end + return @parameters.include?(name) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Type.validparam(name) + self.validstate(name) or self.validparameter(name) or false + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this abstracts accessing parameters and states, and normalizes + # access to always be symbols, not strings + def [](name) + mname = name + if name.is_a?(String) + mname = name.intern + end + unless self.class.validparam(name) + raise "Invalid parameter %s" % [mname] + end + if @states.include?(mname) + # if they're using [], they don't know if we're a state or a string + # thus, return a string + # if they want the actual state object, they should use state() + return @states[mname].is + elsif @parameters.include?(mname) + return @parameters[mname] + else + return nil + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this abstracts setting parameters and states, and normalizes + # access to always be symbols, not strings + def []=(name,value) + mname = name + if name.is_a?(String) + mname = name.intern + end + + if Blink::Type.metaparam(mname) + # call the metaparam method + self.send(("meta" + mname.id2name),value) + elsif stateklass = self.class.validstate(mname) + if value.is_a?(Blink::State) + Blink.debug "'%s' got handed a state for '%s'" % [self,mname] + @states[mname] = value + else + if @states.include?(mname) + @states[mname].should = value + else + @states[mname] = stateklass.new( + :parent => self, + :should => value + ) + #Blink.notice "Adding parent to %s" % mname + #@states[mname].parent = self + end + end + elsif self.class.validparameter(mname) + @parameters[mname] = value + else + raise "Invalid parameter %s" % [mname] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # removing states + def delete(attr) + if @states.has_key?(attr) + @states.delete(attr) + else + raise "Undefined state '#{attr}' in #{self}" + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def state(name) + return @states[name] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def parameter(name) + return @parameters[name] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # instance methods related to instance intrinsics + # e.g., initialize() and name() + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def initialize(hash) + @children = [] + @evalcount = 0 + + @subscriptions = [] + + # states and parameters are treated equivalently from the outside: + # as name-value pairs (using [] and []=) + # internally, however, parameters are merely a hash, while states + # point to State objects + # further, the lists of valid states and parameters are defined + # at the class level + @states = Hash.new(false) + @parameters = Hash.new(false) + + @noop = false + + # which objects to notify when we change + @notify = [] + + # keeping stats for the total number of changes, and how many were + # completely sync'ed + # this isn't really sufficient either, because it adds lots of special cases + # such as failed changes + # it also doesn't distinguish between changes from the current transaction + # vs. changes over the process lifetime + @totalchanges = 0 + @syncedchanges = 0 + @failedchanges = 0 + + hash.each { |var,value| + unless var.is_a? Symbol + hash[var.intern] = value + hash.delete(var) + end + } + + if hash.include?(:noop) + @noop = hash[:noop] + hash.delete(:noop) + end + + # we have to set the name of our object before anything else, + # because it might be used in creating the other states + namevar = self.class.namevar + + # if they're not using :name for the namevar but we got :name (probably + # from the parser) + if namevar != :name and hash.include?(:name) and ! hash[:name].nil? + self[namevar] = hash[:name] + hash.delete(:name) + # else if we got the namevar + elsif hash.has_key?(namevar) and ! hash[namevar].nil? + self[namevar] = hash[namevar] + hash.delete(namevar) + # else something's screwy + else + p hash + p namevar + raise TypeError.new("A name must be provided to %s at initialization time" % + self.class) + end + + hash.each { |param,value| + #Blink.debug("adding param '%s' with value '%s'" % + # [param,value]) + self[param] = value + } + + # add this object to the specific class's list of objects + #Blink.notice("Adding [%s] to %s" % [self.name,self.class]) + self.class[self.name] = self + + # and then add it to the master list + Blink::Type.push(self) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # return the full path to us, for logging and rollback + # some classes (e.g., FileTypeRecords) will have to override this + def fqpath + return self.class, self.name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this might result from a state or from a parameter + def name + return self[self.class.namevar] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def retrieve + # it's important to use the method here, so we always get + # them back in the right order + self.states.collect { |state| + state.retrieve + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def sync + self.collect { |child| + child.sync + }.reject { |event| + ! (event.is_a?(Symbol) or event.is_a?(String)) + }.flatten + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + self.name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # instance methods dealing with contained states + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def states + Blink.debug "%s has %s states" % [self,@states.length] + tmpstates = [] + self.class.states.each { |state| + if @states.include?(state.name) + tmpstates.push(@states[state.name]) + end + } + unless tmpstates.length == @states.length + raise "Something went very wrong with tmpstates creation" + end + return tmpstates + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def eachstate + self.states.each { |state| + yield state + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # iterate across all children, and then iterate across states + # we do children first so we're sure that all dependent objects + # are checked first + # we ignore parameters here, because they only modify how work gets + # done, they don't ever actually result in work specifically + def each + # we want to return the states in the order that each type + # specifies it, because it may (as in the case of File#create) + # be important + @children.each { |child| + yield child + } + self.eachstate { |state| + yield state + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def push(*child) + @children.push(*child) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # instance methods dealing with actually doing work + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def newchange + @totalchanges += 1 + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this method is responsible for collecting state changes + # we always descend into the children before we evaluate our current + # states + # this returns any changes resulting from testing, thus 'collect' + # rather than 'each' + def evaluate + unless defined? @evalcount + Blink.error "No evalcount defined on '%s' of type '%s'" % + [self.name,self.class] + end + # if we're a metaclass and we've already evaluated once... + if self.metaclass and @evalcount > 0 + return + end + @evalcount += 1 + + changes = @children.collect { |child| + child.evaluate + } + + # this only operates on states, not states + children + self.retrieve + unless self.insync? + changes << self.states.find_all { |state| + ! state.insync? + }.collect { |state| + Blink::StateChange.new(state) + } + end + # collect changes and return them + # these changes could be from child objects or from contained states + #self.collect { |child| + # child.evaluate + #} + return changes + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # if all contained objects are in sync, then we're in sync + def insync? + insync = true + + self.states.each { |state| + unless state.insync? + Blink.debug("%s is not in sync" % state) + insync = false + end + } + + Blink.debug("%s sync status is %s" % [self,insync]) + return insync + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # do we actually do work, or do we modify the system instead? + # instances of a metaclass only get executed once per client process, + # while instances of normal classes get run every time + def metaclass + return self.class.metaclass + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #--------------------------------------------------------------- + # Meta-parameter methods: These methods deal with the results + # of specifying metaparameters + #--------------------------------------------------------------- + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this just marks states that we definitely want to retrieve values + # on + def metacheck(args) + unless args.is_a?(Array) + args = [args] + end + + # these are states that we might not have values for but we want to retrieve + # values for anyway + args.each { |state| + unless state.is_a?(Symbol) + state = state.intern + end + next if @states.include?(state) + + stateklass = nil + unless stateklass = self.class.validstate(state) + raise "%s is not a valid state for %s" % [state,self.class] + end + + # XXX it's probably a bad idea to have code this important in + # two places + @states[state] = stateklass.new( + :parent => self + ) + #@states[state] = stateklass.new() + #@states[state].parent = self + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def subscribe(hash) + if hash[:event] == '*' + hash[:event] = :ALL_EVENTS + end + + hash[:source] = self + sub = Blink::Event::Subscription.new(hash) + + # add to the correct area + @subscriptions.push sub + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # return all of the subscriptions to a given event + def subscribers?(event) + @subscriptions.find_all { |sub| + sub.event == event.event or + sub.event == :ALL_EVENTS + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # for each object we require, subscribe to all events that it + # generates + # we might reduce the level of subscription eventually, but for now... + def metarequire(requires) + unless requires.is_a?(Array) + requires = [requires] + end + requires.each { |rname| + # we just have a name and a type, and we need to convert it + # to an object... + type = nil + object = nil + tname = rname[0] + unless type = Blink::Type.type(tname) + raise "Could not find type %s" % tname + end + name = rname[1] + unless object = type[name] + raise "Could not retrieve object '%s' of type '%s'" % + [name,type] + end + Blink.debug("%s requires %s" % [self.name,object]) + + # for now, we only support this one method, 'refresh' + object.subscribe( + :event => '*', + :target => self, + :method => :refresh + ) + #object.addnotify(self) + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def metaonerror(response) + Blink.debug("Would have called metaonerror") + @onerror = response + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def metaschedule(schedule) + @schedule = schedule + end + #--------------------------------------------------------------- +end # Blink::Type +end + +require 'blink/type/service' +require 'blink/type/file' +require 'blink/type/symlink' +require 'blink/type/package' +require 'blink/type/component' +require 'blink/statechange' +#require 'blink/type/typegen' +#require 'blink/type/typegen/filetype' +#require 'blink/type/typegen/filerecord' diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb new file mode 100644 index 000000000..38bf3326d --- /dev/null +++ b/lib/puppet/type/component.rb @@ -0,0 +1,74 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the object allowing us to build complex structures +# this thing contains everything else, including itself + +require 'blink' +require 'blink/type' +require 'blink/transaction' + +module Blink + class Component < Blink::Type + include Enumerable + + @name = :component + @namevar = :name + + @states = [] + @parameters = [:name,:type] + + def each + @children.each { |child| yield child } + end + + def initialize(args) + @children = [] + super(args) + Blink.verbose "Made component with name %s" % self.name + end + + # now we decide whether a transaction is dumb, and just accepts + # changes from the container, or whether it collects them itself + # for now, because i've already got this implemented, let transactions + # collect the changes themselves + def evaluate + transaction = Blink::Transaction.new(@children) + transaction.component = self + return transaction + end + + def push(*ary) + ary.each { |child| + unless child.is_a?(Blink::Element) + Blink.notice "Got object of type %s" % child.class + raise "Containers can only contain Blink::Elements" + end + @children.push child + } + end + + def name + return "%s[%s]" % [@parameters[:type],@parameters[:name]] + end + + def refresh + @children.collect { |child| + if child.respond_to?(:refresh) + child.refresh + end + } + end + + def retrieve + self.collect { |child| + child.retrieve + } + end + + def to_s + return "component(%s)" % self.name + end + end +end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb new file mode 100644 index 000000000..9d0f98c61 --- /dev/null +++ b/lib/puppet/type/file.rb @@ -0,0 +1,450 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'digest/md5' +require 'etc' +require 'blink/type/state' + +module Blink + # we first define all of the state that our file will use + # because the objects must be defined for us to use them in our + # definition of the file object + class State + class FileCreate < Blink::State + require 'etc' + attr_accessor :file + @name = :create + @event = :file_created + + def should=(value) + # default to just about anything meaning 'true' + if value == false or value.nil? + @should = false + else + @should = true + end + end + + def retrieve + stat = nil + + self.is = FileTest.exist?(self.parent[:path]) + Blink.debug "'exists' state is %s" % self.is + end + + + def sync + begin + File.open(self.parent[:path],"w") { # just create an empty file + } + rescue => detail + raise detail + end + return :file_created + end + end + + class FileChecksum < Blink::State + @name = :checksum + @event = :file_modified + + def should=(value) + @checktype = value + state = Blink::Storage.state(self) + if hash = state[self.parent[:path]] + if hash.include?(@checktype) + @should = hash[@checktype] + else + Blink.verbose "Found checksum for %s but not of type %s" % + [self.parent[:path],@checktype] + @should = nil + end + else + Blink.debug "No checksum for %s" % self.parent[:path] + end + end + + def retrieve + unless defined? @checktype + @checktype = "md5" + end + + sum = "" + case @checktype + when "md5": + File.open(self.parent[:path]) { |file| + sum = Digest::MD5.hexdigest(file.read) + } + when "md5lite": + File.open(self.parent[:path]) { |file| + sum = Digest::MD5.hexdigest(file.read(512)) + } + when "timestamp","mtime": + sum = File.stat(self.parent[:path]).mtime + when "time": + sum = File.stat(self.parent[:path]).ctime + end + + self.is = sum + + Blink.debug "checksum state is %s" % self.is + end + + + # at this point, we don't actually modify the system, we just kick + # off an event if we detect a change + def sync + if self.updatesum + return :file_modified + else + return nil + end + end + + def updatesum + state = Blink::Storage.state(self) + unless state.include?(self.parent[:path]) + state[self.parent[:path]] = Hash.new + end + # if we're replacing, vs. updating + if state[self.parent[:path]].include?(@checktype) + Blink.debug "Replacing checksum %s with %s" % + [state[self.parent[:path]][@checktype],@is] + result = true + else + Blink.verbose "Creating checksum %s for %s of type %s" % + [@is,self.parent[:path],@checktype] + result = false + end + state[self.parent[:path]][@checktype] = @is + return result + end + end + + class FileUID < Blink::State + require 'etc' + attr_accessor :file + @name = :owner + @event = :inode_changed + + def retrieve + stat = self.parent.stat(true) + + self.is = stat.uid + if defined? @should + unless @should.is_a?(Integer) + begin + user = Etc.getpwnam(@should) + if user.gid == "" + raise "Could not retrieve uid for '%s'" % self.parent + end + Blink.debug "converting %s to integer '%d'" % + [@should,user.uid] + @should = user.uid + rescue + raise "Could not get any info on user '%s'" % @should + end + end + end + + Blink.debug "chown state is %d" % self.is + end + + def sync + if @is == -1 + self.parent.stat(true) + self.retrieve + Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] + end + + unless self.parent.stat + Blink.error "File '%s' does not exist; cannot chown" % + self.parent[:path] + end + + begin + File.chown(self.should,-1,self.parent[:path]) + rescue => detail + raise "failed to chown '%s' to '%s': %s" % + [self.parent[:path],self.should,detail] + end + + return :inode_changed + end + end + + # this state should actually somehow turn into many states, + # one for each bit in the mode + # I think MetaStates are the answer, but I'm not quite sure + class FileMode < Blink::State + require 'etc' + + @name = :mode + @event = :inode_changed + + def should=(should) + # this is pretty hackish, but i need to make sure the number is in + # octal, yet the number can only be specified as a string right now + unless should.is_a?(Integer) # i've already converted it correctly + unless should =~ /^0/ + should = "0" + should + end + should = Integer(should) + end + @should = should + end + + def retrieve + stat = self.parent.stat(true) + self.is = stat.mode & 007777 + + Blink.debug "chmod state is %o" % self.is + end + + def sync + if @is == -1 + self.parent.stat(true) + self.retrieve + Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] + end + + unless self.parent.stat + Blink.error "File '%s' does not exist; cannot chmod" % + self.parent[:path] + return + end + + begin + File.chmod(self.should,self.parent[:path]) + rescue + raise "failed to chmod #{self.parent[:path]}: #{$!}" + end + return :inode_changed + end + end + + # not used until I can figure out how to solve the problem with + # metastates + class FileSetUID < Blink::State + require 'etc' + + @parent = Blink::State::FileMode + + @name = :setuid + @event = :inode_changed + + def <=>(other) + self.is <=> @parent.value[11] + end + + # this just doesn't seem right... + def sync + unless defined? @is or @is == -1 + self.parent.stat(true) + self.retrieve + Blink.notice "%s: should is '%s'" % [self.class.name,self.should] + end + tmp = 0 + if self.is == true + tmp = 1 + end + @parent.value[11] = tmp + return :inode_changed + end + end + + class FileGroup < Blink::State + require 'etc' + + @name = :group + @event = :inode_changed + + def retrieve + stat = self.parent.stat(true) + + self.is = stat.gid + + # we probably shouldn't actually modify the 'should' value + # but i don't see a good way around it right now + # mmmm, should + if defined? @should + unless self.should.is_a?(Integer) + begin + require 'blink/fact' + group = Etc.getgrnam(self.should) + # apparently os x is six shades of weird + os = Blink::Fact["Operatingsystem"] + + gid = "" + case os + when "Darwin": + gid = group.passwd + else + gid = group.gid + end + if gid == "" + raise "Could not retrieve gid for %s" % self.parent + end + Blink.debug "converting %s to integer %d" % + [self.should,gid] + self.should = gid + rescue + #raise "Could not get any info on group %s" % self.should + raise + end + end + end + Blink.debug "chgrp state is %d" % self.is + end + + def sync + Blink.debug "setting chgrp state to %s" % self.should + if @is == -1 + self.parent.stat(true) + self.retrieve + Blink.notice "%s: after refresh, is '%s'" % [self.class.name,@is] + end + + unless self.parent.stat + Blink.error "File '%s' does not exist; cannot chgrp" % + self.parent[:path] + return + end + + begin + # set owner to nil so it's ignored + File.chown(nil,self.should,self.parent[:path]) + rescue + raise "failed to chgrp %s to %s: %s" % + [self.parent[:path], self.should, $!] + end + return :inode_changed + end + end + end + class Type + class File < Type + attr_reader :params + # class instance variable + @states = [ + Blink::State::FileCreate, + Blink::State::FileUID, + Blink::State::FileGroup, + Blink::State::FileMode, + Blink::State::FileChecksum, + Blink::State::FileSetUID + ] + + @parameters = [ + :path, + :recurse + ] + + @name = :file + @namevar = :path + + # a wrapper method to make sure the file exists before doing anything + def retrieve + unless stat = self.stat(true) + Blink.verbose "File %s does not exist" % self[:path] + @states.each { |name,state| + state.is = -1 + } + return + end + super + end + + def stat(refresh = false) + if @stat.nil? or refresh == true + begin + @stat = ::File.stat(self[:path]) + rescue => error + Blink.debug "Failed to stat %s: %s" % + [self[:path],error] + @stat = nil + end + end + + return @stat + end + + def initialize(hash) + arghash = hash.dup + super + @stat = nil + + # if recursion is enabled and we're a directory... + if @parameters[:recurse] and self.stat.directory? + recurse = self[:recurse] + # we might have a string, rather than a number + if recurse.is_a?(String) + if recurse =~ /^[0-9]+$/ + recurse = Integer(recurse) + elsif recurse =~ /^inf/ # infinite recursion + recurse = true + end + end + + # unless we're at the end of the recursion + if recurse != 0 + arghash.delete("recurse") + if recurse.is_a?(Integer) + recurse -= 1 # reduce the level of recursion + end + + arghash[:recurse] = recurse + + # now make each contained file/dir a child + unless defined? @children + @children = [] + end + + Dir.foreach(self[:path]) { |file| + next if file =~ /^\.\.?/ # skip . and .. + + arghash[:path] = ::File.join(self[:path],file) + + child = nil + # if the file already exists... + if child = self.class[arghash[:path]] + arghash.each { |var,value| + next if var == :path + child[var] = value + } + else # create it anew + child = self.class.new(arghash) + end + @children.push child + } + end + end + end + end # Blink::Type::File + end # Blink::Type + + class FileSource + attr_accessor :name + + @sources = Hash.new(nil) + + def FileSource.[]=(name,sub) + @sources[name] = sub + end + + def FileSource.[](name) + return @sources[name] + end + + def initialize(name) + @name = name + + if block_given? + yield self + end + + FileSource[name] = self + end + end +end diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb new file mode 100644 index 000000000..77bcd76b2 --- /dev/null +++ b/lib/puppet/type/package.rb @@ -0,0 +1,366 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink/type/state' +require 'blink/fact' + +module Blink + class State + class PackageInstalled < Blink::State + @name = :install + + def retrieve + #self.is = Blink::PackageTyping[@object.format][@object.name] + unless @parent.class.listed + @parent.class.getpkglist + end + Blink.debug "package install state is %s" % self.is + end + + def sync + #begin + raise "cannot sync package states yet" + #rescue + # raise "failed to sync #{@params[:file]}: #{$!}" + #end + + #return :package_installed + end + end + end + + class Type + # packages are complicated because each package format has completely + # different commands. We need some way to convert specific packages + # into the general package object... + class Package < Type + attr_reader :version, :format + @states = [ + Blink::State::PackageInstalled + ] + @parameters = [ + :format, + :name, + :status, + :version, + :category, + :platform, + :root, + :vendor, + :description + ] + + @name = :package + @namevar = :name + @listed = false + + @allowedmethods = [:types] + @@types = nil + + def Package.clear + @listed = false + super + end + + def Package.listed + return @listed + end + + def Package.types(array) + unless array.is_a?(Array) + array = [array] + end + @@types = array + Blink.warning "Types are %s" % array.join(" ") + end + + def Package.getpkglist + if @@types.nil? + case Blink::Fact["operatingsystem"] + when "SunOS": @@types = ["sunpkg"] + when "Linux": + case Blink::Fact["distro"] + when "Debian": @@types = ["dpkg"] + else + raise "No default type for " + Blink::Fact["distro"] + end + else + raise "No default type for " + Blink::Fact["operatingsystem"] + end + end + + list = @@types.collect { |type| + if typeobj = Blink::PackagingType[type] + # pull all of the objects + typeobj.list + else + raise "Could not find package type '%s'" % type + end + }.flatten + @listed = true + return list + end + + def Package.installedpkg(hash) + # this is from code, so we don't have to do as much checking + name = hash[:name] + + # if it already exists, modify the existing one + if object = Package[name] + states = {} + object.states.each { |state| + Blink.warning "Adding %s" % state.name.inspect + states[state.name] = state + } + hash.each { |var,value| + if states.include?(var) + Blink.verbose "%s is a set state" % var.inspect + states[var].is = value + else + Blink.verbose "%s is not a set state" % var.inspect + if object[var] and object[var] != value + Blink.warning "Overriding %s => %s on %s with %s" % + [var,object[var],name,value] + end + + object[var] = value + + # swap the values if we're a state + if states.include?(var) + Blink.verbose "Swapping %s because it's a state" % var + states[var].is = value + states[var].should = nil + else + Blink.verbose "%s is not a state" % var.inspect + Blink.verbose "States are %s" % states.keys.collect { |st| + st.inspect + }.join(" ") + end + end + } + return object + else # just create it + return self.new(hash) + end + end + + # okay, there are two ways that a package could be created... + # either through the language, in which case the hash's values should + # be set in 'should', or through comparing against the system, in which + # case the hash's values should be set in 'is' + def initialize(hash) + super + end + + end # Blink::Type::Package + end + + class PackagingType + attr_writer :list, :install, :remove, :check + + @@types = Hash.new(false) + + def PackagingType.[](name) + if @@types.include?(name) + return @@types[name] + else + Blink.warning name.inspect + Blink.warning @@types.keys.collect { |key| + key.inspect + }.join(" ") + return nil + end + end + + # whether a package is installed or not + def [](name) + return @packages[name] + end + + [:list, :install, :remove, :check].each { |method| + self.send(:define_method, method) { + # retrieve the variable + var = eval("@" + method.id2name) + if var.is_a?(Proc) + var.call() + else + raise "only blocks are supported right now" + end + } + } + + def initialize(name) + if block_given? + yield self + end + + @packages = Hash.new(false) + @@types[name] = self + end + + def retrieve + @packages.clear() + + @packages = self.list() + end + end + + PackagingType.new("dpkg") { |type| + type.list = proc { + packages = [] + + # dpkg only prints as many columns as you have available + # which means we don't get all of the info + # stupid stupid + oldcol = ENV["COLUMNS"] + ENV["COLUMNS"] = "500" + + # list out all of the packages + open("| dpkg -l") { |process| + # our regex for matching dpkg output + regex = %r{^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$} + fields = [:status, :name, :install, :description] + hash = {} + + 5.times { process.gets } # throw away the header + + # now turn each returned line into a package object + process.each { |line| + if match = regex.match(line) + hash.clear + + fields.zip(match.captures) { |field,value| + hash[field] = value + } + packages.push Blink::Type::Package.installedpkg(hash) + else + raise "failed to match dpkg line %s" % line + end + } + } + ENV["COLUMNS"] = oldcol + + return packages + } + + # we need package retrieval mechanisms before we can have package + # installation mechanisms... + #type.install = proc { |pkg| + # raise "installation not implemented yet" + #} + + type.remove = proc { |pkg| + cmd = "dpkg -r %s" % pkg.name + output = %x{#{cmd}} + if $? != 0 + raise output + end + } + } + + PackagingType.new("sunpkg") { |type| + type.list = proc { + packages = [] + hash = {} + names = { + "PKGINST" => :name, + "NAME" => nil, + "CATEGORY" => :category, + "ARCH" => :platform, + "VERSION" => :install, + "BASEDIR" => :root, + "HOTLINE" => nil, + "EMAIL" => nil, + "VENDOR" => :vendor, + "DESC" => :description, + "PSTAMP" => nil, + "INSTDATE" => nil, + "STATUS" => nil, + "FILES" => nil + } + + # list out all of the packages + open("| pkginfo -l") { |process| + # we're using the long listing, so each line is a separate piece + # of information + process.each { |line| + case line + when /^$/ then + packages.push Blink::Type::Package.installedpkg(hash) + hash.clear + when /\s*(\w+):\s+(.+)/ + name = $1 + value = $2 + if names.include?(name) + hash[names[name]] = value + else + raise "Could not find %s" % name + end + when /\s+\d+.+/ + # nothing; we're ignoring the FILES info + end + } + } + return packages + } + + # we need package retrieval mechanisms before we can have package + # installation mechanisms... + #type.install = proc { |pkg| + # raise "installation not implemented yet" + #} + + type.remove = proc { |pkg| + cmd = "pkgrm -n %s" % pkg.name + output = %x{#{cmd}} + if $? != 0 + raise output + end + } + } + + # this is how we retrieve packages + class PackageSource + attr_accessor :uri + attr_writer :retrieve + + @@sources = Hash.new(false) + + def PackageSource.get(file) + type = file.sub(%r{:.+},'') + source = nil + if source = @@sources[type] + return source.retrieve(file) + else + raise "Unknown package source: %s" % type + end + end + + def initialize(name) + if block_given? + yield self + end + + @@sources[name] = self + end + + def retrieve(path) + @retrieve.call(path) + end + + end + + PackageSource.new("file") { |obj| + obj.retrieve = proc { |path| + # this might not work for windows... + file = path.sub(%r{^file://},'') + + if FileTest.exists?(file) + return file + else + raise "File %s does not exist" % file + end + } + } +end diff --git a/lib/puppet/type/process.rb b/lib/puppet/type/process.rb new file mode 100644 index 000000000..b42f75f26 --- /dev/null +++ b/lib/puppet/type/process.rb @@ -0,0 +1,83 @@ +#!/usr/local/bin/ruby -w + +# DISABLED +# I'm only working on services, not processes, right now + +module Blink + class State + class ProcessRunning < State + def retrieve + running = 0 + regex = Regexp.new(@params[:pattern]) + begin + # this ps is only tested on Solaris + # XXX yeah, this definitely needs to be fixed... + %x{ps -ef -U #{@params[:user]}}.split("\n").each { |process| + if regex.match(process) + running += 1 + end + } + rescue + # this isn't correct, but what the hell + Blink::Message.new( + :level => :error, + :source => self.parent, + :message => "Failed to run ps" + ) + end + + self.state = running + Blink.debug "there are #{running} #{self.parent} processes for start" + end + + def <=>(other) + self.state < 1 + end + + def fix + require 'etc' + # ruby is really cool + uid = 0 + if @params[:user].is_a? Integer + uid = @params[:user] + else + uid = Etc.getpwnam(@params[:user]).uid + end + Kernel.fork { + Process.uid = uid + Process.euid = uid + string = @params[:binary] + (@params[:arguments] || "") + Blink::Message.new( + :level => :notice, + :source => self.parent, + :message => "starting" + ) + Kernel.exec(string) + } + end + end + end + class Type + class BProcess < Type + attr_reader :stat, :path + @parameters = [:start, :stop, :user, :pattern, :binary, :arguments] + @name = :process + + @namevar = :pattern + + Blink::Relation.new(self, Blink::Operation::Start, { + :user => :user, + :pattern => :pattern, + :binary => :binary, + :arguments => :arguments + }) + + Blink::Relation.new(self, Blink::Operation::Stop, { + :user => :user, + :pattern => :pattern + }) + + end # Blink::Type::BProcess + end # Blink::Type + +end diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb new file mode 100644 index 000000000..f17f17e3f --- /dev/null +++ b/lib/puppet/type/service.rb @@ -0,0 +1,186 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# this is our main way of managing processes right now +# +# a service is distinct from a process in that services +# can only be managed through the interface of an init script +# which is why they have a search path for initscripts and such + +module Blink + class State + class ServiceRunning < State + @name = :running + #@event = :file_created + + # this whole thing is annoying + # i should probably just be using booleans, but for now, i'm not... + def should=(should) + case should + when false,0,"0": + should = 0 + when true,1,"1": + should = 1 + else + Blink.warning "%s: interpreting '%s' as false" % + [self.class,should] + should = 0 + end + @should = should + end + + def retrieve + self.is = self.running() + Blink.debug "Running value for '%s' is '%s'" % + [self.parent.name,self.is] + end + + # should i cache this info? + def running + begin + status = self.parent.initcmd("status") + Blink.debug "initcmd status for '%s' is '%s'" % + [self.parent.name,status] + + if status # the command succeeded + return 1 + else + return 0 + end + rescue SystemCallError + raise "Could not execute %s" % initscript + end + + end + + def sync + if self.running > 0 + status = 1 + else + status = 0 + end + Blink.debug "'%s' status is '%s' and should be '%s'" % + [self,status,should] + event = nil + if self.should > 0 + if status < 1 + Blink.debug "Starting '%s'" % self + if self.parent.initcmd("start") + event = :service_started + else + raise "Failed to start '%s'" % self.parent.name + end + else + Blink.debug "'%s' is already running, yo" % self + #Blink.debug "Starting '%s'" % self + #unless self.parent.initcmd("start") + # raise "Failed to start %s" % self.name + #end + end + elsif status > 0 + Blink.debug "Stopping '%s'" % self + if self.parent.initcmd("stop") + event = :service_stopped + else + raise "Failed to stop %s" % self.name + end + else + Blink.debug "Not running '%s' and shouldn't be running" % self + end + + return event + end + end + end + class Type + class Service < Type + attr_reader :stat + @states = [ + Blink::State::ServiceRunning + ] + @parameters = [ + :name, + :pattern + ] + + @functions = [ + :setpath + ] + + @name = :service + @namevar = :name + + @searchpaths = Array.new + @allowedmethods = [:setpath] + + def Service.search(name) + @searchpaths.each { |path| + # must specify that we want the top-level File, not Blink::...::File + fqname = ::File.join(path,name) + begin + stat = ::File.stat(fqname) + rescue + # should probably rescue specific errors... + Blink.debug("Could not find %s in %s" % [name,path]) + next + end + + # if we've gotten this far, we found a valid script + return fqname + } + raise "Could not find init script for '%s'" % name + end + + def Service.setpath(ary) + # verify each of the paths exists + #ary.flatten! + @searchpaths = ary.find_all { |dir| + retvalue = false + begin + retvalue = ::File.stat(dir).directory? + rescue => detail + Blink.verbose("Directory %s does not exist: %s" % [dir,detail]) + # just ignore it + end + # disallow relative paths + #if dir !~ /^\// + # retvalue = false + #end + retvalue + } + end + + # it'd be nice if i didn't throw the output away... + # this command returns true if the exit code is 0, and returns + # false otherwise + def initcmd(cmd) + script = self.initscript + + Blink.debug "Executing '%s %s' as initcmd for '%s'" % + [script,cmd,self] + + rvalue = Kernel.system("%s %s" % + [script,cmd]) + + Blink.debug "'%s' ran with exit status '%s'" % + [cmd,rvalue] + + + rvalue + end + + def initscript + if defined? @initscript + return @initscript + else + @initscript = Service.search(self.name) + end + end + + def refresh + self.initcmd("restart") + end + end # Blink::Type::Service + end # Blink::Type +end diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb new file mode 100644 index 000000000..4706b04b7 --- /dev/null +++ b/lib/puppet/type/state.rb @@ -0,0 +1,135 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'blink/element' +require 'blink/statechange' + +#--------------------------------------------------------------- +# this is a virtual base class for states +# states are self-contained building blocks for objects + +# States can currently only be used for comparing a virtual "should" value +# against the real state of the system. For instance, you could verify that +# a file's owner is what you want, but you could not create two file objects +# and use these methods to verify that they have the same owner +module Blink +class State < Blink::Element + attr_accessor :is, :should, :parent + + @virtual = true + + #--------------------------------------------------------------- + # every state class must tell us what its name will be (as a symbol) + # this determines how we will refer to the state during usage + # e.g., the Owner state for Files might say its name is :owner; + # this means that we can say "file[:owner] = 'yayness'" + def State.name + return @name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # which event gets generated if this state change happens + def State.generates + return @event + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # if we're not in sync, return a statechange capable of putting us + # in sync + def evaluate + Blink.verbose "evaluating %s" % self + self.retrieve + if self.insync? + Blink.verbose "%s is in sync" % self + return nil + else + return Blink::StateChange.new(self) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # return the full path to us, for logging and rollback + def fqpath + return @parent.fqpath, self.name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # we aren't actually comparing the states themselves, we're only + # comparing the "should" value with the "is" value + def insync? + Blink.debug "%s value is '%s', should be '%s'" % + [self,self.is.inspect,self.should.inspect] + self.is == self.should + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def initialize(hash) + @is = nil + + unless hash.include?(:parent) + raise "State %s was not passed a parent" % self + end + @parent = hash[:parent] + + if hash.include?(:should) + self.should = hash[:should] + else # we got passed no argument + # leave @should undefined + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # for testing whether we should actually do anything + def noop + unless defined? @noop + @noop = false + end + tmp = @noop || self.parent.noop || Blink[:noop] || false + Blink.notice "noop is %s" % tmp + return tmp + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def refresh(transaction) + # self.retrieve + + # we definitely need some way to batch these refreshes, so a + # given object doesn't get refreshed multiple times in a single + # run + # @parent.refresh + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # each state class must define the name() method, and state instances + # do not change that name + # this implicitly means that a given object can only have one state + # instance of a given state class + def name + return self.class.name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # retrieve the current state from the running system + def retrieve + raise "'retrieve' method was not overridden by %s" % self.class + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + return "%s(%s)" % [@parent.name,self.name] + end + #--------------------------------------------------------------- +end +end diff --git a/lib/puppet/type/symlink.rb b/lib/puppet/type/symlink.rb new file mode 100644 index 000000000..772b5a831 --- /dev/null +++ b/lib/puppet/type/symlink.rb @@ -0,0 +1,109 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'etc' +require 'blink/type/state' +require 'blink/type/file' + +module Blink + # okay, how do we deal with parameters that don't have operations + # associated with them? + class State + class SymlinkTarget < Blink::State + require 'etc' + attr_accessor :file + + @name = :target + + def create + begin + Blink.debug("Creating symlink '%s' to '%s'" % + [self.parent[:path],self.should]) + unless File.symlink(self.should,self.parent[:path]) + raise TypeError.new("Could not create symlink '%s'" % + self.parent[:path]) + end + rescue => detail + raise TypeError.new("Cannot create symlink '%s': %s" % + [self.parent[:path],detail]) + end + end + + def remove + if FileTest.symlink?(self.parent[:path]) + Blink.debug("Removing symlink '%s'" % self.parent[:path]) + begin + File.unlink(self.parent[:path]) + rescue + raise TypeError.new("Failed to remove symlink '%s'" % + self.parent[:path]) + end + elsif FileTest.exists?(self.parent[:path]) + raise TypeError.new("Cannot remove normal file '%s'" % + self.parent[:path]) + else + Blink.debug("Symlink '%s' does not exist" % + self.parent[:path]) + end + end + + def retrieve + stat = nil + + if FileTest.symlink?(self.parent[:path]) + self.is = File.readlink(self.parent[:path]) + Blink.debug("link value is '%s'" % self.is) + return + else + self.is = nil + return + end + end + + # this is somewhat complicated, because it could exist and be + # a file + def sync + if self.should.nil? + self.remove() + else # it should exist and be a symlink + if FileTest.symlink?(self.parent[:path]) + path = File.readlink(self.parent[:path]) + if path != self.should + self.remove() + self.create() + end + elsif FileTest.exists?(self.parent[:path]) + raise TypeError.new("Cannot replace normal file '%s'" % + self.parent[:path]) + else + self.create() + end + end + + #self.parent.newevent(:event => :inode_changed) + end + end + end + + class Type + class Symlink < Type + attr_reader :stat, :path, :params + # class instance variable + @states = [ + Blink::State::FileUID, + Blink::State::FileGroup, + Blink::State::FileMode, + Blink::State::SymlinkTarget + ] + + @parameters = [ + :path + ] + + @name = :symlink + @namevar = :path + end # Blink::Type::Symlink + end # Blink::Type + +end diff --git a/lib/puppet/type/typegen.rb b/lib/puppet/type/typegen.rb new file mode 100644 index 000000000..85f04912c --- /dev/null +++ b/lib/puppet/type/typegen.rb @@ -0,0 +1,146 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# parse and write configuration files using objects with minimal parsing abilities + +require 'etc' +require 'blink/type' + +module Blink + class Type +class TypeGenerator < Blink::Type + include Enumerable + + @namevar = :name + @name = :typegen + @abstract = true + + @parameters = [:name] + @states = [] + + #--------------------------------------------------------------- + def TypeGenerator.[](name) + return @subclasses[name] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.inherited(subclass) + #subclass.initvars + super(subclass) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # we don't need to 'super' here because type.rb already runs initvars + # in Type#inherited + def TypeGenerator.initvars + @subclasses = Hash.new(nil) + super + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.name + return @name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.name=(name) + @name = name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.namevar + return @namevar || :name + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.namevar=(namevar) + Blink.debug "Setting namevar for %s to %s" % [self,namevar] + unless namevar.is_a? Symbol + namevar = namevar.intern + end + @namevar = namevar + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def TypeGenerator.newtype(arghash) + unless defined? @parameters + raise "Type %s is set up incorrectly" % self + end + + arghash.each { |key,value| + if key.class != Symbol + # convert to a symbol + arghash[key.intern] = value + arghash.delete key + key = key.intern + end + unless @parameters.include?(key) + raise "Invalid argument %s on class %s" % + [key,self] + end + + } + + # turn off automatically checking all arguments + #@parameters.each { |option| + # unless arghash.include?(option) + # p arghash + # raise "Must pass %s to class %s" % + # [option,self] + # end + #} + + if @subclasses.include?(arghash[:name]) + raise "File type %s already exists" % arghash[:name] + end + + klassname = arghash[:name].capitalize + + # create the file type + Blink::Type.module_eval " + class %s < %s + end" % [klassname,self] + klass = eval(klassname) + klass.name = arghash[:name] + + @subclasses[arghash[:name]] = klass + + arghash.each { |option,value| + method = option.id2name + "=" + if klass.respond_to?(method) + #Blink.debug "Setting %s on %s to '%s'" % [option,klass,arghash[option]] + klass.send(method,arghash[option]) + else + Blink.debug "%s does not respond to %s" % [klass,method] + end + } + + # i couldn't get the method definition stuff to work + # oh well + # probably wouldn't want it in the end anyway + #@parameters.each { |option| + # writer = option.id2name + "=" + # readproc = proc { eval("@" + option.id2name) } + # klass.send(:define_method,option,readproc) + # writeproc = proc { |value| module_eval("@" + option.id2name) = value } + # klass.send(:define_method,writer,writeproc) + # klass.send(writer,hash[option]) + #} + + #Blink::Type.inherited(klass) + Blink::Type.buildtypehash + return klass + end + #--------------------------------------------------------------- +end +#--------------------------------------------------------------- +end +end diff --git a/lib/puppet/type/typegen/filerecord.rb b/lib/puppet/type/typegen/filerecord.rb new file mode 100644 index 000000000..fb9030c12 --- /dev/null +++ b/lib/puppet/type/typegen/filerecord.rb @@ -0,0 +1,243 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# parse and write configuration files using objects with minimal parsing abilities + +require 'etc' +require 'blink/type' +require 'blink/type/typegen' + +#--------------------------------------------------------------- +class Blink::Type::FileRecord < Blink::Type::TypeGenerator + attr_accessor :fields, :namevar, :splitchar, :object + + @parameters = [:name, :splitchar, :fields, :namevar, :filetype, :regex, :joinchar] + @abstract = true + @metaclass = true + + @namevar = :name + @name = :filerecord + + #--------------------------------------------------------------- + def FileRecord.newtype(hash) + #shortname = hash[:name] + #hash[:name] = hash[:filetype].name.capitalize + hash[:name].capitalize + klass = super(hash) + #klass.name = shortname + klass.parameters = hash[:fields] + #klass.namevar = hash[:namevar] + klass.filetype = hash[:filetype] + hash.delete(:fields) + hash.delete(:namevar) + return klass + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.fields=(ary) + @fields = ary + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.fields + return @fields + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.filetype + @filetype + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.filetype=(filetype) + if filetype.is_a?(String) + @filetype = Blink::Type::FileType[filetype] + elsif filetype.is_a?(Blink::Type::FileType) + @filetype = filetype + else + raise "Cannot use objects of type %s as filetypes" % filetype + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.joinchar=(char) + @joinchar = char + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.joinchar + unless defined? @joinchar + @joinchar = nil + end + @joinchar + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.match(object,line) + matchobj = nil + begin + matchobj = self.regex.match(line) + rescue RegexpError => detail + raise + end + + if matchobj.nil? + return nil + else + child = self.new(object) + child.match = matchobj + return child + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.regex=(regex) + @regex = regex + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.regex + # the only time @regex is allowed to be nil is if @splitchar is defined + if @regex.nil? + if @splitchar.nil? + raise "%s defined incorrectly -- splitchar or regex must be specified" % + self + else + ary = [] + text = @fields.collect { |field| + "([^%s]*)" % @splitchar + }.join(@splitchar) + begin + @regex = Regexp.new(text) + rescue RegexpError => detail + raise "Could not create splitregex from %s" % @splitchar + end + Blink.debug("Created regexp %s" % @regex) + end + elsif @regex.is_a?(String) + begin + @regex = Regexp.new(@regex) + rescue RegexpError => detail + raise "Could not create splitregex from %s" % @regex + end + end + return @regex + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.splitchar=(char) + @splitchar = char + #@regex = %r{#{char}} + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileRecord.splitchar + return @splitchar + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def [](field) + # @parameters[field] + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def []=(field,value) + # @parameters[field] = value + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def ==(other) + unless self.class == other.class + return false + end + + unless self.name == other.name + return false + end + @parameters.keys { |field| + unless self[field] == other[field] + Blink.debug("%s -> %s has changed" % [self.name, field]) + return false + end + } + return true + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def initialize(hash) + if self.class == Blink::Type::FileRecord + self.class.newtype(hash) + return + end + @parameters = {} + #if block_given? + # yield self + #end + super(hash) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def match=(matchobj) + @match = matchobj + #puts "captures are [%s]" % [matchobj.captures] + self.class.fields.zip(matchobj.captures) { |field,value| + @parameters[field] = value + #puts "%s => %s" % [field,@parameters[field]] + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def record=(record) + begin + ary = record.split(self.class.regex) + rescue RegexpError=> detail + raise RegexpError.new(detail) + end + self.class.fields.each { |field| + @parameters[field] = ary.shift + #puts "%s => %s" % [field,@parameters[field]] + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def name + if @parameters.include?(self.class.namevar) + return @parameters[self.class.namevar] + else + raise "No namevar '%s' for objects of type %s" % + [self.class.namevar,self.class.to_s] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + ary = self.class.fields.collect { |field| + if ! @parameters.include?(field) + raise "Object %s is missing field %s" % [self.name,field] + else + @parameters[field] + end + }.join(self.class.joinchar || self.class.splitchar) + end + #--------------------------------------------------------------- +end +#--------------------------------------------------------------- diff --git a/lib/puppet/type/typegen/filetype.rb b/lib/puppet/type/typegen/filetype.rb new file mode 100644 index 000000000..4b3b89db8 --- /dev/null +++ b/lib/puppet/type/typegen/filetype.rb @@ -0,0 +1,316 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# parse and write configuration files using objects with minimal parsing abilities + +require 'blink/type' +require 'blink/type/typegen' + +class Blink::Type::FileType < Blink::Type::TypeGenerator + attr_accessor :childtype + + @parameters = [:name, :linesplit, :escapednewlines] + #@abstract = true + @metaclass = true + + @namevar = :name + @name = :filetype + + @modsystem = true + + #--------------------------------------------------------------- + def FileType.newtype(hash) + unless hash.include?(:linesplit) + hash[:linesplit] = "\n" + end + + # i don't think there's any reason to 'super' this + #klass = Blink::Type::TypeGenerator.newtype(hash) + klass = super(hash) + + klass.escapednewlines = true + klass.namevar = :name + klass.parameters = [:name, :path, :complete] + + #klass.childtype = Blink::Type::FileRecord.newtype( + # :name => hash[:name] + "_record", + # :splitchar => hash[:recordsplit], + # :fields => hash[:fields], + # :namevar => hash[:namevar] + #) + #klass.addrecord( + # :name => hash[:name] + "_record", + # :splitchar => hash[:recordsplit], + # :fields => hash[:fields], + # :namevar => hash[:namevar] + #) + + return klass + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # currently not used + def FileType.addrecord(hash) + unless defined? @records + @records = {} + end + hash[:filetype] = self + + # default to the naming field being the first field provided + unless hash.include?(:namevar) + hash[:namevar] = hash[:fields][0] + end + + recordtype = Blink::Type::FileRecord.newtype(hash) + @records[recordtype.name] = recordtype + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.records + return @records + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.escapednewlines=(value) + @escnlines = value + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.escapednewlines + if ! defined? @escnlines or @escnlines.nil? + return false + else + return @escnlines + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.childtype + unless defined? @childtype + @childtype = nil + end + return @childtype + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.childtype=(childtype) + @childtype = childtype + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.regex + return @regex + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.linesplit=(linesplit) + @regex = %r{#{linesplit}} + @linesplit = linesplit + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def FileType.linesplit + return @linesplit + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def [](name) + # return @childhash[name] + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def []=(name,value) + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # we don't really have a 'less-than/greater-than' sense here + # so i'm sticking with 'equals' until those make sense + def ==(other) + unless self.children.length == other.children.length + Blink.debug("file has %s records instead of %s" % + [self.children.length, other.children.length]) + return self.children.length == other.children.length + end + equal = true + self.zip(other.children) { |schild,ochild| + unless schild == ochild + Blink.debug("%s has changed in %s" % + [schild.name,self.name]) + equal = false + break + end + } + + return equal + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # create a new record with a block + def add(type,&block) + obj = self.class.records[type].new(self,&block) + Blink.debug("adding %s" % obj.name) + @childary.push(obj) + @childhash[obj.name] = obj + + return obj + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def children + return @childary + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # remove a record + def delete(name) + if @childhash.has_key?(name) + child = @childhash[name] + + @childhash.delete(child) + @childary.delete(child) + else + raise "No such entry %s" % name + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def each + @childary.each { |child| + yield child + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # create a new file + def initialize(hash) + # if we are the FileType object itself, we create a new type + # otherwise, we create an instance of an existing type + # yes, this should be more straightforward + if self.class == Blink::Type::FileType + self.class.newtype(hash) + return + end + Blink.debug "Creating new '%s' file with path '%s' and name '%s'" % + [self.class.name,hash["path"],hash[:name]] + Blink.debug hash.inspect + @file = hash["path"] + + @childary = [] + @childhash = {} + super + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is where we're pretty different from other objects + # we can choose to either reparse the existing file and compare + # the objects, or we can write our file out and do an + # text comparison + def insync? + tmp = self.class.new(@file) + tmp.retrieve + + return self == tmp + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + #def name + # return @file + #end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # read the whole file in and turn it into each of the appropriate + # objects + def retrieve + str = "" + ::File.open(@file) { |fname| + fname.each { |line| + str += line + } + } + + if self.class.escapednewlines + endreg = %r{\\\n\s*} + str.gsub!(endreg,'') + end + @childary = str.split(self.class.regex).collect { |line| + childobj = nil + self.class.records.each { |name,recordtype| + if childobj = recordtype.match(self,line) + break + end + } + if childobj.nil? + Blink.warning("%s: could not match %s" % [self.name,line]) + #Blink.warning("could not match %s" % line) + next + end + + begin + Blink.debug("got child: %s(%s)" % [childobj.class,childobj.to_s]) + rescue NoMethodError + Blink.warning "Failed: %s" % childobj + end + childobj + }.reject { |child| + child.nil? + } + + @childary.each { |child| + begin + @childhash[child.name] = child + rescue NoMethodError => detail + p child + p child.class + puts detail + exit + end + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def sync + #unless self.insync? + self.write + #end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + return @childary.collect { |child| + child.to_s + }.join(self.class.linesplit) + self.class.linesplit + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def write + ::File.open(@file, "w") { |file| + file.write(self.to_s) + } + end + #--------------------------------------------------------------- +end +#--------------------------------------------------------------- -- cgit