linux_admin 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,7 @@
1
1
  require 'shellwords'
2
2
 
3
3
  class LinuxAdmin
4
- class CommandError < RuntimeError; end
5
-
6
4
  module Common
7
- def write(file, content)
8
- raise ArgumentError, "file and content can not be empty" if file.blank? || content.blank?
9
- File.open(file, "w") do |f|
10
- f.write(content)
11
- end
12
- end
13
-
14
5
  def cmd(cmd)
15
6
  Distro.local.class::COMMANDS[cmd]
16
7
  end
@@ -19,7 +10,9 @@ class LinuxAdmin
19
10
  params = options[:params] || options[:parameters]
20
11
 
21
12
  begin
22
- out = launch(build_cmd(cmd, params))
13
+ launch_params = {}
14
+ launch_params[:chdir] = options[:chdir] if options[:chdir]
15
+ out = launch(build_cmd(cmd, params), launch_params)
23
16
 
24
17
  if options[:return_output] && exitstatus == 0
25
18
  out
@@ -67,9 +60,9 @@ class LinuxAdmin
67
60
  # http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146
68
61
  THREAD_SYNC_KEY = "LinuxAdmin-exitstatus"
69
62
 
70
- def launch(cmd)
63
+ def launch(cmd, spawn_options = {})
71
64
  pipe_r, pipe_w = IO.pipe
72
- pid = Kernel.spawn(cmd, :err => [:child, :out], :out => pipe_w)
65
+ pid = Kernel.spawn(cmd, {:err => [:child, :out], :out => pipe_w}.merge(spawn_options))
73
66
  wait_for_process(pid, pipe_w)
74
67
  wait_for_output(pipe_r)
75
68
  end
@@ -19,33 +19,72 @@ class LinuxAdmin
19
19
  @path = args[:path]
20
20
  end
21
21
 
22
+ def size
23
+ @size ||= begin
24
+ size = nil
25
+ out = run(cmd(:fdisk),
26
+ :return_output => true,
27
+ :params => {"-l" => nil})
28
+ out.each_line { |l|
29
+ if l =~ /Disk #{path}: ([0-9\.]*) ([KMG])B.*/
30
+ size = case $2
31
+ when 'K' then
32
+ $1.to_f.kilobytes
33
+ when 'M' then
34
+ $1.to_f.megabytes
35
+ when 'G' then
36
+ $1.to_f.gigabytes
37
+ end
38
+ break
39
+ end
40
+ }
41
+ size
42
+ end
43
+ end
44
+
22
45
  def partitions
23
46
  @partitions ||= begin
24
47
  partitions = []
25
48
 
26
49
  # requires sudo
27
50
  out = run(cmd(:parted),
51
+ :return_exitstatus => true,
28
52
  :return_output => true,
29
53
  :params => { nil => [@path, 'print'] })
30
54
 
55
+ return [] if out.kind_of?(Fixnum)
56
+
31
57
  out.each_line do |l|
32
58
  if l =~ /^ [0-9].*/
33
59
  p = l.split
34
- id,size,fs_type = p[0], p[3], p[5]
35
- if size =~ /([0-9\.]*)([KMG])B/
36
- size = case $2
37
- when 'K' then
38
- $1.to_f.kilobytes
39
- when 'M' then
40
- $1.to_f.megabytes
41
- when 'G' then
42
- $1.to_f.gigabytes
43
- end
60
+ args = {:disk => self}
61
+ fields = [:id, :start_sector, :end_sector,
62
+ :size, :partition_type, :fs_type]
63
+
64
+ fields.each_index do |i|
65
+ val = p[i]
66
+ case fields[i]
67
+ when :start_sector, :end_sector, :size
68
+ if val =~ /([0-9\.]*)([KMG])B/
69
+ val = case $2
70
+ when 'K' then
71
+ $1.to_f.kilobytes
72
+ when 'M' then
73
+ $1.to_f.megabytes
74
+ when 'G' then
75
+ $1.to_f.gigabytes
76
+ end
77
+ end
78
+
79
+ when :id
80
+ val = val.to_i
81
+
82
+ end
83
+
84
+ args[fields[i]] = val
44
85
  end
