class StickShift::UnixUser

Unix User

Represents a user account on the system.

Constants

DEFAULT_SKEL_DIR

Attributes

app_name[R]
application_uuid[R]
container_name[R]
container_uuid[R]
debug[RW]
gecos[R]
gid[R]
homedir[R]
namespace[R]
quota_blocks[R]
quota_files[R]
uid[R]
uuid[R]

Public Class Methods

new(application_uuid, container_uuid, user_uid=nil, app_name=nil, container_name=nil, namespace=nil, quota_blocks=nil, quota_files=nil, debug=false) click to toggle source
# File lib/stickshift-node/model/unix_user.rb, line 43
def initialize(application_uuid, container_uuid, user_uid=nil,
    app_name=nil, container_name=nil, namespace=nil, quota_blocks=nil, quota_files=nil, debug=false)
  @config = StickShift::Config.instance
  
  @container_uuid = container_uuid
  @application_uuid = application_uuid
  @uuid = container_uuid
  @app_name = app_name
  @container_name = container_name
  @namespace = namespace
  @quota_blocks = quota_blocks
  @quota_files = quota_files
  @debug = debug

  begin
    user_info = Etc.getpwnam(@uuid)
    @uid = user_info.uid
    @gid = user_info.gid
    @gecos = user_info.gecos
    @homedir = "#{user_info.dir}/"
  rescue ArgumentError => e
    @uid = user_uid
    @gid = user_uid
    @gecos = nil
    @homedir = nil
  end
end

Public Instance Methods

add_broker_auth(iv,token) click to toggle source

Public: Add broker authorization keys so gear can communicate with

broker.

iv - A String value for the IV file. token - A String value for the token file.

Examples

add_broker_auth('ivvalue', 'tokenvalue')
# => ["/var/lib/stickshift/UUID/.auth/iv",
      "/var/lib/stickshift/UUID/.auth/token"]

Returns An Array of Strings for the newly created auth files

# File lib/stickshift-node/model/unix_user.rb, line 325
def add_broker_auth(iv,token)
  broker_auth_dir=File.join(@homedir,'.auth')
  FileUtils.mkdir_p broker_auth_dir
  File.open(File.join(broker_auth_dir, 'iv'),
        File::WRONLY|File::TRUNC|File::CREAT) do |file|
    file.write iv
  end
  File.open(File.join(broker_auth_dir, 'token'),
        File::WRONLY|File::TRUNC|File::CREAT) do |file|
    file.write token
  end
  
  FileUtils.chown_R("root", @uuid,broker_auth_dir)
  FileUtils.chmod(0o0750, broker_auth_dir)
  FileUtils.chmod(0o0640, Dir.glob("#{broker_auth_dir}/*"))
end
add_env_var(key, value, prefix_cloud_name = false, &blk) click to toggle source

Public: Add an environment variable to a given gear.

key - The String value of target environment variable. value - The String value to place inside the environment variable. prefix_cloud_name - The String value to append in front of key.

Examples

add_env_var('OPENSHIFT_DB_TYPE',
             'mysql-5.3')
# => 36

Returns the Integer value for how many bytes got written or raises on failure.

# File lib/stickshift-node/model/unix_user.rb, line 275
def add_env_var(key, value, prefix_cloud_name = false, &blk)
  env_dir = File.join(@homedir,'.env/')
  if prefix_cloud_name
    key = (@config.get('CLOUD_NAME') || 'SS') + "_#{key}"
  end
  filename = File.join(env_dir, key)
  File.open(filename,
      File::WRONLY|File::TRUNC|File::CREAT) do |file|
        file.write "export #{key}='#{value}'"
    end

  if block_given?
    blk.call(value)
  end
end
add_ssh_key(key, key_type=nil, comment=nil) click to toggle source

Public: Append an SSH key to a users authorized_keys file

