Skip to content

Commit 6c6dca8

Browse files
authored
[v1.0] Implement functions through library and custom resource (#5)
* move nfs-package name to a node attribute * implement functions through library and custom resource * revert back logging level for tests * lint * better handle convergance with existing resources * fix test to expect exact number of calls
1 parent 4546ad0 commit 6c6dca8

File tree

10 files changed

+366
-82
lines changed

10 files changed

+366
-82
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Cookbook to mount Elastic Filesystem endpoints in Amazon Web Services.
44

5-
Presently, you just configure global defaults (presently the recommended values from Amazon) and individual mounts through node attributes. In the future, a LWRP may be added.
5+
You just configure global defaults (presently the recommended values from Amazon) and individual mounts through node attributes. You also have the option of using the `mount_efs` resource within your own recipes.
66

77
## Requirements
88

@@ -103,6 +103,10 @@ Configure any desired mounts under `node['efs']['mounts']` and include `efs` in
103103
}
104104
```
105105

106+
### mount_efs
107+
108+
This cookbook is implemented with a custom resource so you can use `mount_efs` in your cookbook recipes as well with the same available attributes as the `node['efs']['mounts']` structure.
109+
106110
## Contributing
107111

108112
1. Fork the repository on Github

attributes/default.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
55
default['efs']['retrans'] = 2
66
default['efs']['mounts'] = {}
77
default['efs']['remove_unspecified_mounts'] = false
8+
9+
default['efs']['nfs-package'] = case node['platform']
10+
when 'ubuntu', 'debian'
11+
'nfs-common'
12+
when 'redhat'
13+
'nfs-utils'
14+
end

libraries/efs.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
module EFS
2+
# Class representing an Elastic Filesystem share/mount
3+
class Mount
4+
attr_reader :mount
5+
attr_reader :fsid
6+
attr_reader :region
7+
attr_accessor :rsize
8+
attr_accessor :wsize
9+
attr_accessor :behavior
10+
attr_accessor :timeout
11+
attr_accessor :retrans
12+
attr_accessor :extra_options
13+
14+
def initialize(mount, fsid, region)
15+
raise 'Non-nil region required' if region.nil?
16+
17+
@mount = mount
18+
@fsid = fsid
19+
@region = region
20+
@extra_options = ''
21+
end
22+
23+
def options_from_line(line) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
24+
_linedevice, linemount, _fstype, options, _freq, _pass = line.split(/\s+/)
25+
raise "Mount does not match #{mount}" if linemount != mount
26+
options.split(',').each do |pair|
27+
k, v = pair.split('=')
28+
case k
29+
when 'rsize'
30+
@rsize = v
31+
when 'wsize'
32+
@wsize = v
33+
when 'soft', 'hard'
34+
@behavior = k
35+
when 'timeo'
36+
@timeout = v
37+
when 'retrans'
38+
@retrans = v
39+
when 'nfsvers'
40+
next
41+
else
42+
@extra_options += ',' unless @extra_options.empty?
43+
@extra_options += k
44+
@extra_options += "=#{v}" if v
45+
end
46+
end
47+
end
48+
49+
def load_existing_options
50+
options_from_line(existing_line) if existing_line
51+
end
52+
53+
def standard_options
54+
"nfsvers=4.1,rsize=#{@rsize},wsize=#{@wsize},#{@behavior},timeo=#{@timeout},retrans=#{@retrans}"
55+
end
56+
57+
def options
58+
@extra_options.empty? ? standard_options : standard_options + ',' + @extra_options
59+
end
60+
61+
def device
62+
"#{@fsid}.efs.#{@region}.amazonaws.com:/"
63+
end
64+
65+
def exists?
66+
!existing_line.nil?
67+
end
68+
69+
def mounted?
70+
!mtab_lines.empty?
71+
end
72+
73+
def fstab_lines
74+
file_include('/etc/fstab')
75+
end
76+
77+
def mtab_lines
78+
file_include('/etc/mtab')
79+
end
80+
81+
def file_include(file)
82+
lines = []
83+
IO.readlines(file).each do |line|
84+
lines << line.gsub(/\s+/, ' ').chomp if /\s#{mount}\s/ =~ line
85+
end
86+
lines
87+
end
88+
89+
def existing_line
90+
(existing_lines - other_mounts).first
91+
end
92+
93+
def existing_lines
94+
return fstab_lines unless fstab_lines.empty?
95+
mtab_lines
96+
end
97+
98+
# Returns fstab lines with the same mount point but different devices
99+
def other_mounts
100+
others = []
101+
fstab_lines.each do |line|
102+
localdevice, localmount, _fstype, _options, _freq, _pass = line.split(/\s+/)
103+
others << line if localmount == mount && localdevice != device
104+
end
105+
others
106+
end
107+
108+
def self.remove_unspecified_mounts(mounts, run_context) # rubocop:disable Metrics/AbcSize
109+
IO.readlines('/etc/fstab').each do |line|
110+
device, mount, _fstype, _options, _freq, _pass = line.split(/\s+/)
111+
next unless mount && device.match(/fs-[a-f0-9]{8}\.efs\.[a-z]{2}-[a-z]+-\d\.amazonaws\.com/)
112+
next if mounts.key?(mount) && mounts[mount]['device'] == device
113+
114+
m = Chef::Resource::Mount.new(mount, run_context)
115+
m.device = device
116+
m.action = :nothing
117+
m.run_action(:disable)
118+
m.run_action(:umount) unless mounts.key?(mount)
119+
end
120+
end
121+
end
122+
end

libraries/remove_unspecified_mounts.rb

Lines changed: 0 additions & 13 deletions
This file was deleted.

metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
license 'MIT'
55
description 'Installs/Configures Amazon Elastic Filesystem mounts'
66
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
7-
version '0.1.4'
7+
version '1.0.0'
88

99
source_url 'https://github.com/mattlqx/cookbook-efs' if respond_to?(:source_url)
1010
issues_url 'https://github.com/mattlqx/cookbook-efs/issues' if respond_to?(:issues_url)

recipes/default.rb

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,19 @@
55
# Copyright 2017, Matt Kulka
66
#
77

8-
case node['platform']
9-
when 'ubuntu', 'debian'
10-
package 'nfs-common'
11-
when 'redhat'
12-
package 'nfs-utils'
13-
end
8+
package node['efs']['nfs-package']
149

1510
node['efs']['mounts'].each do |mount_point, attribs|
16-
attribs = attribs.to_hash
17-
attribs['rsize'] ||= node['efs']['rsize']
18-
attribs['wsize'] ||= node['efs']['wsize']
19-
attribs['behavior'] ||= node['efs']['behavior']
20-
attribs['timeout'] ||= node['efs']['timeout']
21-
attribs['retrans'] ||= node['efs']['retrans']
22-
attribs['options'] ||= "nfsvers=4.1,rsize=#{attribs['rsize']},wsize=#{attribs['wsize']}," \
23-
"#{attribs['behavior']},timeo=#{attribs['timeout']},retrans=#{attribs['retrans']}"
24-
25-
begin
26-
region = attribs.fetch('region', nil) || node['ec2']['placement_availability_zone'][0..-2]
27-
rescue NoMethodError
28-
raise "No region specified for mount #{mount_point} and this doesn\'t appear to be an EC2 instance."
29-
end
30-
31-
raise "Mount #{mount_point} has an invalid fsid." unless attribs['fsid'] =~ /fs-[a-f0-9]{8}/
32-
33-
directory mount_point
34-
35-
mount mount_point do
36-
fstype 'nfs4'
37-
device attribs['fsid'] + '.efs.' + region + '.amazonaws.com:/'
11+
mount_efs mount_point do
12+
fsid attribs['fsid']
3813
options attribs['options']
39-
action %i[enable mount]
14+
action :mount
4015
end
4116
end
4217

4318
ruby_block 'remove unspecified efs mounts' do
4419
only_if { node['efs']['remove_unspecified_mounts'] }
4520
block do
46-
remove_unspecified_mounts(node['efs']['mounts'], run_context)
21+
EFS::Mount.remove_unspecified_mounts(node['efs']['mounts'], run_context)
4722
end
4823
end

resources/mount_efs.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
resource_name :mount_efs
2+
3+
default_action :mount
4+
5+
property :mount_point, String, name_property: true, desired_state: false
6+
property :fsid, String, desired_state: false, regex: [/fs-[a-f0-9]{8}/], required: true
7+
property :region, String, desired_state: false
8+
property :rsize, Integer, default: node['efs']['rsize'], desired_state: false,
9+
coerce: proc { |m| m.is_a?(String) ? m.to_i : m }
10+
property :wsize, Integer, default: node['efs']['wsize'], desired_state: false,
11+
coerce: proc { |m| m.is_a?(String) ? m.to_i : m }
12+
property :behavior, String, default: node['efs']['behavior'], desired_state: false
13+
property :timeout, Integer, default: node['efs']['timeout'], desired_state: false,
14+
coerce: proc { |m| m.is_a?(String) ? m.to_i : m }
15+
property :retrans, Integer, default: node['efs']['retrans'], desired_state: false,
16+
coerce: proc { |m| m.is_a?(String) ? m.to_i : m }
17+
property :options, String, desired_state: false
18+
19+
load_current_value do |new_resource|
20+
@mount = EFS::Mount.new(new_resource.mount_point, new_resource.fsid, region_value)
21+
if @mount.exists?
22+
@mount.load_existing_options
23+
else
24+
current_value_does_not_exist!
25+
end
26+
27+
region region_value
28+
rsize @mount.rsize unless @mount.rsize.nil?
29+
wsize @mount.wsize unless @mount.wsize.nil?
30+
behavior @mount.behavior unless @mount.behavior.nil?
31+
timeout @mount.timeout unless @mount.timeout.nil?
32+
retrans @mount.retrans unless @mount.retrans.nil?
33+
options @mount.extra_options unless @mount.extra_options.nil?
34+
end
35+
36+
def region_value
37+
begin
38+
region ||= node['ec2']['placement_availability_zone'][0..-2]
39+
rescue NoMethodError
40+
raise "No region specified for mount #{mount_point} and this doesn\'t appear to be an EC2 instance."
41+
end
42+
region
43+
end
44+
45+
def new_object # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
46+
mount = EFS::Mount.new(mount_point, fsid, region_value)
47+
mount.rsize = rsize unless rsize.nil?
48+
mount.wsize = wsize unless wsize.nil?
49+
mount.behavior = behavior unless behavior.nil?
50+
mount.timeout = timeout unless timeout.nil?
51+
mount.retrans = retrans unless retrans.nil?
52+
mount.extra_options = options unless options.nil?
53+
mount
54+
end
55+
56+
action :mount do
57+
mount = new_object
58+
59+
converge_if_changed :mount_point do
60+
directory new_resource.mount_point
61+
end
62+
63+
converge_if_changed do
64+
mount.other_mounts.each do |line|
65+
localdevice, localmount, _fstype, _options, _freq, _pass = line.split(/\s+/)
66+
mount "#{localmount} #{localdevice} unmount" do
67+
device localdevice
68+
mount_point localmount
69+
action %i[disable umount]
70+
end
71+
end
72+
73+
mount new_resource.mount_point do
74+
fstype 'nfs4'
75+
device mount.device
76+
options mount.options
77+
action %i[enable mount]
78+
end
79+
end
80+
end
81+
82+
action :umount do
83+
mount = new_object
84+
85+
mount new_resource.mount_point do
86+
fstype 'nfs4'
87+
device mount.device
88+
options mount.options
89+
action %i[disable umount]
90+
end
91+
end

0 commit comments

Comments
 (0)