45
- partitions << Partition.new(:disk => self,
46
- :id => id.to_i,
47
- :size => size,
48
- :fs_type => fs_type)
86
+ partitions << Partition.new(args)
87
+
49
88
  end
50
89
  end
51
90
 
@@ -53,8 +92,35 @@ class LinuxAdmin
53
92
  end
54
93
  end
55
94
 
56
- def create_partition
57
- # TODO
95
+ def create_partition(partition_type, size)
96
+ id, start =
97
+ partitions.empty? ? [1, 0] :
98
+ [(partitions.last.id + 1),
99
+ partitions.last.end_sector]
100
+
101
+ run(cmd(:parted),
102
+ :params => { nil => [path, 'mkpart', partition_type,
103
+ start, start + size]})
104
+
105
+ partition = Partition.new(:disk => self,
106
+ :id => id,
107
+ :start_sector => start,
108
+ :end_sector => start+size,
109
+ :size => size,
110
+ :partition_type => partition_type)
111
+ partitions << partition
112
+ partition
113
+ end
114
+
115
+ def clear!
116
+ @partitions = []
117
+
118
+ # clear partition table
119
+ run(cmd(:dd),
120
+ :params => { 'if=' => '/dev/zero', 'of=' => @path,
121
+ 'bs=' => 512, 'count=' => 1})
122
+
123
+ self
58
124
  end
59
125
  end
60
126
  end
@@ -17,7 +17,7 @@ class LinuxAdmin
17
17
  issue = File.read('/etc/issue')
18
18
  if issue.include?('ubuntu')
19
19
  return Distros.ubuntu
20
- elsif ['Fedora', 'red hat', 'centos'].any? { |d| issue.include?(d) }
20
+ elsif ['Fedora', 'red hat', 'Red Hat', 'centos'].any? { |d| issue.include?(d) }
21
21
  return Distros.redhat
22
22
  end
23
23
 
@@ -58,12 +58,23 @@ class LinuxAdmin
58
58
  end
59
59
 
60
60
  class RedHat < Distro
61
- COMMANDS = {:service => '/usr/sbin/service',
62
- :systemctl => '/usr/bin/systemctl',
63
- :parted => '/usr/sbin/parted',
64
- :mount => '/usr/bin/mount',
65
- :umount => '/usr/bin/umount',
66
- :shutdown => '/usr/sbin/shutdown'}
61
+ COMMANDS = {:service => '/sbin/service',
62
+ :systemctl => '/bin/systemctl',
63
+ :parted => '/sbin/parted',
64
+ :mount => '/bin/mount',
65
+ :umount => '/bin/umount',
66
+ :shutdown => '/sbin/shutdown',
67
+ :mke2fs => '/sbin/mke2fs',
68
+ :fdisk => '/sbin/fdisk',
69
+ :dd => '/bin/dd',
70
+ :vgdisplay => '/sbin/vgdisplay',
71
+ :pvdisplay => '/sbin/pvdisplay',
72
+ :lvdisplay => '/sbin/lvdisplay',
73
+ :lvextend => '/sbin/lvextend',
74
+ :vgextend => '/sbin/vgextend',
75
+ :lvcreate => '/sbin/lvcreate',
76
+ :pvcreate => '/sbin/pvcreate',
77
+ :vgcreate => '/sbin/vgcreate'}
67
78
 
68
79
  def initialize
69
80
  @id = :redhat
@@ -0,0 +1 @@
1
+ class CommandError < RuntimeError; end
@@ -24,6 +24,15 @@ class LinuxAdmin
24
24
  refresh
25
25
  end
26
26
 
