vanagon 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +175 -0
  4. data/bin/build +33 -0
  5. data/bin/devkit +22 -0
  6. data/bin/repo +26 -0
  7. data/bin/ship +15 -0
  8. data/lib/vanagon.rb +8 -0
  9. data/lib/vanagon/common.rb +2 -0
  10. data/lib/vanagon/common/pathname.rb +87 -0
  11. data/lib/vanagon/common/user.rb +25 -0
  12. data/lib/vanagon/component.rb +157 -0
  13. data/lib/vanagon/component/dsl.rb +307 -0
  14. data/lib/vanagon/component/source.rb +66 -0
  15. data/lib/vanagon/component/source/git.rb +60 -0
  16. data/lib/vanagon/component/source/http.rb +158 -0
  17. data/lib/vanagon/driver.rb +112 -0
  18. data/lib/vanagon/engine/base.rb +82 -0
  19. data/lib/vanagon/engine/docker.rb +40 -0
  20. data/lib/vanagon/engine/local.rb +40 -0
  21. data/lib/vanagon/engine/pooler.rb +85 -0
  22. data/lib/vanagon/errors.rb +28 -0
  23. data/lib/vanagon/extensions/string.rb +11 -0
  24. data/lib/vanagon/optparse.rb +62 -0
  25. data/lib/vanagon/platform.rb +245 -0
  26. data/lib/vanagon/platform/deb.rb +71 -0
  27. data/lib/vanagon/platform/dsl.rb +293 -0
  28. data/lib/vanagon/platform/osx.rb +100 -0
  29. data/lib/vanagon/platform/rpm.rb +76 -0
  30. data/lib/vanagon/platform/rpm/wrl.rb +39 -0
  31. data/lib/vanagon/platform/solaris_10.rb +182 -0
  32. data/lib/vanagon/platform/solaris_11.rb +138 -0
  33. data/lib/vanagon/platform/swix.rb +35 -0
  34. data/lib/vanagon/project.rb +251 -0
  35. data/lib/vanagon/project/dsl.rb +218 -0
  36. data/lib/vanagon/utilities.rb +299 -0
  37. data/spec/fixures/component/invalid-test-fixture.json +3 -0
  38. data/spec/fixures/component/mcollective.service +1 -0
  39. data/spec/fixures/component/test-fixture.json +4 -0
  40. data/spec/lib/vanagon/common/pathname_spec.rb +103 -0
  41. data/spec/lib/vanagon/common/user_spec.rb +36 -0
  42. data/spec/lib/vanagon/component/dsl_spec.rb +443 -0
  43. data/spec/lib/vanagon/component/source/git_spec.rb +19 -0
  44. data/spec/lib/vanagon/component/source/http_spec.rb +43 -0
  45. data/spec/lib/vanagon/component/source_spec.rb +99 -0
  46. data/spec/lib/vanagon/component_spec.rb +22 -0
  47. data/spec/lib/vanagon/engine/base_spec.rb +40 -0
  48. data/spec/lib/vanagon/engine/docker_spec.rb +40 -0
  49. data/spec/lib/vanagon/engine/pooler_spec.rb +54 -0
  50. data/spec/lib/vanagon/platform/deb_spec.rb +60 -0
  51. data/spec/lib/vanagon/platform/dsl_spec.rb +128 -0
  52. data/spec/lib/vanagon/platform/rpm_spec.rb +41 -0
  53. data/spec/lib/vanagon/platform/solaris_11_spec.rb +44 -0
  54. data/spec/lib/vanagon/platform_spec.rb +53 -0
  55. data/spec/lib/vanagon/project/dsl_spec.rb +203 -0
  56. data/spec/lib/vanagon/project_spec.rb +44 -0
  57. data/spec/lib/vanagon/utilities_spec.rb +140 -0
  58. data/templates/Makefile.erb +116 -0
  59. data/templates/deb/changelog.erb +5 -0
  60. data/templates/deb/conffiles.erb +3 -0
  61. data/templates/deb/control.erb +21 -0
  62. data/templates/deb/dirs.erb +3 -0
  63. data/templates/deb/docs.erb +1 -0
  64. data/templates/deb/install.erb +3 -0
  65. data/templates/deb/postinst.erb +46 -0
  66. data/templates/deb/postrm.erb +15 -0
  67. data/templates/deb/prerm.erb +17 -0
  68. data/templates/deb/rules.erb +25 -0
  69. data/templates/osx/postinstall.erb +24 -0
  70. data/templates/osx/preinstall.erb +19 -0
  71. data/templates/osx/project-installer.xml.erb +19 -0
  72. data/templates/rpm/project.spec.erb +217 -0
  73. data/templates/solaris/10/depend.erb +3 -0
  74. data/templates/solaris/10/pkginfo.erb +13 -0
  75. data/templates/solaris/10/postinstall.erb +37 -0
  76. data/templates/solaris/10/preinstall.erb +7 -0
  77. data/templates/solaris/10/preremove.erb +6 -0
  78. data/templates/solaris/10/proto.erb +5 -0
  79. data/templates/solaris/11/p5m.erb +73 -0
  80. metadata +172 -0