key - The String value of the ssh key. key_type - The String value of the key type ssh-(rsa|dss)). comment - The String value of the comment to append to the key.

Examples

add_ssh_key('AAAAB3NzaC1yc2EAAAADAQABAAABAQDE0DfenPIHn5Bq/...',
            'ssh-rsa',
            'example@example.com')
# => nil

Returns nil on Success or raises on Failure

# File lib/stickshift-node/model/unix_user.rb, line 197
def add_ssh_key(key, key_type=nil, comment=nil)
  self.class.notify_observers(:before_add_ssh_key, self, key)
  ssh_dir = File.join(@homedir, ".ssh")
  cloud_name = @config.get("CLOUD_NAME") || "SS"
  authorized_keys_file = File.join(ssh_dir,"authorized_keys")
  shell    = @config.get("GEAR_SHELL")     || "/bin/bash"
  key_type = "ssh-rsa" if key_type.to_s.strip.length == 0
  comment  = "" unless comment
  
  cmd_entry = "command=\"#{shell}\",no-X11-forwarding #{key_type} #{key} #{cloud_name}-#{@uuid}#{comment}\n"
  FileUtils.mkdir_p ssh_dir
  FileUtils.chmod(0o0750,ssh_dir)
  File.open(authorized_keys_file,
    File::WRONLY|File::APPEND|File::CREAT, 0o0440) do | file |
    file.write(cmd_entry)
  end
  FileUtils.chmod 0o0440, authorized_keys_file
  FileUtils.chown_R("root",@uuid,ssh_dir)
  self.class.notify_observers(:after_add_ssh_key, self, key)
end
create() click to toggle source

Public: Create an empty gear.

Examples

create
# => nil
# a user
# Setup permissions

Returns nil on Success or raises on Failure