27
+ def write!
28
+ content = ''
29
+ @entries.each do |entry|
30
+ content += "#{entry.device} #{entry.mount_point} #{entry.fs_type} #{entry.mount_options} #{entry.dumpable} #{entry.fsck_order}\n"
31
+ end
32
+ File.write('/etc/fstab', content)
33
+ self
34
+ end
35
+
27
36
  private
28
37
 
29
38
  def refresh
@@ -5,7 +5,71 @@
5
5
 
6
6
  class LinuxAdmin
7
7
  class LogicalVolume < LinuxAdmin
8
- def initialize
8
+ # logical volume name
9
+ attr_accessor :name
10
+
11
+ # volume group name
12
+ attr_accessor :volume_group
13
+
14
+ # logical volume size in sectors
15
+ attr_accessor :sectors
16
+
17
+ # other fields available:
18
+ # logical volume access
19
+ # logical volume status
20
+ # internal logical volume number
21
+ # open count of logical volume
22
+ # current logical extents associated to logical volume
23
+ # allocated logical extents of logical volume
24
+ # allocation policy of logical volume
25
+ # read ahead sectors of logical volume
26
+ # major device number of logical volume
27
+ # minor device number of logical volume
28
+
29
+ def initialize(args = {})
30
+ @name = args[:name]
31
+ @volume_group = args[:volume_group]
32
+ @sectors = args[:sectors]
33
+ end
34
+
35
+ def extend_with(vg)
36
+ run(cmd(:lvextend),
37
+ :params => [self.name, vg.name])
38
+ self
39
+ end
40
+
41
+ def self.create(name, vg, size)
42
+ self.scan # initialize local logical volumes
43
+ run(cmd(:lvcreate),
44
+ :params => { '-n' => name, nil => vg.name, '-L' => size})
45
+ lv = LogicalVolume.new :name => name,
46
+ :volume_group => vg,
47
+ :sectors => size
48
+ @lvs << lv
49
+ lv
50
+ end
51
+
52
+ def self.scan
53
+ @lvs ||= begin
54
+ vgs = VolumeGroup.scan
55
+ lvs = []
56
+
57
+ out = run(cmd(:lvdisplay),
58
+ :return_output => true,
59
+ :params => { '-c' => nil})
60
+
61
+ out.each_line do |line|
62
+ fields = line.split(':')
63
+ vgname = fields[1]
64
+ vg = vgs.find { |vg| vg.name == vgname }
65
+
66
+ lvs << LogicalVolume.new(:name => fields[0],
67
+ :volume_group => vg,
68
+ :sectors => fields[6].to_i)
69
+ end
70
+
71
+ lvs
72
+ end
9
73
  end
10
74
  end
11
75
  end
@@ -8,7 +8,10 @@ require 'fileutils'
8
8
  class LinuxAdmin
9
9
  class Partition < LinuxAdmin
10
10
  attr_accessor :id
11
+ attr_accessor :partition_type
11
12
  attr_accessor :fs_type
13
+ attr_accessor :start_sector
14
+ attr_accessor :end_sector
12
15
  attr_accessor :size
13
16
  attr_accessor :disk
14
17
  attr_accessor :mount_point
@@ -18,12 +21,21 @@ class LinuxAdmin
18
21
  @size = args[:size]
19
22
  @disk = args[:disk]
20
23
  @fs_type = args[:fs_type]
24
+ @start_sector = args[:start_sector]
25
+ @end_sector = args[:end_sector]
26
+ @partition_type = args[:partition_type]
21
27
  end
22
28
 
23
29
  def path
24
30
  "#{disk.path}#{id}"
25
31
  end
26
32
 
33
+ def format_to(filesystem)
34
+ run(cmd(:mke2fs),
35
+ :params => { '-t' => filesystem, nil => self.path})
36
+ @fs_type = filesystem
37
+ end
38
+
27
39
  def mount(mount_point=nil)
28
40
  @mount_point = mount_point
29
41
  @mount_point =