@@ -0,0 +1,299 @@
1
+ require 'vanagon/errors'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'json'
5
+ require 'digest'
6
+ require 'erb'
7
+ require 'timeout'
8
+ require 'vanagon/extensions/string'
9
+
10
+ class Vanagon
11
+ module Utilities
12
+
13
+ # Utility to get the md5 sum of a file
14
+ #
15
+ # @param file [String] file to md5sum
16
+ # @return [String] md5sum of the given file
17
+ def get_md5sum(file)
18
+ Digest::MD5.file(file).hexdigest.to_s
19
+ end
20
+
21
+ # Generic file summing utility
22
+ #
23
+ # @param file [String] file to sum
24
+ # @param type [String] type of sum to provide
25
+ # @return [String] sum of the given file
26
+ # @raise [RuntimeError] raises an exception if the given sum type is not supported
27
+ def get_sum(file, type)
28
+ case type.downcase
29
+ when 'md5'
30
+ Digest::MD5.file(file).hexdigest.to_s
31
+ when 'sha512'
32
+ Digest::SHA512.file(file).hexdigest.to_s
33
+ else
34
+ fail "Don't know how to produce a sum of type: '#{type}' for '#{file}'."
35
+ end
36
+ end
37
+
38
+ # Simple wrapper around Net::HTTP. Will make a request of the given type to
39
+ # the given url and return the body as parsed by JSON.
40
+ #
41
+ # @param url [String] The url to make the request against (needs to be parsable by URI
42
+ # @param type [String] One of the supported request types (currently 'get', 'post', 'delete')
43
+ # @param payload [String] The request body data payload used for POST and PUT
44
+ # @param header [Hash] Send additional information in the HTTP request header
45
+ # @return [Hash] The response body is parsed by JSON and returned
46
+ # @raise [RuntimeError, Vanagon::Error] an exception is raised if the
47
+ # action is not supported, or if there is a problem with the http request,
48
+ # or if the response is not JSON
49
+ def http_request(url, type, payload = {}.to_json, header = nil)
50
+ uri = URI.parse(url)
51
+ http = Net::HTTP.new(uri.host, uri.port)
52
+
53
+ case type.downcase
54
+ when "get"
55
+ request = Net::HTTP::Get.new(uri.request_uri)
56
+ when "post"
57
+ request = Net::HTTP::Post.new(uri.request_uri)
58
+ request.body = payload
59
+ when "put"
60
+ request = Net::HTTP::Put.new(uri.request_uri)
61
+ request.body = payload
62
+ when "delete"
63
+ request = Net::HTTP::Delete.new(uri.request_uri)
64
+ else
65
+ fail "ACTION: #{type} not supported by #http_request method. Maybe you should add it?"
66
+ end
67
+
68
+ # Add any headers to the request
69
+ if header && header.is_a?(Hash)
70
+ header.each do |key, val|
71
+ request[key] = val
72
+ end
73
+ end
74
+
75
+ response = http.request(request)
76
+
77
+ JSON.parse(response.body)
78
+
79
+ rescue Errno::ETIMEDOUT, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
80
+ EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
81
+ Net::ProtocolError => e
82
+ raise Vanagon::Error.wrap(e, "Problem reaching #{url}. Is #{uri.host} down?")
83
+ rescue JSON::ParserError => e
84
+ raise Vanagon::Error.wrap(e, "#{uri.host} handed us a response that doesn't look like JSON.")
85
+ end
86
+
87
+ # Similar to rake's sh, the passed command will be executed and an
88
+ # exception will be raised on command failure. However, in contrast to
89
+ # rake's sh, this method returns the output of the command instead of a
90
+ # boolean.
91
+ #
92
+ # @param command [String] The command to be executed
93
+ # @return [String] The standard output of the executed command
94
+ # @raise [Vanagon::Error] If the command fails an exception is raised
95
+ def ex(command)
96
+ ret = `#{command}`
97
+ unless $?.success?
98
+ raise Vanagon::Error, "'#{command}' did not succeed"
99
+ end
100
+ ret
101
+ end
102
+
103
+ # Similar to the command-line utility which, the method will search the
104
+ # PATH for the passed command and return the full path to the command if it
105
+ # exists.
106
+ #
107
+ # @param command [String] Command to search for on PATH
108
+ # @param required [true, false] Whether or not to raise an exception if the command cannot be found
109
+ # @return [String, false] Returns either the full path to the command or false if the command cannot be found
110
+ # @raise [RuntimeError] If the command is required and cannot be found
111
+ def find_program_on_path(command, required = true)
112
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path_elem|
113
+ location = File.join(path_elem, command)
114
+ return location if FileTest.executable?(location)
115
+ end
116
+
117
+ if required
118
+ fail "Could not find '#{command}'. Please install (or ensure it is on $PATH), and try again."
119
+ else
120
+ return false
121
+ end
122
+ end
123
+
124
+ # Method to retry a ruby block and fail if the command does not succeed
125
+ # within the number of tries and timeout.
126
+ #
127
+ # @param tries [Integer] number of times to try calling the block
128
+ # @param timeout [Integer] number of seconds to run the block before timing out
129
+ # @return [true] If the block succeeds, true is returned
130
+ # @raise [Vanagon::Error] if the block fails after the retries are exhausted, an error is raised
131
+ def retry_with_timeout(tries = 5, timeout = 1, &blk)
132
+ tries.times do
133
+ Timeout::timeout(timeout) do
134
+ begin
135
+ blk.call
136
+ return true
137
+ rescue
138
+ warn 'An error was encountered evaluating block. Retrying..'
139
+ end
140
+ end
141
+ end
142
+
143
+ raise Vanagon::Error, "Block failed maximum of #{tries} tries. Exiting.."
144
+ end
145
+
146
+ # Simple wrapper around git command line executes the given commands and
147
+ # returns the results.
148
+ #
149
+ # @param commands [String] The commands to be run
150
+ # @return [String] The output of the command
151
+ def git(*commands)
152
+ git_bin = find_program_on_path('git')
153
+ %x(#{git_bin} #{commands.join(' ')})
154
+ end
155
+
156
+ # Determines if the given directory is a git repo or not
157
+ #
158
+ # @param directory [String] The directory to check
159
+ # @return [true, false] True if the directory is a git repo, false otherwise
160
+ def is_git_repo?(directory = Dir.pwd)
161
+ Dir.chdir(directory) do
162
+ git('rev-parse', '--git-dir', '> /dev/null 2>&1')
163
+ $?.success?
164
+ end
165
+ end
166
+
167
+ # Determines a version for the given directory based on the git describe
168
+ # for the repository
169
+ #
170
+ # @param directory [String] The directory to use in versioning
171
+ # @return [String] The version of the directory accoring to git describe
172
+ # @raise [RuntimeError] If the given directory is not a git repo
173
+ def git_version(directory = Dir.pwd)
174
+ if is_git_repo?(directory)
175
+ Dir.chdir(directory) do
176
+ version = git('describe', '--tags', '2> /dev/null').chomp
177
+ if version.empty?
178
+ warn "Directory '#{directory}' cannot be versioned by git. Maybe it hasn't been tagged yet?"
179
+ end
180
+ return version
181
+ end
182
+ else
183
+ fail "Directory '#{directory}' is not a git repo, cannot get a version"
184
+ end
185
+ end
186
+
187
+ # Sends the desired file/directory to the destination using rsync
188
+ #
189
+ # @param source [String] file or directory to send
190
+ # @param target [String] ssh host to send to (user@machine)
191
+ # @param dest [String] path on target to place the source
192
+ # @param extra_flags [Array] any additional flags to pass to rsync
193
+ # @param port [Integer] Port number for ssh (default 22)
194
+ # @return [String] output of rsync command
195
+ def rsync_to(source, target, dest, port = 22, extra_flags = ["--ignore-existing"])
196
+ rsync = find_program_on_path('rsync')
197
+ flags = "-rHlv --no-perms --no-owner --no-group"
198
+ unless extra_flags.empty?
199
+ flags << " " << extra_flags.join(" ")
200
+ end
201
+ ex("#{rsync} -e '#{ssh_command(port)}' #{flags} #{source} #{target}:#{dest}")
202
+ end
203
+
204
+ # Hacky wrapper to add on the correct flags for ssh to be used in ssh and rsync methods
205
+ #
206
+ # @param port [Integer] Port number for ssh (default 22)
207
+ # @return [String] start of ssh command, including flags for ssh keys
208
+ def ssh_command(port = 22)
209
+ ssh = find_program_on_path('ssh')
210
+ args = ENV['VANAGON_SSH_KEY'] ? " -i #{ENV['VANAGON_SSH_KEY']}" : ""
211
+ args << " -p #{port} "
212
+ args << " -o UserKnownHostsFile=/dev/null"
213
+ args << " -o StrictHostKeyChecking=no"
214
+ return ssh + args
215
+ end
216
+
217
+ # Retrieves the desired file/directory from the destination using rsync
218
+ #
219
+ # @param source [String] path on target to retrieve from
220
+ # @param target [String] ssh host to retrieve from (user@machine)
221
+ # @param dest [String] path on local host to place the source
222
+ # @param port [Integer] port number for ssh (default 22)
223
+ # @param extra_flags [Array] any additional flags to pass to rsync
224
+ # @return [String] output of rsync command
225
+ def rsync_from(source, target, dest, port = 22, extra_flags = [])
226
+ rsync = find_program_on_path('rsync')
227
+ flags = "-rHlv -O --no-perms --no-owner --no-group"
228
+ unless extra_flags.empty?
229
+ flags << " " << extra_flags.join(" ")
230
+ end
231
+ ex("#{rsync} -e '#{ssh_command(port)}' #{flags} #{target}:#{source} #{dest}")
232
+ end
233
+
234
+ # Runs the command on the given host via ssh call
235
+ #
236
+ # @param target [String] ssh host to run command on (user@machine)
237
+ # @param command [String] command to run on the target
238
+ # @param port [Integer] port number for ssh (default 22)
239
+ # @param return_command_output [Boolean] whether or not command output should be returned
240
+ # @return [true, String] Returns true if the command was successful or the
241
+ # output of the command if return_command_output is true
242
+ # @raise [RuntimeError] If there is no target given or the command fails an exception is raised
243
+ def remote_ssh_command(target, command, port = 22, return_command_output: false)
244
+ if target
245
+ puts "Executing '#{command}' on #{target}"
246
+ if return_command_output
247
+ ret = %x(#{ssh_command(port)} -T #{target} '#{command.gsub("'", "'\\\\''")}').chomp
248
+ if $?.success?
249
+ return ret
250
+ else
251
+ raise "Remote ssh command (#{command}) failed on '#{target}'."
252
+ end
253
+ else
254
+ Kernel.system("#{ssh_command(port)} -T #{target} '#{command.gsub("'", "'\\\\''")}'")
255
+ $?.success? or raise "Remote ssh command (#{command}) failed on '#{target}'."
256
+ end
257
+ else
258
+ fail "Need a target to ssh to. Received none."
259
+ end
260
+ end
261
+
262
+ # Runs the command on the local host
263
+ #
264
+ # @param command [String] command to run on the target
265
+ # @return [true] Returns true if the command was successful
266
+ # @raise [RuntimeError] If the command fails an exception is raised
267
+ def local_command(command, workdir)
268
+ puts "Executing '#{command}' locally in #{workdir}"
269
+ Kernel.system(command, :chdir => workdir)
270
+ $?.success? or raise "Local command (#{command}) failed."
271
+ end
272
+
273
+ # Helper method that takes a template file and runs it through ERB
274
+ #
275
+ # @param erbfile [String] template to be evaluated
276
+ # @param b [Binding] binding to evaluate the template under
277
+ # @return [String] the evaluated template
278
+ def erb_string(erbfile, b = binding)
279
+ template = File.read(erbfile)
280
+ message = ERB.new(template, nil, "-")
281
+ message.result(b)
282
+ end
283
+
284
+ # Helper method that takes a template and writes the evaluated contents to a file on disk
285
+ #
286
+ # @param erbfile [String]
287
+ # @param outfile [String]
288
+ # @param remove_orig [true, false]
289
+ # @param opts [Hash]
290
+ def erb_file(erbfile, outfile = nil, remove_orig = false, opts = { :binding => binding })
291
+ outfile ||= File.join(Dir.mktmpdir, File.basename(erbfile).sub(File.extname(erbfile), ""))
292
+ output = erb_string(erbfile, opts[:binding])
293
+ File.open(outfile, 'w') { |f| f.write output }
294
+ puts "Generated: #{outfile}"
295
+ FileUtils.rm_rf erbfile if remove_orig
296
+ outfile
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,3 @@
1
+ {
2
+ "thing": "stuff"
3
+ }
@@ -0,0 +1 @@
1
+ /opt/puppetlabs/puppet/bin/ruby -s mcollective -u root -a '/opt/puppetlabs/puppet/bin/mcollectived --config=/etc/puppetlabs/mcollective/server.cfg '
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "git@github.com:puppetlabs/puppet",
3
+ "ref": "3.7.3"
4
+ }
@@ -0,0 +1,103 @@
1
+ require 'vanagon/common/pathname'
2
+
3
+ describe "Vanagon::Common::Pathname" do
4
+ describe "#has_overrides?" do
5
+ it "is false for a pathname with just a path" do
6
+ dir = Vanagon::Common::Pathname.new("/a/b/c")
7
+ expect(dir.has_overrides?).to be(false)
8
+ end
9
+
10
+ it "is true if the pathname has more than a path set" do
11
+ dir = Vanagon::Common::Pathname.new("/a/b/c", mode: '0755')
12
+ expect(dir.has_overrides?).to be(true)
13
+ end
14
+ end
15
+
16
+ describe "#file" do
17
+ it 'creates a new Pathname instance, marked as a file' do
18
+ dir = Vanagon::Common::Pathname.file("/a/b/c/")
19
+ expect(dir.class).to eq(Vanagon::Common::Pathname)
20
+ expect(dir.configfile?).to eq(false)
21
+ end
22
+ end
23
+
24
+ describe "#configfile" do
25
+ it 'creates a new Pathname instance, marked as a configuration file' do
26
+ dir = Vanagon::Common::Pathname.configfile("/a/b/c/")
27
+ expect(dir.class).to eq(Vanagon::Common::Pathname)
28
+ expect(dir.configfile?).to eq(true)
29
+ end
30
+ end
31
+
32
+ describe "#initialize" do
33
+ it 'strips trailing slashes off of the path to normalize it' do
34
+ dir = Vanagon::Common::Pathname.new("/a/b/c/")
35
+ expect(dir.path).to eq("/a/b/c")
36
+ end
37
+
38
+ it 'removes extra / from the pathname to normalize it' do
39
+ dir = Vanagon::Common::Pathname.new("/a//b///c///")
40
+ expect(dir.path).to eq("/a/b/c")
41
+ end
42
+ end
43
+
44
+ describe "equality" do
45
+ it "is not equal if the paths differ" do
46
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c")
47
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c/d")
48
+ expect(dir1).not_to eq(dir2)
49
+ end
50
+
51
+ it "is not equal if there are different attributes set" do
52
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c")
53
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
54
+ expect(dir1).not_to eq(dir2)
55
+ end
56
+
57
+ it "is equal if there are the same attributes set to the same values" do
58
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
59
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
60
+ expect(dir1).to eq(dir2)
61
+ end
62
+
63
+ it "is equal if the paths are the same and the only attribute set" do
64
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c")
65
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c")
66
+ expect(dir1).to eq(dir2)
67
+ end
68
+ end
69
+
70
+ describe "#hash" do
71
+ it "has the same hash is the attributes are the same" do
72
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
73
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
74
+ expect(dir1.hash).to eq(dir2.hash)
75
+ end
76
+
77
+ it "has different hashes if any attribute is different" do
78
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123', owner: 'alice')
79
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123', owner: 'bob')
80
+ expect(dir1.hash).to_not eq(dir2.hash)
81
+ end
82
+ end
83
+
84
+ describe "uniqueness of pathnames" do
85
+ it "should only add 1 Pathname object with the same attributes to a set" do
86
+ set = Set.new
87
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
88
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
89
+ dir3 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123', owner: 'alice')
90
+ set << dir1 << dir2 << dir3
91
+ expect(set.size).to eq(2)
92
+ end
93
+
94
+ it "should reduce an array to unique elements successfully" do
95
+ dir1 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
96
+ dir2 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123')
97
+ dir3 = Vanagon::Common::Pathname.new("/a/b/c", mode: '0123', owner: 'alice')
98
+ arr = [ dir1, dir2, dir3 ]
99
+ expect(arr.size).to eq(3)
100
+ expect(arr.uniq.size).to eq(2)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,36 @@
1
+ require 'vanagon/common/user'
2
+
3
+ describe 'Vanagon::Common::User' do
4
+ describe 'initialize' do
5
+ it 'group defaults to the name of the user' do
6
+ user = Vanagon::Common::User.new('willamette')
7
+ expect(user.group).to eq('willamette')
8
+ end
9
+ end
10
+
11
+ describe 'equality' do
12
+ it 'is not equal if the names differ' do
13
+ user1 = Vanagon::Common::User.new('willamette')
14
+ user2 = Vanagon::Common::User.new('columbia')
15
+ expect(user1).not_to eq(user2)
16
+ end
17
+
18
+ it 'is not equal if there are different attributes set' do
19
+ user1 = Vanagon::Common::User.new('willamette', 'group1')
20
+ user2 = Vanagon::Common::User.new('willamette', 'group2')
21
+ expect(user1).not_to eq(user2)
22
+ end
23
+
24
+ it 'is equal if there are the same attributes set to the same values' do
25
+ user1 = Vanagon::Common::User.new('willamette', 'group')
26
+ user2 = Vanagon::Common::User.new('willamette', 'group')
27
+ expect(user1).to eq(user2)
28
+ end
29
+
30
+ it 'is equal if the name are the same and the only attribute set' do
31
+ user1 = Vanagon::Common::User.new('willamette')
32
+ user2 = Vanagon::Common::User.new('willamette')
33
+ expect(user1).to eq(user2)
34
+ end
35
+ end
36
+ end