# File lib/stickshift-node/model/unix_user.rb, line 85
def create
  skel_dir = @config.get("GEAR_SKEL_DIR") || DEFAULT_SKEL_DIR
  shell    = @config.get("GEAR_SHELL")     || "/bin/bash"
  gecos    = @config.get("GEAR_GECOS")     || "SS application container"
  notify_observers(:before_unix_user_create)
  basedir = @config.get("GEAR_BASE_DIR")
  
  File.open("/var/lock/ss-create", File::RDWR|File::CREAT, 0o0600) do | lock |
    lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) 
    lock.flock(File::LOCK_EX)
    
    unless @uid
      @uid = @gid = next_uid
    end
    
    unless @homedir 
      @homedir = File.join(basedir,@uuid)
    end
    
    cmd = %Q{useradd -u #{@uid} \
            -d #{@homedir} \
            -s #{shell} \
            -c '#{gecos}' \
            -m \
            -k #{skel_dir} \
            #{@uuid}}
    out,err,rc = shellCmd(cmd)
    raise UserCreationException.new(
            "ERROR: unable to create user account #{@uuid}, #{cmd}"
            ) unless rc == 0
    
    FileUtils.chown("root", @uuid, @homedir)
    FileUtils.chmod 0o0750, @homedir

    if @config.get("CREATE_APP_SYMLINKS").to_i == 1
      unobfuscated = File.join(File.dirname(@homedir),"#{@container_name}-#{namespace}")
      if not File.exists? unobfuscated
        FileUtils.ln_s File.basename(@homedir), unobfuscated, :force=>true
      end
    end

  end
  notify_observers(:after_unix_user_create)
  initialize_homedir(basedir, @homedir, @config.get("CARTRIDGE_BASE_PATH"))
  initialize_stickshift_proxy
end
destroy() click to toggle source

Public: Destroys a gear stopping all processes and removing all files

Examples

destroy
# => nil

Returns nil on Success or raises on Failure

# File lib/stickshift-node/model/unix_user.rb, line 140
def destroy
  if @uid.nil? and (not File.directory?(@homedir.to_s))
    # gear seems to have been destroyed already... suppress any error
    # TODO : remove remaining stuff if it exists, e.g. .httpd/#{uuid}* etc
    return nil
  end
  raise UserDeletionException.new(
        "ERROR: unable to destroy user account #{@uuid}"
        ) if @uid.nil? || @homedir.nil? || @uuid.nil?
  notify_observers(:before_unix_user_destroy)

  initialize_stickshift_proxy
  
  cmd = %Q{/usr/bin/killall -s KILL -u '#{@uuid}' 2> /dev/null}
  (1..10).each do |i|
    out,err,rc = shellCmd(cmd)
    break unless rc == 0
    sleep 0.5
  end
  
  if @config.get("CREATE_APP_SYMLINKS").to_i == 1
    Dir.foreach(File.dirname(@homedir)) do |dent|
      unobfuscate = File.join(File.dirname(@homedir), dent)
      if (File.symlink?(unobfuscate)) &&
          (File.readlink(unobfuscate) == File.basename(@homedir))
        File.unlink(unobfuscate)
      end
    end
  end

  FileUtils.rm_rf(@homedir)

  basedir = @config.get("GEAR_BASE_DIR")
  path = File.join(basedir, ".httpd.d", "#{uuid}_*")
  FileUtils.rm_rf(Dir.glob(path))

  out,err,rc = shellCmd("userdel \"#{@uuid}\"")
  raise UserDeletionException.new(
        "ERROR: unable to destroy user account: #{@uuid}   stdout: #{out}   stderr:#{err}") unless rc == 0
  notify_observers(:after_unix_user_destroy)
end
initialize_homedir(basedir, homedir, cart_basedir) click to toggle source

Private: Create and populate the users home dir.

Examples

initialize_homedir
# => nil
# Creates:
# ~
# ~/.tmp/
# ~/.env/
# APP_UUID, GEAR_UUID, APP_NAME, APP_DNS, HOMEDIR, DATA_DIR, GEAR_DIR, \
#   GEAR_DNS, GEAR_NAME, GEAR_CTL_SCRIPT, PATH, REPO_DIR, TMP_DIR
# ~/app-root
# ~/app-root/data
# ~/app-root/runtime/repo
# ~/app-root/repo -> runtime/repo
# ~/app-root/runtime/data -> ../data

Returns nil on Success and raises on Failure.

# File lib/stickshift-node/model/unix_user.rb, line 386
def initialize_homedir(basedir, homedir, cart_basedir)
  @homedir = homedir
  notify_observers(:before_initialize_homedir)
  homedir = homedir.end_with?('/') ? homedir : homedir + '/'

  tmp_dir = File.join(homedir, ".tmp")
  # Required for polyinstantiated tmp dirs to work
  FileUtils.mkdir_p tmp_dir
  FileUtils.chmod(0o0000, tmp_dir)
        
  env_dir = File.join(homedir, ".env")
  FileUtils.mkdir_p(env_dir)
  FileUtils.chmod(0o0750, env_dir)
  FileUtils.chown(nil, @uuid, env_dir)

  geardir = File.join(homedir, @container_name, "/")
  gearappdir = File.join(homedir, "app-root", "/")

  add_env_var("APP_DNS",
              "#{@app_name}-#{@namespace}.#{@config.get("CLOUD_DOMAIN")}",
              true)
  add_env_var("APP_NAME", @app_name, true)
  add_env_var("APP_UUID", @application_uuid, true)

  add_env_var("DATA_DIR", File.join(gearappdir, "data", "/"), true) {|v|
    FileUtils.mkdir_p(v, :verbose => @debug)
  }

  add_env_var("GEAR_DIR", geardir, true)
  add_env_var("GEAR_DNS",
              "#{@container_name}-#{@namespace}.#{@config.get("CLOUD_DOMAIN")}",
              true)
  add_env_var("GEAR_NAME", @container_name, true)
  add_env_var("GEAR_UUID", @container_uuid, true)

  add_env_var("HOMEDIR", homedir, true)

  add_env_var("PATH",
              "#{cart_basedir}abstract-httpd/info/bin/:#{cart_basedir}abstract/info/bin/:$PATH",
              false)

  add_env_var("REPO_DIR", File.join(gearappdir, "runtime", "repo", "/"), true) {|v|
    FileUtils.mkdir_p(v, :verbose => @debug)
    FileUtils.cd gearappdir do |d|
      FileUtils.ln_s("runtime/repo", "repo", :verbose => @debug)
    end
    FileUtils.cd File.join(gearappdir, "runtime") do |d|
      FileUtils.ln_s("../data", "data", :verbose => @debug)
    end
  }

  add_env_var("TMP_DIR", "/tmp/", true)

  # Update all directory entries ~/app-root/*
  Dir[gearappdir + "/*"].entries.reject{|e| [".", ".."].include? e}.each {|e|
    FileUtils.chmod_R(0o0750, e, :verbose => @debug)
    FileUtils.chown_R(@uuid, @uuid, e, :verbose => @debug)
  }
  FileUtils.chown(nil, @uuid, gearappdir, :verbose => @debug)
  raise "Failed to instantiate gear: missing application directory (#{gearappdir})" unless File.exist?(gearappdir)

  state_file = File.join(gearappdir, "runtime", ".state")
  File.open(state_file, File::WRONLY|File::TRUNC|File::CREAT, 0o0660) {|file|
    file.write "new\n"
  }
  FileUtils.chown(@uuid, @uuid, state_file, :verbose => @debug)

  token = "#{@uuid}_#{@namespace}_#{@container_name}"
  path = File.join(basedir, ".httpd.d", token)

  # path can only exist as a turd from failed app destroy
  FileUtils.rm_rf(path) if File.exist?(path)
  FileUtils.mkdir_p(path)

  notify_observers(:after_initialize_homedir)
end
initialize_stickshift_proxy() click to toggle source

Private: Initialize Stickshift Port Proxy for this gear

The port proxy range is determined by configuration and must produce identical results to the abstract cartridge provided range.

Examples: #initialize_stickshift_proxy

=> true
service stickshift_proxy setproxy 35000 delete 35001 delete etc...

Returns:

true   - port proxy could be initialized properly
false  - port proxy could not be initialized properly
# File lib/stickshift-node/model/unix_user.rb, line 499
def initialize_stickshift_proxy
  notify_observers(:before_initialize_stickshift_proxy)

  port_begin = (@config.get("PORT_BEGIN") || "35531").to_i
  ports_per_user = (@config.get("PORTS_PER_USER") || "5").to_i

  # Note, due to a mismatch between dev and prod this is
  # intentionally not GEAR_MIN_UID and the range must
  # wrap back around on itself.
  uid_begin = (@config.get("UID_BEGIN") || "500").to_i

  wrap_uid = ((65536 - port_begin)/ports_per_user)+uid_begin

  if @uid >= wrap_uid
    tuid = @uid - wrap_uid + uid_begin
  else
    tuid = @uid
  end

  proxy_port_begin = (tuid-uid_begin) * ports_per_user + port_begin

  proxy_port_range = (proxy_port_begin ... (proxy_port_begin + ports_per_user))

  cmd = %Q{/sbin/chkconfig --list stickshift-proxy}
  out,err,rc = shellCmd(cmd)
  if rc == 0
    cmd = %Q{/sbin/service stickshift-proxy setproxy}
    proxy_port_range.each { |i| cmd << " #{i} delete" }
    out, err, rc = shellCmd(cmd)
  end

  notify_observers(:after_initialize_stickshift_proxy)
  return rc == 0
end
name() click to toggle source
# File lib/stickshift-node/model/unix_user.rb, line 71
def name
  @uuid
end
next_uid() click to toggle source

Private: Determine next available user id. This is usually determined

and provided by the broker but is auto determined if not
provided.

Examples:

next_uid =>
# => 504

Returns Integer value for next available uid.

# File lib/stickshift-node/model/unix_user.rb, line 472
def next_uid
  uids = IO.readlines("/etc/passwd").map{ |line| line.split(":")[2].to_i }
  gids = IO.readlines("/etc/group").map{ |line| line.split(":")[2].to_i }
  min_uid = (@config.get("GEAR_MIN_UID") || "500").to_i
  max_uid = (@config.get("GEAR_MAX_UID") || "1500").to_i
  
  (min_uid..max_uid).each do |i|
    if !uids.include?(i) and !gids.include?(i)
      return i
    end
  end
end
remove_broker_auth() click to toggle source

Public: Remove broker authentication keys from gear.

Examples

remove_broker_auth
# => nil

Returns nil on Success and false on Failure

# File lib/stickshift-node/model/unix_user.rb, line 349
def remove_broker_auth
  broker_auth_dir=File.join(@homedir, '.auth')
  FileUtils.rm_rf broker_auth_dir
  File.exists?(broker_auth_dir) ? false : true
end
remove_env_var(key, prefix_cloud_name=false) click to toggle source

Public: Remove an environment variable from a given gear.

key - String name of the environment variable to remove. prefix_cloud_name - String prefix to append to key.

Examples

remove_env_var('OPENSHIFT_DB_TYPE')
# => nil

Returns an nil on success and false on failure.

# File lib/stickshift-node/model/unix_user.rb, line 303
def remove_env_var(key, prefix_cloud_name=false)
  env_dir = File.join(@homedir,".env")
  if prefix_cloud_name
    key = (@config.get("CLOUD_NAME") || "SS") + "_#{key}"
  end
  env_file_path = File.join(env_dir, key)
  FileUtils.rm_f env_file_path
  File.exists?(env_file_path) ? false : true
end
remove_ssh_key(key, comment=nil) click to toggle source

Public: Remove an SSH key from a users authorized_keys file.

key - The String value of the ssh key. comment - The String value of the comment associated with the key.

Examples

remove_ssh_key('AAAAB3NzaC1yc2EAAAADAQABAAABAQDE0DfenPIHn5Bq/...',
            'example@example.com')
# => nil

Returns nil on Success or raises on Failure

# File lib/stickshift-node/model/unix_user.rb, line 230
def remove_ssh_key(key, comment=nil)
  self.class.notify_observers(:before_remove_ssh_key, self, key)
  ssh_dir = File.join(@homedir, '.ssh')
  authorized_keys_file = File.join(ssh_dir,'authorized_keys')
  
  FileUtils.mkdir_p ssh_dir
  FileUtils.chmod(0o0750,ssh_dir)
  keys = []
  File.open(authorized_keys_file, File::RDONLY|File::CREAT, 0o0440) do
        | file |
    keys = file.readlines
  end
  
  if comment
    keys.delete_if{ |k| k.include?(key) && k.include?(comment)}
  else
    keys.delete_if{ |k| k.include?(key)}
  end
  keys.map!{ |k| k.strip }
  
  File.open(authorized_keys_file, File::WRONLY|File::TRUNC|File::CREAT,
            0o0440) do |file|
    file.write(keys.join("\n"))
    file.write("\n")
  end
  
  FileUtils.chmod 0o0440, authorized_keys_file
  FileUtils.chown('root', @uuid, ssh_dir)
  self.class.notify_observers(:after_remove_ssh_key, self, key)
end
run_as() { |block| ... } click to toggle source
# File lib/stickshift-node/model/unix_user.rb, line 355
def run_as(&block)
  old_gid = Process::GID.eid
  old_uid = Process::UID.eid
  fork{
    Process::GID.change_privilege(@gid.to_i)
    Process::UID.change_privilege(@uid.to_i)      
    yield block          
  }
  Process.wait  
end