@@ -38,9 +50,5 @@ class LinuxAdmin
38
50
  run(cmd(:umount),
39
51
  :params => { nil => [@mount_point] })
40
52
  end
41
-
42
- def format_to(fs_type)
43
- #TODO
44
- end
45
53
  end
46
54
  end
@@ -0,0 +1,75 @@
1
+ # LinuxAdmin Physical Volume Representation
2
+ #
3
+ # Copyright (C) 2013 Red Hat Inc.
4
+ # Licensed under the MIT License
5
+
6
+ class LinuxAdmin
7
+ class PhysicalVolume < LinuxAdmin
8
+ # physical volume device name
9
+ attr_accessor :device_name
10
+
11
+ # volume group name
12
+ attr_accessor :volume_group
13
+
14
+ # physical volume size in kilobytes
15
+ attr_accessor :size
16
+
17
+ # other fields available
18
+ # internal physical volume number (obsolete)
19
+ # physical volume status
20
+ # physical volume (not) allocatable
21
+ # current number of logical volumes on this physical volume
22
+ # physical extent size in kilobytes
23
+ # total number of physical extents
24
+ # free number of physical extents
25
+ # allocated number of physical extents
26
+
27
+ def initialize(args = {})
28
+ @device_name = args[:device_name]
29
+ @volume_group = args[:volume_group]
30
+ @size = args[:size]
31
+ end
32
+
33
+ def attach_to(vg)
34
+ run(cmd(:vgextend),
35
+ :params => [vg.name, @device_name])
36
+ self.volume_group = vg
37
+ self
38
+ end
39
+
40
+ # specify disk or partition instance to create physical volume on
41
+ def self.create(device)
42
+ self.scan # initialize local physical volumes
43
+ run(cmd(:pvcreate),
44
+ :params => { nil => device.path})
45
+ pv = PhysicalVolume.new(:device_name => device.path,
46
+ :volume_group => nil,
47
+ :size => device.size)
48
+ @pvs << pv
49
+ pv
50
+ end
51
+
52
+ def self.scan
53
+ @pvs ||= begin
54
+ vgs = VolumeGroup.scan
55
+ pvs = []
56
+
57
+ out = run(cmd(:pvdisplay),
58
+ :return_output => true,
59
+ :params => { '-c' => nil})
60
+
61
+ out.each_line do |line|
62
+ fields = line.split(':')
63
+ vgname = fields[1]
64
+ vg = vgs.find { |vg| vg.name == vgname}
65
+
66
+ pvs << PhysicalVolume.new(:device_name => fields[0],
67
+ :volume_group => vg,
68
+ :size => fields[2].to_i)
69
+ end
70
+
71
+ pvs
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,12 +1,8 @@
1
1
  require 'nokogiri'
2
2
 
3
3
  class LinuxAdmin
4
- class Rhn < LinuxAdmin
5
- def self.systemid_file
6
- "/etc/sysconfig/rhn/systemid"
7
- end
8
-
9
- def self.registered?
4
+ class Rhn < RegistrationSystem
5
+ def registered?
10
6
  id = ""
11
7
  if File.exists?(systemid_file)
12
8
  xml = Nokogiri.XML(File.read(systemid_file))
@@ -15,7 +11,7 @@ class LinuxAdmin
15
11
  id.length > 0
16
12
  end
17
13
 
18
- def self.register(options)
14
+ def register(options)
19
15
  cmd = "rhnreg_ks"
20
16
  params = {}
21
17
 
@@ -36,7 +32,7 @@ class LinuxAdmin
36
32
  run(cmd, :params => params)
37
33
  end
38
34
 
39
- def self.subscribe(options)
35
+ def subscribe(options)
40
36
  raise ArgumentError, "pools, username and password are required" if options[:pools].blank? || options[:username].blank? || options[:password].blank?
41
37
  cmd = "rhn-channel -a"
42
38
 
@@ -49,5 +45,11 @@ class LinuxAdmin
49
45
 
