summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README32
-rwxr-xr-xbin/blinker89
-rwxr-xr-xbin/sleeper97
-rw-r--r--examples/assignments1
-rw-r--r--examples/code.bl1
-rw-r--r--examples/code/file.bl7
-rw-r--r--examples/cron2.bl57
-rw-r--r--examples/events.bl36
-rw-r--r--examples/facts47
-rw-r--r--examples/objects25
-rw-r--r--examples/one.bl30
-rwxr-xr-xexamples/root/bin/sleeper97
-rw-r--r--examples/root/etc/configfile0
-rwxr-xr-xexamples/root/etc/init.d/sleeper25
-rw-r--r--examples/service.bl52
-rw-r--r--install.rb268
-rw-r--r--notes/design99
-rw-r--r--notes/execution28
-rw-r--r--notes/features13
-rw-r--r--notes/goals17
-rw-r--r--notes/issues43
-rw-r--r--notes/language/notes7
-rw-r--r--notes/notes44
-rw-r--r--test/blinktest.rb43
-rw-r--r--test/tc_basic.rb93
-rw-r--r--test/tc_file.rb96
-rw-r--r--test/tc_interpreter.rb34
-rw-r--r--test/tc_lexer.rb87
-rw-r--r--test/tc_oparse.rb111
-rw-r--r--test/tc_package.rb27
-rw-r--r--test/tc_parser.rb29
-rw-r--r--test/tc_selector.rb32
-rw-r--r--test/tc_service.rb73
-rw-r--r--test/tc_symlink.rb59
-rw-r--r--test/text/assignments9
-rw-r--r--test/text/facts9
-rw-r--r--test/text/functions3
-rw-r--r--test/text/groups7
-rw-r--r--test/text/one5
-rw-r--r--test/text/selectors27
-rw-r--r--test/ts_objects.rb6
-rw-r--r--test/ts_other.rb6
-rw-r--r--test/ts_parsing.rb5
43 files changed, 1876 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 000000000..0731cdc0e
--- /dev/null
+++ b/README
@@ -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})