linux_admin 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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}