50
46
  run(cmd, :params => params)
51
47
  end
48
+
49
+ private
50
+
51
+ def systemid_file
52
+ "/etc/sysconfig/rhn/systemid"
53
+ end
52
54
  end
53
55
  end
@@ -0,0 +1,89 @@
1
+ require 'date'
2
+
3
+ class LinuxAdmin
4
+ class SubscriptionManager < RegistrationSystem
5
+
6
+ def validate_credentials(options)
7
+ !!organizations(options)
8
+ end
9
+
10
+ def registered?
11
+ run("subscription-manager identity", :return_exitstatus => true) == 0
12
+ end
13
+
14
+ def refresh
15
+ run("subscription-manager refresh")
16
+ end
17
+
18
+ def organizations(options)
19
+ raise ArgumentError, "username and password are required" unless options[:username] && options[:password]
20
+ cmd = "subscription-manager orgs"
21
+
22
+ params = {"--username=" => options[:username], "--password=" => options[:password]}
23
+ params.merge!(proxy_params(options))
24
+ params["--serverurl="] = options[:server_url] if options[:server_url]
25
+
26
+ output = run(cmd, :params => params, :return_output => true)
27
+ parse_output(output).index_by {|i| i[:name]}
28
+ end
29
+
30
+ def register(options)
31
+ raise ArgumentError, "username and password are required" unless options[:username] && options[:password]
32
+ cmd = "subscription-manager register"
33
+
34
+ params = {"--username=" => options[:username], "--password=" => options[:password]}
35
+ params.merge!(proxy_params(options))
36
+ params["--org="] = options[:org] if options[:server_url] && options[:org]
37
+ params["--serverurl="] = options[:server_url] if options[:server_url]
38
+
39
+ run(cmd, :params => params)
40
+ end
41
+
42
+ def subscribe(options)
43
+ cmd = "subscription-manager attach"
44
+ pools = options[:pools].collect {|pool| ["--pool", pool]}
45
+ params = proxy_params(options).to_a + pools
46
+
47
+ run(cmd, :params => params)
48
+ end
49
+
50
+ def available_subscriptions
51
+ cmd = "subscription-manager list --all --available"
52
+ output = run(cmd, :return_output => true)
53
+ parse_output(output).index_by {|i| i[:pool_id]}
54
+ end
55
+
56
+ private
57
+
58
+ def parse_output(output)
59
+ # Strip the 3 line header off the top
60
+ content = output.split("\n")[3..-1].join("\n")
61
+ parse_content(content)
62
+ end
63
+
64
+ def parse_content(content)
65
+ # Break into content groupings by "\n\n" then process each grouping
66
+ content.split("\n\n").each_with_object([]) do |group, group_array|
67
+ group = group.split("\n").each_with_object({}) do |line, hash|
68
+ next if line.blank?
69
+ key, value = line.split(":", 2)
70
+ hash[key.strip.downcase.tr(" -", "_").to_sym] = value.strip
71
+ end
72
+ group_array.push(format_values(group))
73
+ end
74
+ end
75
+
76
+ def format_values(content_group)
77
+ content_group[:ends] = Date.strptime(content_group[:ends], "%m/%d/%Y") if content_group[:ends]
78
+ content_group
79
+ end
80
+
81
+ def proxy_params(options)
82
+ config = {}
83
+ config["--proxy="] = options[:proxy_address] if options[:proxy_address]
84
+ config["--proxyuser="] = options[:proxy_username] if options[:proxy_username]
85
+ config["--proxypassword="] = options[:proxy_password] if options[:proxy_password]
86
+ config
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,45 @@
1
+ class LinuxAdmin
2
+ class RegistrationSystem < LinuxAdmin
3
+ def self.registration_type(reload = false)
4
+ return @registration_type if @registration_type && !reload
5
+ @registration_type = registration_type_uncached
6
+ end
7
+
8
+ def self.method_missing(meth, *args, &block)
9
+ if white_list_methods.include?(meth)
10
+ r = self.registration_type.new
11
+ raise NotImplementedError, "#{meth} not implemented for #{self.name}" unless r.respond_to?(meth)
12
+ r.send(meth, *args, &block)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def registered?
19
+ false
20
+ end
21
+
22
+ private
23
+
24
+ def self.registration_type_uncached
25
+ if SubscriptionManager.new.registered?
26
+ SubscriptionManager
27
+ elsif Rhn.new.registered?
28
+ Rhn
29
+ else
30
+ self
31
+ end
32
+ end
33
+ private_class_method :registration_type_uncached
34
+
35
+ def self.white_list_methods
36
+ @white_list_methods ||= begin
37
+ all_methods = RegistrationSystem.instance_methods(false) + Rhn.instance_methods(false) + SubscriptionManager.instance_methods(false)
38
+ all_methods.uniq
39
+ end
40
+ end
41
+ private_class_method :white_list_methods
42
+ end
43
+ end
44
+
45
+ Dir.glob(File.join(File.dirname(__FILE__), "registration_system", "*.rb")).each { |f| require f }
@@ -1,3 +1,3 @@
1
1
  class LinuxAdmin
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -0,0 +1,73 @@
1
+ # LinuxAdmin Volume Group Representation
2
+ #
3
+ # Copyright (C) 2013 Red Hat Inc.
4
+ # Licensed under the MIT License
5
+
6
+ class LinuxAdmin
7
+ class VolumeGroup < LinuxAdmin
8
+ # volume group name
9
+ attr_accessor :name
10
+
11
+ # other fields available
12
+ # volume group access
13
+ # volume group status
14
+ # internal volume group number
15
+ # maximum number of logical volumes
16
+ # current number of logical volumes
17
+ # open count of all logical volumes in this volume group
18
+ # maximum logical volume size
19
+ # maximum number of physical volumes
20
+ # current number of physical volumes
21
+ # actual number of physical volumes
22
+ # size of volume group in kilobytes
23
+ # physical extent size
24
+ # total number of physical extents for this volume group
25
+ # allocated number of physical extents for this volume group
26
+ # free number of physical extents for this volume group
27
+ # uuid of volume group
28
+
29
+ def initialize(args = {})
30
+ @name = args[:name]
31
+ end
32
+
33
+ def attach_to(lv)
34
+ run(cmd(:lvextend),
35
+ :params => [lv.name, self.name])
36
+ self
37
+ end
38
+
39
+ def extend_with(pv)
40
+ run(cmd(:vgextend),
41
+ :params => [@name, pv.device_name])
42
+ pv.volume_group = self
43
+ self
44
+ end
45
+
46
+ def self.create(name, pv)
47
+ self.scan # initialize local volume groups
48
+ run(cmd(:vgcreate),
49
+ :params => [name, pv.device_name])
50
+ vg = VolumeGroup.new :name => name
51
+ pv.volume_group = vg
52
+ @vgs << vg
53
+ vg
54
+ end
55
+
56
+ def self.scan
57
+ @vgs ||= begin
58
+ vgs = []
59
+
60
+ out = run(cmd(:vgdisplay),
61
+ :return_output => true,
62
+ :params => { '-c' => nil})
63
+
64
+ out.each_line do |line|
65
+ fields = line.split(':')
66
+ vgs << VolumeGroup.new(:name => fields[0])
67
+ end
68
+
69
+ vgs
70
+ end
71
+ end
72
+ end
73
+ end
@@ -59,7 +59,7 @@ class LinuxAdmin
59
59
  end
60
60
 
61
61
  def self.version_available(*packages)
62
- raise ArgumentError, "packages requires at least one package name" unless packages
62
+ raise ArgumentError, "packages requires at least one package name" if packages.blank?
63
63
 
64
64
  cmd = "repoquery --qf=\"%{name} %{version}\""
65
65
  params = {nil => packages}