diff options
43 files changed, 1876 insertions, 0 deletions
@@ -0,0 +1,32 @@ +$Id$ + +Introduction +============ +There are currently three main components to Blink: + +Objects +------- +These are the "primitives" of the language. If you want to affect the system, +these are the only way to do it. Their base class is in blink/objects.rb, and +they all inherit directly from that class. + +Attributes +---------- +Each of the Objects are basically a collection of attributes. The attributes +themselves are how the system is actually modified -- e.g., you set +'file("/etc/passwd").owner = X', which modifies an attribute value, and that +results in the system itelf being modified. + +Each attribute derives from blink/attribute.rb, but most attributes are actually +defined in the same file as the class that uses them (e.g., blink/objects/file.rb). + +The Wrapper +----------- +At the top level is the Blink module. It doesn't do much right now + +Starting +======== +You can start with bin/blinker, but it's probably better to just start in test/. +I've been writing simple unit tests for most of the work I've done, so you can +see how things do (and should) work by looking in there. Just run 'ruby +<testfile>'. diff --git a/bin/blinker b/bin/blinker new file mode 100755 index 000000000..d54041dc5 --- /dev/null +++ b/bin/blinker @@ -0,0 +1,89 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'facter' + +# collect enough information to make some simple decisions +begin + facts = Facter.retrieve('OperatingSystem','OperatingSystemRelease','Hostname') +rescue => error + puts "Could not retrieve facts: %s" % error + exit(47) +end + +# set up how our environment will behave +args = Hash.new + +args[:debug] = 1 + +args[:depthfirst] = 1 +args[:opinstances] = 1 + +Blink.init(args) + +# and now build a simple component, consisting of a process +# that depends on a file +component = Blink::Component.new(:name => "sleeper") + +# add our testing directory to the search path for services +Blink::Objects::Service.addpath( + File.expand_path("~/svn/blink/examples/root/etc/init.d") +) + +# here's the process itself +sleeper = Blink::Objects::Service.new( + :running => 1, + :name => "sleeper" +) + +# add it to the component +component.push(sleeper) + +#groupselector = Blink::Selector.new( + # proc { facts['Hostname'] == 'culain' } => 'luke', + # proc { facts['Hostname'] == 'kirby' } => 'sysadmin' +#) + +# and the file that our component depends on +configfile = Blink::Objects::BFile.new( + :path => File.expand_path("~/svn/blink/examples/root/etc/configfile"), + :owner => "luke", + :group => "sysadmin", + :mode => 0644 +) + #:check => "md5" + +component.push(configfile) + +# i guess i should call subscribe on the object triggering the event, +# not the one responding.... +# except that really, the one responding is merely listening to events +# that pass through it... +# XXX okay, i need to decide whether i use generic components and lose +# the actual dependency information, or whether i use literal dependency +# information +configfile.subscribe( + :event => :inode_changed, + :object => sleeper +) { |me,object,event| + puts "#{me} restarting because of #{event} from #{object}" + me.callop("stop") + me.callop("start") +} + +component.retrieve() + +#unless component.insync? +# component.sync() +#end + +puts sleeper.insync?() +sleeper.sync() +sleeper.retrieve() +puts sleeper.insync?() + +#other.contains(test) + +#Blink.run diff --git a/bin/sleeper b/bin/sleeper new file mode 100755 index 000000000..5382383cd --- /dev/null +++ b/bin/sleeper @@ -0,0 +1,97 @@ +#!/usr/bin/perl -w + +### +# sleep indefinitely as a debug + +use strict; +use Getopt::Long; +use Pod::Usage; + +#----------------------------------------------------------------- +sub daemonize +{ + use POSIX 'setsid'; + $| = 1; + chdir '/' or die "Can't chdir to /: $!\n"; + open STDIN, "/dev/null" or die "Can't read /dev/null: $!\n"; + open STDOUT, "> /dev/null" or die "Can't write to /dev/null: $!\n"; + defined(my $pid = fork()) or die "Can't fork: $!\n"; + #print STDERR $pid, "\n"; + exit if $pid; + setsid or die "Can't start a new session: $!\n"; + open STDERR, ">&STDOUT" or die "Can't dup stdout: $!\n"; +} +#----------------------------------------------------------------- + +my ($help,$opt_result,$debug,$fun); + +$opt_result = GetOptions +( + "help" => \$help, + "debug" => \$debug, + "fun" => \$fun, +); + +if (! $opt_result) +{ + pod2usage('-exitval' => 1, '-verbose' => 0); + exit(1); +} + +if ($help) +{ + pod2usage('-exitval' => 1, '-verbose' => 2); + exit; +} + +unless ($debug) { + daemonize(); +} + +while(1){ + sleep 600; +} + +=head1 NAME + +template - this is a template script and should be copied and modded + +=head1 SYNOPSIS + +template [-help] + +=head1 DESCRIPTION + +B<template> is a stub script. + +=head1 OPTIONS + +=over 4 + +=item help + +Prints out help page. + +=back + +B<Example> + +template + +=head1 BUGS + +This script shouldn't be modified, or has apparently not been documented. + +=head1 SEE ALSO + +L<Cat> + +=head1 AUTHOR + +Luke A. Kanies, luke.kanies@cat.com + +=for html <hr> + +I<$Id: sleeper,v 1.1 2004/03/04 03:23:03 luke Exp $> + +=cut diff --git a/examples/assignments b/examples/assignments new file mode 100644 index 000000000..d21895392 --- /dev/null +++ b/examples/assignments @@ -0,0 +1 @@ +name = "value" diff --git a/examples/code.bl b/examples/code.bl new file mode 100644 index 000000000..782991582 --- /dev/null +++ b/examples/code.bl @@ -0,0 +1 @@ +$var = "funtest"; diff --git a/examples/code/file.bl b/examples/code/file.bl new file mode 100644 index 000000000..220a727b9 --- /dev/null +++ b/examples/code/file.bl @@ -0,0 +1,7 @@ +# $Id$ + +file["/etc/sudoers"] { + :owner => "root" + :group => "root" + :mode => "0440" +} diff --git a/examples/cron2.bl b/examples/cron2.bl new file mode 100644 index 000000000..61d48b4dc --- /dev/null +++ b/examples/cron2.bl @@ -0,0 +1,57 @@ +# $Id: cron2.bl,v 1.1 2003/06/19 06:29:53 luke Exp $ + +# some data definitions + +# this kind of sucks because i have two very different syntaxes: +# one for inside the object, and one for outside +# that doesn't really seem to make sense..... + +# is there a way around it? can i scope all variables automatically +# inside the objects? + +# there's a difference between a collector that joins members of an object +# and a collector that joins multiple objects together, right? +cron("cfengine") = { + comment => "This is cfengine"; + command => "/usr/local/sbin/cfexecd -F"; + minute => ( 0,30 ); + user => user("root") || "root"; # this sucks +}; + +# this is less pure, but it's less muddy at the same time, somehow +# hmmm +cron("cfengine") = { + $comment = "This is cfengine"; + $command = "/usr/local/sbin/cfexecd -F"; + $minute = ( 0,30 ); + $user = user("root") || "root"; # this sucks; is it a code reference? + # a string? i have no idea +}; + +# are these hash members? am i assigning a hash, or filling in the members +# on an object? +chunk("cronjob") = { + separator => "\n"; + requires => cron; + members => (comment || name, command); +}; + +# this ends up abstracting the members +# how do i get to the username now? +# a chunk is formatting for an individual object; there doesn't need to +# be an abstraction +chunk("cronline") = { + separator => " "; + requires => chunk("cronjob"); + members => (minute || "*", hour || "*", mday || "*", month || "*", wday || "*", + command); +}; + +pile("crontab") = { + separator => "\n"; + members => chunk("cronline")->user(user); +}; + +# and then some actual logic, and stuff + + diff --git a/examples/events.bl b/examples/events.bl new file mode 100644 index 000000000..475d66fab --- /dev/null +++ b/examples/events.bl @@ -0,0 +1,36 @@ +load(trigger); + +trigger { + onchange => { + /etc/inetd.conf => process(restart => inetd), + /etc/syslog.conf => process(restart => syslogd), + /etc/syslog-ng.conf => process(restart => syslog-ng), + }, +}; + +load(process); + +process::inetd.binary("/usr/sbin/inetd"); +process::inetd.pattern("inetd"); +process::inetd.arguments("-s -t"); +process::inetd.config("/etc/inet/inetd.conf"); + +# crap, does this get interpreted at run time, set time, or execution time? +# ugh +# and how the hell do I pass that back in to the process? +# basically, the arguments need to be parsed, and then passed to the +# method, but it's the method that knows it's dealing with its own object +process::inetd.start($_.binary, " ", $_.arguments); +process::inetd.restart("/usr/bin/pkill -HUP ", $_.pattern); + +# this is a system-level notification, isn't it? notifies that +# process is now subscribed to this event +# we could have both parts do something; the system is responsible +# for registering the subscription, and the process module is responsible +# for actually dealing with it + +# what i'm trying to say here is "reload inetd if this file changes" +process::inetd.event(filechange($_.config) => $_.restart); +process::inetd.subscribe(filechange.{$_.config} => {$_.restart}); + +*sycamore:: { process(inetd)++ } diff --git a/examples/facts b/examples/facts new file mode 100644 index 000000000..de8d83e6e --- /dev/null +++ b/examples/facts @@ -0,0 +1,47 @@ +platform? { + :sunos => { thing("name"){ :param => "value" } } +} + +thing("name") { + :param => platform{ + :sunos => "sunosvalue" + :aix => "aixvalue" + :default => "defaultvalue" + } +} + +thing("name") { + :param => test ? true : false + :param => platform? < + :sunos => "sunosvalue" + :aix => "aixvalue" + hostname?(:myhost) => "hostvalue" + > + true : false +} + +string = case platform + when :sunos then "sunosvalue" + when :aix then "aixvalue" + when :aix then "aixvalue" +end + +string = platform ? { + :sunos => :value1 + :aix => :value2 + :hpux => :value3 +} + +if platform == :sunos then "sunosvalue" +string = Fact["platform"] ? { + :sunos => "sunosvalue" +} + +platform = retrieve("platform") + + ----------------------- + / :sunos => :sunosvalue \ +string = platform? < :aix => :aixvalue > + \ :hpux => :hpuxvalue / + ----------------------- + diff --git a/examples/objects b/examples/objects new file mode 100644 index 000000000..4c4e6cf8b --- /dev/null +++ b/examples/objects @@ -0,0 +1,25 @@ +type = filetype["colon-separated"] { + :recordseparator => "\n" + :fieldseparator => "\n" + :fields = %{name password uid gid ....} + :namefield = "name" +} + +passwd = filetype["colon-separated"].new("/etc/passwd") +shadow = filetype["colon-separated"].new("/etc/shadow") + +user = jointype { + passwd => "name" + shadow => "name" +} + +passwd = type["/etc/passwd"] + + +user.new() + + +passwd["yaytest"] = { + :uid => 100 + ... +} diff --git a/examples/one.bl b/examples/one.bl new file mode 100644 index 000000000..428372dd9 --- /dev/null +++ b/examples/one.bl @@ -0,0 +1,30 @@ +#!/home/luke/cvs/blink/bin/blink + +# okay, we'll have a constraint system later, and it will look something like +# the following: + +# Constraint.isbinary = sub { |path| -x path } +# binary.constrain(isbinary) +# protocol.constrain { |string| +# string == "udp" || string == "tcp" +# } + +class Base { + strings = format + +} + +class Inetd { + strings = wait, endpoint, command, binary, protocol, uid, name + +} + +Inetd::telnet = { + wait => "nowait", + endpoint => "stream", + command => "in.telnetd", + binary => "/usr/sbin/in.telnetd", + protocol => "tcp6", + uid => "root", + name => "telnet", +} diff --git a/examples/root/bin/sleeper b/examples/root/bin/sleeper new file mode 100755 index 000000000..6c1495928 --- /dev/null +++ b/examples/root/bin/sleeper @@ -0,0 +1,97 @@ +#!/usr/bin/perl -w + +### +# sleep indefinitely as a debug + +use strict; +use Getopt::Long; +use Pod::Usage; + +#----------------------------------------------------------------- +sub daemonize +{ + use POSIX 'setsid'; + $| = 1; + chdir '/' or die "Can't chdir to /: $!\n"; + open STDIN, "/dev/null" or die "Can't read /dev/null: $!\n"; + open STDOUT, "> /dev/null" or die "Can't write to /dev/null: $!\n"; + defined(my $pid = fork()) or die "Can't fork: $!\n"; + #print STDERR $pid, "\n"; + exit if $pid; + setsid or die "Can't start a new session: $!\n"; + open STDERR, ">&STDOUT" or die "Can't dup stdout: $!\n"; +} +#----------------------------------------------------------------- + +my ($help,$opt_result,$debug,$fun); + +$opt_result = GetOptions +( + "help" => \$help, + "debug" => \$debug, + "fun" => \$fun, +); + +if (! $opt_result) +{ + pod2usage('-exitval' => 1, '-verbose' => 0); + exit(1); +} + +if ($help) +{ + pod2usage('-exitval' => 1, '-verbose' => 2); + exit; +} + +unless ($debug) { + daemonize(); +} + +while(1){ + sleep 600; +} + +=head1 NAME + +template - this is a template script and should be copied and modded + +=head1 SYNOPSIS + +template [-help] + +=head1 DESCRIPTION + +B<template> is a stub script. + +=head1 OPTIONS + +=over 4 + +=item help + +Prints out help page. + +=back + +B<Example> + +template + +=head1 BUGS + +This script shouldn't be modified, or has apparently not been documented. + +=head1 SEE ALSO + +L<Cat> + +=head1 AUTHOR + +Luke A. Kanies, luke.kanies@cat.com + +=for html <hr> + +I<$Id$> + +=cut diff --git a/examples/root/etc/configfile b/examples/root/etc/configfile new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/examples/root/etc/configfile diff --git a/examples/root/etc/init.d/sleeper b/examples/root/etc/init.d/sleeper new file mode 100755 index 000000000..7a6614087 --- /dev/null +++ b/examples/root/etc/init.d/sleeper @@ -0,0 +1,25 @@ +#! /bin/sh + +PATH=$PATH:/home/luke/svn/blink/examples/root/bin + +case "$1" in + start) + sleeper + ;; + stop) + kill `ps -ef | grep -v grep | grep sleeper | grep perl | awk '{print $2}'` + ;; + status) + if sh -c "ps -ef | grep -v grep | grep -v init.d | grep sleeper"; then + exit 0 + else + exit 1 + fi + ;; + *) + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/examples/service.bl b/examples/service.bl new file mode 100644 index 000000000..0426ff6ec --- /dev/null +++ b/examples/service.bl @@ -0,0 +1,52 @@ +service("ftp") = { + port => "21"; + protocol => "tcp"; + name => "ftp"; + comment => "this protocol sucks"; +}; + +service("telnet") = { + port => "23"; + protocol => "tcp"; + name => "telnet"; +}; + +inetsvc("telnet") = { + port => "telnet"; + protocol => "tcp"; + binary => "/usr/sbin/in.telnetd"; + command => "in.telnetd" +}; + +inetsvc("ftp") = { + port => "ftp"; + protocol => "tcp"; + binary => "/usr/sbin/in.ftpd"; + command => "in.ftpdd" +}; + +chunk("inetdline") = { + separator => " "; + members => (port, protocol, binary, command); +}; + +chunk("inetdset") = { + separator => "\n"; + members => (comment || name); +}; + +# yep, this sucks... +chunk("service") = { + separator => "\n"; + members => (name, protocol . "/" . name, " " * alias || "", "# " . comment || ""); +}; + +pile("inetd") = { + separator => "\n"; + members => chunk("inetdset"); +}; + +pile("services") = { + separator => "\n"; + members => chunk("service"); +}; diff --git a/install.rb b/install.rb new file mode 100644 index 000000000..6af135214 --- /dev/null +++ b/install.rb @@ -0,0 +1,268 @@ +#! /usr/bin/env ruby
+#--
+# Copyright 2004 Austin Ziegler <ruby-install@halostatue.ca>
+# Install utility. Based on the original installation script for rdoc by the
+# Pragmatic Programmers.
+#
+# This program is free software. It may be redistributed and/or modified under
+# the terms of the GPL version 2 (or later) or the Ruby licence.
+#
+# Usage
+# -----
+# In most cases, if you have a typical project layout, you will need to do
+# absolutely nothing to make this work for you. This layout is:
+#
+# bin/ # executable files -- "commands"
+# lib/ # the source of the library
+# tests/ # unit tests
+#
+# The default behaviour:
+# 1) Run all unit test files (ending in .rb) found in all directories under
+# tests/.
+# 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd),
+# all .rb files in lib/, ./README, ./ChangeLog, and ./Install.
+# 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd),
+# and all .rb files in lib/. This is disabled by default on Win32.
+# 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a
+# if a corresponding batch file (.bat or .cmd) exists in the bin directory,
+# it will be copied over as well. Otherwise, a batch file (always .bat) will
+# be created to run the specified command.
+# 5) Install all library files ending in .rb from lib/ into Ruby's
+# site_lib/version directory.
+#
+# $Id$
+#++
+
+require 'rbconfig'
+require 'find'
+require 'fileutils'
+require 'rdoc/rdoc'
+require 'optparse'
+require 'ostruct'
+
+InstallOptions = OpenStruct.new
+
+def glob(list)
+ g = list.map { |i| Dir.glob(i) }
+ g.flatten!
+ g.compact!
+ g.reject! { |e| e =~ /\.svn/ }
+ g
+end
+
+ # Set these values to what you want installed.
+bins = %w{bin/blinker}
+rdoc = glob(%w{bin/blinker lib/**/*.rb README ChangeLog Install}).reject { |e| e=~ /\.(bat|cmd)$/ }
+ri = glob(%w(bin/**/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ }
+libs = glob(%w{lib/**/*.rb})
+tests = glob(%w{tests/**/*.rb})
+
+def do_bins(bins, target, strip = 'bin/')
+ bins.each do |bf|
+ obf = bf.gsub(/#{strip}/, '')
+ install_binfile(bf, obf, target)
+ end
+end
+
+def do_libs(libs, strip = 'lib/')
+ libs.each do |lf|
+ olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, ''))
+ op = File.dirname(olf)
+ File.makedirs(op, true)
+ File.chmod(0755, op)
+ File.install(lf, olf, 0755, true)
+ end
+end
+
+##
+# Prepare the file installation.
+#
+def prepare_installation
+ InstallOptions.rdoc = true
+ if RUBY_PLATFORM == "i386-mswin32"
+ InstallOptions.ri = false
+ else
+ InstallOptions.ri = true
+ end
+ InstallOptions.tests = true
+
+ ARGV.options do |opts|
+ opts.banner = "Usage: #{File.basename($0)} [options]"
+ opts.separator ""
+ opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc|
+ InstallOptions.rdoc = onrdoc
+ end
+ opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri|
+ InstallOptions.ri = onri
+ end
+ opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest|
+ InstallOptions.tests = ontest
+ end
+ opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick|
+ InstallOptions.rdoc = false
+ InstallOptions.ri = false
+ InstallOptions.tests = false
+ end
+ opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full|
+ InstallOptions.rdoc = true
+ InstallOptions.ri = true
+ InstallOptions.tests = true
+ end
+ opts.separator("")
+ opts.on_tail('--help', "Shows this help text.") do
+ $stderr.puts opts
+ exit
+ end
+
+ opts.parse!
+ end
+
+ bds = [".", ENV['TMP'], ENV['TEMP']]
+
+ version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".")
+ ld = File.join(Config::CONFIG["libdir"], "ruby", version)
+
+ sd = Config::CONFIG["sitelibdir"]
+ if sd.nil?
+ sd = $:.find { |x| x =~ /site_ruby/ }
+ if sd.nil?
+ sd = File.join(ld, "site_ruby")
+ elsif sd !~ Regexp.quote(version)
+ sd = File.join(sd, version)
+ end
+ end
+
+ if (destdir = ENV['DESTDIR'])
+ bd = "#{destdir}#{Config::CONFIG['bindir']}"
+ sd = "#{destdir}#{sd}"
+ bds << bd
+
+ FileUtils.makedirs(bd)
+ FileUtils.makedirs(sd)
+ else
+ bds << Config::CONFIG['bindir']
+ end
+
+ InstallOptions.bin_dirs = bds.compact
+ InstallOptions.site_dir = sd
+ InstallOptions.bin_dir = bd
+ InstallOptions.lib_dir = ld
+end
+
+##
+# Build the rdoc documentation. Also, try to build the RI documentation.
+#
+def build_rdoc(files)
+ r = RDoc::RDoc.new
+ r.document(["--main", "README", "--title", "Diff::LCS -- A Diff Algorithm",
+ "--line-numbers"] + files)
+
+rescue RDoc::RDocError => e
+ $stderr.puts e.message
+rescue Exception => e
+ $stderr.puts "Couldn't build RDoc documentation\n#{e.message}"
+end
+
+def build_ri(files)
+ ri = RDoc::RDoc.new
+ ri.document(["--ri-site", "--merge"] + files)
+rescue RDoc::RDocError => e
+ $stderr.puts e.message
+rescue Exception => e
+ $stderr.puts "Couldn't build Ri documentation\n#{e.message}"
+end
+
+def run_tests(test_list)
+ begin
+ require 'test/unit/ui/console/testrunner'
+ $:.unshift "lib"
+ test_list.each do |test|
+ next if File.directory?(test)
+ require test
+ end
+
+ tests = []
+ ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) }
+ tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) }
+ tests.delete_if { |o| o == Test::Unit::TestCase }
+
+ tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) }
+ $:.shift
+ rescue LoadError
+ puts "Missing testrunner library; skipping tests"
+ end
+end
+
+##
+# Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way
+# to insert a #! line; on a Unix install, the command is named as expected
+# (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under
+# windows, we add an '.rb' extension and let file associations do their stuff.
+def install_binfile(from, op_file, target)
+ tmp_dir = nil
+ InstallOptions.bin_dirs.each do |t|
+ if File.directory?(t) and File.writable?(t)
+ tmp_dir = t
+ break
+ end
+ end
+
+ fail "Cannot find a temporary directory" unless tmp_dir
+ tmp_file = File.join(tmp_dir, '_tmp')
+ ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
+
+ File.open(from) do |ip|
+ File.open(tmp_file, "w") do |op|
+ ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
+ op.puts "#!#{ruby}"
+ op.write ip.read
+ end
+ end
+
+ if Config::CONFIG["target_os"] =~ /win/io
+ installed_wrapper = false
+
+ if File.exists?("#{from}.bat")
+ FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true)
+ installed_wrapper = true
+ end
+
+ if File.exists?("#{from}.cmd")
+ FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true)
+ installed_wrapper = true
+ end
+
+ if not installed_wrapper
+ tmp_file2 = File.join(tmp_dir, '_tmp_wrapper')
+ cwn = File.join(Config::CONFIG['bindir'], op_file)
+ cwv = CMD_WRAPPER.gsub('<ruby>', ruby.gsub(%r{/}) { "\\" }).gsub!('<command>', cwn.gsub(%r{/}) { "\\" } )
+
+ File.open(tmp_file2, "wb") { |cw| cw.puts cwv }
+ FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true)
+
+ File.unlink(tmp_file2)
+ installed_wrapper = true
+ end
+ end
+ FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true)
+ File.unlink(tmp_file)
+end
+
+CMD_WRAPPER = <<-EOS
+@echo off
+if "%OS%"=="Windows_NT" goto WinNT
+<ruby> -x "<command>" %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto done
+:WinNT
+<ruby> -x "<command>" %*
+goto done
+:done
+EOS
+
+prepare_installation
+
+run_tests(tests) if InstallOptions.tests
+build_rdoc(rdoc) if InstallOptions.rdoc
+build_ri(ri) if InstallOptions.ri
+do_bins(bins, Config::CONFIG['bindir'])
+do_libs(libs)
diff --git a/notes/design b/notes/design new file mode 100644 index 000000000..af134dbdf --- /dev/null +++ b/notes/design @@ -0,0 +1,99 @@ +Phase I: + Context + The only place you can discover non-object state + e.g., hard classes, schedules, hostgroups, etc. + +Phase II: + Objects + Build the objects up based on developed Context + This step stores triggers with the objects but cannot yet react to them + +Phase III: + Run + Results in a list of operations + +Phase IV: + Check: + Results in list of dirty objects + +Phase V: + Fix + Does all of the work + Results in triggers being pulled + (triggers are just operations, aren't they?) + +Phase V: + Run triggers + Might result in more triggers + mmm, recursion + +Phase I: + Build the data structures which we will or will not use + +Phase II: + Decide which data structures we will use, and add all hooks for triggers + and such + +Phase III: + Analyze the system for compliance with what we think it's supposed to + look like + +Phase IV: + Do all the work that we've determined needs to be done + +Phase V: + React with any triggers, ad infinitum + +------------- +The entire thing needs to be OO based, and every object needs to have both +an 'as_string' and an 'as_boolean' method that gets implicitly called. + +Methods each active object needs to have: + boolean + is the object configured + on + enables the object + off + disables the object + prepare? + uh, does stuff in preparation + check + compares configured state to server state + converge (still a bad name) + converges server state to configured state + +--------------- +What kind of builtin objects do I need? + +FileConfig + Straight monitoring of a file and verifying on-disk config (perms, et al) + Classes to set or actions to trigger if the file changes + need to differentiate inode changes from data changes +FileChunk + Some kind of format that can be filled in with values + Needs to be able to specify fixed or variable fields, yuck +CreatedFile + Collections of chunks + Do the chunks have to be ordered? Does that order come from order in + the file or some other way? Probably have a separated "ordered file" + object + Compares to the file on disk + It'd be really, really nice to have this in a database somewhere + Classes to set or actions to trigger if the file changes + +Process + Describes a running process to the greatest extent possible + Classes to set or actions to trigger if anything happens with the process + +Link + something similar to cfengine's Copy, something that says 'always make sure + this file is pinned to some other file' + It'd be cool if this system would automagically pin every file on the + system to a central repository, so you really could say every file + on the system was managed + or at least, every file that exists in the central repository + +Package + Maintains the list of installed packages, and has some means for retrieving + packages from a central location + diff --git a/notes/execution b/notes/execution new file mode 100644 index 000000000..e3433b396 --- /dev/null +++ b/notes/execution @@ -0,0 +1,28 @@ +execution path: + +parse the entire file recursively + imports are definitely inline + + + +what I'm trying to resolve is: + can the entire thing be parsed and then executed, or do the objects + need to be able to take into account the results of execution? + most other languages make a clear differentation between values + and executables, so why shouldn't we? as long as we support some + kind of executable, it should be fine + i need to keep in mind that the definitions of operations and objects + will not actually change as a result of execution, merely the details + does this matter? + +objects are created during parsing + this means that i have the choice of: + --not creating operations at parse time + --dealing with this nasty interface to parse-time operations + if we don't have this nasty interface, then execution can't affect + the contents of operations + would that make sense? a file with multiple operations whose + different operations can change the values + +here's the crucial question: + is there a difference between executing the code and performing the operations? diff --git a/notes/features b/notes/features new file mode 100644 index 000000000..ce6fe7d86 --- /dev/null +++ b/notes/features @@ -0,0 +1,13 @@ +High-level: + ability to define many objects in one section, e.g.: + Files { + "/etc/inetd.conf" = { .... } + } + + ability to set local and global defaults for a section: + Files { + template = { o=root, g=root } + "/etc/inetd.conf" < template = { ... } + } + + diff --git a/notes/goals b/notes/goals new file mode 100644 index 000000000..3bd1e9d86 --- /dev/null +++ b/notes/goals @@ -0,0 +1,17 @@ +Lists and iteration + +consistent syntax + +datatype sigils? + +module interface + +references? + +eval? + +multiline strings + +all syntax supported anywhere + +ability to evaluate any object for truth or falseness diff --git a/notes/issues b/notes/issues new file mode 100644 index 000000000..2b7fa5957 --- /dev/null +++ b/notes/issues @@ -0,0 +1,43 @@ +1. How do I specify delayed evaluation? + +This is essentially a question of a simple syntax for stored procedures. + +2. How do I specify "call this method on yourself"? + ruby's method works relatively well, but i wish i could leave out + the "|me|" stuff + +This is just a bit more complexity onto the stored procedure syntax. It is +different than storing a non-object procedure, so the syntax does have to +reflect that. + +3. Do I support implicit lists, e.g.: + cron.user('root').each { do stuff } + + something like "any instance method when called on the class produces a + list of all instances which meet the criteria of the call" + this gets muddied if i want to use variable sigils + + if i do support lists, do i try to support roll-back? + definitely not in the first version.... + + or rather, if i support lists, do i support them as facts? do + lvalues in this case ever actually do anything, such that they would need + to be rolled back? + + really, this isn't something that would be rolled back, because i'm stating + that something is a certain way based on logic in the script, not the state + of the real system + at this point; that might change... + +4. The difference between object initialization and later method calls + +5. Why _shouldn't_ I just use Ruby? + no standard 'to_b' method + specifically ordered + +6. Parsing ordering + Will I actually be able to abstract that aspect? I kinda doubt it. + +7. Module interface + I need something, preferably something that has hooks to other languages. + I can begin without one, but there definitely needs to be support for it. diff --git a/notes/language/notes b/notes/language/notes new file mode 100644 index 000000000..052828dc8 --- /dev/null +++ b/notes/language/notes @@ -0,0 +1,7 @@ +Selectors: + should they always error if a host interprets without a value? + e.g., should they always have what amounts to a default? + otherwise, we'll get valueless statements + +Truth: + we need to handle true/falseness diff --git a/notes/notes b/notes/notes new file mode 100644 index 000000000..899040d9d --- /dev/null +++ b/notes/notes @@ -0,0 +1,44 @@ +things i need: + locking + automatic reexecution for any object + with a reread of all config stuff? is this even possible? + state maintenance + event handling and subscription + failure propagation + +my basic process: + load the configs + create each object tree in a stack + register each event, with subscriptions and paths + register all callbacks + run a recursive check on each tree + depth-first? configurable? + do checks result in events or methods or what? + should checks return methods which just get executed in order? + but then what do events cause? don't i have to register callbacks? + what if the callbacks cause a change which kicks off an event? can + i worry about infinite loops? + + and i can't just register all the callbacks upfront + but all callbacks will be registered before any work is done + execute each fix + execute resulting callbacks + destruct each object tree + +hmmm + do i need object trees with callbacks? how do they relate? + currently they just seem to give containers first crack at responding + to an event + i'm looking for closure-like performance from each tree + how much should the checks know? they obviously shouldn't know much + they should not need to know the event that a fix will kick off, the + fix should know that + so it looks like i'm trying to decide between idempotent operations and + idempotent processes + if a fix gets called, it should just fix; it shouldn't need to check + whether it's necessary + tree vs array + should everything stay in the tree? what does "containment" really mean? + should i just save containment for later? because it's really about + forcing all changes to go through the container, rather than + direct modification diff --git a/test/blinktest.rb b/test/blinktest.rb new file mode 100644 index 000000000..4596eaaf7 --- /dev/null +++ b/test/blinktest.rb @@ -0,0 +1,43 @@ +# $Id$ + +unless defined? TestSuite + $VERBOSE = true + + $:.unshift File.join(Dir.getwd, '../lib') + + class TestSuite + attr_accessor :subdir + + def initialize(files) + files.collect { |file| + if file =~ /\.rb/ + file + else + "tc_" + file + ".rb" + end + }.sort { |a,b| + File.stat(a) <=> File.stat(b) + }.each { |file| + require file + } + end + end + + def textfiles + files = Dir.entries("text").reject { |file| + file =~ %r{\.swp} + }.reject { |file| + file =~ %r{\.disabled} + }.collect { |file| + File.join("text",file) + }.find_all { |file| + FileTest.file?(file) + }.each { |file| + yield file + } + end +end + +if __FILE__ == $0 # if we're executing the top-level library... + TestSuite.new(Dir.glob("ts_*")) +end diff --git a/test/tc_basic.rb b/test/tc_basic.rb new file mode 100644 index 000000000..f7ffbebfa --- /dev/null +++ b/test/tc_basic.rb @@ -0,0 +1,93 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'test/unit' + +# $Id$ + +class TestBasic < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + @component = nil + @configfile = nil + @sleeper = nil + + Blink[:debug] = 1 + + assert_nothing_raised() { + unless Blink::Component.has_key?("sleeper") + Blink::Component.new( + :name => "sleeper" + ) + end + @component = Blink::Component["sleeper"] + } + + assert_nothing_raised() { + unless Blink::Objects::File.has_key?("../examples/root/etc/configfile") + Blink::Objects::File.new( + :path => "../examples/root/etc/configfile" + ) + end + @configfile = Blink::Objects::File["../examples/root/etc/configfile"] + } + assert_nothing_raised() { + unless Blink::Objects::Service.has_key?("sleeper") + Blink::Objects::Service.new( + :name => "sleeper", + :running => 1 + ) + Blink::Objects::Service.addpath( + File.expand_path("../examples/root/etc/init.d") + ) + end + @sleeper = Blink::Objects::Service["sleeper"] + } + assert_nothing_raised() { + @component.push( + @configfile, + @sleeper + ) + } + + #puts "Component is %s, id %s" % [@component, @component.object_id] + #puts "ConfigFile is %s, id %s" % [@configfile, @configfile.object_id] + end + + def test_name_calls + [@component,@sleeper,@configfile].each { |obj| + assert_nothing_raised(){ + obj.name + } + } + end + + def test_name_equality + #puts "Component is %s, id %s" % [@component, @component.object_id] + assert_equal( + "sleeper", + @component.name + ) + + assert_equal( + "../examples/root/etc/configfile", + @configfile.name + ) + + assert_equal( + "sleeper", + @sleeper.name + ) + end + + def test_object_retrieval + [@component,@sleeper,@configfile].each { |obj| + assert_equal( + obj.class[obj.name].object_id, + obj.object_id + ) + } + end +end diff --git a/test/tc_file.rb b/test/tc_file.rb new file mode 100644 index 000000000..97a5d8591 --- /dev/null +++ b/test/tc_file.rb @@ -0,0 +1,96 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'test/unit' + +# $Id$ + +class TestFile < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + @file = nil + @path = "../examples/root/etc/configfile" + Blink[:debug] = 1 + assert_nothing_raised() { + unless Blink::Objects::File.has_key?(@path) + Blink::Objects::File.new( + :path => @path + ) + end + @file = Blink::Objects::File[@path] + } + end + + def test_owner + [Process.uid,%x{whoami}.chomp].each { |user| + assert_nothing_raised() { + @file[:owner] = user + } + assert_nothing_raised() { + @file.retrieve + } + assert_nothing_raised() { + @file.sync + } + assert_nothing_raised() { + @file.retrieve + } + assert(@file.insync?()) + } + assert_nothing_raised() { + @file[:owner] = "root" + } + assert_nothing_raised() { + @file.retrieve + } + # we might already be in sync + assert(!@file.insync?()) + assert_nothing_raised() { + @file.delete(:owner) + } + end + + def test_group + [%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group| + assert_nothing_raised() { + @file[:group] = group + } + assert_nothing_raised() { + @file.retrieve + } + assert_nothing_raised() { + @file.sync + } + assert_nothing_raised() { + @file.retrieve + } + assert(@file.insync?()) + assert_nothing_raised() { + @file.delete(:group) + } + } + end + + def test_modes + [0644,0755,0777,0641].each { |mode| + assert_nothing_raised() { + @file[:mode] = mode + } + assert_nothing_raised() { + @file.retrieve + } + assert_nothing_raised() { + @file.sync + } + assert_nothing_raised() { + @file.retrieve + } + assert(@file.insync?()) + assert_nothing_raised() { + @file.delete(:mode) + } + } + end +end diff --git a/test/tc_interpreter.rb b/test/tc_interpreter.rb new file mode 100644 index 000000000..c3c9fc6c9 --- /dev/null +++ b/test/tc_interpreter.rb @@ -0,0 +1,34 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'blink/parser/parser' +require 'test/unit' +require 'blinktest.rb' + +# $Id$ + +class TestInterpreter < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + Blink.init(:debug => 1) + #@lexer = Blink::Parser::Lexer.new() + @parser = Blink::Parser::Parser.new() + end + + def test_files + textfiles { |file| + Blink.debug("parsing %s" % file) + interpreter = nil + assert_nothing_raised() { + @parser.file = file + interpreter = @parser.parse + #p interpreter + } + assert_nothing_raised() { + interpreter.run() + } + } + end +end diff --git a/test/tc_lexer.rb b/test/tc_lexer.rb new file mode 100644 index 000000000..2c1c25dee --- /dev/null +++ b/test/tc_lexer.rb @@ -0,0 +1,87 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'blink/parser/lexer' +require 'test/unit' +require 'blinktest.rb' + +# $Id$ + +#%q{service("telnet") = \{ +# port => "23", +# protocol => "tcp", +# name => "telnet", +#\} +#} => [[:WORD, "service"], [:LPAREN, "("], [:DQUOTE, "\""], [:WORD, "telnet"], [:DQUOTE, "\""], [:RPAREN, ")"], [:EQUALS, "="], [:lbrace, "{"], [:WORD, "port"], [:FARROW, "=>"], [:DQUOTE, "\""], [:WORD, "23"], [:DQUOTE, "\""], [:COMMA, ","], [:WORD, "protocol"], [:FARROW, "=>"], [:DQUOTE, "\""], [:WORD, "tcp"], [:DQUOTE, "\""], [:COMMA, ","], [:WORD, "name"], [:FARROW, "=>"], [:DQUOTE, "\""], [:WORD, "telnet"], [:DQUOTE, "\""], [:COMMA, ","], [:RBRACE, "}"]] + +class TestLexer < Test::Unit::TestCase + def setup + Blink.init(:debug => 1) + @lexer = Blink::Parser::Lexer.new() + end + + def test_simple_lex + strings = { +%q{\\} => [[:BACKSLASH,"\\"],[false,false]], +%q{simplest scanner test} => [[:WORD,"simplest"],[:WORD,"scanner"],[:WORD,"test"],[false,false]], +%q{returned scanner test +} => [[:WORD,"returned"],[:WORD,"scanner"],[:WORD,"test"],[false,false]] + } + strings.each { |str,ary| + @lexer.string = str + assert_equal( + ary, + @lexer.fullscan() + ) + } + end + + def test_quoted_strings + strings = { +%q{a simple "scanner" test +} => [[:WORD,"a"],[:WORD,"simple"],[:QTEXT,"scanner"],[:WORD,"test"],[false,false]], +%q{a harder "scanner test" +} => [[:WORD,"a"],[:WORD,"harder"],[:QTEXT,"scanner test"],[false,false]], +%q{a hardest "scanner \"test\"" +} => [[:WORD,"a"],[:WORD,"hardest"],[:QTEXT,'scanner "test"'],[false,false]], +%q{function("call")} => [[:WORD,"function"],[:LPAREN,"("],[:QTEXT,'call'],[:RPAREN,")"],[false,false]] +} + strings.each { |str,array| + @lexer.string = str + assert_equal( + array, + @lexer.fullscan() + ) + } + end + + def test_errors + strings = %w{ + ^ + @ + < + > + } + strings.each { |str| + @lexer.string = str + assert_raise(RuntimeError) { + @lexer.fullscan() + } + } + end + + def test_more_error + assert_raise(TypeError) { + @lexer.fullscan() + } + end + + def test_files + textfiles() { |file| + @lexer.file = file + assert_nothing_raised() { + @lexer.fullscan() + } + } + end +end diff --git a/test/tc_oparse.rb b/test/tc_oparse.rb new file mode 100644 index 000000000..e737863b2 --- /dev/null +++ b/test/tc_oparse.rb @@ -0,0 +1,111 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'blink/oparse' +require 'test/unit' + +# $Id$ + +class TestOParse < Test::Unit::TestCase + def setup + Blink[:debug] = 1 + + @passwdtype = Blink::OParse["passwd"] + if @passwdtype.nil? + assert_nothing_raised() { + @passwdtype = Blink::OParse.newtype( + :name => "passwd", + :recordsplit => ":", + :fields => %w{name password uid gid gcos home shell}, + :namevar => "name" + ) + } + end + + @passwdtype = Blink::OParse["passwd"] + if @passwdtype.nil? + assert_nothing_raised() { + @passwdtype = Blink::OParse.newtype( + :name => "passwd", + :recordsplit => ":", + :fields => %w{name password uid gid gcos home shell}, + :namevar => "name" + ) + } + end + end + + def test_passwd1_nochange + file = nil + type = nil + assert_nothing_raised() { + file = @passwdtype.new("/etc/passwd") + } + assert_nothing_raised() { + file.retrieve + } + + assert(file.insync?) + + contents = "" + File.open("/etc/passwd") { |ofile| + ofile.each { |line| + contents += line + } + } + + assert_equal( + contents, + file.to_s + ) + + end + + def test_passwd2_change + file = nil + type = nil + Kernel.system("cp /etc/passwd /tmp/oparsepasswd") + assert_nothing_raised() { + file = @passwdtype.new("/tmp/oparsepasswd") + } + assert_nothing_raised() { + file.retrieve + } + + assert(file.insync?) + + assert_nothing_raised() { + file.add { |obj| + obj["name"] = "yaytest" + obj["password"] = "x" + obj["uid"] = "10000" + obj["gid"] = "10000" + obj["home"] = "/home/yaytest" + obj["gcos"] = "The Yaytest" + obj["shell"] = "/bin/sh" + } + } + + assert(!file.insync?) + + assert_nothing_raised() { + file.sync + } + + assert(file.insync?) + + assert_nothing_raised() { + file.delete("bin") + } + + assert(!file.insync?) + + assert_nothing_raised() { + file.sync + } + + assert(file.insync?) + + #Kernel.system("rm /tmp/oparsepasswd") + end +end diff --git a/test/tc_package.rb b/test/tc_package.rb new file mode 100644 index 000000000..ac9973e35 --- /dev/null +++ b/test/tc_package.rb @@ -0,0 +1,27 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink/objects/package' +require 'test/unit' +require 'facter' + +# $Id$ + +class TestPackagingType < Test::Unit::TestCase + def test_listing + platform = Facter["operatingsystem"].value + type = nil + case platform + when "SunOS" + type = "sunpkg" + when "Linux" + type = "dpkg" + else + type = :invalid + end + + assert_nothing_raised() { + Blink::Objects::PackagingType[type].list + } + end +end + diff --git a/test/tc_parser.rb b/test/tc_parser.rb new file mode 100644 index 000000000..40fcac3fa --- /dev/null +++ b/test/tc_parser.rb @@ -0,0 +1,29 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'blink/parser/parser' +require 'test/unit' +require 'blinktest.rb' + +# $Id$ + +class TestParser < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + Blink.init(:debug => 1, :parseonly => true) + #@lexer = Blink::Parser::Lexer.new() + @parser = Blink::Parser::Parser.new() + end + + def test_each_file + textfiles { |file| + Blink.debug("parsing %s" % file) + assert_nothing_raised() { + @parser.file = file + @parser.parse + } + } + end +end diff --git a/test/tc_selector.rb b/test/tc_selector.rb new file mode 100644 index 000000000..97d02859f --- /dev/null +++ b/test/tc_selector.rb @@ -0,0 +1,32 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink/selector' +require 'test/unit' + +# $Id$ + +class TestSelector < Test::Unit::TestCase + def setup + @os = Blink::Fact["operatingsystem"] + @hostname = Blink::Fact["hostname"] + end + + def test_values + Blink[:debug] = 1 + + selector = nil + assert_nothing_raised() { + selector = Blink::Selector.new { |select| + select.add("value1") { + Blink::Fact["hostname"] == @hostname + } + } + } + + assert_equal( + "value1", + selector.evaluate() + ) + + end +end diff --git a/test/tc_service.rb b/test/tc_service.rb new file mode 100644 index 000000000..4967f3a01 --- /dev/null +++ b/test/tc_service.rb @@ -0,0 +1,73 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'test/unit' + +# $Id$ + +class TestService < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + @sleeper = nil + + Blink[:debug] = 1 + assert_nothing_raised() { + unless Blink::Objects::Service.has_key?("sleeper") + Blink::Objects::Service.new( + :name => "sleeper", + :running => 1 + ) + Blink::Objects::Service.addpath( + File.expand_path("../examples/root/etc/init.d") + ) + end + @sleeper = Blink::Objects::Service["sleeper"] + } + end + + def test_process_start + assert_nothing_raised() { + @sleeper[:running] = 1 + } + assert_nothing_raised() { + @sleeper.retrieve + } + assert_equal( + Kernel.system("../examples/root/etc/init.d/sleeper status"), + @sleeper.insync?() + ) + assert_nothing_raised() { + @sleeper.sync + } + assert_nothing_raised() { + @sleeper.retrieve + } + assert_equal( + Kernel.system("../examples/root/etc/init.d/sleeper status"), + @sleeper.insync? + ) + end + + def test_process_evaluate + assert_nothing_raised() { + @sleeper[:running] = 1 + } + assert_nothing_raised() { + @sleeper.evaluate + } + # it really feels like this should be implicit... + assert_nothing_raised() { + @sleeper.retrieve + } + assert_equal( + Kernel.system("../examples/root/etc/init.d/sleeper status"), + @sleeper.insync?() + ) + assert_equal( + true, + @sleeper.insync?() + ) + end +end diff --git a/test/tc_symlink.rb b/test/tc_symlink.rb new file mode 100644 index 000000000..4dd2bd864 --- /dev/null +++ b/test/tc_symlink.rb @@ -0,0 +1,59 @@ +$:.unshift '../lib' if __FILE__ == $0 # Make this library first! + +require 'blink' +require 'test/unit' + +# $Id$ + +class TestSymlink < Test::Unit::TestCase + # hmmm + # this is complicated, because we store references to the created + # objects in a central store + def setup + @symlink = nil + @path = "../examples/root/etc/symlink" + + Kernel.system("rm -f %s" % @path) + Blink[:debug] = 1 + assert_nothing_raised() { + unless Blink::Objects::Symlink.has_key?(@path) + Blink::Objects::Symlink.new( + :path => @path + ) + end + @symlink = Blink::Objects::Symlink[@path] + } + end + + def test_target + assert_nothing_raised() { + @symlink[:target] = "configfile" + } + assert_nothing_raised() { + @symlink.retrieve + } + # we might already be in sync + assert(!@symlink.insync?()) + assert_nothing_raised() { + @symlink.sync + } + assert_nothing_raised() { + @symlink.retrieve + } + assert(@symlink.insync?()) + assert_nothing_raised() { + @symlink[:target] = nil + } + assert_nothing_raised() { + @symlink.retrieve + } + assert(!@symlink.insync?()) + assert_nothing_raised() { + @symlink.sync + } + assert_nothing_raised() { + @symlink.retrieve + } + assert(@symlink.insync?()) + end +end diff --git a/test/text/assignments b/test/text/assignments new file mode 100644 index 000000000..f45b83392 --- /dev/null +++ b/test/text/assignments @@ -0,0 +1,9 @@ +# $Id$ + +platform = :sunos + +platform = "this is a string of text" + +apache = service["apache"] { + :running => "1" +} diff --git a/test/text/facts b/test/text/facts new file mode 100644 index 000000000..a1562dc82 --- /dev/null +++ b/test/text/facts @@ -0,0 +1,9 @@ +# $Id$ + +fact["videocard"] { + :interpreter => "/bin/sh", + :code => "lspci | grep VGA", + :os => "Linux" +} +platform = retrieve("platform") +card = retrieve("videocard") diff --git a/test/text/functions b/test/text/functions new file mode 100644 index 000000000..6d295bdb1 --- /dev/null +++ b/test/text/functions @@ -0,0 +1,3 @@ +# $Id$ + +platform = retrieve("platform") diff --git a/test/text/groups b/test/text/groups new file mode 100644 index 000000000..56690eefb --- /dev/null +++ b/test/text/groups @@ -0,0 +1,7 @@ +# $Id$ + +# there need to be two forms of adding to groups: +# add the current host to a group, and add a list of hosts to a +# group by name + +group = "crap" diff --git a/test/text/one b/test/text/one new file mode 100644 index 000000000..12394b72d --- /dev/null +++ b/test/text/one @@ -0,0 +1,5 @@ +# $Id$ + +service["sleeper"] { + :running => "1", +} diff --git a/test/text/selectors b/test/text/selectors new file mode 100644 index 000000000..7a2b26357 --- /dev/null +++ b/test/text/selectors @@ -0,0 +1,27 @@ +# $Id$ + +platform = :sunos + +funtest = platform ? { + :sunos => "yayness" + :aix => :goodness + :default => "badness" +} + +# this is a comment + +sleeper = service["sleeper"] { + :owner => platform ? { + :sunos => "luke" + :default => "root" + }, + # this is a bit retarded, because we'll get a null value if we're not on sunos + # but we don't currently allow selectors as parameter statements... + :group => platform ? :sunos => :luke +} + +# i guess it has to be solved this way... + +platform ? :sunos => service["sleeper"] { + :group => :luke +} diff --git a/test/ts_objects.rb b/test/ts_objects.rb new file mode 100644 index 000000000..3cada1731 --- /dev/null +++ b/test/ts_objects.rb @@ -0,0 +1,6 @@ +# $Id$ + +require "blinktest.rb" + +TestSuite.new(%w{basic file symlink service package}) + diff --git a/test/ts_other.rb b/test/ts_other.rb new file mode 100644 index 000000000..f1076e6d1 --- /dev/null +++ b/test/ts_other.rb @@ -0,0 +1,6 @@ +# $Id$ + +require "blinktest.rb" + +TestSuite.new(%w{oparse selector}) + diff --git a/test/ts_parsing.rb b/test/ts_parsing.rb new file mode 100644 index 000000000..b0a49e4a1 --- /dev/null +++ b/test/ts_parsing.rb @@ -0,0 +1,5 @@ +# $Id$ + +require "blinktest.rb" + +suite = TestSuite.new(%w{interpreter lexer parser}) |