├── .rubocop_local.yml
├── .rspec
├── spec
├── data
│ ├── yum
│ │ ├── output_repoquery_single
│ │ ├── output_repoquery_multiple
│ │ ├── second.repo
│ │ ├── first.repo
│ │ └── output_repo_list
│ ├── rhn
│ │ ├── output_rhn-channel_list
│ │ ├── output_rhn-channel_list_available
│ │ ├── systemid
│ │ └── systemid.missing_system_id
│ ├── subscription_manager
│ │ ├── output_orgs
│ │ ├── output_repos
│ │ ├── output_list_installed_not_subscribed
│ │ ├── output_list_installed_subscribed
│ │ └── output_list_all_available
│ ├── rpm
│ │ └── cmd_output_for_list_installed
│ └── time_date
│ │ └── timedatectl_output
├── system_spec.rb
├── partition_spec.rb
├── common_spec.rb
├── etc_issue_spec.rb
├── hardware_spec.rb
├── service_spec.rb
├── scap_spec.rb
├── deb_spec.rb
├── chrony_spec.rb
├── distro_spec.rb
├── fstab_spec.rb
├── ssh_spec.rb
├── dns_spec.rb
├── registration_system_spec.rb
├── time_date_spec.rb
├── volume_group_spec.rb
├── physical_volume_spec.rb
├── rpm_spec.rb
├── service
│ ├── sys_v_init_service_spec.rb
│ └── systemd_service_spec.rb
├── spec_helper.rb
├── ip_address_spec.rb
├── mountable_spec.rb
├── rhn_spec.rb
├── logical_volume_spec.rb
├── hosts_spec.rb
├── yum_spec.rb
├── subscription_manager_spec.rb
└── disk_spec.rb
├── lib
├── linux_admin
│ ├── package.rb
│ ├── version.rb
│ ├── logging.rb
│ ├── network_interface
│ │ ├── network_interface_generic.rb
│ │ └── network_interface_rh.rb
│ ├── hardware.rb
│ ├── null_logger.rb
│ ├── yum
│ │ └── repo_file.rb
│ ├── system.rb
│ ├── exceptions.rb
│ ├── etc_issue.rb
│ ├── volume.rb
│ ├── common.rb
│ ├── partition.rb
│ ├── chrony.rb
│ ├── dns.rb
│ ├── deb.rb
│ ├── service.rb
│ ├── time_date.rb
│ ├── service
│ │ ├── sys_v_init_service.rb
│ │ └── systemd_service.rb
│ ├── mountable.rb
│ ├── ip_address.rb
│ ├── distro.rb
│ ├── registration_system.rb
│ ├── ssh_agent.rb
│ ├── rpm.rb
│ ├── physical_volume.rb
│ ├── volume_group.rb
│ ├── scap.rb
│ ├── ssh.rb
│ ├── fstab.rb
│ ├── logical_volume.rb
│ ├── hosts.rb
│ ├── registration_system
│ │ ├── rhn.rb
│ │ └── subscription_manager.rb
│ ├── yum.rb
│ ├── disk.rb
│ └── network_interface.rb
└── linux_admin.rb
├── Rakefile
├── .rubocop.yml
├── .gitignore
├── .travis.yml
├── Gemfile
├── examples
└── subscription_manager_hosted.rb
├── LICENSE.txt
├── README.md
└── linux_admin.gemspec
/.rubocop_local.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --warnings
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/spec/data/yum/output_repoquery_single:
--------------------------------------------------------------------------------
1 | subscription-manager 1.1.23.1
2 |
--------------------------------------------------------------------------------
/lib/linux_admin/package.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Package
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/linux_admin/version.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | VERSION = "1.2.0".freeze
3 | end
4 |
--------------------------------------------------------------------------------
/spec/data/rhn/output_rhn-channel_list:
--------------------------------------------------------------------------------
1 | rhel-x86_64-server-6
2 | rhel-x86_64-server-6-cf-me-2
3 |
--------------------------------------------------------------------------------
/spec/data/yum/output_repoquery_multiple:
--------------------------------------------------------------------------------
1 | curl 7.19.7
2 | subscription-manager 1.1.23.1
3 | wget 1.12
4 |
--------------------------------------------------------------------------------
/lib/linux_admin/logging.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | module Logging
3 | def logger
4 | LinuxAdmin.logger
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/linux_admin/network_interface/network_interface_generic.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class NetworkInterfaceGeneric < NetworkInterface
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | require 'rspec/core/rake_task'
4 | RSpec::Core::RakeTask.new('spec')
5 | task :test => :spec
6 | task :default => :spec
--------------------------------------------------------------------------------
/spec/data/rhn/output_rhn-channel_list_available:
--------------------------------------------------------------------------------
1 | rhel-x86_64-server-6-cf-me-2
2 | rhel-x86_64-server-6-cf-me-2-beta
3 | rhel-x86_64-server-6-cf-me-3
4 | rhel-x86_64-server-6-cf-me-3-beta
5 |
--------------------------------------------------------------------------------
/lib/linux_admin/hardware.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Hardware
3 | def total_cores
4 | File.readlines("/proc/cpuinfo").count { |line| line =~ /^processor\s+:\s+\d+/ }
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/data/subscription_manager/output_orgs:
--------------------------------------------------------------------------------
1 | +-------------------------------------------+
2 | myUser Organizations
3 | +-------------------------------------------+
4 |
5 | Name: SomeOrg
6 | Key: 1234567
7 |
--------------------------------------------------------------------------------
/lib/linux_admin/null_logger.rb:
--------------------------------------------------------------------------------
1 | require 'logger'
2 |
3 | module LinuxAdmin
4 | class NullLogger < Logger
5 | def initialize(*_args)
6 | end
7 |
8 | def add(*_args, &_block)
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - https://raw.githubusercontent.com/ManageIQ/guides/master/.rubocop_base.yml
3 | # put all local rubocop config into .rubocop_local.yml as it will be loaded by .rubocop_cc.yml as well
4 | - .rubocop_local.yml
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | bin
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - "2.1"
4 | - "2.2.6"
5 | - "2.3.3"
6 | - "2.4.0"
7 | - ruby-head
8 | - jruby-head
9 | sudo: false
10 | cache: bundler
11 | matrix:
12 | allow_failures:
13 | - rvm: ruby-head
14 | - rvm: jruby-head
15 | fast_finish: true
16 |
--------------------------------------------------------------------------------
/spec/data/yum/second.repo:
--------------------------------------------------------------------------------
1 | [my-local-repo-c]
2 | name=My Local Repo c
3 | baseurl=https://mirror.example.com/c/content/os_ver
4 | enabled=0
5 | cost=100
6 | gpgcheck=1
7 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-my-local-server
8 | sslverify=0
9 | metadata_expire=1
10 |
--------------------------------------------------------------------------------
/lib/linux_admin/yum/repo_file.rb:
--------------------------------------------------------------------------------
1 | require 'inifile'
2 |
3 | module LinuxAdmin
4 | class Yum
5 | class RepoFile < IniFile
6 | def self.create(filename)
7 | File.write(filename, "")
8 | self.new(:filename => filename)
9 | end
10 | end
11 | end
12 | end
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in linux_admin.gemspec
4 | gemspec
5 |
6 | # HACK: Rails 5 dropped support for Ruby < 2.2.2
7 | active_support_version = "< 5" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
8 | gem 'activesupport', active_support_version
9 |
--------------------------------------------------------------------------------
/lib/linux_admin/system.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class System
3 | def self.reboot!
4 | Common.run!(Common.cmd(:shutdown),
5 | :params => { "-r" => "now" })
6 | end
7 |
8 | def self.shutdown!
9 | Common.run!(Common.cmd(:shutdown),
10 | :params => { "-h" => "0" })
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/linux_admin/exceptions.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class CredentialError < AwesomeSpawn::CommandResultError
3 | def initialize(result)
4 | super("Invalid username or password", result)
5 | end
6 | end
7 |
8 | class NetworkInterfaceError < AwesomeSpawn::CommandResultError; end
9 |
10 | class MissingConfigurationFileError < StandardError; end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/linux_admin/etc_issue.rb:
--------------------------------------------------------------------------------
1 | require 'singleton'
2 |
3 | module LinuxAdmin
4 | class EtcIssue
5 | include Singleton
6 |
7 | PATH = '/etc/issue'
8 |
9 | def include?(osname)
10 | data.downcase.include?(osname.to_s.downcase)
11 | end
12 |
13 | def data
14 | @data ||= File.exist?(PATH) ? File.read(PATH) : ""
15 | end
16 |
17 | def refresh
18 | @data = nil
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/system_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::System do
2 | describe "#reboot!" do
3 | it "reboots the system" do
4 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:shutdown), :params => {'-r' => 'now'})
5 | LinuxAdmin::System.reboot!
6 | end
7 | end
8 |
9 | describe "#shutdown!" do
10 | it "shuts down the system" do
11 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:shutdown), :params => {'-h' => '0'})
12 | LinuxAdmin::System.shutdown!
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/data/rpm/cmd_output_for_list_installed:
--------------------------------------------------------------------------------
1 | ruby193-rubygem-some_really_long_name 1.0.7-1.el6
2 | fipscheck-lib 1.2.0-7.el6
3 | aic94xx-firmware 30-2.el6
4 | latencytop-common 0.5-9.el6
5 | uuid 1.6.1-10.el6
6 | ConsoleKit 0.4.1-3.el6
7 | cpuspeed 1.5-19.el6
8 | mailcap 2.1.31-2.el6
9 | freetds 0.82-7.1.el6cf
10 | elinks 0.12-0.21.pre5.el6_3
11 | abrt-cli 2.0.8-15.el6
12 | libattr 2.4.44-7.el6
13 | passwd 0.77-4.el6_2.2
14 | vim-enhanced 7.2.411-1.8.el6
15 | popt 1.13-7.el6
16 | hesiod 3.1.0-19.el6
17 | pinfo 0.6.9-12.el6
18 | libpng 1.2.49-1.el6_2
19 | libdhash 0.4.2-9.el6
20 | zlib-devel 1.2.3-29.el6
21 |
--------------------------------------------------------------------------------
/spec/data/time_date/timedatectl_output:
--------------------------------------------------------------------------------
1 | Local time: Thu 2015-09-24 17:54:02 EDT
2 | Universal time: Thu 2015-09-24 21:54:02 UTC
3 | RTC time: Thu 2015-09-24 21:54:02
4 | Time zone: America/New_York (EDT, -0400)
5 | NTP enabled: yes
6 | NTP synchronized: yes
7 | RTC in local TZ: no
8 | DST active: yes
9 | Last DST change: DST began at
10 | Sun 2015-03-08 01:59:59 EST
11 | Sun 2015-03-08 03:00:00 EDT
12 | Next DST change: DST ends (the clock jumps one hour backwards) at
13 | Sun 2015-11-01 01:59:59 EDT
14 | Sun 2015-11-01 01:00:00 EST
15 |
--------------------------------------------------------------------------------
/spec/data/yum/first.repo:
--------------------------------------------------------------------------------
1 | [my-local-repo-a]
2 | name = My Local Repo A
3 | baseurl = https://mirror.example.com/a/content/os_ver
4 | enabled = 0
5 | gpgcheck = 1
6 | gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-my-local-server
7 | sslverify = 1
8 | sslcacert = /etc/rhsm/ca/my-loacl-server.pem
9 | sslclientkey = /etc/pki/entitlement/0123456789012345678-key.pem
10 | sslclientcert = /etc/pki/entitlement/0123456789012345678.pem
11 | metadata_expire = 86400
12 |
13 | [my-local-repo-b]
14 | name = My Local Repo B
15 | baseurl = https://mirror.example.com/b/content/os_ver
16 | enabled = 1
17 | gpgcheck = 0
18 | sslverify = 0
19 | metadata_expire = 86400
20 |
--------------------------------------------------------------------------------
/lib/linux_admin/volume.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Volume
3 | private
4 |
5 | def self.process_volume_display_line(line)
6 | groups = VolumeGroup.scan
7 | fields = line.split(':')
8 | vgname = fields[1]
9 | vg = groups.find { |g| g.name == vgname }
10 | return fields, vg
11 | end
12 |
13 | protected
14 |
15 | def self.scan_volumes(cmd)
16 | volumes = []
17 |
18 | out = Common.run!(cmd, :params => {'-c' => nil}).output
19 |
20 | out.each_line do |line|
21 | fields, vg = process_volume_display_line(line.lstrip)
22 | volumes << yield(fields, vg)
23 | end
24 |
25 | volumes
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/data/subscription_manager/output_repos:
--------------------------------------------------------------------------------
1 | +----------------------------------------------------------+
2 | Available Repositories in /etc/yum.repos.d/redhat.repo
3 | +----------------------------------------------------------+
4 | Repo ID: some-repo-source-rpms
5 | Repo Name: Some Repo (Source RPMs)
6 | Repo URL: https://my.host.example.com/repos/some-repo/source/rpms
7 | Enabled: 1
8 |
9 | Repo ID: some-repo-rpms
10 | Repo Name: Some Repo
11 | Repo URL: https://my.host.example.com/repos/some-repo/rpms
12 | Enabled: 1
13 |
14 | Repo ID: some-repo-2-beta-rpms
15 | Repo Name: Some Repo (Beta RPMs)
16 | Repo URL: https://my.host.example.com/repos/some-repo-2/beta/rpms
17 | Enabled: 0
18 |
19 |
--------------------------------------------------------------------------------
/lib/linux_admin/common.rb:
--------------------------------------------------------------------------------
1 | require 'awesome_spawn'
2 |
3 | module LinuxAdmin
4 | module Common
5 | include Logging
6 |
7 | BIN_DIRS = %w(/bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin)
8 |
9 | def self.cmd(name)
10 | BIN_DIRS.collect { |dir| "#{dir}/#{name}" }.detect { |cmd| File.exist?(cmd) }
11 | end
12 |
13 | def self.cmd?(name)
14 | !cmd(name).nil?
15 | end
16 |
17 | def self.run(cmd, options = {})
18 | AwesomeSpawn.logger ||= logger
19 | AwesomeSpawn.run(cmd, options)
20 | end
21 |
22 | def self.run!(cmd, options = {})
23 | AwesomeSpawn.logger ||= logger
24 | AwesomeSpawn.run!(cmd, options)
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/data/subscription_manager/output_list_installed_not_subscribed:
--------------------------------------------------------------------------------
1 | +-------------------------------------------+
2 | Installed Product Status
3 | +-------------------------------------------+
4 | Product Name: Red Hat Enterprise Linux Server
5 | Product ID: 69
6 | Version: 6.3
7 | Arch: x86_64
8 | Status: Not Subscribed
9 | Starts: 09/27/2012
10 | Ends: 09/26/2013
11 |
12 | Product Name: Red Hat CloudForms
13 | Product ID: 167
14 | Version: 2.1
15 | Arch: x86_64
16 | Status: Subscribed
17 | Starts: 01/03/2013
18 | Ends:
19 |
20 |
--------------------------------------------------------------------------------
/spec/data/subscription_manager/output_list_installed_subscribed:
--------------------------------------------------------------------------------
1 | +-------------------------------------------+
2 | Installed Product Status
3 | +-------------------------------------------+
4 | Product Name: Red Hat Enterprise Linux Server
5 | Product ID: 69
6 | Version: 6.3
7 | Arch: x86_64
8 | Status: Subscribed
9 | Starts: 09/27/2012
10 | Ends: 09/26/2013
11 |
12 | Product Name: Red Hat CloudForms
13 | Product ID: 167
14 | Version: 2.1
15 | Arch: x86_64
16 | Status: Subscribed
17 | Starts: 01/03/2013
18 | Ends: 01/03/2014
19 |
20 |
--------------------------------------------------------------------------------
/examples/subscription_manager_hosted.rb:
--------------------------------------------------------------------------------
1 | $:.push("../lib")
2 | require 'linux_admin'
3 |
4 | username = "MyUsername"
5 | password = "MyPassword"
6 |
7 |
8 | reg_status = LinuxAdmin.registered?
9 | puts "Registration Status: #{reg_status.to_s}"
10 |
11 | unless reg_status
12 | puts "Registering to Subscription Manager..."
13 | LinuxAdmin::SubscriptionManager.register(:username => username, :password => password)
14 | end
15 |
16 | reg_type = LinuxAdmin.registration_type
17 | puts "Registration System: #{reg_type}"
18 |
19 | puts "Subscribing to channels..."
20 | reg_type.subscribe(reg_type.available_subscriptions.keys.first)
21 | puts "Checking for updates..."
22 | if LinuxAdmin::Yum.updates_available?
23 | puts "Updates Available \n Updating..."
24 | puts "Updates Applied" if LinuxAdmin::Yum.update
25 | end
26 |
--------------------------------------------------------------------------------
/spec/partition_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Partition do
2 | before(:each) do
3 | @disk = LinuxAdmin::Disk.new :path => '/dev/sda'
4 | @partition = LinuxAdmin::Partition.new :disk => @disk, :id => 2
5 | end
6 |
7 | describe "#path" do
8 | it "returns partition path" do
9 | expect(@partition.path).to eq('/dev/sda2')
10 | end
11 | end
12 |
13 | describe "#mount" do
14 | context "mount_point not specified" do
15 | it "sets default mount_point" do
16 | expect(described_class).to receive(:mount_point_exists?).and_return(false)
17 | expect(File).to receive(:directory?).with('/mnt/sda2').and_return(true)
18 | expect(LinuxAdmin::Common).to receive(:run!)
19 | @partition.mount
20 | expect(@partition.mount_point).to eq('/mnt/sda2')
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/linux_admin/partition.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 |
3 | module LinuxAdmin
4 | class Partition
5 | include Mountable
6 |
7 | attr_accessor :id
8 | attr_accessor :partition_type
9 | attr_accessor :start_sector
10 | attr_accessor :end_sector
11 | attr_accessor :size
12 | attr_accessor :disk
13 |
14 | def initialize(args={})
15 | @id = args[:id]
16 | @size = args[:size]
17 | @disk = args[:disk]
18 | @fs_type = args[:fs_type]
19 | @start_sector = args[:start_sector]
20 | @end_sector = args[:end_sector]
21 | @partition_type = args[:partition_type]
22 | end
23 |
24 | def path
25 | "#{disk.path}#{id}"
26 | end
27 |
28 | def mount(mount_point=nil)
29 | mount_point ||= "/mnt/#{disk.path.split(File::SEPARATOR).last}#{id}"
30 | super(mount_point)
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/linux_admin/chrony.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Chrony
3 | SERVICE_NAME = "chronyd".freeze
4 |
5 | def initialize(conf = "/etc/chrony.conf")
6 | raise MissingConfigurationFileError, "#{conf} does not exist" unless File.exist?(conf)
7 | @conf = conf
8 | end
9 |
10 | def clear_servers
11 | data = File.read(@conf)
12 | data.gsub!(/^server\s+.+\n/, "")
13 | File.write(@conf, data)
14 | end
15 |
16 | def add_servers(*servers)
17 | data = File.read(@conf)
18 | data << "\n" unless data.end_with?("\n")
19 | servers.each { |s| data << "server #{s} iburst\n" }
20 | File.write(@conf, data)
21 | restart_service_if_running
22 | end
23 |
24 | private
25 |
26 | def restart_service_if_running
27 | service = Service.new(SERVICE_NAME)
28 | service.restart if service.running?
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/linux_admin/dns.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Dns
3 | attr_accessor :filename
4 | attr_accessor :nameservers
5 | attr_accessor :search_order
6 |
7 | def initialize(filename = "/etc/resolv.conf")
8 | @filename = filename
9 | reload
10 | end
11 |
12 | def reload
13 | @search_order = []
14 | @nameservers = []
15 |
16 | File.read(@filename).split("\n").each do |line|
17 | if line =~ /^search .*/
18 | @search_order += line.split(/^search\s+/)[1].split
19 | elsif line =~ /^nameserver .*/
20 | @nameservers.push(line.split[1])
21 | end
22 | end
23 | end
24 |
25 | def save
26 | search = "search #{@search_order.join(" ")}\n" unless @search_order.empty?
27 | servers = @nameservers.collect { |ns| "nameserver #{ns}\n" }.join
28 | File.write(@filename, "#{search}#{servers}")
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/linux_admin/deb.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Deb < Package
3 | APT_CACHE_CMD = '/usr/bin/apt-cache'
4 |
5 | def self.from_line(apt_cache_line, in_description=false)
6 | tag,value = apt_cache_line.split(':')
7 | tag = tag.strip.downcase
8 | [tag, value]
9 | end
10 |
11 | def self.from_string(apt_cache_string)
12 | in_description = false
13 | apt_cache_string.split("\n").each.with_object({}) do |line,deb|
14 | tag,value = self.from_line(line)
15 | if tag == 'description-en'
16 | in_description = true
17 | elsif tag == 'homepage'
18 | in_description = false
19 | end
20 |
21 | if in_description && tag != 'description-en'
22 | deb['description-en'] << line
23 | else
24 | deb[tag] = value.strip
25 | end
26 | end
27 | end
28 |
29 | def self.info(pkg)
30 | from_string(Common.run!(APT_CACHE_CMD, :params => ["show", pkg]).output)
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/linux_admin/service.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Service
3 | include Logging
4 |
5 | def self.service_type(reload = false)
6 | return @service_type if @service_type && !reload
7 | @service_type = service_type_uncached
8 | end
9 |
10 | class << self
11 | private
12 | alias_method :orig_new, :new
13 | end
14 |
15 | def self.new(*args)
16 | if self == LinuxAdmin::Service
17 | service_type.new(*args)
18 | else
19 | orig_new(*args)
20 | end
21 | end
22 |
23 | attr_accessor :name
24 |
25 | def initialize(name)
26 | @name = name
27 | end
28 |
29 | alias_method :id, :name
30 | alias_method :id=, :name=
31 |
32 | private
33 |
34 | def self.service_type_uncached
35 | Common.cmd?(:systemctl) ? SystemdService : SysVInitService
36 | end
37 | private_class_method :service_type_uncached
38 | end
39 | end
40 |
41 | Dir.glob(File.join(File.dirname(__FILE__), "service", "*.rb")).each { |f| require f }
42 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Red Hat, Inc.
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/spec/common_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Common do
2 | describe "#cmd" do
3 | it "looks up local command from id" do
4 | expect(described_class.cmd(:dd)).to match(%r{bin/dd})
5 | end
6 |
7 | it "returns nil when it can't find the command" do
8 | expect(described_class.cmd(:kasgbdlcvjhals)).to be_nil
9 | end
10 | end
11 |
12 | describe "#cmd?" do
13 | it "returns true when the command exists" do
14 | expect(described_class.cmd?(:dd)).to be true
15 | end
16 |
17 | it "returns false when the command doesn't exist" do
18 | expect(described_class.cmd?(:kasgbdlcvjhals)).to be false
19 | end
20 | end
21 |
22 | [described_class, LinuxAdmin].each do |klass|
23 | describe "#{klass}.run" do
24 | it "runs a command with AwesomeSpawn.run" do
25 | expect(AwesomeSpawn).to receive(:run).with("echo", nil => "test")
26 | klass.run("echo", nil => "test")
27 | end
28 | end
29 |
30 | describe "#{klass}.run!" do
31 | it "runs a command with AwesomeSpawn.run!" do
32 | expect(AwesomeSpawn).to receive(:run!).with("echo", nil => "test")
33 | klass.run!("echo", nil => "test")
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/etc_issue_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::EtcIssue do
2 | subject { described_class.instance }
3 | before do
4 | # Reset the singleton so subsequent tests get a new instance
5 | subject.refresh
6 | end
7 |
8 | it "should not find the phrase when the file is missing" do
9 | expect(File).to receive(:exist?).with('/etc/issue').at_least(:once).and_return(false)
10 | expect(subject).not_to include("phrase")
11 | end
12 |
13 | it "should not find phrase when the file is empty" do
14 | etc_issue_contains("")
15 | expect(subject).not_to include("phrase")
16 | end
17 |
18 | it "should not find phrase when the file has a different phrase" do
19 | etc_issue_contains("something\nelse")
20 | expect(subject).not_to include("phrase")
21 | end
22 |
23 | it "should find phrase in same case" do
24 | etc_issue_contains("phrase")
25 | expect(subject).to include("phrase")
26 | end
27 |
28 | it "should find upper phrase in file" do
29 | etc_issue_contains("PHRASE\nother")
30 | expect(subject).to include("phrase")
31 | end
32 |
33 | it "should find phrase when searching with upper" do
34 | etc_issue_contains("other\nphrase")
35 | expect(subject).to include("PHRASE")
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/linux_admin/time_date.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class TimeDate
3 | COMMAND = 'timedatectl'
4 |
5 | TimeCommandError = Class.new(StandardError)
6 |
7 | def self.system_timezone_detailed
8 | result = Common.run(Common.cmd(COMMAND), :params => ["status"])
9 | result.output.split("\n").each do |l|
10 | return l.split(':')[1].strip if l =~ /Time.*zone/
11 | end
12 | end
13 |
14 | def self.system_timezone
15 | system_timezone_detailed.split[0]
16 | end
17 |
18 | def self.timezones
19 | result = Common.run!(Common.cmd(COMMAND), :params => ["list-timezones"])
20 | result.output.split("\n")
21 | rescue AwesomeSpawn::CommandResultError => e
22 | raise TimeCommandError, e.message
23 | end
24 |
25 | def self.system_time=(time)
26 | Common.run!(Common.cmd(COMMAND), :params => ["set-time", "#{time.strftime("%F %T")}", :adjust_system_clock])
27 | rescue AwesomeSpawn::CommandResultError => e
28 | raise TimeCommandError, e.message
29 | end
30 |
31 | def self.system_timezone=(zone)
32 | Common.run!(Common.cmd(COMMAND), :params => ["set-timezone", zone])
33 | rescue AwesomeSpawn::CommandResultError => e
34 | raise TimeCommandError, e.message
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/hardware_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Hardware do
2 | CONTENT = <<-EOF
3 | processor : 0
4 | vendor_id : GenuineIntel
5 | cpu family : 6
6 | model : 58
7 | model name : Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz
8 | stepping : 9
9 | microcode : 0x17
10 | cpu MHz : 2614.148
11 | cache size : 6144 KB
12 | physical id : 0
13 | siblings : 8
14 | core id : 0
15 | cpu cores : 4
16 | apicid : 0
17 | initial apicid : 0
18 | fpu : yes
19 | fpu_exception : yes
20 | cpuid level : 13
21 | wp : yes
22 | flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt
23 | bugs :
24 | bogomips : 5387.35
25 | clflush size : 64
26 | cache_alignment : 64
27 | address sizes : 36 bits physical, 48 bits virtual
28 | power management:
29 |
30 | processor : 1
31 |
32 | processor : 2
33 |
34 | processor : 3
35 | processor : 4
36 | processor : 5
37 | processor : 6
38 | processor : 7
39 | EOF
40 |
41 | it "total_cores" do
42 | allow(File).to receive(:readlines).and_return(CONTENT.lines)
43 |
44 | expect(described_class.new.total_cores).to eq(8)
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LinuxAdmin
2 |
3 | [](http://badge.fury.io/rb/linux_admin)
4 | [](https://travis-ci.org/ManageIQ/linux_admin)
5 | [](https://codeclimate.com/github/ManageIQ/linux_admin)
6 | [](https://coveralls.io/r/ManageIQ/linux_admin)
7 | [](https://gemnasium.com/ManageIQ/linux_admin)
8 | [](https://hakiri.io/github/ManageIQ/linux_admin/master)
9 |
10 |
11 | LinuxAdmin is a module to simplify management of linux systems.
12 | It should be a single place to manage various system level configurations,
13 | registration, updates, etc.
14 |
15 | ## Installation
16 |
17 | Add this line to your application's Gemfile:
18 |
19 | gem 'linux_admin'
20 |
21 | And then execute:
22 |
23 | $ bundle
24 |
25 | Or install it yourself as:
26 |
27 | $ gem install linux_admin
28 |
29 | ## Usage
30 |
31 | TODO: Write usage instructions here
32 |
33 | ## Contributing
34 |
35 | 1. Fork it
36 | 2. Create your feature branch (`git checkout -b my-new-feature`)
37 | 3. Commit your changes (`git commit -am 'Add some feature'`)
38 | 4. Push to the branch (`git push origin my-new-feature`)
39 | 5. Create new Pull Request
40 |
--------------------------------------------------------------------------------
/lib/linux_admin/service/sys_v_init_service.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class SysVInitService < Service
3 | def running?
4 | Common.run(Common.cmd(:service),
5 | :params => {nil => [name, "status"]}).exit_status == 0
6 | end
7 |
8 | def enable
9 | Common.run!(Common.cmd(:chkconfig),
10 | :params => {nil => [name, "on"]})
11 | self
12 | end
13 |
14 | def disable
15 | Common.run!(Common.cmd(:chkconfig),
16 | :params => {nil => [name, "off"]})
17 | self
18 | end
19 |
20 | def start
21 | Common.run!(Common.cmd(:service),
22 | :params => {nil => [name, "start"]})
23 | self
24 | end
25 |
26 | def stop
27 | Common.run!(Common.cmd(:service),
28 | :params => {nil => [name, "stop"]})
29 | self
30 | end
31 |
32 | def restart
33 | status =
34 | Common.run(Common.cmd(:service),
35 | :params => {nil => [name, "restart"]}).exit_status
36 |
37 | # attempt to manually stop/start if restart fails
38 | if status != 0
39 | self.stop
40 | self.start
41 | end
42 |
43 | self
44 | end
45 |
46 | def reload
47 | Common.run!(Common.cmd(:service), :params => [name, "reload"])
48 | self
49 | end
50 |
51 | def status
52 | Common.run(Common.cmd(:service), :params => [name, "status"]).output
53 | end
54 |
55 | def show
56 | raise NotImplementedError
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/linux_admin/mountable.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | module Mountable
3 | attr_accessor :fs_type
4 | attr_accessor :mount_point
5 |
6 | module ClassMethods
7 | def mount_point_exists?(mount_point)
8 | result = Common.run!(Common.cmd(:mount))
9 | result.output.split("\n").any? { |line| line.split[2] == mount_point.to_s }
10 | end
11 |
12 | def mount_point_available?(mount_point)
13 | !mount_point_exists?(mount_point)
14 | end
15 | end
16 |
17 | def self.included(base)
18 | base.extend(ClassMethods)
19 | end
20 |
21 | def discover_mount_point
22 | result = Common.run!(Common.cmd(:mount))
23 | mount_line = result.output.split("\n").find { |line| line.split[0] == path }
24 | @mount_point = mount_line.split[2] if mount_line
25 | end
26 |
27 | def format_to(filesystem)
28 | Common.run!(Common.cmd(:mke2fs),
29 | :params => {'-t' => filesystem, nil => path})
30 | @fs_type = filesystem
31 | end
32 |
33 | def mount(mount_point)
34 | FileUtils.mkdir(mount_point) unless File.directory?(mount_point)
35 |
36 | if self.class.mount_point_exists?(mount_point)
37 | raise ArgumentError, "disk already mounted at #{mount_point}"
38 | end
39 |
40 | Common.run!(Common.cmd(:mount), :params => {nil => [path, mount_point]})
41 | @mount_point = mount_point
42 | end
43 |
44 | def umount
45 | Common.run!(Common.cmd(:umount), :params => {nil => [@mount_point]})
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/data/rhn/systemid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | username
7 | SomeUsername
8 |
9 |
10 | operating_system
11 | redhat-release-server
12 |
13 |
14 | description
15 | Initial Registration Parameters:
16 | OS: redhat-release-server
17 | Release: 6Server
18 | CPU Arch: x86_64
19 |
20 |
21 | checksum
22 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
23 |
24 |
25 | profile_name
26 | SomeProfileName
27 |
28 |
29 | system_id
30 | ID-0123456789
31 |
32 |
33 | architecture
34 | x86_64
35 |
36 |
37 | os_release
38 | 6Server
39 |
40 |
41 | fields
42 |
43 | system_id
44 | os_release
45 | operating_system
46 | architecture
47 | username
48 | type
49 |
50 |
51 |
52 | type
53 | REAL
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/spec/data/rhn/systemid.missing_system_id:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | username
7 | SomeUsername
8 |
9 |
10 | operating_system
11 | redhat-release-server
12 |
13 |
14 | description
15 | Initial Registration Parameters:
16 | OS: redhat-release-server
17 | Release: 6Server
18 | CPU Arch: x86_64
19 |
20 |
21 | checksum
22 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
23 |
24 |
25 | profile_name
26 | SomeProfileName
27 |
28 |
29 | system_id
30 |
31 |
32 |
33 | architecture
34 | x86_64
35 |
36 |
37 | os_release
38 | 6Server
39 |
40 |
41 | fields
42 |
43 | system_id
44 | os_release
45 | operating_system
46 | architecture
47 | username
48 | type
49 |
50 |
51 |
52 | type
53 | REAL
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/lib/linux_admin.rb:
--------------------------------------------------------------------------------
1 | require 'more_core_extensions/all'
2 |
3 | require 'linux_admin/logging'
4 | require 'linux_admin/null_logger'
5 |
6 | require 'linux_admin/common'
7 | require 'linux_admin/exceptions'
8 | require 'linux_admin/package'
9 | require 'linux_admin/registration_system'
10 | require 'linux_admin/rpm'
11 | require 'linux_admin/deb'
12 | require 'linux_admin/version'
13 | require 'linux_admin/yum'
14 | require 'linux_admin/ssh'
15 | require 'linux_admin/ssh_agent'
16 | require 'linux_admin/service'
17 | require 'linux_admin/mountable'
18 | require 'linux_admin/disk'
19 | require 'linux_admin/hardware'
20 | require 'linux_admin/hosts'
21 | require 'linux_admin/partition'
22 | require 'linux_admin/etc_issue'
23 | require 'linux_admin/distro'
24 | require 'linux_admin/system'
25 | require 'linux_admin/fstab'
26 | require 'linux_admin/volume'
27 | require 'linux_admin/logical_volume'
28 | require 'linux_admin/physical_volume'
29 | require 'linux_admin/volume_group'
30 | require 'linux_admin/scap'
31 | require 'linux_admin/time_date'
32 | require 'linux_admin/ip_address'
33 | require 'linux_admin/dns'
34 | require 'linux_admin/network_interface'
35 | require 'linux_admin/chrony'
36 | require 'forwardable'
37 |
38 | module LinuxAdmin
39 | class << self
40 | extend Forwardable
41 | extend Gem::Deprecate
42 | attr_writer :logger
43 |
44 | def_delegators Common, :run
45 | def_delegators Common, :run!
46 | deprecate :run, "AwesomeSpawn.run", 2017, 6
47 | deprecate :run!, "AwesomeSpawn.run!", 2017, 6
48 | end
49 |
50 | def self.logger
51 | @logger ||= NullLogger.new
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/data/yum/output_repo_list:
--------------------------------------------------------------------------------
1 | Loaded plugins: product-id, rhnplugin, security, subscription-manager
2 | This system is receiving updates from Red Hat Subscription Management.
3 | This system is not registered with RHN Classic or RHN Satellite.
4 | You can use rhn_register to register.
5 | RHN Satellite or RHN Classic support will be disabled.
6 | rhel-6-server-rpms | 3.7 kB 00:00
7 | rhel-ha-for-rhel-6-server-rpms | 3.7 kB 00:00
8 | rhel-lb-for-rhel-6-server-rpms | 3.7 kB 00:00
9 | repo id repo name status
10 | rhel-6-server-rpms Red Hat Enterprise Linux 6 Server (RPMs) 11,016
11 | rhel-ha-for-rhel-6-server-rpms Red Hat Enterprise Linux High Availability (for RHEL 6 Server) (RPMs) 269
12 | rhel-lb-for-rhel-6-server-rpms Red Hat Enterprise Linux Load Balancer (for RHEL 6 Server) (RPMs) 0
13 | repolist: 11,909
14 |
--------------------------------------------------------------------------------
/lib/linux_admin/ip_address.rb:
--------------------------------------------------------------------------------
1 | require 'ipaddr'
2 |
3 | module LinuxAdmin
4 | class IpAddress
5 | def address
6 | address_list.detect { |ip| IPAddr.new(ip).ipv4? }
7 | end
8 |
9 | def address6
10 | address_list.detect { |ip| IPAddr.new(ip).ipv6? }
11 | end
12 |
13 | def mac_address(interface)
14 | result = Common.run(Common.cmd("ip"), :params => ["addr", "show", interface])
15 | return nil if result.failure?
16 |
17 | parse_output(result.output, %r{link/ether}, 1)
18 | end
19 |
20 | def netmask(interface)
21 | result = Common.run(Common.cmd("ifconfig"), :params => [interface])
22 | return nil if result.failure?
23 |
24 | parse_output(result.output, /netmask/, 3)
25 | end
26 |
27 | def gateway
28 | result = Common.run(Common.cmd("ip"), :params => ["route"])
29 | return nil if result.failure?
30 |
31 | parse_output(result.output, /^default/, 2)
32 | end
33 |
34 | private
35 |
36 | def parse_output(output, regex, col)
37 | the_line = output.split("\n").detect { |l| l =~ regex }
38 | the_line.nil? ? nil : the_line.strip.split(' ')[col]
39 | end
40 |
41 | def address_list
42 | result = nil
43 | # Added retry to account for slow DHCP not assigning an IP quickly at boot; specifically:
44 | # https://github.com/ManageIQ/manageiq-appliance/commit/160d8ccbfbfd617bdb5445e56cdab66b9323b15b
45 | 5.times do
46 | result = Common.run(Common.cmd("hostname"), :params => ["-I"])
47 | break if result.success?
48 | end
49 |
50 | result.success? ? result.output.split(' ') : []
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/data/subscription_manager/output_list_all_available:
--------------------------------------------------------------------------------
1 | +-------------------------------------------+
2 | Available Subscriptions
3 | +-------------------------------------------+
4 | Subscription Name: Example Subscription
5 | SKU: SER0123
6 | Pool Id: 82c042fca983889b10178893f29b06e3
7 | Quantity: 1690
8 | Service Level: None
9 | Service Type: None
10 | Multi-Entitlement: No
11 | Ends: 01/01/2022
12 | System Type: Physical
13 |
14 | Subscription Name: My Private Subscription
15 | SKU: SER9876
16 | Pool Id: 4f738052ec866192c775c62f408ab868
17 | Quantity: Unlimited
18 | Service Level: None
19 | Service Type: None
20 | Multi-Entitlement: No
21 | Ends: 06/04/2013
22 | System Type: Virtual
23 |
24 | Subscription Name: Shared Subscription - With other characters, (2 sockets) (Up to 1 guest)
25 | SKU: RH0123456
26 | Pool Id: 3d81297f352305b9a3521981029d7d83
27 | Quantity: 1
28 | Service Level: Self-support
29 | Service Type: L1-L3
30 | Multi-Entitlement: No
31 | Ends: 05/15/2013
32 | System Type: Virtual
33 |
34 | Subscription Name: Example Subscription, Premium (up to 2 sockets) 3 year
35 | SKU: MCT0123A9
36 | Pool Id: 87cefe63b67984d5c7e401d833d2f87f
37 | Quantity: 1
38 | Service Level: Premium
39 | Service Type: L1-L3
40 | Multi-Entitlement: No
41 | Ends: 07/05/2013
42 | System Type: Virtual
43 |
--------------------------------------------------------------------------------
/lib/linux_admin/distro.rb:
--------------------------------------------------------------------------------
1 | require 'linux_admin/etc_issue'
2 |
3 | module LinuxAdmin
4 | module Distros
5 | def self.generic
6 | @generic ||= Distro.new(:generic)
7 | end
8 |
9 | def self.rhel
10 | @rhel ||= Distro.new(:rhel, '/etc/redhat-release', ['red hat', 'centos'], LinuxAdmin::Rpm)
11 | end
12 |
13 | def self.fedora
14 | @fedora ||= Distro.new(:fedora, "/etc/fedora-release", ['Fedora'], LinuxAdmin::Rpm)
15 | end
16 |
17 | def self.ubuntu
18 | @ubuntu ||= Distro.new(:ubuntu, nil, ['ubuntu'], LinuxAdmin::Deb)
19 | end
20 |
21 | def self.all
22 | @distros ||= [rhel, fedora, ubuntu, generic]
23 | end
24 |
25 | def self.local
26 | @local ||= begin
27 | Distros.all.detect(&:detected?) || Distros.generic
28 | end
29 | end
30 |
31 | class Distro
32 | attr_accessor :release_file, :etc_issue_keywords, :info_class
33 |
34 | def initialize(id, release_file = nil, etc_issue_keywords = [], info_class = nil)
35 | @id = id
36 | @release_file = release_file
37 | @etc_issue_keywords = etc_issue_keywords
38 | @info_class = info_class
39 | end
40 |
41 | def detected?
42 | detected_by_etc_issue? || detected_by_etc_release?
43 | end
44 |
45 | def detected_by_etc_issue?
46 | etc_issue_keywords && etc_issue_keywords.any? { |k| EtcIssue.instance.include?(k) }
47 | end
48 |
49 | def detected_by_etc_release?
50 | release_file && File.exist?(release_file)
51 | end
52 |
53 | def info(pkg)
54 | info_class ? info_class.info(pkg) : nil
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/linux_admin/registration_system.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class RegistrationSystem
3 | include Logging
4 |
5 | def self.registration_type(reload = false)
6 | return @registration_type if @registration_type && !reload
7 | @registration_type = registration_type_uncached
8 | end
9 |
10 | def self.method_missing(meth, *args, &block)
11 | if white_list_methods.include?(meth)
12 | r = self.registration_type.new
13 | raise NotImplementedError, "#{meth} not implemented for #{self.name}" unless r.respond_to?(meth)
14 | r.send(meth, *args, &block)
15 | else
16 | super
17 | end
18 | end
19 |
20 | def registered?(_options = nil)
21 | false
22 | end
23 |
24 | private
25 |
26 | def self.registration_type_uncached
27 | if Rhn.new.registered?
28 | Rhn
29 | elsif SubscriptionManager.new.registered?
30 | SubscriptionManager
31 | else
32 | self
33 | end
34 | end
35 | private_class_method :registration_type_uncached
36 |
37 | def self.white_list_methods
38 | @white_list_methods ||= begin
39 | all_methods = RegistrationSystem.instance_methods(false) + Rhn.instance_methods(false) + SubscriptionManager.instance_methods(false)
40 | all_methods.uniq
41 | end
42 | end
43 | private_class_method :white_list_methods
44 |
45 | def install_server_certificate(server, cert_path)
46 | host = server.start_with?("http") ? URI.parse(server).host : server
47 |
48 | LinuxAdmin::Rpm.upgrade("http://#{host}/#{cert_path}")
49 | end
50 | end
51 | end
52 |
53 | Dir.glob(File.join(File.dirname(__FILE__), "registration_system", "*.rb")).each { |f| require f }
54 |
--------------------------------------------------------------------------------
/lib/linux_admin/ssh_agent.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | module LinuxAdmin
3 | class SSHAgent
4 | attr_accessor :pid
5 | attr_reader :socket
6 |
7 | def initialize(ssh_private_key, agent_socket = nil)
8 | @socket = agent_socket
9 | @private_key = ssh_private_key
10 | end
11 |
12 | def start
13 | if @socket
14 | FileUtils.mkdir_p(File.dirname(@socket))
15 | agent_details = `ssh-agent -a #{@socket}`
16 | else
17 | agent_details = `ssh-agent`
18 | @socket = parse_ssh_agent_socket(agent_details)
19 | end
20 | @pid = parse_ssh_agent_pid(agent_details)
21 | IO.popen({'SSH_AUTH_SOCK' => @socket, 'SSH_AGENT_PID' => @pid}, ['ssh-add', '-'], :mode => 'w') do |f|
22 | f.puts(@private_key)
23 | end
24 | raise StandardError, "Couldn't add key to agent" if $CHILD_STATUS.to_i != 0
25 | end
26 |
27 | def with_service
28 | start
29 | yield @socket
30 | ensure
31 | stop
32 | end
33 |
34 | def stop
35 | system({'SSH_AGENT_PID' => @pid}, '(ssh-agent -k) &> /dev/null') if process_exists?(@pid)
36 | File.delete(@socket) if File.exist?(@socket)
37 | @socket = nil
38 | @pid = nil
39 | end
40 |
41 | private
42 |
43 | def process_exists?(process_pid)
44 | Process.kill(0, process_pid) == 1
45 | rescue
46 | false
47 | end
48 |
49 | def parse_ssh_agent_socket(output)
50 | parse_ssh_agent_output(output, 1)
51 | end
52 |
53 | def parse_ssh_agent_pid(output)
54 | parse_ssh_agent_output(output, 2)
55 | end
56 |
57 | def parse_ssh_agent_output(output, index)
58 | output.split('=')[index].split(' ')[0].chop
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/linux_admin/rpm.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Rpm < Package
3 | extend Logging
4 |
5 | def self.rpm_cmd
6 | Common.cmd(:rpm)
7 | end
8 |
9 | def self.list_installed
10 | out = Common.run!("#{rpm_cmd} -qa --qf \"%{NAME} %{VERSION}-%{RELEASE}\n\"").output
11 | out.split("\n").each_with_object({}) do |line, pkg_hash|
12 | name, ver = line.split(" ")
13 | pkg_hash[name] = ver
14 | end
15 | end
16 |
17 | # Import a GPG file for use with RPM
18 | #
19 | # Rpm.import_key("/etc/pki/my-gpg-key")
20 | def self.import_key(file)
21 | logger.info("#{self.class.name}##{__method__} Importing RPM-GPG-KEY: #{file}")
22 | Common.run!("rpm", :params => {"--import" => file})
23 | end
24 |
25 | def self.info(pkg)
26 | params = { "-qi" => pkg}
27 | in_description = false
28 | out = Common.run!(rpm_cmd, :params => params).output
29 | # older versions of rpm may have multiple fields per line,
30 | # split up lines with multiple tags/values:
31 | out.gsub!(/(^.*:.*)\s\s+(.*:.*)$/, "\\1\n\\2")
32 | out.split("\n").each.with_object({}) do |line, rpm|
33 | next if !line || line.empty?
34 | tag,value = line.split(':')
35 | tag = tag.strip
36 | if tag == 'Description'
37 | in_description = true
38 | elsif in_description
39 | rpm['description'] ||= ""
40 | rpm['description'] << line + " "
41 | else
42 | tag = tag.downcase.gsub(/\s/, '_')
43 | rpm[tag] = value.strip
44 | end
45 | end
46 | end
47 |
48 | def self.upgrade(pkg)
49 | cmd = "rpm -U"
50 | params = { nil => pkg }
51 |
52 | Common.run(cmd, :params => params).exit_status == 0
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/service_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Service do
2 | context ".service_type" do
3 | it "on systemctl systems" do
4 | stub_to_service_type(:systemd_service)
5 | expect(described_class.service_type).to eq(LinuxAdmin::SystemdService)
6 | end
7 |
8 | it "on sysv systems" do
9 | stub_to_service_type(:sys_v_init_service)
10 | expect(described_class.service_type).to eq(LinuxAdmin::SysVInitService)
11 | end
12 |
13 | it "should memoize results" do
14 | expect(described_class).to receive(:service_type_uncached).once.and_return("anything_non_nil")
15 | described_class.service_type
16 | described_class.service_type
17 | end
18 |
19 | it "with reload should refresh results" do
20 | expect(described_class).to receive(:service_type_uncached).twice.and_return("anything_non_nil")
21 | described_class.service_type
22 | described_class.service_type(true)
23 | end
24 | end
25 |
26 | context ".new" do
27 | it "on systemctl systems" do
28 | stub_to_service_type(:systemd_service)
29 | expect(described_class.new("xxx")).to be_kind_of(LinuxAdmin::SystemdService)
30 | end
31 |
32 | it "on sysv systems" do
33 | stub_to_service_type(:sys_v_init_service)
34 | expect(described_class.new("xxx")).to be_kind_of(LinuxAdmin::SysVInitService)
35 | end
36 | end
37 |
38 | it "#id / #id=" do
39 | s = described_class.new("xxx")
40 | expect(s.id).to eq("xxx")
41 |
42 | s.id = "yyy"
43 | expect(s.id).to eq("yyy")
44 | expect(s.name).to eq("yyy")
45 |
46 | s.name = "zzz"
47 | expect(s.id).to eq("zzz")
48 | expect(s.name).to eq("zzz")
49 | end
50 |
51 | def stub_to_service_type(system)
52 | allow(LinuxAdmin::Common).to receive(:cmd?).with(:systemctl).and_return(system == :systemd_service)
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/linux_admin.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'linux_admin/version'
5 |
6 | Gem::Specification.new do |spec|
7 |
8 | # Dynamically create the authors information {name => e-mail}
9 | authors_hash = Hash[`git log --no-merges --reverse --format='%an,%ae'`.split("\n").uniq.collect {|i| i.split(",")}]
10 |
11 | spec.name = "linux_admin"
12 | spec.version = LinuxAdmin::VERSION
13 | spec.authors = authors_hash.keys
14 | spec.email = authors_hash.values
15 | spec.description = %q{
16 | LinuxAdmin is a module to simplify management of linux systems.
17 | It should be a single place to manage various system level configurations,
18 | registration, updates, etc.
19 | }
20 | spec.summary = %q{LinuxAdmin is a module to simplify management of linux systems.}
21 | spec.homepage = "http://github.com/ManageIQ/linux_admin"
22 | spec.license = "MIT"
23 |
24 | spec.files = `git ls-files -- lib/*`.split("\n")
25 | spec.files += %w[README.md LICENSE.txt]
26 | spec.executables = `git ls-files -- bin/*`.split("\n")
27 | spec.test_files = `git ls-files -- spec/*`.split("\n")
28 | spec.require_paths = ["lib"]
29 |
30 | spec.required_ruby_version = ">= 2.0.0"
31 |
32 | spec.add_development_dependency "bundler", "~> 1.3"
33 | spec.add_development_dependency "rake"
34 | spec.add_development_dependency "rspec", "~> 3.0"
35 | spec.add_development_dependency "coveralls"
36 | spec.add_development_dependency "rubocop"
37 |
38 | spec.add_dependency "awesome_spawn", "~> 1.3"
39 | spec.add_dependency "inifile"
40 | spec.add_dependency "more_core_extensions", "~> 3.0"
41 | spec.add_dependency "nokogiri", ">= 1.8.1", "~> 1.8"
42 | spec.add_dependency "openscap"
43 | spec.add_dependency "net-ssh", "~> 4.2.0"
44 | end
45 |
--------------------------------------------------------------------------------
/lib/linux_admin/physical_volume.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class PhysicalVolume < Volume
3 | # physical volume device name
4 | attr_accessor :device_name
5 |
6 | # volume group name
7 | attr_accessor :volume_group
8 |
9 | # physical volume size in kilobytes
10 | attr_accessor :size
11 |
12 | # other fields available
13 | # internal physical volume number (obsolete)
14 | # physical volume status
15 | # physical volume (not) allocatable
16 | # current number of logical volumes on this physical volume
17 | # physical extent size in kilobytes
18 | # total number of physical extents
19 | # free number of physical extents
20 | # allocated number of physical extents
21 |
22 | def initialize(args = {})
23 | @device_name = args[:device_name]
24 | @volume_group = args[:volume_group]
25 | @size = args[:size]
26 | end
27 |
28 | def attach_to(vg)
29 | Common.run!(Common.cmd(:vgextend),
30 | :params => [vg.name, @device_name])
31 | self.volume_group = vg
32 | self
33 | end
34 |
35 | # specify disk or partition instance to create physical volume on
36 | def self.create(device)
37 | self.scan # initialize local physical volumes
38 | Common.run!(Common.cmd(:pvcreate),
39 | :params => { nil => device.path})
40 | pv = PhysicalVolume.new(:device_name => device.path,
41 | :volume_group => nil,
42 | :size => device.size)
43 | @pvs << pv
44 | pv
45 | end
46 |
47 | def self.scan
48 | @pvs ||= begin
49 | scan_volumes(Common.cmd(:pvdisplay)) do |fields, vg|
50 | PhysicalVolume.new(:device_name => fields[0],
51 | :volume_group => vg,
52 | :size => fields[2].to_i)
53 | end
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/linux_admin/service/systemd_service.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class SystemdService < Service
3 | def running?
4 | Common.run(command_path, :params => ["status", name]).success?
5 | end
6 |
7 | def enable
8 | Common.run!(command_path, :params => ["enable", name])
9 | self
10 | end
11 |
12 | def disable
13 | Common.run!(command_path, :params => ["disable", name])
14 | self
15 | end
16 |
17 | def start
18 | Common.run!(command_path, :params => ["start", name])
19 | self
20 | end
21 |
22 | def stop
23 | Common.run!(command_path, :params => ["stop", name])
24 | self
25 | end
26 |
27 | def restart
28 | status = Common.run(command_path, :params => ["restart", name]).exit_status
29 |
30 | # attempt to manually stop/start if restart fails
31 | if status != 0
32 | stop
33 | start
34 | end
35 |
36 | self
37 | end
38 |
39 | def reload
40 | Common.run!(command_path, :params => ["reload", name])
41 | self
42 | end
43 |
44 | def status
45 | Common.run(command_path, :params => ["status", name]).output
46 | end
47 |
48 | def show
49 | output = Common.run!(command_path, :params => ["show", name]).output
50 | output.split("\n").each_with_object({}) do |line, h|
51 | k, v = line.split("=", 2)
52 | h[k] = cast_show_value(k, v)
53 | end
54 | end
55 |
56 | private
57 |
58 | def command_path
59 | Common.cmd(:systemctl)
60 | end
61 |
62 | def cast_show_value(key, value)
63 | return value.to_i if value =~ /^\d+$/
64 |
65 | case key
66 | when /^.*Timestamp$/
67 | Time.parse(value)
68 | when /Exec(Start|Stop)/
69 | parse_exec_value(value)
70 | else
71 | value
72 | end
73 | end
74 |
75 | def parse_exec_value(value)
76 | value[1..-2].strip.split(" ; ").map { |s| s.split("=", 2) }.to_h
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/linux_admin/volume_group.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class VolumeGroup
3 | # volume group name
4 | attr_accessor :name
5 |
6 | # other fields available
7 | # volume group access
8 | # volume group status
9 | # internal volume group number
10 | # maximum number of logical volumes
11 | # current number of logical volumes
12 | # open count of all logical volumes in this volume group
13 | # maximum logical volume size
14 | # maximum number of physical volumes
15 | # current number of physical volumes
16 | # actual number of physical volumes
17 | # size of volume group in kilobytes
18 | # physical extent size
19 | # total number of physical extents for this volume group
20 | # allocated number of physical extents for this volume group
21 | # free number of physical extents for this volume group
22 | # uuid of volume group
23 |
24 | def initialize(args = {})
25 | @name = args[:name]
26 | end
27 |
28 | def attach_to(lv)
29 | Common.run!(Common.cmd(:lvextend),
30 | :params => [lv.name, name])
31 | self
32 | end
33 |
34 | def extend_with(pv)
35 | Common.run!(Common.cmd(:vgextend),
36 | :params => [@name, pv.device_name])
37 | pv.volume_group = self
38 | self
39 | end
40 |
41 | def self.create(name, pv)
42 | self.scan # initialize local volume groups
43 | Common.run!(Common.cmd(:vgcreate),
44 | :params => [name, pv.device_name])
45 | vg = VolumeGroup.new :name => name
46 | pv.volume_group = vg
47 | @vgs << vg
48 | vg
49 | end
50 |
51 | def self.scan
52 | @vgs ||= begin
53 | vgs = []
54 |
55 | out = Common.run!(Common.cmd(:vgdisplay), :params => {'-c' => nil}).output
56 |
57 | out.each_line do |line|
58 | fields = line.lstrip.split(':')
59 | vgs << VolumeGroup.new(:name => fields[0])
60 | end
61 |
62 | vgs
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/spec/scap_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Scap do
2 | subject { described_class.new("rhel7") }
3 |
4 | describe "#lockdown" do
5 | it "raises if given no rules" do
6 | allow(described_class).to receive(:openscap_available?).and_return(true)
7 | allow(described_class).to receive(:ssg_available?).and_return(true)
8 |
9 | expect { subject.lockdown("value1" => true) }.to raise_error(RuntimeError)
10 | end
11 | end
12 |
13 | describe "#profile_xml (private)" do
14 | it "creates a Profile tag" do
15 | profile_xml = subject.send(:profile_xml, "test-profile", [], {})
16 | expect(profile_xml).to match(%r{.*}m)
17 | end
18 |
19 | it "creates a title tag" do
20 | profile_xml = subject.send(:profile_xml, "test-profile", [], {})
21 | expect(profile_xml).to match(%r{
test-profile}m)
22 | end
23 |
24 | it "creates a description tag" do
25 | profile_xml = subject.send(:profile_xml, "test-profile", [], {})
26 | expect(profile_xml).to match(%r{test-profile}m)
27 | end
28 |
29 | it "creates a select tag for each rule" do
30 | profile_xml = subject.send(:profile_xml, "test-profile", %w(rule1 rule2), {})
31 | expect(profile_xml).to match(%r{}m)
32 | expect(profile_xml).to match(%r{}m)
33 | end
34 |
35 | it "creates a refine-value tag for each value" do
36 | profile_xml = subject.send(:profile_xml, "test-profile", [], "key1" => "val1", "key2" => "val2")
37 | expect(profile_xml).to match(%r{}m)
38 | expect(profile_xml).to match(%r{}m)
39 | end
40 | end
41 |
42 | describe ".ds_file" do
43 | it "returns the platform ds file path" do
44 | file = described_class.ds_file("rhel7")
45 | expect(file.to_s).to eq("/usr/share/xml/scap/ssg/content/ssg-rhel7-ds.xml")
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/deb_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Deb do
2 | describe "#info" do
3 | it "returns package metadata" do
4 | # as output w/ apt-cache show ruby on ubuntu 13.04
5 | data = <
11 | Original-Maintainer: akira yamada
12 | Architecture: all
13 | Source: ruby-defaults
14 | Version: 4.9
15 | Replaces: irb, rdoc
16 | Provides: irb, rdoc
17 | Depends: ruby1.9.1 (>= 1.9.3.194-1)
18 | Suggests: ri, ruby-dev
19 | Conflicts: irb, rdoc
20 | Filename: pool/main/r/ruby-defaults/ruby_4.9_all.deb
21 | Size: 4896
22 | MD5sum: b1991f2e0eafb04f5930ed242cfe1476
23 | SHA1: a7c55fbb83dd8382631ea771b5555d989351f840
24 | SHA256: 84d042e0273bd2f0082dd9e7dda0246267791fd09607041a35485bfff92f38d9
25 | Description-en: Interpreter of object-oriented scripting language Ruby (default version)
26 | Ruby is the interpreted scripting language for quick and easy
27 | object-oriented programming. It has many features to process text
28 | files and to do system management tasks (as in perl). It is simple,
29 | straight-forward, and extensible.
30 | .
31 | This package is a dependency package, which depends on Debian's default Ruby
32 | version (currently v1.9.3).
33 | Homepage: http://www.ruby-lang.org/
34 | Description-md5: da2991b37e3991230d79ba70f9c01682
35 | Bugs: https://bugs.launchpad.net/ubuntu/+filebug
36 | Origin: Ubuntu
37 | Supported: 9m
38 | Task: kubuntu-desktop, kubuntu-full, kubuntu-active, kubuntu-active-desktop, kubuntu-active-full, kubuntu-active, edubuntu-desktop-gnome, ubuntustudio-font-meta
39 | EOS
40 | expect(LinuxAdmin::Common).to receive(:run!)
41 | .with(described_class::APT_CACHE_CMD, :params => %w(show ruby))
42 | .and_return(double(:output => data))
43 | metadata = described_class.info("ruby")
44 | expect(metadata['package']).to eq('ruby')
45 | expect(metadata['priority']).to eq('optional')
46 | expect(metadata['section']).to eq('interpreters')
47 | expect(metadata['architecture']).to eq('all')
48 | expect(metadata['version']).to eq('4.9')
49 | expect(metadata['origin']).to eq('Ubuntu')
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/chrony_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Chrony do
2 | CHRONY_CONF = <<-EOF
3 | # commented server baz.example.net
4 | server foo.example.net
5 | server bar.example.net iburst
6 | driftfile /var/lib/chrony/drift
7 | makestep 10 3
8 | rtcsync
9 | EOF
10 |
11 | subject do
12 | allow(File).to receive(:exist?).and_return(true)
13 | described_class.new
14 | end
15 |
16 | describe ".new" do
17 | it "raises when the given config file doesn't exist" do
18 | expect { described_class.new("nonsense/file") }.to raise_error(LinuxAdmin::MissingConfigurationFileError)
19 | end
20 | end
21 |
22 | describe "#clear_servers" do
23 | it "removes all the server lines from the conf file" do
24 | allow(File).to receive(:read).and_return(CHRONY_CONF.dup)
25 | expect(File).to receive(:write) do |_file, contents|
26 | expect(contents).to eq "# commented server baz.example.net\ndriftfile /var/lib/chrony/drift\nmakestep 10 3\nrtcsync\n"
27 | end
28 | subject.clear_servers
29 | end
30 | end
31 |
32 | describe "#add_servers" do
33 | it "adds server lines to the conf file" do
34 | allow(File).to receive(:read).and_return(CHRONY_CONF.dup)
35 | expect(File).to receive(:write) do |_file, contents|
36 | expect(contents).to eq(CHRONY_CONF + "server baz.example.net iburst\nserver foo.bar.example.com iburst\n")
37 | end
38 | allow(subject).to receive(:restart_service_if_running)
39 | subject.add_servers("baz.example.net", "foo.bar.example.com")
40 | end
41 |
42 | it "restarts the service if it is running" do
43 | allow(File).to receive(:read).and_return(CHRONY_CONF.dup)
44 | allow(File).to receive(:write)
45 |
46 | chronyd_service = double
47 | expect(LinuxAdmin::Service).to receive(:new).with("chronyd").and_return(chronyd_service)
48 | expect(chronyd_service).to receive(:running?).and_return true
49 | expect(chronyd_service).to receive(:restart)
50 | subject.add_servers("baz.example.net", "foo.bar.example.com")
51 | end
52 |
53 | it "doesn't restart the service if it is not running" do
54 | allow(File).to receive(:read).and_return(CHRONY_CONF.dup)
55 | allow(File).to receive(:write)
56 |
57 | chronyd_service = double
58 | expect(LinuxAdmin::Service).to receive(:new).with("chronyd").and_return(chronyd_service)
59 | expect(chronyd_service).to receive(:running?).and_return false
60 | expect(chronyd_service).not_to receive(:restart)
61 | subject.add_servers("baz.example.net", "foo.bar.example.com")
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/spec/distro_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Distros::Distro do
2 | let(:subject) { LinuxAdmin::Distros.local }
3 | describe "#local" do
4 | before do
5 | allow(LinuxAdmin::Distros).to receive(:local).and_call_original
6 | end
7 |
8 | [['ubuntu', :ubuntu],
9 | ['Fedora', :fedora],
10 | ['red hat', :rhel],
11 | ['CentOS', :rhel],
12 | ['centos', :rhel]].each do |i, d|
13 | context "/etc/issue contains '#{i}'" do
14 | before(:each) do
15 | etc_issue_contains(i)
16 | exists("/etc/fedora-release" => false, "/etc/redhat-release" => false)
17 | end
18 |
19 | it "returns Distros.#{d}" do
20 | distro = LinuxAdmin::Distros.send(d)
21 | expect(subject).to eq(distro)
22 | end
23 | end
24 | end
25 |
26 | context "/etc/issue did not match" do
27 | before(:each) do
28 | etc_issue_contains('')
29 | end
30 |
31 | context "/etc/redhat-release exists" do
32 | it "returns Distros.rhel" do
33 | exists("/etc/fedora-release" => false, "/etc/redhat-release" => true)
34 | expect(subject).to eq(LinuxAdmin::Distros.rhel)
35 | end
36 | end
37 |
38 | context "/etc/fedora-release exists" do
39 | it "returns Distros.fedora" do
40 | exists("/etc/fedora-release" => true, "/etc/redhat-release" => false)
41 | expect(subject).to eq(LinuxAdmin::Distros.fedora)
42 | end
43 | end
44 | end
45 |
46 | it "returns Distros.generic" do
47 | etc_issue_contains('')
48 | exists("/etc/fedora-release" => false, "/etc/redhat-release" => false)
49 | expect(subject).to eq(LinuxAdmin::Distros.generic)
50 | end
51 | end
52 |
53 | describe "#info" do
54 | it "dispatches to redhat lookup mechanism" do
55 | stub_distro(LinuxAdmin::Distros.rhel)
56 | expect(LinuxAdmin::Rpm).to receive(:info).with('ruby')
57 | LinuxAdmin::Distros.local.info 'ruby'
58 | end
59 |
60 | it "dispatches to ubuntu lookup mechanism" do
61 | stub_distro(LinuxAdmin::Distros.ubuntu)
62 | expect(LinuxAdmin::Deb).to receive(:info).with('ruby')
63 | LinuxAdmin::Distros.local.info 'ruby'
64 | end
65 |
66 | it "dispatches to ubuntu lookup mechanism" do
67 | stub_distro(LinuxAdmin::Distros.generic)
68 | expect { LinuxAdmin::Distros.local.info 'ruby' }.not_to raise_error
69 | end
70 | end
71 |
72 | private
73 |
74 | def exists(files)
75 | files.each_pair { |file, value| allow(File).to receive(:exist?).with(file).and_return(value) }
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/spec/fstab_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::FSTab do
2 | subject { described_class.dup }
3 |
4 | it "has newline, single spaces, tab" do
5 | fstab = < profile_id) do
68 | xml.title(profile_id)
69 | xml.description(profile_id)
70 | rules.each { |r| xml.select(:idref => r, :selected => "true") }
71 | values.each { |k, v| xml.send("refine-value", :idref => k, :selector => v) }
72 | end
73 | end
74 | builder.doc.root.to_xml
75 | end
76 |
77 | def write_ds_xml(io, profile_xml)
78 | File.open(self.class.ds_file(platform)) do |f|
79 | doc = Nokogiri::XML(f)
80 | model_xml_element(doc).add_next_sibling("\n#{profile_xml}")
81 | io.write(doc.root.to_xml)
82 | end
83 | end
84 |
85 | def model_xml_element(doc)
86 | doc.css("//nist_list|model", "nist_list" => "http://checklists.nist.gov/xccdf/1.2").detect { |model| model.namespace.prefix.nil? }
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/linux_admin/ssh.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class SSH
3 | attr_reader :ip
4 | attr_reader :username
5 | attr_reader :private_key
6 | attr_reader :agent
7 |
8 | def initialize(ip, username, private_key = nil, password = nil)
9 | @ip = ip
10 | @private_key = private_key
11 | @username = username
12 | @password = password
13 | end
14 |
15 | def perform_commands(commands = [], agent_socket = nil, stdin = nil)
16 | require 'net/ssh'
17 | if block_given?
18 | execute_commands(commands, agent_socket, stdin, &Proc.new)
19 | else
20 | execute_commands(commands, agent_socket, stdin)
21 | end
22 | end
23 |
24 | private
25 |
26 | def execute_commands(commands, agent_socket, stdin)
27 | result = nil
28 | args = {:verify_host_key => false, :number_of_password_prompts => 0}
29 | if agent_socket
30 | args.merge!(:forward_agent => true,
31 | :agent_socket_factory => -> { UNIXSocket.open(agent_socket) })
32 | elsif @private_key
33 | args[:key_data] = [@private_key]
34 | elsif @password
35 | args[:password] = @password
36 | end
37 | Net::SSH.start(@ip, @username, args) do |ssh|
38 | if block_given?
39 | result = yield ssh
40 | else
41 | commands.each do |cmd|
42 | result = ssh_exec!(ssh, cmd, stdin)
43 | result[:last_command] = cmd
44 | break if result[:exit_status] != 0
45 | end
46 | end
47 | end
48 | result
49 | end
50 |
51 | def ssh_exec!(ssh, command, stdin)
52 | stdout_data = ''
53 | stderr_data = ''
54 | exit_status = nil
55 | exit_signal = nil
56 |
57 | ssh.open_channel do |channel|
58 | channel.request_pty unless stdin
59 | channel.exec(command) do |_, success|
60 | channel.send_data(stdin) if stdin
61 | channel.eof!
62 | raise StandardError, "Command \"#{command}\" was unable to execute" unless success
63 | channel.on_data do |_, data|
64 | stdout_data << data
65 | end
66 | channel.on_extended_data do |_, _, data|
67 | stderr_data << data
68 | end
69 | channel.on_request('exit-status') do |_, data|
70 | exit_status = data.read_long
71 | end
72 |
73 | channel.on_request('exit-signal') do |_, data|
74 | exit_signal = data.read_long
75 | end
76 | end
77 | end
78 | ssh.loop
79 | {
80 | :stdout => stdout_data,
81 | :stderr => stderr_data,
82 | :exit_status => exit_status,
83 | :exit_signal => exit_signal
84 | }
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/spec/ssh_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::SSH do
2 | before(:each) do
3 | require 'net/ssh'
4 | @example_ssh_key = <<-eos
5 | -----BEGIN RSA PRIVATE KEY-----
6 | MIIEowIBAAKCAQEAasdvkMNteq7hjnqNE61ESnEHnaOtHxZffdsQ33R7BXcu9eCH
7 | ncZadHmmfRZgMPQnHGX0NzboVKfdpdF40o+iGQKyy3wKqdgGnTAWqx/hxrsdsdgh
8 | f/g7AABNjoWp1OiTX2Dn99SH9xPQtpdnwGlBmtPplV2wNcKouQwGbwb/u1EHHxnO
9 | aSQk2tvKHRMgroLsyuM5ay7TrK5cip2QTMn9fieHIepH0qd8ETG+1Uf+XmZxhdMN
10 | QsuiSbAAmuU9qwlXh6QOg1spJ97B/ZY6Ci4d5cdBCCtfjGDBm8CzbrfhYVNhGcNx
11 | c3Y43ld6MJoE+hMuBrBSuZyBmf63AAMqJ8KeQwIDAQABAoIBAFMFXBoSWOy1V9z3
12 | IkoY1ilBp5WKRvaPEMCgeCfZIjIG0Nmt5Knbkp2b3A8YWnGQlDHqdYz53t9RHaaU
13 | KdCnJ9vUSYeeeNElMsCwMYVoZvA5Xpv9cw2YOeTrOzssX1VZv9WW1zHd0Srz9N3r
14 | 719MgpyK4dZACw6ODeD/gh8+OH5OAN9sVbIGniApHZENxJrZzL22qE2asf8vVfgu
15 | 9XNx3WhsdL6ktoeJWLKxTLwVP6xRK/ixvsayUFeMBC4E1+sBuz1z7p27kd8cEC5o
16 | AEHSwF6nr+ix0JctbAYqPVFZi5zFH10WgxyF3asAiQPYrCrY9gT8OQSRDhhKyIXE
17 | rHlLIfkCgYEA+RuY4vsqeU69APFXRCy9s/dcy8tS0Pslq/fq4KsdC4qRu39RjotN
18 | /OXOtVMPRfjTiRHXi/0fvSfyMVgstCtfYpOcoz98tw2AX34AgAH6h57I29rVxk5q
19 | OomgcauRYlmO9ge3429pvECL5EzBbRKwfwuMKHMNetLHdVMc7d165f0CgYEAzdGf
20 | 6VMnZkK66XP/RzMvwUwvdhy+vIUjXFMjotQXYcYcLZadiZET7riOSRyVRvlNg0va
21 | CexWtX+0yOZOrXCvLzSpO8Z91VnkHbRhnpy4bvW/qcFpwIvYeaMgr5C1lBHCIvZS
22 | wSojNrdjAbdQoqab9X7Mxj2ubYGxUSSg715wqT8CgYBaY20iT0imI6/o+6lSj3l2
23 | J7eAKxKtybNtptOPF6RVq74dbqFFO77cmPZcTPspxJPdFKBFp18w36G9zeTKq0I9
24 | HpqjkZHLShbej3XW/ODO/Qqc29bd0e4xt2aEWGC0cxKwqzRKTk7rg/A+sqsszK9G
25 | KgZ9VuH5QyokpDfHB6pkcQKBgChsq8PgGTT0llGT/ue1HgQROqEwNCZC4BcaHT21
26 | +oGxr4cktfx3Cjsw9IFXo9o0zQyksUaRrNYpJxDuazWVlFLpPPQIoF5vMWbELwhA
27 | L9lbWzG0U1kGHpaFe73/5ioW8tJ7HvXhmNj+W+vSXXwUzT0CkqW9J61Kc9FEKHfb
28 | TLVxAoGBAM8WKsTfEMxuv5pabGTo8BC1ruz4c5qLJGLGnH4kWJm5o6ippugFsOlK
29 | rDUWive4uLSKi3Fsb2kPw6gHuRGerFN1CBpCENLib3xG5Bd4XhmKZMDxqU2bO0gd
30 | sV1Tr/acrE0aWBkD9RYrR2/UwG1zfXuIJeufdWf8c0SY3X6J7jJN
31 | -----END RSA PRIVATE KEY-----
32 | eos
33 | end
34 |
35 | it "should create a call using agent" do
36 | expect(Net::SSH).to receive(:start).with("127.0.0.1", "root", :verify_host_key => false, :forward_agent => true, :number_of_password_prompts => 0, :agent_socket_factory => Proc).and_return(true)
37 | ssh_agent = LinuxAdmin::SSHAgent.new(@example_ssh_key)
38 | ssh_agent.with_service do |socket|
39 | ssh = LinuxAdmin::SSH.new("127.0.0.1", "root")
40 | ssh.perform_commands(%w("ls", "pwd"), socket)
41 | end
42 | end
43 |
44 | it "should preform command using private key" do
45 | expect(Net::SSH).to receive(:start).with("127.0.0.1", "root", :verify_host_key => false, :number_of_password_prompts => 0, :key_data => [@example_ssh_key]).and_return(true)
46 | LinuxAdmin::SSH.new("127.0.0.1", "root", @example_ssh_key).perform_commands(%w("ls", "pwd"))
47 | end
48 |
49 | it "should preform command using password" do
50 | expect(Net::SSH).to receive(:start).with("127.0.0.1", "root", :verify_host_key => false, :number_of_password_prompts => 0, :password => "password").and_return(true)
51 | LinuxAdmin::SSH.new("127.0.0.1", "root", nil, "password").perform_commands(%w("ls", "pwd"))
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/linux_admin/fstab.rb:
--------------------------------------------------------------------------------
1 | require 'singleton'
2 |
3 | module LinuxAdmin
4 | class FSTabEntry
5 | attr_accessor :device
6 | attr_accessor :mount_point
7 | attr_accessor :fs_type
8 | attr_accessor :mount_options
9 | attr_accessor :dumpable
10 | attr_accessor :fsck_order
11 | attr_accessor :comment
12 |
13 | def initialize(args = {})
14 | @device = args[:device]
15 | @mount_point = args[:mount_point]
16 | @fs_type = args[:fs_type]
17 | @mount_options = args[:mount_options]
18 | @dumpable = args[:dumpable].to_i unless args[:dumpable].nil?
19 | @fsck_order = args[:fsck_order].to_i unless args[:fsck_order].nil?
20 | @comment = args[:comment]
21 | end
22 |
23 | def self.from_line(fstab_line)
24 | columns, comment = fstab_line.split('#')
25 | comment = "##{comment}" unless comment.blank?
26 | columns = columns.chomp.split
27 |
28 | FSTabEntry.new :device => columns[0],
29 | :mount_point => columns[1],
30 | :fs_type => columns[2],
31 | :mount_options => columns[3],
32 | :dumpable => columns[4],
33 | :fsck_order => columns[5],
34 | :comment => comment
35 | end
36 |
37 | def has_content?
38 | !self.columns.first.nil?
39 | end
40 |
41 | def columns
42 | [self.device, self.mount_point, self.fs_type,
43 | self.mount_options, self.dumpable, self.fsck_order, self.comment]
44 | end
45 |
46 | def column_lengths
47 | self.columns.collect { |c| c ? c.size : 0 }
48 | end
49 |
50 | def formatted_columns(max_lengths)
51 | self.columns.collect.
52 | with_index { |col, i| col.to_s.rjust(max_lengths[i]) }.join(" ")
53 | end
54 | end
55 |
56 | class FSTab
57 | include Singleton
58 |
59 | attr_accessor :entries
60 | attr_accessor :maximum_column_lengths
61 |
62 | def initialize
63 | refresh
64 | end
65 |
66 | def write!
67 | content = ''
68 | @entries.each do |entry|
69 | if entry.has_content?
70 | content << entry.formatted_columns(@maximum_column_lengths) << "\n"
71 | else
72 | content << "#{entry.comment}"
73 | end
74 | end
75 |
76 | File.write('/etc/fstab', content)
77 | self
78 | end
79 |
80 | private
81 |
82 | def read
83 | File.read('/etc/fstab').lines
84 | end
85 |
86 | def refresh
87 | @entries = []
88 | @maximum_column_lengths = Array.new(7, 0) # # of columns
89 | read.each do |line|
90 | entry = FSTabEntry.from_line(line)
91 | @entries << entry
92 |
93 | lengths = entry.column_lengths
94 | lengths.each_index do |i|
95 | @maximum_column_lengths[i] =
96 | lengths[i] if lengths[i] > @maximum_column_lengths[i]
97 | end
98 | end
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/spec/dns_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Dns do
2 | RESOLV_CONF = < 0))
40 |
41 | LinuxAdmin::SubscriptionManager.new.registered?
42 | end
43 |
44 | it "with a proxy" do
45 | expect(LinuxAdmin::Common).to receive(:run).with(
46 | "subscription-manager identity",
47 | :params => {
48 | "--proxy=" => "https://example.com",
49 | "--proxyuser=" => "user",
50 | "--proxypassword=" => "pass"
51 | }
52 | ).and_return(double(:exit_status => 0))
53 |
54 | LinuxAdmin::SubscriptionManager.new.registered?(
55 | :proxy_address => "https://example.com",
56 | :proxy_username => "user",
57 | :proxy_password => "pass"
58 | )
59 | end
60 | end
61 | end
62 |
63 | context ".method_missing" do
64 | before do
65 | stub_registered_to_system(:rhn)
66 | end
67 |
68 | it "exists on the subclass" do
69 | expect(LinuxAdmin::RegistrationSystem.registered?).to be_truthy
70 | end
71 |
72 | it "does not exist on the subclass" do
73 | expect { LinuxAdmin::RegistrationSystem.organizations }.to raise_error(NotImplementedError)
74 | end
75 |
76 | it "is an unknown method" do
77 | expect { LinuxAdmin::RegistrationSystem.method_does_not_exist }.to raise_error(NoMethodError)
78 | end
79 | end
80 |
81 | def stub_registered_to_system(*system)
82 | allow_any_instance_of(LinuxAdmin::SubscriptionManager).to receive_messages(:registered? => (system.include?(:sm)))
83 | allow_any_instance_of(LinuxAdmin::Rhn).to receive_messages(:registered? => (system.include?(:rhn)))
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/lib/linux_admin/logical_volume.rb:
--------------------------------------------------------------------------------
1 | require 'pathname'
2 |
3 | module LinuxAdmin
4 | class LogicalVolume < Volume
5 | include Mountable
6 |
7 | DEVICE_PATH = Pathname.new('/dev/')
8 |
9 | # logical volume name
10 | attr_reader :name
11 |
12 | # volume group name
13 | attr_accessor :volume_group
14 |
15 | # logical volume size in sectors
16 | attr_accessor :sectors
17 |
18 | # other fields available:
19 | # logical volume access
20 | # logical volume status
21 | # internal logical volume number
22 | # open count of logical volume
23 | # current logical extents associated to logical volume
24 | # allocated logical extents of logical volume
25 | # allocation policy of logical volume
26 | # read ahead sectors of logical volume
27 | # major device number of logical volume
28 | # minor device number of logical volume
29 |
30 | # path to logical volume
31 | def path
32 | "/dev/#{self.volume_group.name}/#{self.name}"
33 | end
34 |
35 | def path=(value)
36 | @path = value.include?(File::SEPARATOR) ? value : DEVICE_PATH.join(@volume_group.name, value)
37 | end
38 |
39 | def name=(value)
40 | @name = value.include?(File::SEPARATOR) ? value.split(File::SEPARATOR).last : value
41 | end
42 |
43 | def initialize(args = {})
44 | @volume_group = args[:volume_group]
45 | @sectors = args[:sectors]
46 | provided_name = args[:name].to_s
47 | self.path = provided_name
48 | self.name = provided_name
49 | end
50 |
51 | def extend_with(vg)
52 | Common.run!(Common.cmd(:lvextend),
53 | :params => [self.name, vg.name])
54 | self
55 | end
56 |
57 | private
58 |
59 | def self.bytes_to_string(bytes)
60 | if bytes > 1_073_741_824 # 1.gigabytes
61 | (bytes / 1_073_741_824).to_s + "G"
62 | elsif bytes > 1_048_576 # 1.megabytes
63 | (bytes / 1_048_576).to_s + "M"
64 | elsif bytes > 1_024 # 1.kilobytes
65 | (bytes / 1_024).to_s + "K"
66 | else
67 | bytes.to_s
68 | end
69 | end
70 |
71 | public
72 |
73 | def self.create(name, vg, value)
74 | self.scan # initialize local logical volumes
75 | params = { '-n' => name, nil => vg.name}
76 | size = nil
77 | if value <= 100
78 | # size = # TODO size from extents
79 | params.merge!({'-l' => "#{value}%FREE"})
80 | else
81 | size = value
82 | params.merge!({'-L' => bytes_to_string(size)})
83 | end
84 | Common.run!(Common.cmd(:lvcreate), :params => params)
85 |
86 | lv = LogicalVolume.new(:name => name,
87 | :volume_group => vg,
88 | :sectors => size)
89 | @lvs << lv
90 | lv
91 | end
92 |
93 | def self.scan
94 | @lvs ||= begin
95 | scan_volumes(Common.cmd(:lvdisplay)) do |fields, vg|
96 | LogicalVolume.new(:name => fields[0],
97 | :volume_group => vg,
98 | :sectors => fields[6].to_i)
99 | end
100 | end
101 | end
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/lib/linux_admin/hosts.rb:
--------------------------------------------------------------------------------
1 | module LinuxAdmin
2 | class Hosts
3 | attr_accessor :filename
4 | attr_accessor :raw_lines
5 | attr_accessor :parsed_file
6 |
7 | def initialize(filename = "/etc/hosts")
8 | @filename = filename
9 | self.reload
10 | end
11 |
12 | def reload
13 | @raw_lines = File.read(@filename).split("\n")
14 | parse_file
15 | end
16 |
17 | def save
18 | cleanup_empty
19 | @raw_lines = assemble_lines
20 | File.write(@filename, @raw_lines.join("\n") + "\n")
21 | end
22 |
23 | def add_alias(address, hostname, comment = nil)
24 | add_name(address, hostname, false, comment)
25 | end
26 |
27 | alias_method :update_entry, :add_alias
28 |
29 | def set_loopback_hostname(hostname, comment = nil)
30 | ["::1", "127.0.0.1"].each { |address| add_name(address, hostname, true, comment, false) }
31 | end
32 |
33 | def set_canonical_hostname(address, hostname, comment = nil)
34 | add_name(address, hostname, true, comment)
35 | end
36 |
37 | def hostname=(name)
38 | if Common.cmd?("hostnamectl")
39 | Common.run!(Common.cmd('hostnamectl'), :params => ['set-hostname', name])
40 | else
41 | File.write("/etc/hostname", name)
42 | Common.run!(Common.cmd('hostname'), :params => {:file => "/etc/hostname"})
43 | end
44 | end
45 |
46 | def hostname
47 | result = Common.run(Common.cmd("hostname"))
48 | result.success? ? result.output.strip : nil
49 | end
50 |
51 | private
52 |
53 | def add_name(address, hostname, fqdn, comment, remove_existing = true)
54 | # Delete entries for this hostname first
55 | @parsed_file.each { |i| i[:hosts].to_a.delete(hostname) } if remove_existing
56 |
57 | # Add entry
58 | line_number = @parsed_file.find_path(address).first
59 |
60 | if line_number.blank?
61 | @parsed_file.push(:address => address, :hosts => [hostname], :comment => comment)
62 | else
63 | if fqdn
64 | new_hosts = @parsed_file.fetch_path(line_number, :hosts).to_a.unshift(hostname)
65 | else
66 | new_hosts = @parsed_file.fetch_path(line_number, :hosts).to_a.push(hostname)
67 | end
68 | @parsed_file.store_path(line_number, :hosts, new_hosts)
69 | @parsed_file.store_path(line_number, :comment, comment) if comment
70 | end
71 | end
72 |
73 | def parse_file
74 | @parsed_file = []
75 | @raw_lines.each { |line| @parsed_file.push(parse_line(line.strip)) }
76 | @parsed_file.delete_blank_paths
77 | end
78 |
79 | def parse_line(line)
80 | data, comment = line.split("#", 2)
81 | address, hosts = data.to_s.split(" ", 2)
82 | hostnames = hosts.to_s.split(" ")
83 |
84 | { :address => address.to_s, :hosts => hostnames, :comment => comment.to_s.strip, :blank => line.blank?}
85 | end
86 |
87 | def cleanup_empty
88 | @parsed_file.each do |h|
89 | h.delete(:hosts) if h[:address].blank?
90 | h.delete(:address) if h[:hosts].blank?
91 | end
92 |
93 | @parsed_file.delete_blank_paths
94 | end
95 |
96 | def assemble_lines
97 | @parsed_file.each_with_object([]) { |l, a| a.push(l[:blank] ? "" : build_line(l[:address], l[:hosts], l[:comment])) }
98 | end
99 |
100 | def build_line(address, hosts, comment)
101 | line = [address.to_s.ljust(16), hosts.to_a.uniq]
102 | line.push("##{comment}") if comment
103 | line.flatten.join(" ").strip
104 | end
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/spec/time_date_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::TimeDate do
2 | RUN_COMMAND = LinuxAdmin::Common.cmd("timedatectl")
3 |
4 | def timedatectl_result
5 | output = File.read(Pathname.new(data_file_path("time_date/timedatectl_output")))
6 | AwesomeSpawn::CommandResult.new("", output, "", 0)
7 | end
8 |
9 | describe ".system_timezone_detailed" do
10 | it "returns the correct timezone" do
11 | awesome_spawn_args = [
12 | RUN_COMMAND,
13 | :params => ["status"]
14 | ]
15 | expect(AwesomeSpawn).to receive(:run).with(*awesome_spawn_args).and_return(timedatectl_result)
16 | tz = described_class.system_timezone_detailed
17 | expect(tz).to eq("America/New_York (EDT, -0400)")
18 | end
19 | end
20 |
21 | describe ".system_timezone" do
22 | it "returns the correct timezone" do
23 | awesome_spawn_args = [
24 | RUN_COMMAND,
25 | :params => ["status"]
26 | ]
27 | expect(AwesomeSpawn).to receive(:run).with(*awesome_spawn_args).and_return(timedatectl_result)
28 | tz = described_class.system_timezone
29 | expect(tz).to eq("America/New_York")
30 | end
31 | end
32 |
33 | describe ".timezones" do
34 | let(:timezones) do
35 | <<-EOS
36 | Africa/Bangui
37 | Africa/Banjul
38 | Africa/Bissau
39 | Africa/Blantyre
40 | Africa/Brazzaville
41 | Africa/Bujumbura
42 | Africa/Cairo
43 | America/Havana
44 | America/Hermosillo
45 | America/Indiana/Indianapolis
46 | America/Indiana/Knox
47 | America/Argentina/San_Juan
48 | America/Argentina/San_Luis
49 | America/Argentina/Tucuman
50 | America/Argentina/Ushuaia
51 | EOS
52 | end
53 |
54 | it "returns the correct list" do
55 | awesome_spawn_args = [
56 | RUN_COMMAND,
57 | :params => ["list-timezones"]
58 | ]
59 | result = AwesomeSpawn::CommandResult.new("", timezones, "", 0)
60 | expect(AwesomeSpawn).to receive(:run!).with(*awesome_spawn_args).and_return(result)
61 | expect(described_class.timezones).to eq(timezones.split("\n"))
62 | end
63 | end
64 |
65 | describe ".system_time=" do
66 | it "sets the time" do
67 | time = Time.new(2015, 1, 1, 1, 1, 1)
68 | awesome_spawn_args = [
69 | RUN_COMMAND,
70 | :params => ["set-time", "2015-01-01 01:01:01", :adjust_system_clock]
71 | ]
72 | expect(AwesomeSpawn).to receive(:run!).with(*awesome_spawn_args)
73 | described_class.system_time = time
74 | end
75 |
76 | it "raises when the command fails" do
77 | time = Time.new(2015, 1, 1, 1, 1, 1)
78 | err = AwesomeSpawn::CommandResultError.new("message", nil)
79 | allow(AwesomeSpawn).to receive(:run!).and_raise(err)
80 | expect do
81 | described_class.send(:system_time=, time)
82 | end.to raise_error(described_class::TimeCommandError, "message")
83 | end
84 | end
85 |
86 | describe ".system_timezone=" do
87 | it "sets the timezone" do
88 | zone = "Location/City"
89 | awesome_spawn_args = [
90 | RUN_COMMAND,
91 | :params => ["set-timezone", zone]
92 | ]
93 | expect(AwesomeSpawn).to receive(:run!).with(*awesome_spawn_args)
94 | described_class.system_timezone = zone
95 | end
96 |
97 | it "raises when the command fails" do
98 | zone = "Location/City"
99 | err = AwesomeSpawn::CommandResultError.new("message", nil)
100 | allow(AwesomeSpawn).to receive(:run!).and_raise(err)
101 | expect do
102 | described_class.send(:system_timezone=, zone)
103 | end.to raise_error(described_class::TimeCommandError, "message")
104 | end
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/spec/volume_group_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::VolumeGroup do
2 | before(:each) do
3 | @groups = < 'vg'
18 | lv = LinuxAdmin::LogicalVolume.new :name => 'lv', :volume_group => vg
19 | expect(LinuxAdmin::Common).to receive(:run!)
20 | .with(LinuxAdmin::Common.cmd(:lvextend), :params => %w(lv vg))
21 | vg.attach_to(lv)
22 | end
23 |
24 | it "returns self" do
25 | vg = described_class.new :name => 'vg'
26 | lv = LinuxAdmin::LogicalVolume.new :name => 'lv', :volume_group => vg
27 | allow(LinuxAdmin::Common).to receive(:run!)
28 | expect(vg.attach_to(lv)).to eq(vg)
29 | end
30 | end
31 |
32 | describe "#extend_with" do
33 | it "uses vgextend" do
34 | vg = described_class.new :name => 'vg'
35 | pv = LinuxAdmin::PhysicalVolume.new :device_name => '/dev/hda'
36 | expect(LinuxAdmin::Common).to receive(:run!)
37 | .with(LinuxAdmin::Common.cmd(:vgextend), :params => ['vg', '/dev/hda'])
38 | vg.extend_with(pv)
39 | end
40 |
41 | it "assigns volume group to physical volume" do
42 | vg = described_class.new :name => 'vg'
43 | pv = LinuxAdmin::PhysicalVolume.new :device_name => '/dev/hda'
44 | allow(LinuxAdmin::Common).to receive(:run!)
45 | vg.extend_with(pv)
46 | expect(pv.volume_group).to eq(vg)
47 | end
48 |
49 | it "returns self" do
50 | vg = described_class.new :name => 'vg'
51 | pv = LinuxAdmin::PhysicalVolume.new :device_name => '/dev/hda'
52 | allow(LinuxAdmin::Common).to receive(:run!)
53 | expect(vg.extend_with(pv)).to eq(vg)
54 | end
55 | end
56 |
57 | describe "#create" do
58 | before(:each) do
59 | @pv = LinuxAdmin::PhysicalVolume.new :device_name => '/dev/hda'
60 | end
61 |
62 | it "uses vgcreate" do
63 | described_class.instance_variable_set(:@vgs, [])
64 | expect(LinuxAdmin::Common).to receive(:run!)
65 | .with(LinuxAdmin::Common.cmd(:vgcreate), :params => ['vg', '/dev/hda'])
66 | described_class.create 'vg', @pv
67 | end
68 |
69 | it "returns new volume group" do
70 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
71 | vg = described_class.create 'vg', @pv
72 | expect(vg).to be_an_instance_of(described_class)
73 | expect(vg.name).to eq('vg')
74 | end
75 |
76 | it "adds volume group to local registry" do
77 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
78 | vg = described_class.create 'vg', @pv
79 | expect(described_class.scan).to include(vg)
80 | end
81 | end
82 |
83 | describe "#scan" do
84 | it "uses vgdisplay" do
85 | expect(LinuxAdmin::Common).to receive(:run!)
86 | .with(LinuxAdmin::Common.cmd(:vgdisplay), :params => {'-c' => nil})
87 | .and_return(double(:output => @groups))
88 | described_class.scan
89 | end
90 |
91 | it "returns local volume groups" do
92 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @groups))
93 | vgs = described_class.scan
94 |
95 | expect(vgs[0]).to be_an_instance_of(described_class)
96 | expect(vgs[0].name).to eq('vg_foobar')
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/spec/physical_volume_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::PhysicalVolume do
2 | before(:each) do
3 | @physical_volumes = < 'vg'
22 | pv = described_class.new :device_name => '/dev/hda'
23 | expect(LinuxAdmin::Common).to receive(:run!)
24 | .with(LinuxAdmin::Common.cmd(:vgextend),
25 | :params => ['vg', '/dev/hda'])
26 | pv.attach_to(vg)
27 | end
28 |
29 | it "assigns volume group to physical volume" do
30 | vg = LinuxAdmin::VolumeGroup.new :name => 'vg'
31 | pv = described_class.new :device_name => '/dev/hda'
32 | allow(LinuxAdmin::Common).to receive(:run!)
33 | pv.attach_to(vg)
34 | expect(pv.volume_group).to eq(vg)
35 | end
36 |
37 | it "returns self" do
38 | vg = LinuxAdmin::VolumeGroup.new :name => 'vg'
39 | pv = described_class.new :device_name => '/dev/hda'
40 | allow(LinuxAdmin::Common).to receive(:run!)
41 | expect(pv.attach_to(vg)).to eq(pv)
42 | end
43 | end
44 |
45 | describe "#create" do
46 | before do
47 | @disk = LinuxAdmin::Disk.new :path => '/dev/hda'
48 | allow(@disk).to receive(:size)
49 | end
50 |
51 | let(:disk) {@disk}
52 |
53 | it "uses pvcreate" do
54 | described_class.instance_variable_set(:@pvs, [])
55 | expect(LinuxAdmin::Common).to receive(:run!)
56 | .with(LinuxAdmin::Common.cmd(:pvcreate),
57 | :params => {nil => '/dev/hda'})
58 | described_class.create disk
59 | end
60 |
61 | it "returns new physical volume" do
62 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
63 | pv = described_class.create disk
64 | expect(pv).to be_an_instance_of(described_class)
65 | expect(pv.device_name).to eq('/dev/hda')
66 | end
67 |
68 | it "adds physical volume to local registry" do
69 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
70 | pv = described_class.create disk
71 | expect(described_class.scan).to include(pv)
72 | end
73 | end
74 |
75 | describe "#scan" do
76 | it "uses pvdisplay" do
77 | expect(LinuxAdmin::Common).to receive(:run!)
78 | .with(LinuxAdmin::Common.cmd(:pvdisplay),
79 | :params => {'-c' => nil})
80 | .and_return(double(:output => @physical_volumes))
81 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @groups)) # stub out call to vgdisplay
82 | described_class.scan
83 | end
84 |
85 | it "returns local physical volumes" do
86 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @physical_volumes), double(:output => @groups))
87 | pvs = described_class.scan
88 |
89 | expect(pvs[0]).to be_an_instance_of(described_class)
90 | expect(pvs[0].device_name).to eq('/dev/vda2')
91 | expect(pvs[0].size).to eq(24139776)
92 | end
93 |
94 | it "resolves volume group references" do
95 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @physical_volumes), double(:output => @groups))
96 | pvs = described_class.scan
97 | expect(pvs[0].volume_group).to be_an_instance_of(LinuxAdmin::VolumeGroup)
98 | expect(pvs[0].volume_group.name).to eq('vg_foobar')
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/spec/rpm_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Rpm do
2 | it ".list_installed" do
3 | allow(LinuxAdmin::Common).to receive(:run!)
4 | .and_return(double(:output => sample_output("rpm/cmd_output_for_list_installed")))
5 | expect(described_class.list_installed).to eq({
6 | "ruby193-rubygem-some_really_long_name" =>"1.0.7-1.el6",
7 | "fipscheck-lib" =>"1.2.0-7.el6",
8 | "aic94xx-firmware" =>"30-2.el6",
9 | "latencytop-common" =>"0.5-9.el6",
10 | "uuid" =>"1.6.1-10.el6",
11 | "ConsoleKit" =>"0.4.1-3.el6",
12 | "cpuspeed" =>"1.5-19.el6",
13 | "mailcap" =>"2.1.31-2.el6",
14 | "freetds" =>"0.82-7.1.el6cf",
15 | "elinks" =>"0.12-0.21.pre5.el6_3",
16 | "abrt-cli" =>"2.0.8-15.el6",
17 | "libattr" =>"2.4.44-7.el6",
18 | "passwd" =>"0.77-4.el6_2.2",
19 | "vim-enhanced" =>"7.2.411-1.8.el6",
20 | "popt" =>"1.13-7.el6",
21 | "hesiod" =>"3.1.0-19.el6",
22 | "pinfo" =>"0.6.9-12.el6",
23 | "libpng" =>"1.2.49-1.el6_2",
24 | "libdhash" =>"0.4.2-9.el6",
25 | "zlib-devel" =>"1.2.3-29.el6",
26 | })
27 | end
28 |
29 | it ".import_key" do
30 | expect(LinuxAdmin::Common).to receive(:run!).with("rpm", :params => {"--import" => "abc"})
31 | expect { described_class.import_key("abc") }.to_not raise_error
32 | end
33 |
34 | describe "#info" do
35 | it "returns package metadata" do
36 | # as output w/ rpm -qi ruby on F19
37 | data = < {"-qi" => "ruby"}]
62 | result = AwesomeSpawn::CommandResult.new("", data, "", 0)
63 | expect(LinuxAdmin::Common).to receive(:run!).with(*arguments).and_return(result)
64 | metadata = described_class.info("ruby")
65 | expect(metadata['name']).to eq('ruby')
66 | expect(metadata['version']).to eq('2.0.0.247')
67 | expect(metadata['release']).to eq('15.fc19')
68 | expect(metadata['architecture']).to eq('x86_64')
69 | expect(metadata['group']).to eq('Development/Languages')
70 | expect(metadata['size']).to eq('64473')
71 | expect(metadata['license']).to eq('(Ruby or BSD) and Public Domain')
72 | expect(metadata['source_rpm']).to eq('ruby-2.0.0.247-15.fc19.src.rpm')
73 | expect(metadata['build_host']).to eq('buildvm-16.phx2.fedoraproject.org')
74 | expect(metadata['packager']).to eq('Fedora Project')
75 | expect(metadata['vendor']).to eq('Fedora Project')
76 | expect(metadata['summary']).to eq('An interpreter of object-oriented scripting language')
77 | end
78 | end
79 |
80 | it ".upgrade" do
81 | expect(LinuxAdmin::Common).to receive(:run).with("rpm -U", :params => {nil => "abc"})
82 | .and_return(double(:exit_status => 0))
83 | expect(described_class.upgrade("abc")).to be_truthy
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/service/sys_v_init_service_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::SysVInitService do
2 | before do
3 | @service = described_class.new 'foo'
4 | end
5 |
6 | describe "#running?" do
7 | it "checks service" do
8 | expect(LinuxAdmin::Common).to receive(:run)
9 | .with(LinuxAdmin::Common.cmd(:service),
10 | :params => {nil => %w(foo status)}).and_return(double(:exit_status => 0))
11 | @service.running?
12 | end
13 |
14 | context "service is running" do
15 | it "returns true" do
16 | @service = described_class.new :id => :foo
17 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 0))
18 | expect(@service).to be_running
19 | end
20 | end
21 |
22 | context "service is not running" do
23 | it "returns false" do
24 | @service = described_class.new :id => :foo
25 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 1))
26 | expect(@service).not_to be_running
27 | end
28 | end
29 | end
30 |
31 | describe "#enable" do
32 | it "enables service" do
33 | expect(LinuxAdmin::Common).to receive(:run!)
34 | .with(LinuxAdmin::Common.cmd(:chkconfig),
35 | :params => {nil => %w(foo on)})
36 | @service.enable
37 | end
38 |
39 | it "returns self" do
40 | expect(LinuxAdmin::Common).to receive(:run!) # stub out cmd invocation
41 | expect(@service.enable).to eq(@service)
42 | end
43 | end
44 |
45 | describe "#disable" do
46 | it "disable service" do
47 | expect(LinuxAdmin::Common).to receive(:run!)
48 | .with(LinuxAdmin::Common.cmd(:chkconfig),
49 | :params => {nil => %w(foo off)})
50 | @service.disable
51 | end
52 |
53 | it "returns self" do
54 | expect(LinuxAdmin::Common).to receive(:run!)
55 | expect(@service.disable).to eq(@service)
56 | end
57 | end
58 |
59 | describe "#start" do
60 | it "starts service" do
61 | expect(LinuxAdmin::Common).to receive(:run!)
62 | .with(LinuxAdmin::Common.cmd(:service),
63 | :params => {nil => %w(foo start)})
64 | @service.start
65 | end
66 |
67 | it "returns self" do
68 | expect(LinuxAdmin::Common).to receive(:run!)
69 | expect(@service.start).to eq(@service)
70 | end
71 | end
72 |
73 | describe "#stop" do
74 | it "stops service" do
75 | expect(LinuxAdmin::Common).to receive(:run!)
76 | .with(LinuxAdmin::Common.cmd(:service),
77 | :params => {nil => %w(foo stop)})
78 | @service.stop
79 | end
80 |
81 | it "returns self" do
82 | expect(LinuxAdmin::Common).to receive(:run!)
83 | expect(@service.stop).to eq(@service)
84 | end
85 | end
86 |
87 | describe "#restart" do
88 | it "stops service" do
89 | expect(LinuxAdmin::Common).to receive(:run)
90 | .with(LinuxAdmin::Common.cmd(:service),
91 | :params => {nil => %w(foo restart)}).and_return(double(:exit_status => 0))
92 | @service.restart
93 | end
94 |
95 | context "service restart fails" do
96 | it "manually stops/starts service" do
97 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 1))
98 | expect(@service).to receive(:stop)
99 | expect(@service).to receive(:start)
100 | @service.restart
101 | end
102 | end
103 |
104 | it "returns self" do
105 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 0))
106 | expect(@service.restart).to eq(@service)
107 | end
108 | end
109 |
110 | describe "#reload" do
111 | it "reloads service" do
112 | expect(LinuxAdmin::Common).to receive(:run!)
113 | .with(LinuxAdmin::Common.cmd(:service), :params => %w(foo reload))
114 | expect(@service.reload).to eq(@service)
115 | end
116 | end
117 |
118 | describe "#status" do
119 | it "returns the service status" do
120 | status = "service status here"
121 | expect(LinuxAdmin::Common).to receive(:run)
122 | .with(LinuxAdmin::Common.cmd(:service),
123 | :params => %w(foo status)).and_return(double(:output => status))
124 | expect(@service.status).to eq(status)
125 | end
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/lib/linux_admin/registration_system/rhn.rb:
--------------------------------------------------------------------------------
1 | require 'nokogiri'
2 |
3 | module LinuxAdmin
4 | class Rhn < RegistrationSystem
5 | SATELLITE5_SERVER_CERT_PATH = "pub/rhn-org-trusted-ssl-cert-1.0-1.noarch.rpm"
6 | INSTALLED_SERVER_CERT_PATH = "/usr/share/rhn/RHN-ORG-TRUSTED-SSL-CERT"
7 |
8 | def initialize
9 | warn("[DEPRECATION] 'LinuxAdmin::Rhn' is deprecated. Please use 'LinuxAdmin::SubscriptionManager' instead.")
10 | end
11 |
12 | def registered?(_options = nil)
13 | id = ""
14 | if File.exist?(systemid_file)
15 | xml = Nokogiri.XML(File.read(systemid_file))
16 | id = xml.xpath('/params/param/value/struct/member[name="system_id"]/value/string').text
17 | end
18 | id.length > 0
19 | end
20 |
21 | def register(options)
22 | cmd = "rhnreg_ks"
23 | params = {}
24 |
25 | if options[:activationkey]
26 | params["--activationkey="] = options[:activationkey]
27 | elsif options[:username] && options[:password]
28 | params["--username="] = options[:username]
29 | params["--password="] = options[:password]
30 | else
31 | raise ArgumentError, "activation key or username and password are required"
32 | end
33 |
34 | install_server_certificate(options[:server_url], SATELLITE5_SERVER_CERT_PATH) if options[:server_url]
35 | certificate_installed = LinuxAdmin::Rpm.list_installed["rhn-org-trusted-ssl-cert"]
36 |
37 | params["--proxy="] = options[:proxy_address] if options[:proxy_address]
38 | params["--proxyUser="] = options[:proxy_username] if options[:proxy_username]
39 | params["--proxyPassword="] = options[:proxy_password] if options[:proxy_password]
40 | params["--serverUrl="] = options[:server_url] if options[:server_url]
41 | params["--systemorgid="] = options[:org] if options[:server_url] && options[:org]
42 | params["--sslCACert="] = INSTALLED_SERVER_CERT_PATH if certificate_installed
43 |
44 | Common.run!(cmd, :params => params)
45 | end
46 |
47 | def enable_channel(repo, options)
48 | cmd = "rhn-channel -a"
49 | params = user_pwd(options).merge("--channel=" => repo)
50 |
51 | logger.info("#{self.class.name}##{__method__} Enabling channel: #{repo}")
52 | Common.run!(cmd, :params => params)
53 | end
54 | alias_method :subscribe, :enable_channel
55 | alias_method :enable_repo, :enable_channel
56 |
57 | def disable_channel(repo, options)
58 | cmd = "rhn-channel -r"
59 | params = user_pwd(options).merge("--channel=" => repo)
60 |
61 | Common.run!(cmd, :params => params)
62 | end
63 | alias_method :disable_repo, :disable_channel
64 |
65 | def enabled_channels
66 | cmd = "rhn-channel -l"
67 |
68 | Common.run!(cmd).output.split("\n").compact
69 | end
70 | alias_method :enabled_repos, :enabled_channels
71 | alias_method :subscribed_products, :enabled_channels
72 |
73 | def available_channels(options)
74 | cmd = "rhn-channel -L"
75 | params = user_pwd(options)
76 |
77 | Common.run!(cmd, :params => params).output.chomp.split("\n").compact
78 | end
79 |
80 | def all_repos(options)
81 | available = available_channels_with_status(options)
82 | merge_enabled_channels_with_status(available)
83 | end
84 |
85 | private
86 |
87 | def available_channels_with_status(options)
88 | available_channels(options).collect { |ac| {:repo_id => ac, :enabled => false} }
89 | end
90 |
91 | def merge_enabled_channels_with_status(available)
92 | enabled_channels.each_with_object(available) do |enabled, all|
93 | if repo = all.detect { |i| i[:repo_id] == enabled }
94 | repo[:enabled] = true
95 | else
96 | all.push({:repo_id => enabled, :enabled => true})
97 | end
98 | end
99 | end
100 |
101 | def user_pwd(options)
102 | raise ArgumentError, "username and password are required" if options[:username].blank? || options[:password].blank?
103 |
104 | {"--user=" => options[:username], "--password=" => options[:password]}
105 | end
106 |
107 | def systemid_file
108 | "/etc/sysconfig/rhn/systemid"
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/linux_admin/yum.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'inifile'
3 |
4 | module LinuxAdmin
5 | class Yum
6 | def self.create_repo(path, options = {})
7 | raise ArgumentError, "path is required" unless path
8 | options = {:database => true, :unique_file_names => true}.merge(options)
9 |
10 | FileUtils.mkdir_p(path)
11 |
12 | cmd = "createrepo"
13 | params = {nil => path}
14 | params["--database"] = nil if options[:database]
15 | params["--unique-md-filenames"] = nil if options[:unique_file_names]
16 |
17 | Common.run!(cmd, :params => params)
18 | end
19 |
20 | def self.download_packages(path, packages, options = {})
21 | raise ArgumentError, "path is required" unless path
22 | raise ArgumentError, "packages are required" unless packages
23 | options = {:mirror_type => :package}.merge(options)
24 |
25 | FileUtils.mkdir_p(path)
26 |
27 | cmd = case options[:mirror_type]
28 | when :package; "repotrack"
29 | else; raise ArgumentError, "mirror_type unsupported"
30 | end
31 | params = {"-p" => path}
32 | params["-a"] = options[:arch] if options[:arch]
33 | params[nil] = packages
34 |
35 | Common.run!(cmd, :params => params)
36 | end
37 |
38 | def self.repo_settings
39 | self.parse_repo_dir("/etc/yum.repos.d")
40 | end
41 |
42 | def self.updates_available?(*packages)
43 | cmd = "yum check-update"
44 | params = {nil => packages} unless packages.blank?
45 |
46 | spawn = Common.run(cmd, :params => params)
47 | case spawn.exit_status
48 | when 0; false
49 | when 100; true
50 | else raise "Error: #{cmd} returns '#{spawn.exit_status}', '#{spawn.error}'"
51 | end
52 | end
53 |
54 | def self.update(*packages)
55 | cmd = "yum -y update"
56 | params = {nil => packages} unless packages.blank?
57 |
58 | out = Common.run!(cmd, :params => params)
59 |
60 | # Handle errors that exit 0 https://bugzilla.redhat.com/show_bug.cgi?id=1141318
61 | raise AwesomeSpawn::CommandResultError.new(out.error, out) if out.error.include?("No Match for argument")
62 |
63 | out
64 | end
65 |
66 | def self.version_available(*packages)
67 | raise ArgumentError, "packages requires at least one package name" if packages.blank?
68 |
69 | cmd = "repoquery --qf=\"%{name} %{version}\""
70 | params = {nil => packages}
71 |
72 | out = Common.run!(cmd, :params => params).output
73 |
74 | out.split("\n").each_with_object({}) do |i, versions|
75 | name, version = i.split(" ", 2)
76 | versions[name.strip] = version.strip
77 | end
78 | end
79 |
80 | def self.repo_list(scope = "enabled")
81 | # Scopes could be "enabled", "all"
82 |
83 | cmd = "yum repolist"
84 | params = {nil => scope}
85 | output = Common.run!(cmd, :params => params).output
86 |
87 | parse_repo_list_output(output)
88 | end
89 |
90 | private
91 |
92 | def self.parse_repo_dir(dir)
93 | repo_files = Dir.glob(File.join(dir, '*.repo'))
94 | repo_files.each_with_object({}) do |file, content|
95 | content[file] = self.parse_repo_file(file)
96 | end
97 | end
98 |
99 | def self.parse_repo_file(file)
100 | int_keys = ["enabled", "cost", "gpgcheck", "sslverify", "metadata_expire"]
101 | content = IniFile.load(file).to_h
102 | content.each do |name, data|
103 | int_keys.each { |k| data[k] = data[k].to_i if data.has_key?(k) }
104 | end
105 | end
106 |
107 | def self.parse_repo_list_output(content)
108 | collect_content = false
109 | index_start = "repo id"
110 | index_end = "repolist:"
111 |
112 | content.split("\n").each_with_object([]) do |line, array|
113 | collect_content = false if line.start_with?(index_end)
114 | collect_content = true if line.start_with?(index_start)
115 | next if line.start_with?(index_start)
116 | next if !collect_content
117 |
118 | repo_id, _repo_name, _status = line.split(/\s{2,}/)
119 | array.push(repo_id)
120 | end
121 | end
122 | end
123 | end
124 |
125 | Dir.glob(File.join(File.dirname(__FILE__), "yum", "*.rb")).each { |f| require f }
126 |
--------------------------------------------------------------------------------
/spec/service/systemd_service_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::SystemdService do
2 | let(:command) { LinuxAdmin::Common.cmd(:systemctl) }
3 |
4 | before do
5 | @service = described_class.new 'foo'
6 | end
7 |
8 | describe "#running?" do
9 | it "checks service" do
10 | expect(LinuxAdmin::Common).to receive(:run)
11 | .with(command, :params => %w(status foo)).and_return(double(:success? => true))
12 | @service.running?
13 | end
14 |
15 | it "returns true when service is running" do
16 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:success? => true))
17 | expect(@service).to be_running
18 | end
19 |
20 | it "returns false when service is not running" do
21 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:success? => false))
22 | expect(@service).not_to be_running
23 | end
24 | end
25 |
26 | describe "#enable" do
27 | it "enables service" do
28 | expect(LinuxAdmin::Common).to receive(:run!) .with(command, :params => %w(enable foo))
29 | @service.enable
30 | end
31 |
32 | it "returns self" do
33 | expect(LinuxAdmin::Common).to receive(:run!) # stub out cmd invocation
34 | expect(@service.enable).to eq(@service)
35 | end
36 | end
37 |
38 | describe "#disable" do
39 | it "disables service" do
40 | expect(LinuxAdmin::Common).to receive(:run!).with(command, :params => %w(disable foo))
41 | @service.disable
42 | end
43 |
44 | it "returns self" do
45 | expect(LinuxAdmin::Common).to receive(:run!)
46 | expect(@service.disable).to eq(@service)
47 | end
48 | end
49 |
50 | describe "#start" do
51 | it "starts service" do
52 | expect(LinuxAdmin::Common).to receive(:run!).with(command, :params => %w(start foo))
53 | @service.start
54 | end
55 |
56 | it "returns self" do
57 | expect(LinuxAdmin::Common).to receive(:run!)
58 | expect(@service.start).to eq(@service)
59 | end
60 | end
61 |
62 | describe "#stop" do
63 | it "stops service" do
64 | expect(LinuxAdmin::Common).to receive(:run!).with(command, :params => %w(stop foo))
65 | @service.stop
66 | end
67 |
68 | it "returns self" do
69 | expect(LinuxAdmin::Common).to receive(:run!)
70 | expect(@service.stop).to eq(@service)
71 | end
72 | end
73 |
74 | describe "#restart" do
75 | it "restarts service" do
76 | expect(LinuxAdmin::Common).to receive(:run).with(command, :params => %w(restart foo)).and_return(double(:exit_status => 0))
77 | @service.restart
78 | end
79 |
80 | it "manually stops then starts service when restart fails" do
81 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 1))
82 | expect(@service).to receive(:stop)
83 | expect(@service).to receive(:start)
84 | @service.restart
85 | end
86 |
87 | it "returns self" do
88 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:exit_status => 0))
89 | expect(@service.restart).to eq(@service)
90 | end
91 | end
92 |
93 | describe "#reload" do
94 | it "reloads service" do
95 | expect(LinuxAdmin::Common).to receive(:run!).with(command, :params => %w(reload foo))
96 | expect(@service.reload).to eq(@service)
97 | end
98 | end
99 |
100 | describe "#status" do
101 | it "returns the service status" do
102 | status = "service status here"
103 | expect(LinuxAdmin::Common).to receive(:run)
104 | .with(command, :params => %w(status foo)).and_return(double(:output => status))
105 | expect(@service.status).to eq(status)
106 | end
107 | end
108 |
109 | describe "#show" do
110 | it "returns a hash of runtime information" do
111 | output = <<-EOS
112 | MainPID=29189
113 | ExecMainStartTimestamp=Wed 2017-02-08 13:49:57 EST
114 | ExecStart={ path=/bin/sh ; argv[]=/bin/sh -c /bin/evmserver.sh start ; status=0/0 }
115 | ExecStop={ path=/bin/sh ; argv[]=/bin/sh -c /bin/evmserver.sh stop ; status=0/0 }
116 | ControlGroup=/system.slice/evmserverd.service
117 | MemoryCurrent=2865373184
118 | EOS
119 |
120 | hash = {
121 | "MainPID" => 29_189,
122 | "ExecMainStartTimestamp" => Time.new(2017, 2, 8, 13, 49, 57, "-05:00"),
123 | "ExecStart" => {"path" => "/bin/sh", "argv[]" => "/bin/sh -c /bin/evmserver.sh start", "status" => "0/0"},
124 | "ExecStop" => {"path" => "/bin/sh", "argv[]" => "/bin/sh -c /bin/evmserver.sh stop", "status" => "0/0"},
125 | "ControlGroup" => "/system.slice/evmserverd.service",
126 | "MemoryCurrent" => 2_865_373_184
127 | }
128 | expect(LinuxAdmin::Common).to receive(:run!)
129 | .with(command, :params => %w(show foo)).and_return(double(:output => output))
130 | expect(@service.show).to eq(hash)
131 | end
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file was generated by the `rspec --init` command. Conventionally, all
2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3 | # The generated `.rspec` file contains `--require spec_helper` which will cause this
4 | # file to always be loaded, without a need to explicitly require it in any files.
5 | #
6 | # Given that it is always loaded, you are encouraged to keep this file as
7 | # light-weight as possible. Requiring heavyweight dependencies from this file
8 | # will add to the boot time of your test suite on EVERY test run, even for an
9 | # individual file that may not need all of that loaded. Instead, make a
10 | # separate helper file that requires this one and then use it only in the specs
11 | # that actually need it.
12 | #
13 | # The `.rspec` file also contains a few flags that are not defaults but that
14 | # users commonly want.
15 | #
16 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17 | RSpec.configure do |config|
18 | # The settings below are suggested to provide a good initial experience
19 | # with RSpec, but feel free to customize to your heart's content.
20 |
21 | # These two settings work together to allow you to limit a spec run
22 | # to individual examples or groups you care about by tagging them with
23 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
24 | # get run.
25 | config.filter_run :focus
26 | config.run_all_when_everything_filtered = true
27 |
28 | # Many RSpec users commonly either run the entire suite or an individual
29 | # file, and it's useful to allow more verbose output when running an
30 | # individual spec file.
31 | if config.files_to_run.one?
32 | # Use the documentation formatter for detailed output,
33 | # unless a formatter has already been configured
34 | # (e.g. via a command-line flag).
35 | config.default_formatter = 'doc'
36 | end
37 |
38 | # Print the 10 slowest examples and example groups at the
39 | # end of the spec run, to help surface which specs are running
40 | # particularly slow.
41 | # config.profile_examples = 10
42 |
43 | # Run specs in random order to surface order dependencies. If you find an
44 | # order dependency and want to debug it, you can fix the order by providing
45 | # the seed, which is printed after each run.
46 | # --seed 1234
47 | config.order = :random
48 |
49 | # Seed global randomization in this process using the `--seed` CLI option.
50 | # Setting this allows you to use `--seed` to deterministically reproduce
51 | # test failures related to randomization by passing the same `--seed` value
52 | # as the one that triggered the failure.
53 | Kernel.srand config.seed
54 |
55 | # rspec-expectations config goes here. You can use an alternate
56 | # assertion/expectation library such as wrong or the stdlib/minitest
57 | # assertions if you prefer.
58 | config.expect_with :rspec do |expectations|
59 | # Enable only the newer, non-monkey-patching expect syntax.
60 | # For more details, see:
61 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
62 | expectations.syntax = :expect
63 | end
64 |
65 | # rspec-mocks config goes here. You can use an alternate test double
66 | # library (such as bogus or mocha) by changing the `mock_with` option here.
67 | config.mock_with :rspec do |mocks|
68 | # Enable only the newer, non-monkey-patching expect syntax.
69 | # For more details, see:
70 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
71 | mocks.syntax = :expect
72 |
73 | # Prevents you from mocking or stubbing a method that does not exist on
74 | # a real object. This is generally recommended.
75 | mocks.verify_partial_doubles = true
76 | end
77 |
78 | config.after do
79 | clear_caches
80 | end
81 | end
82 |
83 | begin
84 | require 'coveralls'
85 | Coveralls.wear!
86 | rescue LoadError
87 | end
88 |
89 | require 'linux_admin'
90 |
91 | def etc_issue_contains(contents)
92 | LinuxAdmin::EtcIssue.instance.send(:refresh)
93 | allow(File).to receive(:exist?).with('/etc/issue').at_least(:once).and_return(true)
94 | allow(File).to receive(:read).with('/etc/issue').at_least(:once).and_return(contents)
95 | end
96 |
97 | def stub_distro(distro = LinuxAdmin::Distros.rhel)
98 | # simply alias test distro to redhat distro for time being
99 | allow(LinuxAdmin::Distros).to receive_messages(:local => distro)
100 | end
101 |
102 | def data_file_path(to)
103 | File.expand_path(to, File.join(File.dirname(__FILE__), "data"))
104 | end
105 |
106 | def sample_output(to)
107 | File.read(data_file_path(to))
108 | end
109 |
110 | def clear_caches
111 | LinuxAdmin::RegistrationSystem.instance_variable_set(:@registration_type, nil)
112 | LinuxAdmin::Service.instance_variable_set(:@service_type, nil)
113 |
114 | # reset the distro, tested in various placed & used extensively
115 | LinuxAdmin::Distros.instance_variable_set(:@local, nil)
116 | end
117 |
--------------------------------------------------------------------------------
/lib/linux_admin/registration_system/subscription_manager.rb:
--------------------------------------------------------------------------------
1 | require 'date'
2 |
3 | module LinuxAdmin
4 | class SubscriptionManager < RegistrationSystem
5 | def run!(cmd, options = {})
6 | Common.run!(cmd, options)
7 | rescue AwesomeSpawn::CommandResultError => err
8 | raise CredentialError.new(err.result) if err.result.error.downcase.include?("invalid username or password")
9 | raise
10 | end
11 |
12 | SATELLITE6_SERVER_CERT_PATH = "pub/katello-ca-consumer-latest.noarch.rpm"
13 |
14 | def validate_credentials(options)
15 | !!organizations(options)
16 | end
17 |
18 | def registered?(options = nil)
19 | args = ["subscription-manager identity"]
20 | args << {:params => proxy_params(options)} if options
21 | Common.run(*args).exit_status.zero?
22 | end
23 |
24 | def refresh
25 | run!("subscription-manager refresh")
26 | end
27 |
28 | def organizations(options)
29 | raise ArgumentError, "username and password are required" unless options[:username] && options[:password]
30 |
31 | install_server_certificate(options[:server_url], SATELLITE6_SERVER_CERT_PATH) if options[:server_url]
32 |
33 | cmd = "subscription-manager orgs"
34 |
35 | params = {"--username=" => options[:username], "--password=" => options[:password]}
36 | params.merge!(proxy_params(options))
37 |
38 | result = run!(cmd, :params => params)
39 | parse_output(result.output).each_with_object({}) { |i, h| h[i[:name]] = i }
40 | end
41 |
42 | def register(options)
43 | raise ArgumentError, "username and password are required" unless options[:username] && options[:password]
44 |
45 | install_server_certificate(options[:server_url], SATELLITE6_SERVER_CERT_PATH) if options[:server_url]
46 |
47 | cmd = "subscription-manager register"
48 |
49 | params = {"--username=" => options[:username], "--password=" => options[:password]}
50 | params.merge!(proxy_params(options))
51 | params["--environment="] = options[:environment] if options[:environment]
52 | params["--org="] = options[:org] if options[:server_url] && options[:org]
53 |
54 | run!(cmd, :params => params)
55 | end
56 |
57 | def subscribe(options)
58 | cmd = "subscription-manager attach"
59 | params = proxy_params(options)
60 |
61 | if options[:pools].blank?
62 | params.merge!({"--auto" => nil})
63 | else
64 | pools = options[:pools].collect {|pool| ["--pool", pool]}
65 | params = params.to_a + pools
66 | end
67 |
68 | run!(cmd, :params => params)
69 | end
70 |
71 | def subscribed_products
72 | cmd = "subscription-manager list --installed"
73 | output = run!(cmd).output
74 |
75 | parse_output(output).select {|p| p[:status].downcase == "subscribed"}.collect {|p| p[:product_id]}
76 | end
77 |
78 | def available_subscriptions
79 | cmd = "subscription-manager list --all --available"
80 | output = run!(cmd).output
81 | parse_output(output).each_with_object({}) { |i, h| h[i[:pool_id]] = i }
82 | end
83 |
84 | def enable_repo(repo, options = nil)
85 | cmd = "subscription-manager repos"
86 | params = {"--enable=" => repo}
87 |
88 | logger.info("#{self.class.name}##{__method__} Enabling repository: #{repo}")
89 | run!(cmd, :params => params)
90 | end
91 |
92 | def disable_repo(repo, options = nil)
93 | cmd = "subscription-manager repos"
94 | params = {"--disable=" => repo}
95 |
96 | run!(cmd, :params => params)
97 | end
98 |
99 | def all_repos(options = nil)
100 | cmd = "subscription-manager repos"
101 | output = run!(cmd).output
102 |
103 | parse_output(output)
104 | end
105 |
106 | def enabled_repos
107 | all_repos.select { |i| i[:enabled] }.collect { |r| r[:repo_id] }
108 | end
109 |
110 | private
111 |
112 | def parse_output(output)
113 | # Strip the 3 line header off the top
114 | content = output.split("\n")[3..-1].join("\n")
115 | parse_content(content)
116 | end
117 |
118 | def parse_content(content)
119 | # Break into content groupings by "\n\n" then process each grouping
120 | content.split("\n\n").each_with_object([]) do |group, group_array|
121 | group = group.split("\n").each_with_object({}) do |line, hash|
122 | next if line.blank?
123 | key, value = line.split(":", 2)
124 | hash[key.strip.downcase.tr(" -", "_").to_sym] = value.strip unless value.blank?
125 | end
126 | group_array.push(format_values(group))
127 | end
128 | end
129 |
130 | def format_values(content_group)
131 | content_group[:enabled] = content_group[:enabled].to_i == 1 if content_group[:enabled]
132 | content_group[:ends] = Date.strptime(content_group[:ends], "%m/%d/%Y") if content_group[:ends]
133 | content_group[:starts] = Date.strptime(content_group[:starts], "%m/%d/%Y") if content_group[:starts]
134 | content_group
135 | end
136 |
137 | def proxy_params(options)
138 | config = {}
139 | config["--proxy="] = options[:proxy_address] if options[:proxy_address]
140 | config["--proxyuser="] = options[:proxy_username] if options[:proxy_username]
141 | config["--proxypassword="] = options[:proxy_password] if options[:proxy_password]
142 | config
143 | end
144 | end
145 | end
146 |
--------------------------------------------------------------------------------
/spec/ip_address_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::IpAddress do
2 | let(:ip) { described_class.new }
3 |
4 | ADDR_SPAWN_ARGS = [
5 | LinuxAdmin::Common.cmd("hostname"),
6 | :params => ["-I"]
7 | ]
8 |
9 | MAC_SPAWN_ARGS = [
10 | LinuxAdmin::Common.cmd("ip"),
11 | :params => %w(addr show eth0)
12 | ]
13 |
14 | MASK_SPAWN_ARGS = [
15 | LinuxAdmin::Common.cmd("ifconfig"),
16 | :params => %w(eth0)
17 | ]
18 |
19 | GW_SPAWN_ARGS = [
20 | LinuxAdmin::Common.cmd("ip"),
21 | :params => %w(route)
22 | ]
23 |
24 | IP_ADDR_SHOW_ETH0 = <<-IP_OUT
25 | 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
26 | link/ether 00:0c:29:ed:0e:8b brd ff:ff:ff:ff:ff:ff
27 | inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic eth0
28 | valid_lft 1297sec preferred_lft 1297sec
29 | inet6 fe80::20c:29ff:feed:e8b/64 scope link
30 | valid_lft forever preferred_lft forever
31 |
32 | IP_OUT
33 |
34 | IFCFG = <<-IP_OUT
35 | eth0: flags=4163 mtu 1500
36 | inet 192.168.1.9 netmask 255.255.255.0 broadcast 192.168.1.255
37 | inet6 fe80::20c:29ff:feed:e8b prefixlen 64 scopeid 0x20
38 | ether 00:0c:29:ed:0e:8b txqueuelen 1000 (Ethernet)
39 | RX packets 10171 bytes 8163955 (7.7 MiB)
40 | RX errors 0 dropped 0 overruns 0 frame 0
41 | TX packets 2871 bytes 321915 (314.3 KiB)
42 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
43 |
44 | IP_OUT
45 |
46 | IP_ROUTE = <<-IP_OUT
47 | default via 192.168.1.1 dev eth0 proto static metric 100
48 | 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.9 metric 100
49 | IP_OUT
50 |
51 | def result(output, exit_status)
52 | AwesomeSpawn::CommandResult.new("", output, "", exit_status)
53 | end
54 |
55 | describe "#address" do
56 | it "returns an address" do
57 | ip_addr = "192.168.1.2"
58 | expect(AwesomeSpawn).to receive(:run).with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 0))
59 | expect(ip.address).to eq(ip_addr)
60 | end
61 |
62 | it "returns nil when no address is found" do
63 | ip_addr = ""
64 | expect(AwesomeSpawn).to receive(:run).at_least(5).times.with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 1))
65 | expect(ip.address).to be_nil
66 | end
67 |
68 | it "returns only IPv4 addresses" do
69 | ip_addr = "fd12:3456:789a:1::1 192.168.1.2"
70 | expect(AwesomeSpawn).to receive(:run).with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 0))
71 | expect(ip.address).to eq("192.168.1.2")
72 | end
73 | end
74 |
75 | describe "#address6" do
76 | it "returns an address" do
77 | ip_addr = "fd12:3456:789a:1::1"
78 | expect(AwesomeSpawn).to receive(:run).with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 0))
79 | expect(ip.address6).to eq(ip_addr)
80 | end
81 |
82 | it "returns nil when no address is found" do
83 | ip_addr = ""
84 | expect(AwesomeSpawn).to receive(:run).at_least(5).times.with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 1))
85 | expect(ip.address6).to be_nil
86 | end
87 |
88 | it "returns only IPv6 addresses" do
89 | ip_addr = "192.168.1.2 fd12:3456:789a:1::1"
90 | expect(AwesomeSpawn).to receive(:run).with(*ADDR_SPAWN_ARGS).and_return(result(ip_addr, 0))
91 | expect(ip.address6).to eq("fd12:3456:789a:1::1")
92 | end
93 | end
94 |
95 | describe "#mac_address" do
96 | it "returns the correct MAC address" do
97 | expect(AwesomeSpawn).to receive(:run).with(*MAC_SPAWN_ARGS).and_return(result(IP_ADDR_SHOW_ETH0, 0))
98 | expect(ip.mac_address("eth0")).to eq("00:0c:29:ed:0e:8b")
99 | end
100 |
101 | it "returns nil when the command fails" do
102 | expect(AwesomeSpawn).to receive(:run).with(*MAC_SPAWN_ARGS).and_return(result("", 1))
103 | expect(ip.mac_address("eth0")).to be_nil
104 | end
105 |
106 | it "returns nil if the link/ether line is not present" do
107 | bad_output = IP_ADDR_SHOW_ETH0.gsub(%r{link/ether}, "")
108 | expect(AwesomeSpawn).to receive(:run).with(*MAC_SPAWN_ARGS).and_return(result(bad_output, 0))
109 | expect(ip.mac_address("eth0")).to be_nil
110 | end
111 | end
112 |
113 | describe "#netmask" do
114 | it "returns the correct netmask" do
115 | expect(AwesomeSpawn).to receive(:run).with(*MASK_SPAWN_ARGS).and_return(result(IFCFG, 0))
116 | expect(ip.netmask("eth0")).to eq("255.255.255.0")
117 | end
118 |
119 | it "returns nil when the command fails" do
120 | expect(AwesomeSpawn).to receive(:run).with(*MASK_SPAWN_ARGS).and_return(result("", 1))
121 | expect(ip.netmask("eth0")).to be_nil
122 | end
123 |
124 | it "returns nil if the netmask line is not present" do
125 | bad_output = IFCFG.gsub(/netmask/, "")
126 | expect(AwesomeSpawn).to receive(:run).with(*MASK_SPAWN_ARGS).and_return(result(bad_output, 0))
127 | expect(ip.netmask("eth0")).to be_nil
128 | end
129 | end
130 |
131 | describe "#gateway" do
132 | it "returns the correct gateway address" do
133 | expect(AwesomeSpawn).to receive(:run).with(*GW_SPAWN_ARGS).and_return(result(IP_ROUTE, 0))
134 | expect(ip.gateway).to eq("192.168.1.1")
135 | end
136 |
137 | it "returns nil when the command fails" do
138 | expect(AwesomeSpawn).to receive(:run).with(*GW_SPAWN_ARGS).and_return(result("", 1))
139 | expect(ip.gateway).to be_nil
140 | end
141 |
142 | it "returns nil if the default line is not present" do
143 | bad_output = IP_ROUTE.gsub(/default/, "")
144 | expect(AwesomeSpawn).to receive(:run).with(*GW_SPAWN_ARGS).and_return(result(bad_output, 0))
145 | expect(ip.gateway).to be_nil
146 | end
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/lib/linux_admin/disk.rb:
--------------------------------------------------------------------------------
1 | require 'linux_admin/partition'
2 |
3 | module LinuxAdmin
4 | class Disk
5 | PARTED_FIELDS =
6 | [:id, :start_sector, :end_sector,
7 | :size, :partition_type, :fs_type]
8 |
9 | attr_accessor :path
10 |
11 | def self.local
12 | Dir.glob(['/dev/[vhs]d[a-z]', '/dev/xvd[a-z]']).collect do |d|
13 | Disk.new :path => d
14 | end
15 | end
16 |
17 | def initialize(args = {})
18 | @path = args[:path]
19 | end
20 |
21 | def size
22 | @size ||= begin
23 | size = nil
24 | out = Common.run!(Common.cmd(:fdisk), :params => {"-l" => nil}).output
25 | out.each_line do |l|
26 | /Disk #{path}: .*B, (\d+) bytes/.match(l) do |m|
27 | size = m[1].to_i
28 | break
29 | end
30 | end
31 | size
32 | end
33 | end
34 |
35 | def partitions
36 | @partitions ||=
37 | parted_output.collect { |disk|
38 | partition_from_parted(disk)
39 | }
40 | end
41 |
42 | def create_partition_table(type = "msdos")
43 | Common.run!(Common.cmd(:parted), :params => {nil => parted_options_array("mklabel", type)})
44 | end
45 |
46 | def has_partition_table?
47 | result = Common.run(Common.cmd(:parted), :params => {nil => parted_options_array("print")})
48 |
49 | result_indicates_partition_table?(result)
50 | end
51 |
52 | def create_partition(partition_type, *args)
53 | create_partition_table unless has_partition_table?
54 |
55 | start = finish = size = nil
56 | case args.length
57 | when 1 then
58 | start = partitions.empty? ? 0 : partitions.last.end_sector
59 | size = args.first
60 | finish = start + size
61 |
62 | when 2 then
63 | start = args[0]
64 | finish = args[1]
65 |
66 | else
67 | raise ArgumentError, "must specify start/finish or size"
68 | end
69 |
70 | id = partitions.empty? ? 1 : (partitions.last.id + 1)
71 | options = parted_options_array('mkpart', '-a', 'opt', partition_type, start, finish)
72 | Common.run!(Common.cmd(:parted), :params => {nil => options})
73 |
74 | partition = Partition.new(:disk => self,
75 | :id => id,
76 | :start_sector => start,
77 | :end_sector => finish,
78 | :size => size,
79 | :partition_type => partition_type)
80 | partitions << partition
81 | partition
82 | end
83 |
84 | def create_partitions(partition_type, *args)
85 | check_if_partitions_overlap(args)
86 |
87 | args.each { |arg|
88 | self.create_partition(partition_type, arg[:start], arg[:end])
89 | }
90 | end
91 |
92 | def clear!
93 | @partitions = []
94 |
95 | # clear partition table
96 | Common.run!(Common.cmd(:dd),
97 | :params => { 'if=' => '/dev/zero', 'of=' => @path,
98 | 'bs=' => 512, 'count=' => 1})
99 |
100 | self
101 | end
102 |
103 | private
104 |
105 | def str_to_bytes(val, unit)
106 | case unit
107 | when 'K' then
108 | val.to_f * 1_024 # 1.kilobytes
109 | when 'M' then
110 | val.to_f * 1_048_576 # 1.megabyte
111 | when 'G' then
112 | val.to_f * 1_073_741_824 # 1.gigabytes
113 | end
114 | end
115 |
116 | def overlapping_ranges?(ranges)
117 | ranges.find do |range1|
118 | ranges.any? do |range2|
119 | range1 != range2 &&
120 | ranges_overlap?(range1, range2)
121 | end
122 | end
123 | end
124 |
125 | def ranges_overlap?(range1, range2) # copied from activesupport Range#overlaps?
126 | range1.cover?(range2.first) || range2.cover?(range1.first)
127 | end
128 |
129 | def check_if_partitions_overlap(partitions)
130 | ranges =
131 | partitions.collect do |partition|
132 | start = partition[:start]
133 | finish = partition[:end]
134 | start.delete('%')
135 | finish.delete('%')
136 | start.to_f..finish.to_f
137 | end
138 |
139 | if overlapping_ranges?(ranges)
140 | raise ArgumentError, "overlapping partitions"
141 | end
142 | end
143 |
144 | def parted_output
145 | # TODO: Should this really catch non-zero RC, set output to the default "" and silently return [] ?
146 | # If so, should other calls to parted also do the same?
147 | # requires sudo
148 | out = Common.run(Common.cmd(:parted),
149 | :params => { nil => parted_options_array('print') }).output
150 | split = []
151 | out.each_line do |l|
152 | if l =~ /^ [0-9].*/
153 | split << l.split
154 | end
155 | end
156 | split
157 | end
158 |
159 |
160 | def partition_from_parted(output_disk)
161 | args = {:disk => self}
162 | PARTED_FIELDS.each_index do |i|
163 | val = output_disk[i]
164 | case PARTED_FIELDS[i]
165 | when :start_sector, :end_sector, :size
166 | if val =~ /([0-9\.]*)([KMG])B/
167 | val = str_to_bytes($1, $2)
168 | end
169 |
170 | when :id
171 | val = val.to_i
172 |
173 | end
174 | args[PARTED_FIELDS[i]] = val
175 | end
176 |
177 | Partition.new(args)
178 | end
179 |
180 | def parted_options_array(*args)
181 | args = args.first if args.first.kind_of?(Array)
182 | parted_default_options + args
183 | end
184 |
185 | def parted_default_options
186 | @parted_default_options ||= ['--script', path].freeze
187 | end
188 |
189 | def result_indicates_partition_table?(result)
190 | # parted exits with 1 but writes this oddly spelled error to stdout.
191 | missing = (result.exit_status == 1 && result.output.include?("unrecognised disk label"))
192 | !missing
193 | end
194 | end
195 | end
196 |
--------------------------------------------------------------------------------
/spec/mountable_spec.rb:
--------------------------------------------------------------------------------
1 | class TestMountable
2 | include LinuxAdmin::Mountable
3 |
4 | def path
5 | "/dev/foo"
6 | end
7 | end
8 |
9 | describe LinuxAdmin::Mountable do
10 | before(:each) do
11 | @mountable = TestMountable.new
12 |
13 | # stub out calls that modify system
14 | allow(FileUtils).to receive(:mkdir)
15 | allow(LinuxAdmin::Common).to receive(:run!)
16 |
17 | @mount_out1 = < ""))
36 | TestMountable.mount_point_exists?('/mnt/usb')
37 | end
38 |
39 | context "disk mounted at specified location" do
40 | before do
41 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out1))
42 | end
43 |
44 | it "returns true" do
45 | expect(TestMountable.mount_point_exists?('/mnt/usb')).to be_truthy
46 | end
47 |
48 | it "returns true when using a pathname" do
49 | path = Pathname.new("/mnt/usb")
50 | expect(TestMountable.mount_point_exists?(path)).to be_truthy
51 | end
52 | end
53 |
54 | context "no disk mounted at specified location" do
55 | before do
56 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out2))
57 | end
58 |
59 | it "returns false" do
60 | expect(TestMountable.mount_point_exists?('/mnt/usb')).to be_falsey
61 | end
62 |
63 | it "returns false when using a pathname" do
64 | path = Pathname.new("/mnt/usb")
65 | expect(TestMountable.mount_point_exists?(path)).to be_falsey
66 | end
67 | end
68 | end
69 |
70 | describe "#mount_point_available?" do
71 | it "uses mount" do
72 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:mount))
73 | .and_return(double(:output => ""))
74 | TestMountable.mount_point_available?('/mnt/usb')
75 | end
76 |
77 | context "disk mounted at specified location" do
78 | before do
79 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out1))
80 | end
81 |
82 | it "returns false" do
83 | expect(TestMountable.mount_point_available?('/mnt/usb')).to be_falsey
84 | end
85 |
86 | it "returns false when using a pathname" do
87 | path = Pathname.new("/mnt/usb")
88 | expect(TestMountable.mount_point_available?(path)).to be_falsey
89 | end
90 | end
91 |
92 | context "no disk mounted at specified location" do
93 | before do
94 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out2))
95 | end
96 |
97 | it "returns true" do
98 | expect(TestMountable.mount_point_available?('/mnt/usb')).to be_truthy
99 | end
100 |
101 | it "returns true when using a pathname" do
102 | path = Pathname.new("/mnt/usb")
103 | expect(TestMountable.mount_point_available?(path)).to be_truthy
104 | end
105 | end
106 | end
107 |
108 | describe "#discover_mount_point" do
109 | it "sets the correct mountpoint when the path is mounted" do
110 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out3))
111 | @mountable.discover_mount_point
112 | expect(@mountable.mount_point).to eq("/tmp")
113 | end
114 |
115 | it "sets mount_point to nil when the path is not mounted" do
116 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @mount_out1))
117 | @mountable.discover_mount_point
118 | expect(@mountable.mount_point).to be_nil
119 | end
120 | end
121 |
122 | describe "#format_to" do
123 | it "uses mke2fs" do
124 | expect(LinuxAdmin::Common).to receive(:run!)
125 | .with(LinuxAdmin::Common.cmd(:mke2fs),
126 | :params => {'-t' => 'ext4', nil => '/dev/foo'})
127 | @mountable.format_to('ext4')
128 | end
129 |
130 | it "sets fs type" do
131 | expect(LinuxAdmin::Common).to receive(:run!) # ignore actual formatting cmd
132 | @mountable.format_to('ext4')
133 | expect(@mountable.fs_type).to eq('ext4')
134 | end
135 | end
136 |
137 | describe "#mount" do
138 | it "sets mount point" do
139 | # ignore actual mount cmds
140 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => ""))
141 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => ""))
142 |
143 | expect(@mountable.mount('/mnt/sda2')).to eq('/mnt/sda2')
144 | expect(@mountable.mount_point).to eq('/mnt/sda2')
145 | end
146 |
147 | context "mountpoint does not exist" do
148 | it "creates mountpoint" do
149 | expect(TestMountable).to receive(:mount_point_exists?).and_return(false)
150 | expect(File).to receive(:directory?).with('/mnt/sda2').and_return(false)
151 | expect(FileUtils).to receive(:mkdir).with('/mnt/sda2')
152 | expect(LinuxAdmin::Common).to receive(:run!) # ignore actual mount cmd
153 | @mountable.mount '/mnt/sda2'
154 | end
155 | end
156 |
157 | context "disk mounted at mountpoint" do
158 | it "raises argument error" do
159 | expect(TestMountable).to receive(:mount_point_exists?).and_return(true)
160 | expect(File).to receive(:directory?).with('/mnt/sda2').and_return(true)
161 | expect { @mountable.mount '/mnt/sda2' }.to raise_error(ArgumentError, "disk already mounted at /mnt/sda2")
162 | end
163 | end
164 |
165 | it "mounts partition" do
166 | expect(TestMountable).to receive(:mount_point_exists?).and_return(false)
167 | expect(LinuxAdmin::Common).to receive(:run!)
168 | .with(LinuxAdmin::Common.cmd(:mount),
169 | :params => {nil => ['/dev/foo', '/mnt/sda2']})
170 | @mountable.mount '/mnt/sda2'
171 | end
172 | end
173 |
174 | describe "#umount" do
175 | it "unmounts partition" do
176 | @mountable.mount_point = '/mnt/sda2'
177 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:umount),
178 | :params => {nil => ['/mnt/sda2']})
179 | @mountable.umount
180 | end
181 | end
182 | end
183 |
--------------------------------------------------------------------------------
/spec/rhn_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Rhn do
2 | context "#registered?" do
3 | it "with registered system_id" do
4 | allow_any_instance_of(described_class).to receive_messages(:systemid_file => data_file_path("rhn/systemid"))
5 | expect(described_class.new).to be_registered
6 | end
7 |
8 | it "with unregistered system_id" do
9 | allow_any_instance_of(described_class).to receive_messages(:systemid_file => data_file_path("rhn/systemid.missing_system_id"))
10 | expect(described_class.new).to_not be_registered
11 | end
12 |
13 | it "with missing systemid file" do
14 | allow_any_instance_of(described_class).to receive_messages(:systemid_file => data_file_path("rhn/systemid.missing_file"))
15 | expect(described_class.new).to_not be_registered
16 | end
17 | end
18 |
19 | context "#register" do
20 | it "no username or activation key" do
21 | expect { described_class.new.register({}) }.to raise_error(ArgumentError)
22 | end
23 |
24 | context "with username and password" do
25 | let(:base_options) { {:username => "SomeUser@SomeDomain.org",
26 | :password => "SomePass",
27 | :org => "2",
28 | :proxy_address => "1.2.3.4",
29 | :proxy_username => "ProxyUser",
30 | :proxy_password => "ProxyPass",
31 | :server_cert => "/path/to/cert",
32 | }
33 | }
34 | let(:run_params) { {:params=>{"--username="=>"SomeUser@SomeDomain.org", "--password="=>"SomePass", "--proxy="=>"1.2.3.4", "--proxyUser="=>"ProxyUser", "--proxyPassword="=>"ProxyPass"}} }
35 |
36 | it "with server_url" do
37 | run_params.store_path(:params, "--systemorgid=", "2")
38 | run_params.store_path(:params, "--serverUrl=", "https://server.url")
39 | run_params.store_path(:params, "--sslCACert=", "/usr/share/rhn/RHN-ORG-TRUSTED-SSL-CERT")
40 | base_options.store_path(:server_url, "https://server.url")
41 |
42 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhnreg_ks", run_params)
43 | expect(LinuxAdmin::Rpm).to receive(:upgrade).with("http://server.url/pub/rhn-org-trusted-ssl-cert-1.0-1.noarch.rpm")
44 | expect(LinuxAdmin::Rpm).to receive(:list_installed).and_return({"rhn-org-trusted-ssl-cert" => "1.0"})
45 |
46 | described_class.new.register(base_options)
47 | end
48 |
49 | it "without server_url" do
50 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhnreg_ks", run_params)
51 | expect_any_instance_of(described_class).not_to receive(:install_server_certificate)
52 | expect(LinuxAdmin::Rpm).to receive(:list_installed).and_return({"rhn-org-trusted-ssl-cert" => nil})
53 |
54 | described_class.new.register(base_options)
55 | end
56 | end
57 |
58 | it "with activation key" do
59 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhnreg_ks",
60 | :params => {"--activationkey=" => "123abc",
61 | "--proxy=" => "1.2.3.4",
62 | "--proxyUser=" => "ProxyUser",
63 | "--proxyPassword=" => "ProxyPass"})
64 | expect(LinuxAdmin::Rpm).to receive(:list_installed).and_return({"rhn-org-trusted-ssl-cert" => nil})
65 |
66 | described_class.new.register(
67 | :activationkey => "123abc",
68 | :proxy_address => "1.2.3.4",
69 | :proxy_username => "ProxyUser",
70 | :proxy_password => "ProxyPass",
71 | )
72 | end
73 | end
74 |
75 | it "#enable_channel" do
76 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhn-channel -a",
77 | :params => {"--user=" => "SomeUser",
78 | "--password=" => "SomePass",
79 | "--channel=" => 123})
80 |
81 | described_class.new.enable_channel(123, :username => "SomeUser", :password => "SomePass")
82 | end
83 |
84 | it "#enabled_channels" do
85 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhn-channel -l")
86 | .and_return(double(:output => sample_output("rhn/output_rhn-channel_list")))
87 |
88 | expect(described_class.new.enabled_channels).to eq(["rhel-x86_64-server-6", "rhel-x86_64-server-6-cf-me-2"])
89 | end
90 |
91 | it "#disable_channel" do
92 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhn-channel -r", :params => {"--user=" => "SomeUser",
93 | "--password=" => "SomePass",
94 | "--channel=" => 123})
95 |
96 | described_class.new.disable_channel(123, :username => "SomeUser", :password => "SomePass")
97 | end
98 |
99 | it "#available_channels" do
100 | credentials = {
101 | :username => "some_user",
102 | :password => "password"
103 | }
104 | expected = [
105 | "rhel-x86_64-server-6-cf-me-2",
106 | "rhel-x86_64-server-6-cf-me-2-beta",
107 | "rhel-x86_64-server-6-cf-me-3",
108 | "rhel-x86_64-server-6-cf-me-3-beta"
109 | ]
110 | cmd = "rhn-channel -L"
111 | params = {
112 | :params => {
113 | "--user=" => "some_user",
114 | "--password=" => "password"
115 | }
116 | }
117 |
118 | expect(LinuxAdmin::Common).to receive(:run!).once.with(cmd, params)
119 | .and_return(double(:output => sample_output("rhn/output_rhn-channel_list_available")))
120 |
121 | expect(described_class.new.available_channels(credentials)).to eq(expected)
122 | end
123 |
124 | it "#all_repos" do
125 | credentials = {
126 | :username => "some_user",
127 | :password => "password"
128 | }
129 | expected = [
130 | {:repo_id => "rhel-x86_64-server-6-cf-me-2", :enabled => true},
131 | {:repo_id => "rhel-x86_64-server-6-cf-me-2-beta", :enabled => false},
132 | {:repo_id => "rhel-x86_64-server-6-cf-me-3", :enabled => false},
133 | {:repo_id => "rhel-x86_64-server-6-cf-me-3-beta", :enabled => false},
134 | {:repo_id => "rhel-x86_64-server-6", :enabled => true}
135 | ]
136 |
137 | expect(LinuxAdmin::Common).to receive(:run!).once
138 | .and_return(double(:output => sample_output("rhn/output_rhn-channel_list_available")))
139 | expect(LinuxAdmin::Common).to receive(:run!).once.with("rhn-channel -l")
140 | .and_return(double(:output => sample_output("rhn/output_rhn-channel_list")))
141 |
142 | expect(described_class.new.all_repos(credentials)).to eq(expected)
143 | end
144 | end
145 |
--------------------------------------------------------------------------------
/spec/logical_volume_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::LogicalVolume do
2 | before(:each) do
3 | @logical_volumes = < 'vg'
23 | lv = described_class.new :name => 'lv', :volume_group => vg
24 | expect(LinuxAdmin::Common).to receive(:run!)
25 | .with(LinuxAdmin::Common.cmd(:lvextend),
26 | :params => %w(lv vg))
27 | lv.extend_with(vg)
28 | end
29 |
30 | it "returns self" do
31 | vg = LinuxAdmin::VolumeGroup.new :name => 'vg'
32 | lv = described_class.new :name => 'lv', :volume_group => vg
33 | allow(LinuxAdmin::Common).to receive(:run!)
34 | expect(lv.extend_with(vg)).to eq(lv)
35 | end
36 | end
37 |
38 | describe "#path" do
39 | it "returns /dev/vgname/lvname" do
40 | vg = LinuxAdmin::VolumeGroup.new :name => 'vg'
41 | lv = described_class.new :name => 'lv', :volume_group => vg
42 | expect(lv.path).to eq('/dev/vg/lv')
43 | end
44 | end
45 |
46 | describe "#create" do
47 | before(:each) do
48 | @vg = LinuxAdmin::VolumeGroup.new :name => 'vg'
49 | end
50 |
51 | it "uses lvcreate" do
52 | described_class.instance_variable_set(:@lvs, [])
53 | expect(LinuxAdmin::Common).to receive(:run!)
54 | .with(LinuxAdmin::Common.cmd(:lvcreate),
55 | :params => {'-n' => 'lv',
56 | nil => 'vg',
57 | '-L' => '256G'})
58 | described_class.create 'lv', @vg, 274_877_906_944 # 256.gigabytes
59 | end
60 |
61 | context "size is specified" do
62 | it "passes -L option to lvcreate" do
63 | described_class.instance_variable_set(:@lvs, [])
64 | expect(LinuxAdmin::Common).to receive(:run!)
65 | .with(LinuxAdmin::Common.cmd(:lvcreate),
66 | :params => {'-n' => 'lv',
67 | nil => 'vg',
68 | '-L' => '256G'})
69 | described_class.create 'lv', @vg, 274_877_906_944 # 256.gigabytes
70 | end
71 | end
72 |
73 | context "extents is specified" do
74 | it "passes -l option to lvcreate" do
75 | described_class.instance_variable_set(:@lvs, [])
76 | expect(LinuxAdmin::Common).to receive(:run!)
77 | .with(LinuxAdmin::Common.cmd(:lvcreate),
78 | :params => {'-n' => 'lv',
79 | nil => 'vg',
80 | '-l' => '100%FREE'})
81 | described_class.create 'lv', @vg, 100
82 | end
83 | end
84 |
85 | it "returns new logical volume" do
86 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
87 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
88 | lv = described_class.create 'lv', @vg, 274_877_906_944 # 256.gigabytes
89 | expect(lv).to be_an_instance_of(described_class)
90 | expect(lv.name).to eq('lv')
91 | end
92 |
93 | context "name is specified" do
94 | it "sets path under volume group" do
95 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
96 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
97 | lv = described_class.create 'lv', @vg, 274_877_906_944 # 256.gigabytes
98 | expect(lv.path.to_s).to eq("#{described_class::DEVICE_PATH}#{@vg.name}/lv")
99 | end
100 | end
101 |
102 | context "path is specified" do
103 | it "sets name" do
104 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
105 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
106 | lv = described_class.create '/dev/lv', @vg, 274_877_906_944 # 256.gigabytes
107 | expect(lv.name).to eq("lv")
108 | end
109 | end
110 |
111 | context "path is specified as Pathname" do
112 | it "sets name" do
113 | require 'pathname'
114 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
115 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
116 | lv = described_class.create Pathname.new("/dev/#{@vg.name}/lv"), @vg, 274_877_906_944 # 256.gigabytes
117 | expect(lv.name).to eq("lv")
118 | expect(lv.path).to eq("/dev/vg/lv")
119 | end
120 | end
121 |
122 | it "adds logical volume to local registry" do
123 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
124 | allow(LinuxAdmin::Common).to receive_messages(:run! => double(:output => ""))
125 | lv = described_class.create 'lv', @vg, 274_877_906_944 # 256.gigabytes
126 | expect(described_class.scan).to include(lv)
127 | end
128 | end
129 |
130 | describe "#scan" do
131 | it "uses lvdisplay" do
132 | expect(LinuxAdmin::Common).to receive(:run!)
133 | .with(LinuxAdmin::Common.cmd(:lvdisplay),
134 | :params => {'-c' => nil})
135 | .and_return(double(:output => @logical_volumes))
136 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @groups)) # stub out call to vgdisplay
137 | described_class.scan
138 | end
139 |
140 | it "returns local logical volumes" do
141 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @logical_volumes))
142 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @groups))
143 | lvs = described_class.scan
144 |
145 | expect(lvs[0]).to be_an_instance_of(described_class)
146 | expect(lvs[0].path).to eq('/dev/vg_foobar/lv_swap')
147 | expect(lvs[0].name).to eq('lv_swap')
148 | expect(lvs[0].sectors).to eq(4128768)
149 |
150 | expect(lvs[1]).to be_an_instance_of(described_class)
151 | expect(lvs[1].path).to eq('/dev/vg_foobar/lv_root')
152 | expect(lvs[1].name).to eq('lv_root')
153 | expect(lvs[1].sectors).to eq(19988480)
154 | end
155 |
156 | it "resolves volume group references" do
157 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @logical_volumes))
158 | expect(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => @groups))
159 | lvs = described_class.scan
160 | expect(lvs[0].volume_group).to be_an_instance_of(LinuxAdmin::VolumeGroup)
161 | expect(lvs[0].volume_group.name).to eq('vg_foobar')
162 | expect(lvs[1].volume_group).to be_an_instance_of(LinuxAdmin::VolumeGroup)
163 | expect(lvs[1].volume_group.name).to eq('vg_foobar')
164 | end
165 | end
166 | end
167 |
--------------------------------------------------------------------------------
/spec/hosts_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Hosts do
2 | TEST_HOSTNAME = "test-hostname"
3 | etc_hosts = "\n #Some Comment\n127.0.0.1\tlocalhost localhost.localdomain # with a comment\n127.0.1.1 my.domain.local"
4 | before do
5 | allow(File).to receive(:read).and_return(etc_hosts)
6 | @instance = LinuxAdmin::Hosts.new
7 | end
8 |
9 | describe "#reload" do
10 | it "sets raw_lines" do
11 | expected_array = ["", " #Some Comment", "127.0.0.1\tlocalhost localhost.localdomain # with a comment", "127.0.1.1 my.domain.local"]
12 | expect(@instance.raw_lines).to eq(expected_array)
13 | end
14 |
15 | it "sets parsed_file" do
16 | expected_hash = [{:blank=>true}, {:comment=>"Some Comment"}, {:address=>"127.0.0.1", :hosts=>["localhost", "localhost.localdomain"], :comment=>"with a comment"}, {:address=>"127.0.1.1", :hosts=>["my.domain.local"]}]
17 | expect(@instance.parsed_file).to eq(expected_hash)
18 | end
19 | end
20 |
21 | describe "#update_entry" do
22 | it "removes an existing entry and creates a new one" do
23 | expected_hash = [{:blank=>true}, {:comment=>"Some Comment"}, {:address=>"127.0.0.1", :hosts=>["localhost", "localhost.localdomain"], :comment=>"with a comment"}, {:address=>"127.0.1.1", :hosts=>[]}, {:address=>"1.2.3.4", :hosts=>["my.domain.local"], :comment=>nil}]
24 | @instance.update_entry("1.2.3.4", "my.domain.local")
25 | expect(@instance.parsed_file).to eq(expected_hash)
26 | end
27 |
28 | it "updates an existing entry" do
29 | expected_hash = [{:blank=>true}, {:comment=>"Some Comment"}, {:address=>"127.0.0.1", :hosts=>["localhost", "localhost.localdomain", "new.domain.local"], :comment=>"with a comment"}, {:address=>"127.0.1.1", :hosts=>["my.domain.local"]}]
30 | @instance.update_entry("127.0.0.1", "new.domain.local")
31 | expect(@instance.parsed_file).to eq(expected_hash)
32 | end
33 | end
34 |
35 | describe "#set_loopback_hostname" do
36 | etc_hosts_v6_loopback = <<-EOT
37 |
38 | #Some Comment
39 | ::1\tlocalhost localhost.localdomain # with a comment
40 | 127.0.0.1\tlocalhost localhost.localdomain # with a comment
41 | 127.0.1.1 my.domain.local
42 | EOT
43 |
44 | before do
45 | allow(File).to receive(:read).and_return(etc_hosts_v6_loopback)
46 | @instance_v6_loopback = LinuxAdmin::Hosts.new
47 | end
48 |
49 | it "adds the hostname to the start of the hosts list for the loopback addresses" do
50 | expected_hash = [{:blank => true},
51 | {:comment => "Some Comment"},
52 | {:address => "::1",
53 | :hosts => ["examplehost.example.com", "localhost", "localhost.localdomain"],
54 | :comment => "with a comment"},
55 | {:address => "127.0.0.1",
56 | :hosts => ["examplehost.example.com", "localhost", "localhost.localdomain"],
57 | :comment => "with a comment"},
58 | {:address => "127.0.1.1", :hosts => ["my.domain.local"]}]
59 | @instance_v6_loopback.set_loopback_hostname("examplehost.example.com")
60 | expect(@instance_v6_loopback.parsed_file).to eq(expected_hash)
61 | end
62 | end
63 |
64 | describe "#set_canonical_hostname" do
65 | it "removes an existing entry and creates a new one" do
66 | expected_hash = [{:blank => true},
67 | {:comment => "Some Comment"},
68 | {:address => "127.0.0.1", :hosts => ["localhost", "localhost.localdomain"], :comment => "with a comment"},
69 | {:address => "127.0.1.1", :hosts => []},
70 | {:address => "1.2.3.4", :hosts => ["my.domain.local"], :comment => nil}]
71 | @instance.set_canonical_hostname("1.2.3.4", "my.domain.local")
72 | expect(@instance.parsed_file).to eq(expected_hash)
73 | end
74 |
75 | it "adds the hostname to the start of the hosts list" do
76 | expected_hash = [{:blank => true},
77 | {:comment => "Some Comment"},
78 | {:address => "127.0.0.1", :hosts => ["examplehost.example.com", "localhost", "localhost.localdomain"], :comment => "with a comment"},
79 | {:address => "127.0.1.1", :hosts => ["my.domain.local"]}]
80 | @instance.set_canonical_hostname("127.0.0.1", "examplehost.example.com")
81 | expect(@instance.parsed_file).to eq(expected_hash)
82 | end
83 | end
84 |
85 | describe "#save" do
86 | it "properly generates file with new content" do
87 | allow(File).to receive(:write)
88 | expected_array = ["", "#Some Comment", "127.0.0.1 localhost localhost.localdomain #with a comment", "127.0.1.1 my.domain.local", "1.2.3.4 test"]
89 | @instance.update_entry("1.2.3.4", "test")
90 | @instance.save
91 | expect(@instance.raw_lines).to eq(expected_array)
92 | end
93 |
94 | it "properly generates file with removed content" do
95 | allow(File).to receive(:write)
96 | expected_array = ["", "#Some Comment", "127.0.0.1 localhost localhost.localdomain my.domain.local #with a comment"]
97 | @instance.update_entry("127.0.0.1", "my.domain.local")
98 | @instance.save
99 | expect(@instance.raw_lines).to eq(expected_array)
100 | end
101 |
102 | it "ends the file with a new line" do
103 | expect(File).to receive(:write) do |_file, contents|
104 | expect(contents).to end_with("\n")
105 | end
106 | @instance.save
107 | end
108 | end
109 |
110 | describe "#hostname=" do
111 | it "sets the hostname using hostnamectl when the command exists" do
112 | spawn_args = [
113 | LinuxAdmin::Common.cmd('hostnamectl'),
114 | :params => ['set-hostname', TEST_HOSTNAME]
115 | ]
116 | expect(LinuxAdmin::Common).to receive(:cmd?).with("hostnamectl").and_return(true)
117 | expect(AwesomeSpawn).to receive(:run!).with(*spawn_args)
118 | @instance.hostname = TEST_HOSTNAME
119 | end
120 |
121 | it "sets the hostname with hostname when hostnamectl does not exist" do
122 | spawn_args = [
123 | LinuxAdmin::Common.cmd('hostname'),
124 | :params => {:file => "/etc/hostname"}
125 | ]
126 | expect(LinuxAdmin::Common).to receive(:cmd?).with("hostnamectl").and_return(false)
127 | expect(File).to receive(:write).with("/etc/hostname", TEST_HOSTNAME)
128 | expect(AwesomeSpawn).to receive(:run!).with(*spawn_args)
129 | @instance.hostname = TEST_HOSTNAME
130 | end
131 | end
132 |
133 | describe "#hostname" do
134 | let(:spawn_args) do
135 | [LinuxAdmin::Common.cmd('hostname'), {}]
136 | end
137 |
138 | it "returns the hostname" do
139 | result = AwesomeSpawn::CommandResult.new("", TEST_HOSTNAME, nil, 0)
140 | expect(AwesomeSpawn).to receive(:run).with(*spawn_args).and_return(result)
141 | expect(@instance.hostname).to eq(TEST_HOSTNAME)
142 | end
143 |
144 | it "returns nil when the command fails" do
145 | result = AwesomeSpawn::CommandResult.new("", "", "An error has happened", 1)
146 | expect(AwesomeSpawn).to receive(:run).with(*spawn_args).and_return(result)
147 | expect(@instance.hostname).to be_nil
148 | end
149 | end
150 | end
151 |
--------------------------------------------------------------------------------
/spec/yum_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Yum do
2 | before(:each) do
3 | allow(FileUtils).to receive_messages(:mkdir_p => true)
4 | end
5 |
6 | context ".create_repo" do
7 | it "default arguments" do
8 | expect(LinuxAdmin::Common).to receive(:run!).once
9 | .with("createrepo", :params => {nil => "some/path", "--database" => nil, "--unique-md-filenames" => nil})
10 | described_class.create_repo("some/path")
11 | end
12 |
13 | it "bare create" do
14 | expect(LinuxAdmin::Common).to receive(:run!).once.with("createrepo", :params => {nil => "some/path"})
15 | described_class.create_repo("some/path", :database => false, :unique_file_names => false)
16 | end
17 | end
18 |
19 | context ".download_packages" do
20 | it "with valid input" do
21 | expect(LinuxAdmin::Common).to receive(:run!).once
22 | .with("repotrack", :params => {"-p" => "some/path", nil => "pkg_a pkg_b"})
23 | described_class.download_packages("some/path", "pkg_a pkg_b")
24 | end
25 |
26 | it "without mirror type" do
27 | expect { described_class.download_packages("some/path", "pkg_a pkg_b", :mirror_type => nil) }.to raise_error(ArgumentError)
28 | end
29 | end
30 |
31 | it ".repo_settings" do
32 | expect(described_class).to receive(:parse_repo_dir).once.with("/etc/yum.repos.d").and_return(true)
33 | expect(described_class.repo_settings).to be_truthy
34 | end
35 |
36 | it ".parse_repo_dir" do
37 | expect(described_class.parse_repo_dir(data_file_path("yum"))).to eq({
38 | File.join(data_file_path("yum"), "first.repo") =>
39 | { "my-local-repo-a" =>
40 | { "name" =>"My Local Repo A",
41 | "baseurl" =>"https://mirror.example.com/a/content/os_ver",
42 | "enabled" =>0,
43 | "gpgcheck" =>1,
44 | "gpgkey" =>"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-my-local-server",
45 | "sslverify" =>1,
46 | "sslcacert" =>"/etc/rhsm/ca/my-loacl-server.pem",
47 | "sslclientkey" =>"/etc/pki/entitlement/0123456789012345678-key.pem",
48 | "sslclientcert" =>"/etc/pki/entitlement/0123456789012345678.pem",
49 | "metadata_expire" =>86400},
50 | "my-local-repo-b" =>
51 | { "name" =>"My Local Repo B",
52 | "baseurl" =>"https://mirror.example.com/b/content/os_ver",
53 | "enabled" =>1,
54 | "gpgcheck" =>0,
55 | "sslverify" =>0,
56 | "metadata_expire" =>86400}},
57 | File.join(data_file_path("yum"), "second.repo") =>
58 | { "my-local-repo-c" =>
59 | { "name" =>"My Local Repo c",
60 | "baseurl" =>"https://mirror.example.com/c/content/os_ver",
61 | "enabled" =>0,
62 | "cost" =>100,
63 | "gpgcheck" =>1,
64 | "gpgkey" =>"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-my-local-server",
65 | "sslverify" =>0,
66 | "metadata_expire" =>1}},})
67 | end
68 |
69 | context ".updates_available?" do
70 | it "check updates for a specific package" do
71 | expect(LinuxAdmin::Common).to receive(:run).once.with("yum check-update", :params => {nil => ["abc"]})
72 | .and_return(double(:exit_status => 100))
73 | expect(described_class.updates_available?("abc")).to be_truthy
74 | end
75 |
76 | it "updates are available" do
77 | allow(LinuxAdmin::Common).to receive_messages(:run => double(:exit_status => 100))
78 | expect(described_class.updates_available?).to be_truthy
79 | end
80 |
81 | it "updates not available" do
82 | allow(LinuxAdmin::Common).to receive_messages(:run => double(:exit_status => 0))
83 | expect(described_class.updates_available?).to be_falsey
84 | end
85 |
86 | it "other exit code" do
87 | allow(LinuxAdmin::Common).to receive_messages(:run => double(:exit_status => 255, :error => 'test'))
88 | expect { described_class.updates_available? }.to raise_error(RuntimeError)
89 | end
90 |
91 | it "other error" do
92 | allow(LinuxAdmin::Common).to receive(:run).and_raise(RuntimeError)
93 | expect { described_class.updates_available? }.to raise_error(RuntimeError)
94 | end
95 | end
96 |
97 | context ".update" do
98 | it "no arguments" do
99 | expect(LinuxAdmin::Common).to receive(:run!).once.with("yum -y update", :params => nil)
100 | .and_return(AwesomeSpawn::CommandResult.new("", "", "", 0))
101 | described_class.update
102 | end
103 |
104 | it "with arguments" do
105 | expect(LinuxAdmin::Common).to receive(:run!).once.with("yum -y update", :params => {nil => ["1 2", "3"]})
106 | .and_return(AwesomeSpawn::CommandResult.new("", "", "", 0))
107 | described_class.update("1 2", "3")
108 | end
109 |
110 | it "with bad arguments" do
111 | error = AwesomeSpawn::CommandResult.new("", "Loaded plugins: product-id\nNo Packages marked for Update\n", "Blah blah ...\nNo Match for argument: \n", 0)
112 | expect(LinuxAdmin::Common).to receive(:run!).once
113 | .with("yum -y update", :params => {nil => [""]}).and_return(error)
114 | expect { described_class.update("") }.to raise_error(AwesomeSpawn::CommandResultError)
115 | end
116 | end
117 |
118 | context ".version_available" do
119 | it "no packages" do
120 | expect { described_class.version_available }.to raise_error(ArgumentError)
121 | end
122 |
123 | it "with one package" do
124 | expect(LinuxAdmin::Common).to receive(:run!).once
125 | .with("repoquery --qf=\"%{name} %{version}\"", :params => {nil => ["subscription-manager"]})
126 | .and_return(double(:output => sample_output("yum/output_repoquery_single")))
127 | expect(described_class.version_available("subscription-manager")).to eq({"subscription-manager" => "1.1.23.1"})
128 | end
129 |
130 | it "with multiple packages" do
131 | expect(LinuxAdmin::Common).to receive(:run!).once
132 | .with("repoquery --qf=\"%{name} %{version}\"", :params => {nil => ["curl", "subscription-manager", "wget"]})
133 | .and_return(double(:output => sample_output("yum/output_repoquery_multiple")))
134 | expect(described_class.version_available("curl", "subscription-manager", "wget")).to eq({
135 | "curl" => "7.19.7",
136 | "subscription-manager" => "1.1.23.1",
137 | "wget" => "1.12"
138 | })
139 | end
140 | end
141 |
142 | context ".repo_list" do
143 | it "with no arguments" do
144 | expect(LinuxAdmin::Common).to receive(:run!).with("yum repolist", :params => {nil => "enabled"})
145 | .and_return(double(:output => sample_output("yum/output_repo_list")))
146 | expect(described_class.repo_list).to eq(["rhel-6-server-rpms", "rhel-ha-for-rhel-6-server-rpms", "rhel-lb-for-rhel-6-server-rpms"])
147 | end
148 |
149 | it "with argument" do
150 | expect(LinuxAdmin::Common).to receive(:run!).with("yum repolist", :params => {nil => "enabled"})
151 | .and_return(double(:output => sample_output("yum/output_repo_list")))
152 | expect(described_class.repo_list("enabled")).to eq(["rhel-6-server-rpms", "rhel-ha-for-rhel-6-server-rpms", "rhel-lb-for-rhel-6-server-rpms"])
153 | end
154 | end
155 | end
156 |
--------------------------------------------------------------------------------
/lib/linux_admin/network_interface/network_interface_rh.rb:
--------------------------------------------------------------------------------
1 | require 'ipaddr'
2 | require 'pathname'
3 |
4 | module LinuxAdmin
5 | class NetworkInterfaceRH < NetworkInterface
6 | IFACE_DIR = "/etc/sysconfig/network-scripts"
7 |
8 | # @return [Hash] Key value mappings in the interface file
9 | attr_reader :interface_config
10 |
11 | # @param interface [String] Name of the network interface to manage
12 | def initialize(interface)
13 | @interface_file = self.class.path_to_interface_config_file(interface)
14 | super
15 | parse_conf
16 | end
17 |
18 | # Parses the interface configuration file into the @interface_config hash
19 | def parse_conf
20 | @interface_config = {}
21 |
22 | if @interface_file.file?
23 | File.foreach(@interface_file) do |line|
24 | next if line =~ /^\s*#/
25 |
26 | key, value = line.split('=').collect(&:strip)
27 | @interface_config[key] = value
28 | end
29 | end
30 |
31 | @interface_config["NM_CONTROLLED"] = "no"
32 | end
33 |
34 | # Set the IPv4 address for this interface
35 | #
36 | # @param address [String]
37 | # @raise ArgumentError if the address is not formatted properly
38 | def address=(address)
39 | validate_ip(address)
40 | @interface_config["BOOTPROTO"] = "static"
41 | @interface_config["IPADDR"] = address
42 | end
43 |
44 | # Set the IPv6 address for this interface
45 | #
46 | # @param address [String] IPv6 address including the prefix length (i.e. '::1/127')
47 | # @raise ArgumentError if the address is not formatted properly
48 | def address6=(address)
49 | validate_ip(address)
50 | @interface_config['IPV6INIT'] = 'yes'
51 | @interface_config['DHCPV6C'] = 'no'
52 | @interface_config['IPV6ADDR'] = address
53 | end
54 |
55 | # Set the IPv4 gateway address for this interface
56 | #
57 | # @param address [String]
58 | # @raise ArgumentError if the address is not formatted properly
59 | def gateway=(address)
60 | validate_ip(address)
61 | @interface_config["GATEWAY"] = address
62 | end
63 |
64 | # Set the IPv6 gateway address for this interface
65 | #
66 | # @param address [String] IPv6 address optionally including the prefix length
67 | # @raise ArgumentError if the address is not formatted properly
68 | def gateway6=(address)
69 | validate_ip(address)
70 | @interface_config['IPV6_DEFAULTGW'] = address
71 | end
72 |
73 | # Set the IPv4 sub-net mask for this interface
74 | #
75 | # @param mask [String]
76 | # @raise ArgumentError if the mask is not formatted properly
77 | def netmask=(mask)
78 | validate_ip(mask)
79 | @interface_config["NETMASK"] = mask
80 | end
81 |
82 | # Sets one or both DNS servers for this network interface
83 | #
84 | # @param servers [Array] The DNS servers
85 | def dns=(*servers)
86 | server1, server2 = servers.flatten
87 | @interface_config["DNS1"] = server1
88 | @interface_config["DNS2"] = server2 if server2
89 | end
90 |
91 | # Sets the search domain list for this network interface
92 | #
93 | # @param domains [Array] the list of search domains
94 | def search_order=(*domains)
95 | @interface_config["DOMAIN"] = "\"#{domains.flatten.join(' ')}\""
96 | end
97 |
98 | # Set up the interface to use DHCP
99 | # Removes any previously set static IPv4 networking information
100 | def enable_dhcp
101 | @interface_config["BOOTPROTO"] = "dhcp"
102 | @interface_config.delete("IPADDR")
103 | @interface_config.delete("NETMASK")
104 | @interface_config.delete("GATEWAY")
105 | @interface_config.delete("PREFIX")
106 | @interface_config.delete("DNS1")
107 | @interface_config.delete("DNS2")
108 | @interface_config.delete("DOMAIN")
109 | end
110 |
111 | # Set up the interface to use DHCPv6
112 | # Removes any previously set static IPv6 networking information
113 | def enable_dhcp6
114 | @interface_config['IPV6INIT'] = 'yes'
115 | @interface_config['DHCPV6C'] = 'yes'
116 | @interface_config.delete('IPV6ADDR')
117 | @interface_config.delete('IPV6_DEFAULTGW')
118 | @interface_config.delete("DNS1")
119 | @interface_config.delete("DNS2")
120 | @interface_config.delete("DOMAIN")
121 | end
122 |
123 | # Applies the given static network configuration to the interface
124 | #
125 | # @param ip [String] IPv4 address
126 | # @param mask [String] subnet mask
127 | # @param gw [String] gateway address
128 | # @param dns [Array] list of dns servers
129 | # @param search [Array] list of search domains
130 | # @return [Boolean] true on success, false otherwise
131 | # @raise ArgumentError if an IP is not formatted properly
132 | def apply_static(ip, mask, gw, dns, search = nil)
133 | self.address = ip
134 | self.netmask = mask
135 | self.gateway = gw
136 | self.dns = dns
137 | self.search_order = search if search
138 | save
139 | end
140 |
141 | # Applies the given static IPv6 network configuration to the interface
142 | #
143 | # @param ip [String] IPv6 address
144 | # @param prefix [Number] prefix length for IPv6 address
145 | # @param gw [String] gateway address
146 | # @param dns [Array] list of dns servers
147 | # @param search [Array] list of search domains
148 | # @return [Boolean] true on success, false otherwise
149 | # @raise ArgumentError if an IP is not formatted properly or interface does not start
150 | def apply_static6(ip, prefix, gw, dns, search = nil)
151 | self.address6 = "#{ip}/#{prefix}"
152 | self.gateway6 = gw
153 | self.dns = dns
154 | self.search_order = search if search
155 | save
156 | end
157 |
158 | # Writes the contents of @interface_config to @interface_file as `key`=`value` pairs
159 | # and resets the interface
160 | #
161 | # @return [Boolean] true if the interface was successfully brought up with the
162 | # new configuration, false otherwise
163 | def save
164 | old_contents = @interface_file.file? ? File.read(@interface_file) : ""
165 |
166 | stop_success = stop
167 | # Stop twice because when configure both ipv4 and ipv6 as dhcp, ipv6 dhcp client will
168 | # exit and leave a /var/run/dhclient6-eth0.pid file. Then stop (ifdown eth0) will try
169 | # to kill this exited process so it returns 1. In the second call, this `.pid' file
170 | # has been deleted and ifdown returns 0.
171 | # See: https://bugzilla.redhat.com/show_bug.cgi?id=1472396
172 | stop_success = stop unless stop_success
173 | return false unless stop_success
174 |
175 | File.write(@interface_file, @interface_config.delete_blanks.collect { |k, v| "#{k}=#{v}" }.join("\n"))
176 |
177 | unless start
178 | File.write(@interface_file, old_contents)
179 | start
180 | return false
181 | end
182 |
183 | reload
184 | end
185 |
186 | def self.path_to_interface_config_file(interface)
187 | Pathname.new(IFACE_DIR).join("ifcfg-#{interface}")
188 | end
189 |
190 | private
191 |
192 | # Validate that the given address is formatted correctly
193 | #
194 | # @param ip [String]
195 | # @raise ArgumentError if the address is not correctly formatted
196 | def validate_ip(ip)
197 | IPAddr.new(ip)
198 | rescue ArgumentError
199 | raise ArgumentError, "#{ip} is not a valid IPv4 or IPv6 address"
200 | end
201 | end
202 | end
203 |
--------------------------------------------------------------------------------
/lib/linux_admin/network_interface.rb:
--------------------------------------------------------------------------------
1 | require 'ipaddr'
2 |
3 | module LinuxAdmin
4 | class NetworkInterface
5 | # Cached class instance variable for what distro we are running on
6 | @dist_class = nil
7 |
8 | # Gets the subclass specific to the local Linux distro
9 | #
10 | # @param clear_cache [Boolean] Determines if the cached value will be reevaluated
11 | # @return [Class] The proper class to be used
12 | def self.dist_class(clear_cache = false)
13 | @dist_class = nil if clear_cache
14 | @dist_class ||= begin
15 | if [Distros.rhel, Distros.fedora].include?(Distros.local)
16 | NetworkInterfaceRH
17 | else
18 | NetworkInterfaceGeneric
19 | end
20 | end
21 | end
22 |
23 | # Creates an instance of the correct NetworkInterface subclass for the local distro
24 | def self.new(*args)
25 | self == LinuxAdmin::NetworkInterface ? dist_class.new(*args) : super
26 | end
27 |
28 | # @return [String] the interface for networking operations
29 | attr_reader :interface
30 |
31 | # @param interface [String] Name of the network interface to manage
32 | def initialize(interface)
33 | @interface = interface
34 | reload
35 | end
36 |
37 | # Gathers current network information for this interface
38 | #
39 | # @return [Boolean] true if network information was gathered successfully
40 | def reload
41 | @network_conf = {}
42 | begin
43 | ip_output = ip_show
44 | rescue NetworkInterfaceError
45 | return false
46 | end
47 |
48 | parse_ip4(ip_output)
49 | parse_ip6(ip_output, :global)
50 | parse_ip6(ip_output, :link)
51 |
52 | @network_conf[:mac] = parse_ip_output(ip_output, %r{link/ether}, 1)
53 |
54 | [4, 6].each do |version|
55 | @network_conf["gateway#{version}".to_sym] = parse_ip_output(ip_route(version), /^default/, 2)
56 | end
57 | true
58 | end
59 |
60 | # Retrieve the IPv4 address assigned to the interface
61 | #
62 | # @return [String] IPv4 address for the managed interface
63 | def address
64 | @network_conf[:address]
65 | end
66 |
67 | # Retrieve the IPv6 address assigned to the interface
68 | #
69 | # @return [String] IPv6 address for the managed interface
70 | # @raise [ArgumentError] if the given scope is not `:global` or `:link`
71 | def address6(scope = :global)
72 | case scope
73 | when :global
74 | @network_conf[:address6_global]
75 | when :link
76 | @network_conf[:address6_link]
77 | else
78 | raise ArgumentError, "Unrecognized address scope #{scope}"
79 | end
80 | end
81 |
82 | # Retrieve the MAC address associated with the interface
83 | #
84 | # @return [String] the MAC address
85 | def mac_address
86 | @network_conf[:mac]
87 | end
88 |
89 | # Retrieve the IPv4 sub-net mask assigned to the interface
90 | #
91 | # @return [String] IPv4 netmask
92 | def netmask
93 | @network_conf[:mask] ||= IPAddr.new('255.255.255.255').mask(prefix).to_s if prefix
94 | end
95 |
96 | # Retrieve the IPv6 sub-net mask assigned to the interface
97 | #
98 | # @return [String] IPv6 netmask
99 | # @raise [ArgumentError] if the given scope is not `:global` or `:link`
100 | def netmask6(scope = :global)
101 | if [:global, :link].include?(scope)
102 | @network_conf["mask6_#{scope}".to_sym] ||= IPAddr.new('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff').mask(prefix6(scope)).to_s if prefix6(scope)
103 | else
104 | raise ArgumentError, "Unrecognized address scope #{scope}"
105 | end
106 | end
107 |
108 | # Retrieve the IPv4 sub-net prefix length assigned to the interface
109 | #
110 | # @return [Numeric] IPv4 prefix length
111 | def prefix
112 | @network_conf[:prefix]
113 | end
114 |
115 | # Retrieve the IPv6 sub-net prefix length assigned to the interface
116 | #
117 | # @return [Numeric] IPv6 prefix length
118 | def prefix6(scope = :global)
119 | if [:global, :link].include?(scope)
120 | @network_conf["prefix6_#{scope}".to_sym]
121 | else
122 | raise ArgumentError, "Unrecognized address scope #{scope}"
123 | end
124 | end
125 |
126 | # Retrieve the IPv4 default gateway associated with the interface
127 | #
128 | # @return [String] IPv4 gateway address
129 | def gateway
130 | @network_conf[:gateway4]
131 | end
132 |
133 | # Retrieve the IPv6 default gateway associated with the interface
134 | #
135 | # @return [String] IPv6 gateway address
136 | def gateway6
137 | @network_conf[:gateway6]
138 | end
139 |
140 | # Brings up the network interface
141 | #
142 | # @return [Boolean] whether the command succeeded or not
143 | def start
144 | Common.run(Common.cmd("ifup"), :params => [@interface]).success?
145 | end
146 |
147 | # Brings down the network interface
148 | #
149 | # @return [Boolean] whether the command succeeded or not
150 | def stop
151 | Common.run(Common.cmd("ifdown"), :params => [@interface]).success?
152 | end
153 |
154 | private
155 |
156 | # Parses the output of `ip addr show`
157 | #
158 | # @param output [String] The command output
159 | # @param regex [Regexp] Regular expression to match the desired output line
160 | # @param col [Fixnum] The whitespace delimited column to be returned
161 | # @return [String] The parsed data
162 | def parse_ip_output(output, regex, col)
163 | the_line = output.split("\n").detect { |l| l =~ regex }
164 | the_line.nil? ? nil : the_line.strip.split(' ')[col]
165 | end
166 |
167 | # Runs the command `ip addr show `
168 | #
169 | # @return [String] The command output
170 | # @raise [NetworkInterfaceError] if the command fails
171 | def ip_show
172 | Common.run!(Common.cmd("ip"), :params => ["addr", "show", @interface]).output
173 | rescue AwesomeSpawn::CommandResultError => e
174 | raise NetworkInterfaceError.new(e.message, e.result)
175 | end
176 |
177 | # Runs the command `ip -[4/6] route` and returns the output
178 | #
179 | # @param version [Fixnum] Version of IP protocol (4 or 6)
180 | # @return [String] The command output
181 | # @raise [NetworkInterfaceError] if the command fails
182 | def ip_route(version)
183 | Common.run!(Common.cmd("ip"), :params => ["-#{version}", 'route']).output
184 | rescue AwesomeSpawn::CommandResultError => e
185 | raise NetworkInterfaceError.new(e.message, e.result)
186 | end
187 |
188 | # Parses the IPv4 information from the output of `ip addr show `
189 | #
190 | # @param ip_output [String] The command output
191 | def parse_ip4(ip_output)
192 | cidr_ip = parse_ip_output(ip_output, /inet /, 1)
193 | return unless cidr_ip
194 |
195 | parts = cidr_ip.split('/')
196 | @network_conf[:address] = parts[0]
197 | @network_conf[:prefix] = parts[1].to_i
198 | end
199 |
200 | # Parses the IPv6 information from the output of `ip addr show `
201 | #
202 | # @param ip_output [String] The command output
203 | # @param scope [Symbol] The IPv6 scope (either `:global` or `:local`)
204 | def parse_ip6(ip_output, scope)
205 | cidr_ip = parse_ip_output(ip_output, /inet6 .* scope #{scope}/, 1)
206 | return unless cidr_ip
207 |
208 | parts = cidr_ip.split('/')
209 | @network_conf["address6_#{scope}".to_sym] = parts[0]
210 | @network_conf["prefix6_#{scope}".to_sym] = parts[1].to_i
211 | end
212 | end
213 | end
214 |
215 | Dir.glob(File.join(File.dirname(__FILE__), "network_interface", "*.rb")).each { |f| require f }
216 |
--------------------------------------------------------------------------------
/spec/subscription_manager_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::SubscriptionManager do
2 | context "#registered?" do
3 | it "system with subscription-manager commands" do
4 | expect(LinuxAdmin::Common).to receive(:run).once.with("subscription-manager identity")
5 | .and_return(double(:exit_status => 0))
6 | expect(described_class.new.registered?).to be_truthy
7 | end
8 |
9 | it "system without subscription-manager commands" do
10 | expect(LinuxAdmin::Common).to receive(:run).once.with("subscription-manager identity")
11 | .and_return(double(:exit_status => 255))
12 | expect(described_class.new.registered?).to be_falsey
13 | end
14 | end
15 |
16 | it "#refresh" do
17 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager refresh", {})
18 | described_class.new.refresh
19 | end
20 |
21 | context "#register" do
22 | it "no username" do
23 | expect { described_class.new.register }.to raise_error(ArgumentError)
24 | end
25 |
26 | context "with username and password" do
27 | let(:base_options) { {:username => "SomeUser@SomeDomain.org",
28 | :password => "SomePass",
29 | :environment => "Library",
30 | :org => "IT",
31 | :proxy_address => "1.2.3.4",
32 | :proxy_username => "ProxyUser",
33 | :proxy_password => "ProxyPass",
34 | }
35 | }
36 | let(:run_params) { {:params=>{"--username="=>"SomeUser@SomeDomain.org", "--password="=>"SomePass", "--proxy="=>"1.2.3.4", "--proxyuser="=>"ProxyUser", "--proxypassword="=>"ProxyPass", "--environment="=>"Library"}} }
37 |
38 | it "with server_url" do
39 | run_params.store_path(:params, "--org=", "IT")
40 | base_options.store_path(:server_url, "https://server.url")
41 |
42 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager register", run_params)
43 | expect(LinuxAdmin::Rpm).to receive(:upgrade).with("http://server.url/pub/katello-ca-consumer-latest.noarch.rpm")
44 |
45 | described_class.new.register(base_options)
46 | end
47 |
48 | it "without server_url" do
49 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager register", run_params)
50 | expect_any_instance_of(described_class).not_to receive(:install_server_certificate)
51 |
52 | described_class.new.register(base_options)
53 | end
54 | end
55 | end
56 |
57 | context "#subscribe" do
58 | it "with pools" do
59 | expect(LinuxAdmin::Common).to receive(:run!).once
60 | .with("subscription-manager attach", :params => [["--pool", 123], ["--pool", 456]])
61 | described_class.new.subscribe({:pools => [123, 456]})
62 | end
63 |
64 | it "without pools" do
65 | expect(LinuxAdmin::Common).to receive(:run!).once
66 | .with("subscription-manager attach", :params => {"--auto" => nil})
67 | described_class.new.subscribe({})
68 | end
69 | end
70 |
71 | context "#subscribed_products" do
72 | it "subscribed" do
73 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager list --installed", {})
74 | .and_return(double(:output => sample_output("subscription_manager/output_list_installed_subscribed")))
75 | expect(described_class.new.subscribed_products).to eq(["69", "167"])
76 | end
77 |
78 | it "not subscribed" do
79 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager list --installed", {})
80 | .and_return(double(:output => sample_output("subscription_manager/output_list_installed_not_subscribed")))
81 | expect(described_class.new.subscribed_products).to eq(["167"])
82 | end
83 | end
84 |
85 | it "#available_subscriptions" do
86 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager list --all --available", {})
87 | .and_return(double(:output => sample_output("subscription_manager/output_list_all_available")))
88 | expect(described_class.new.available_subscriptions).to eq({
89 | "82c042fca983889b10178893f29b06e3" => {
90 | :subscription_name => "Example Subscription",
91 | :sku => "SER0123",
92 | :pool_id => "82c042fca983889b10178893f29b06e3",
93 | :quantity => "1690",
94 | :service_level => "None",
95 | :service_type => "None",
96 | :multi_entitlement => "No",
97 | :ends => Date.parse("2022-01-01"),
98 | :system_type => "Physical",
99 | },
100 | "4f738052ec866192c775c62f408ab868" => {
101 | :subscription_name => "My Private Subscription",
102 | :sku => "SER9876",
103 | :pool_id => "4f738052ec866192c775c62f408ab868",
104 | :quantity => "Unlimited",
105 | :service_level => "None",
106 | :service_type => "None",
107 | :multi_entitlement => "No",
108 | :ends => Date.parse("2013-06-04"),
109 | :system_type => "Virtual",
110 | },
111 | "3d81297f352305b9a3521981029d7d83" => {
112 | :subscription_name => "Shared Subscription - With other characters, (2 sockets) (Up to 1 guest)",
113 | :sku => "RH0123456",
114 | :pool_id => "3d81297f352305b9a3521981029d7d83",
115 | :quantity => "1",
116 | :service_level => "Self-support",
117 | :service_type => "L1-L3",
118 | :multi_entitlement => "No",
119 | :ends => Date.parse("2013-05-15"),
120 | :system_type => "Virtual",
121 | },
122 | "87cefe63b67984d5c7e401d833d2f87f" => {
123 | :subscription_name => "Example Subscription, Premium (up to 2 sockets) 3 year",
124 | :sku => "MCT0123A9",
125 | :pool_id => "87cefe63b67984d5c7e401d833d2f87f",
126 | :quantity => "1",
127 | :service_level => "Premium",
128 | :service_type => "L1-L3",
129 | :multi_entitlement => "No",
130 | :ends => Date.parse("2013-07-05"),
131 | :system_type => "Virtual",
132 | },
133 | })
134 | end
135 |
136 | context "#organizations" do
137 | it "with valid credentials" do
138 | run_options = ["subscription-manager orgs", {:params=>{"--username="=>"SomeUser", "--password="=>"SomePass", "--proxy="=>"1.2.3.4", "--proxyuser="=>"ProxyUser", "--proxypassword="=>"ProxyPass"}}]
139 |
140 | expect(LinuxAdmin::Rpm).to receive(:upgrade).with("http://192.168.1.1/pub/katello-ca-consumer-latest.noarch.rpm")
141 | expect(LinuxAdmin::Common).to receive(:run!).once.with(*run_options)
142 | .and_return(double(:output => sample_output("subscription_manager/output_orgs")))
143 |
144 | expect(described_class.new.organizations({:username=>"SomeUser", :password=>"SomePass", :proxy_address=>"1.2.3.4", :proxy_username=>"ProxyUser", :proxy_password=>"ProxyPass", :server_url=>"192.168.1.1"})).to eq({"SomeOrg"=>{:name=>"SomeOrg", :key=>"1234567"}})
145 | end
146 |
147 | it "with invalid credentials" do
148 | run_options = ["subscription-manager orgs", {:params=>{"--username="=>"BadUser", "--password="=>"BadPass"}}]
149 | error = AwesomeSpawn::CommandResultError.new("",
150 | double(
151 | :error => "Invalid username or password. To create a login, please visit https://www.redhat.com/wapps/ugc/register.html",
152 | :exit_status => 255
153 | )
154 | )
155 | expect(AwesomeSpawn).to receive(:run!).once.with(*run_options).and_raise(error)
156 | expect { described_class.new.organizations({:username=>"BadUser", :password=>"BadPass"}) }.to raise_error(LinuxAdmin::CredentialError)
157 | end
158 | end
159 |
160 | it "#enable_repo" do
161 | expect(LinuxAdmin::Common).to receive(:run!).once
162 | .with("subscription-manager repos", :params => {"--enable=" => "abc"})
163 |
164 | described_class.new.enable_repo("abc")
165 | end
166 |
167 | it "#disable_repo" do
168 | expect(LinuxAdmin::Common).to receive(:run!).once
169 | .with("subscription-manager repos", :params => {"--disable=" => "abc"})
170 |
171 | described_class.new.disable_repo("abc")
172 | end
173 |
174 | it "#all_repos" do
175 | expected = [
176 | {
177 | :repo_id => "some-repo-source-rpms",
178 | :repo_name => "Some Repo (Source RPMs)",
179 | :repo_url => "https://my.host.example.com/repos/some-repo/source/rpms",
180 | :enabled => true
181 | },
182 | {
183 | :repo_id => "some-repo-rpms",
184 | :repo_name => "Some Repo",
185 | :repo_url => "https://my.host.example.com/repos/some-repo/rpms",
186 | :enabled => true
187 | },
188 | {
189 | :repo_id => "some-repo-2-beta-rpms",
190 | :repo_name => "Some Repo (Beta RPMs)",
191 | :repo_url => "https://my.host.example.com/repos/some-repo-2/beta/rpms",
192 | :enabled => false
193 | }
194 | ]
195 |
196 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager repos", {})
197 | .and_return(double(:output => sample_output("subscription_manager/output_repos")))
198 |
199 | expect(described_class.new.all_repos).to eq(expected)
200 | end
201 |
202 | it "#enabled_repos" do
203 | expected = ["some-repo-source-rpms", "some-repo-rpms"]
204 |
205 | expect(LinuxAdmin::Common).to receive(:run!).once.with("subscription-manager repos", {})
206 | .and_return(double(:output => sample_output("subscription_manager/output_repos")))
207 |
208 | expect(described_class.new.enabled_repos).to eq(expected)
209 | end
210 | end
211 |
--------------------------------------------------------------------------------
/spec/disk_spec.rb:
--------------------------------------------------------------------------------
1 | describe LinuxAdmin::Disk do
2 | describe "#local" do
3 | it "returns local disks" do
4 | expect(Dir).to receive(:glob).with(['/dev/[vhs]d[a-z]', '/dev/xvd[a-z]']).
5 | and_return(['/dev/hda', '/dev/sda'])
6 | disks = LinuxAdmin::Disk.local
7 | paths = disks.collect { |disk| disk.path }
8 | expect(paths).to include('/dev/hda')
9 | expect(paths).to include('/dev/sda')
10 | end
11 | end
12 |
13 | describe "#size" do
14 | it "uses fdisk" do
15 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
16 | expect(LinuxAdmin::Common).to receive(:run!)
17 | .with(LinuxAdmin::Common.cmd(:fdisk),
18 | :params => {"-l" => nil})
19 | .and_return(double(:output => ""))
20 | disk.size
21 | end
22 |
23 | it "returns disk size" do
24 | fdisk = < '/dev/hda'
39 | allow(LinuxAdmin::Common).to receive(:run!).and_return(double(:output => fdisk))
40 | expect(disk.size).to eq(500_107_862_016)
41 | end
42 | end
43 |
44 | describe "#partitions" do
45 | it "uses parted" do
46 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
47 | expect(LinuxAdmin::Common).to receive(:run)
48 | .with(LinuxAdmin::Common.cmd(:parted),
49 | :params => {nil => %w(--script /dev/hda print)}).and_return(double(:output => ""))
50 | disk.partitions
51 | end
52 |
53 | it "returns [] on non-zero parted rc" do
54 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
55 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:output => "", :exit_status => 1))
56 | expect(disk.partitions).to eq([])
57 | end
58 |
59 | it "sets partitons" do
60 | partitions = < partitions))
74 |
75 | expect(disk.partitions[0].id).to eq(1)
76 | expect(disk.partitions[0].disk).to eq(disk)
77 | expect(disk.partitions[0].size).to eq(86_436_216_832.0) # 80.5.gigabytes
78 | expect(disk.partitions[0].start_sector).to eq(1_320_157_184) # 1259.megabytes
79 | expect(disk.partitions[0].end_sector).to eq(87_832_081_203.2) # 81.8.gigabytes
80 | expect(disk.partitions[0].partition_type).to eq('primary')
81 | expect(disk.partitions[0].fs_type).to eq('ntfs')
82 | expect(disk.partitions[1].id).to eq(2)
83 | expect(disk.partitions[1].disk).to eq(disk)
84 | expect(disk.partitions[1].size).to eq(86_436_216_832.0) # 80.5.gigabytes
85 | expect(disk.partitions[1].start_sector).to eq(87_832_081_203.2) # 81.8.gigabytes
86 | expect(disk.partitions[1].end_sector).to eq(173_946_175_488) # 162.gigabytes
87 | expect(disk.partitions[1].partition_type).to eq('primary')
88 | expect(disk.partitions[1].fs_type).to eq('ext4')
89 | expect(disk.partitions[2].id).to eq(3)
90 | expect(disk.partitions[2].disk).to eq(disk)
91 | expect(disk.partitions[2].size).to eq(1_126_170_624) # 1074.megabytes
92 | expect(disk.partitions[2].start_sector).to eq(173_946_175_488) # 162.gigabytes
93 | expect(disk.partitions[2].end_sector).to eq(175_019_917_312) # 163.gigabytes
94 | expect(disk.partitions[2].partition_type).to eq('logical')
95 | expect(disk.partitions[2].fs_type).to eq('linux-swap(v1)')
96 | end
97 | end
98 |
99 | describe "#create_partitions" do
100 | before(:each) do
101 | @disk = LinuxAdmin::Disk.new(:path => '/dev/hda')
102 | end
103 |
104 | it "dispatches to create_partition" do
105 | expect(@disk).to receive(:create_partition).with("primary", "0%", "50%")
106 | @disk.create_partitions "primary", :start => "0%", :end => "50%"
107 | end
108 |
109 | context "multiple partitions specified" do
110 | it "calls create_partition for each partition" do
111 | expect(@disk).to receive(:create_partition).with("primary", "0%", "49%")
112 | expect(@disk).to receive(:create_partition).with("primary", "50%", "100%")
113 | @disk.create_partitions("primary", {:start => "0%", :end => "49%"},
114 | {:start => "50%", :end => "100%"})
115 | end
116 |
117 | context "partitions overlap" do
118 | it "raises argument error" do
119 | expect{
120 | @disk.create_partitions("primary", {:start => "0%", :end => "50%"},
121 | {:start => "49%", :end => "100%"})
122 | }.to raise_error(ArgumentError)
123 | end
124 | end
125 | end
126 | end
127 |
128 | describe "#create_partition" do
129 | before(:each) do
130 | # test disk w/ existing partition
131 | @disk = LinuxAdmin::Disk.new :path => '/dev/hda'
132 | @disk.instance_variable_set(:@partitions,
133 | [LinuxAdmin::Partition.new(:id => 1,
134 | :end_sector => 1024)])
135 | allow(@disk).to receive_messages(:has_partition_table? => true)
136 | end
137 |
138 | it "uses parted" do
139 | params = ['--script', '/dev/hda', 'mkpart', '-a', 'opt', 'primary', 1024, 2048]
140 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:parted), :params => {nil => params})
141 | @disk.create_partition 'primary', 1024
142 | end
143 |
144 | it "accepts start/end params" do
145 | params = ['--script', '/dev/hda', 'mkpart', '-a', 'opt', 'primary', "0%", "50%"]
146 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:parted), :params => {nil => params})
147 | @disk.create_partition 'primary', "0%", "50%"
148 | end
149 |
150 | context "missing params" do
151 | it "raises ArgumentError" do
152 | expect{
153 | @disk.create_partition 'primary'
154 | }.to raise_error(ArgumentError)
155 |
156 | expect{
157 | @disk.create_partition 'primary', '0%', '50%', 100
158 | }.to raise_error(ArgumentError)
159 | end
160 | end
161 |
162 | it "returns partition" do
163 | expect(LinuxAdmin::Common).to receive(:run!) # stub out call to parted
164 | partition = @disk.create_partition 'primary', 1024
165 | expect(partition).to be_an_instance_of(LinuxAdmin::Partition)
166 | end
167 |
168 | it "increments partition id" do
169 | expect(LinuxAdmin::Common).to receive(:run!) # stub out call to parted
170 | partition = @disk.create_partition 'primary', 1024
171 | expect(partition.id).to eq(2)
172 | end
173 |
174 | it "sets partition start to first unused sector on disk" do
175 | expect(LinuxAdmin::Common).to receive(:run!) # stub out call to parted
176 | partition = @disk.create_partition 'primary', 1024
177 | expect(partition.start_sector).to eq(1024)
178 | end
179 |
180 | it "stores new partition locally" do
181 | expect(LinuxAdmin::Common).to receive(:run!) # stub out call to parted
182 | expect {
183 | @disk.create_partition 'primary', 1024
184 | }.to change{@disk.partitions.size}.by(1)
185 | end
186 |
187 | it "creates partition table if missing" do
188 | allow(@disk).to receive_messages(:has_partition_table? => false)
189 | expect(@disk).to receive(:create_partition_table)
190 | expect(LinuxAdmin::Common).to receive(:run!)
191 | @disk.create_partition 'primary', 1024
192 | end
193 | end
194 |
195 | describe "#has_partition_table?" do
196 | it "positive case" do
197 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
198 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:output => "", :exit_status => 0))
199 | expect(disk).to have_partition_table
200 | end
201 |
202 | it "negative case" do
203 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
204 | output = "\e[?1034h\r\rError: /dev/sdb: unrecognised disk label\n"
205 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:output => output, :exit_status => 1))
206 | expect(disk).not_to have_partition_table
207 | end
208 | end
209 |
210 | it "#create_partition_table" do
211 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
212 | options = {:params => {nil => %w(--script /dev/hda mklabel msdos)}}
213 | expect(LinuxAdmin::Common).to receive(:run!).with(LinuxAdmin::Common.cmd(:parted), options)
214 | disk.create_partition_table
215 | end
216 |
217 | describe "#clear!" do
218 | it "clears partitions" do
219 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
220 | expect(LinuxAdmin::Common).to receive(:run).and_return(double(:output => "")) # stub out call to cmds
221 | disk.partitions << LinuxAdmin::Partition.new
222 |
223 | expect(LinuxAdmin::Common).to receive(:run!)
224 | disk.clear!
225 | expect(disk.partitions).to be_empty
226 | end
227 |
228 | it "uses dd to clear partition table" do
229 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
230 | expect(LinuxAdmin::Common).to receive(:run!)
231 | .with(LinuxAdmin::Common.cmd(:dd),
232 | :params => {'if=' => '/dev/zero', 'of=' => '/dev/hda',
233 | 'bs=' => 512, 'count=' => 1})
234 | disk.clear!
235 | end
236 |
237 | it "returns self" do
238 | disk = LinuxAdmin::Disk.new :path => '/dev/hda'
239 | allow(LinuxAdmin::Common).to receive(:run!) # stub out call to dd
240 | expect(disk.clear!).to eq(disk)
241 | end
242 | end
243 |
244 | end
245 |
--------------------------------------------------------------------------------