lib/cfoundry/app.rb in cfoundry-0.2.0 vs lib/cfoundry/app.rb in cfoundry-0.2.1
- old
+ new
@@ -4,81 +4,141 @@
require "tmpdir"
require "cfoundry/zip"
module CFoundry
+ # Class for representing a user's application on a given target (via
+ # Client).
+ #
+ # Goes not guarantee that the app exists; used for both app creation and
+ # retrieval, as the attributes are all lazily retrieved. Setting attributes
+ # does not perform any requests; use #update! to commit your changes.
class App
+ # Application name.
attr_reader :name
+ # Application instance count.
+ attr_accessor :total_instances
+
+ # Services bound to the application.
+ attr_accessor :services
+
+ # Application environment variables.
+ attr_accessor :env
+
+ # Application memory limit.
+ attr_accessor :memory
+
+ # Application framework.
+ attr_accessor :framework
+
+ # Application runtime.
+ attr_accessor :runtime
+
+ # Application startup command.
+ #
+ # Used for standalone apps.
+ attr_accessor :command
+
+ # Application debug mode.
+ attr_accessor :debug_mode
+
+ # Application state.
+ attr_accessor :state
+ alias_method :status, :state
+
+ # URIs mapped to the application.
+ attr_accessor :uris
+ alias_method :urls, :uris
+
+
+ # Create an App object.
+ #
+ # You'll usually call Client#app instead
def initialize(name, client, manifest = nil)
@name = name
@client = client
@manifest = manifest
end
- def inspect
+ def inspect # :nodoc:
"#<App '#@name'>"
end
- def manifest
- @manifest ||= @client.rest.app(@name)
- end
-
+ # Delete the application from the target.
+ #
+ # Keeps the metadata, but clears target-specific state from it.
def delete!
@client.rest.delete_app(@name)
if @manifest
@manifest.delete "meta"
@manifest.delete "version"
@manifest.delete "state"
end
end
+ # Create the application on the target.
+ #
+ # Call this after setting the various attributes.
def create!
@client.rest.create_app(@manifest.merge("name" => @name))
@manifest = nil
end
+ # Check if the application exists on the target.
def exists?
@client.rest.app(@name)
true
rescue CFoundry::NotFound
false
end
+ # Retrieve all of the instances of the app, as Instance objects.
def instances
@client.rest.instances(@name).collect do |m|
Instance.new(@name, m["index"], @client, m)
end
end
+ # Retrieve application statistics, e.g. CPU load and memory usage.
def stats
@client.rest.stats(@name)
end
+ # Update application attributes. Does not restart the application.
def update!(what = {})
# TODO: hacky; can we not just set in meta field?
# we write to manifest["debug"] but read from manifest["meta"]["debug"]
what[:debug] = debug_mode
@client.rest.update_app(@name, manifest.merge(what))
@manifest = nil
end
+ # Stop the application.
def stop!
update! "state" => "STOPPED"
end
+ # Start the application.
def start!
update! "state" => "STARTED"
end
+ # Restart the application.
def restart!
stop!
start!
end
+ # Determine application health.
+ #
+ # If all instances are running, returns "RUNNING". If only some are
+ # started, returns the precentage of them that are healthy.
+ #
+ # Otherwise, returns application's status.
def health
s = state
if s == "STARTED"
healthy_count = manifest["runningInstances"]
expected = manifest["instances"]
@@ -95,24 +155,30 @@
else
s
end
end
+ # Check that all application instances are running.
def healthy?
# invalidate cache so the check is fresh
@manifest = nil
health == "RUNNING"
end
+ alias_method :running?, :healthy?
+ # Is the application stopped?
def stopped?
state == "STOPPED"
end
+ # Is the application started?
+ #
+ # Note that this does not imply that all instances are running. See
+ # #healthy?
def started?
state == "STARTED"
end
- alias_method :running?, :started?
{ :total_instances => "instances",
:state => "state",
:status => "state",
:services => "services",
@@ -128,103 +194,143 @@
@manifest ||= {}
@manifest[attr] = v
end
end
+ # Shortcut for uris[0]
def uri
uris[0]
end
+ # Shortcut for uris = [x]
def uri=(x)
self.uris = [x]
end
alias :url :uri
alias :url= :uri=
- def framework
+ def framework # :nodoc:
manifest["staging"]["framework"] ||
manifest["staging"]["model"]
end
- def framework=(v)
+ def framework=(v) # :nodoc:
@manifest ||= {}
@manifest["staging"] ||= {}
if @manifest["staging"].key? "model"
@manifest["staging"]["model"] = v
else
@manifest["staging"]["framework"] = v
end
end
- def runtime
+ def runtime # :nodoc:
manifest["staging"]["runtime"] ||
manifest["staging"]["stack"]
end
- def runtime=(v)
+ def runtime=(v) # :nodoc:
@manifest ||= {}
@manifest["staging"] ||= {}
if @manifest["staging"].key? "stack"
@manifest["staging"]["stack"] = v
else
@manifest["staging"]["runtime"] = v
end
end
- def command
+
+ def command # :nodoc:
manifest["staging"]["command"]
end
- def command=(v)
+ def command=(v) # :nodoc:
@manifest ||= {}
@manifest["staging"] ||= {}
@manifest["staging"]["command"] = v
end
- def memory
+
+ def memory # :nodoc:
manifest["resources"]["memory"]
end
- def memory=(v)
+ def memory=(v) # :nodoc:
@manifest ||= {}
@manifest["resources"] ||= {}
@manifest["resources"]["memory"] = v
end
- def debug_mode
- manifest.fetch("debug") { manifest["meta"] && manifest["meta"]["debug"] }
+
+ def debug_mode # :nodoc:
+ manifest.fetch("debug") do
+ manifest["meta"] && manifest["meta"]["debug"]
+ end
end
- def debug_mode=(v)
+ def debug_mode=(v) # :nodoc:
@manifest ||= {}
@manifest["debug"] = v
end
+
+ # Bind services to application.
def bind(*service_names)
update!("services" => services + service_names)
end
+ # Unbind services from application.
def unbind(*service_names)
update!("services" =>
services.reject { |s|
service_names.include?(s)
})
end
+ # Retrieve file listing under path for the first instance of the application.
+ #
+ # [path]
+ # A sequence of strings representing path segments.
+ #
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
def files(*path)
Instance.new(@name, 0, @client).files(*path)
end
+ # Retrieve file contents for the first instance of the application.
+ #
+ # [path]
+ # A sequence of strings representing path segments.
+ #
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
def file(*path)
Instance.new(@name, 0, @client).file(*path)
end
+ # Default paths to exclude from upload payload.
+ #
+ # Value: .git, _darcs, .svn
UPLOAD_EXCLUDE = %w{.git _darcs .svn}
+ # Upload application's code to target. Do this after #create! and before
+ # #start!
+ #
+ # [path]
+ # A path pointing to either a directory, or a .jar, .war, or .zip
+ # file.
+ #
+ # If a .vmcignore file is detected under the given path, it will be used
+ # to exclude paths from the payload, similar to a .gitignore.
+ #
+ # [check_resources]
+ # If set to `false`, the entire payload will be uploaded
+ # without checking the resource cache.
+ #
+ # Only do this if you know what you're doing.
def upload(path, check_resources = true)
unless File.exist? path
raise "invalid application path '#{path}'"
end
@@ -246,10 +352,14 @@
FileUtils.rm_rf(tmpdir) if tmpdir
end
private
+ def manifest
+ @manifest ||= @client.rest.app(@name)
+ end
+
def prepare_package(path, to)
if path =~ /\.(jar|war|zip)$/
CFoundry::Zip.unpack(path, to)
elsif war_file = Dir.glob("#{path}/*.war").first
CFoundry::Zip.unpack(war_file, to)
@@ -289,10 +399,13 @@
end
end
end
end
+ # Minimum size for an application payload to bother checking resources.
+ #
+ # Value: 64kb
RESOURCE_CHECK_LIMIT = 64 * 1024
def determine_resources(path)
fingerprints = []
total_size = 0
@@ -347,61 +460,91 @@
def find_sockets(path)
files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
files && files.select { |f| File.socket? f }
end
+ # Class represnting a running instance of an application.
class Instance
- attr_reader :app, :index, :manifest
+ # The application this instance belongs to.
+ attr_reader :app
+ # Application instance number.
+ attr_reader :index
+
+ # Create an Instance object.
+ #
+ # You'll usually call App#instances instead
def initialize(appname, index, client, manifest = {})
@app = appname
@index = index
@client = client
@manifest = manifest
end
- def inspect
+ def inspect # :nodoc:
"#<App::Instance '#@app' \##@index>"
end
+ # Instance state.
def state
@manifest["state"]
end
alias_method :status, :state
+ # Instance start time.
def since
Time.at(@manifest["since"])
end
+ # Instance debugger data. If instance is in debug mode, returns a hash
+ # containing :ip and :port keys.
def debugger
return unless @manifest["debug_ip"] and @manifest["debug_port"]
- { "ip" => @manifest["debug_ip"],
- "port" => @manifest["debug_port"]
+
+ { :ip => @manifest["debug_ip"],
+ :port => @manifest["debug_port"]
}
end
+ # Instance console data. If instance has a console, returns a hash
+ # containing :ip and :port keys.
def console
return unless @manifest["console_ip"] and @manifest["console_port"]
- { "ip" => @manifest["console_ip"],
- "port" => @manifest["console_port"]
+
+ { :ip => @manifest["console_ip"],
+ :port => @manifest["console_port"]
}
end
+ # True if instance is starting or running, false if it's down or
+ # flapping.
def healthy?
case state
when "STARTING", "RUNNING"
true
when "DOWN", "FLAPPING"
false
end
end
+ # Retrieve file listing under path for this instance.
+ #
+ # [path]
+ # A sequence of strings representing path segments.
+ #
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
def files(*path)
@client.rest.files(@app, @index, *path).split("\n").collect do |entry|
path + [entry.split(/\s+/, 2)[0]]
end
end
+ # Retrieve file contents for this instance.
+ #
+ # [path]
+ # A sequence of strings representing path segments.
+ #
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
def file(*path)
@client.rest.files(@app, @index, *path)
end
end
end