Class: Navo::Suite

Inherits:
Object
  • Object
show all
Defined in:
lib/navo/suite.rb

Overview

A test suite.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, config:) ⇒ Suite

Returns a new instance of Suite.



11
12
13
14
# File 'lib/navo/suite.rb', line 11

def initialize(name:, config:)
  @name = name
  @config = config
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



9
10
11
# File 'lib/navo/suite.rb', line 9

def name
  @name
end

Instance Method Details

#[](key) ⇒ Object



20
21
22
# File 'lib/navo/suite.rb', line 20

def [](key)
  @config[key.to_s]
end

#busser_binObject



213
214
215
# File 'lib/navo/suite.rb', line 213

def busser_bin
  File.join(busser_directory, %w[gems bin busser])
end

#busser_directoryObject



209
210
211
# File 'lib/navo/suite.rb', line 209

def busser_directory
  '/tmp/busser'
end

#busser_envObject



217
218
219
220
221
222
223
224
# File 'lib/navo/suite.rb', line 217

def busser_env
  %W[
    BUSSER_ROOT=#{busser_directory}
    GEM_HOME=#{File.join(busser_directory, 'gems')}
    GEM_PATH=#{File.join(busser_directory, 'gems')}
    GEM_CACHE=#{File.join(busser_directory, %w[gems cache])}
  ]
end

#chef_config_dirObject



28
29
30
# File 'lib/navo/suite.rb', line 28

def chef_config_dir
  '/etc/chef'
end

#chef_run_dirObject



32
33
34
# File 'lib/navo/suite.rb', line 32

def chef_run_dir
  '/var/chef'
end

#chef_solo_configObject



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/navo/suite.rb', line 65

def chef_solo_config
  return "  node_name \#{name.inspect}\n  environment \#{@config['chef']['environment'].inspect}\n  file_cache_path \#{File.join(chef_run_dir, 'cache').inspect}\n  file_backup_path \#{File.join(chef_run_dir, 'backup').inspect}\n  cookbook_path \#{File.join(chef_run_dir, 'cookbooks').inspect}\n  data_bag_path \#{File.join(chef_run_dir, 'data_bags').inspect}\n  environment_path \#{File.join(chef_run_dir, 'environments').inspect}\n  role_path \#{File.join(chef_run_dir, 'roles').inspect}\n  encrypted_data_bag_secret \#{File.join(chef_config_dir, 'encrypted_data_bag_secret').inspect}\n  CONF\nend\n"

#containerDocker::Container

Returns the Docker::Container used by this test suite, starting it if necessary.

Returns:

  • (Docker::Container)


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/navo/suite.rb', line 171

def container
  @container ||=
    begin
      if state['container']
        begin
          container = Docker::Container.get(state['container'])
        rescue Docker::Error::NotFoundError
          # Continue creating the container since it doesn't exist
        end
      end

      if !container
        container = Docker::Container.create(
          'Image' => image.id,
          'OpenStdin' => true,
          'StdinOnce' => true,
          'HostConfig' => {
            'Privileged' => @config['docker']['privileged'],
            'Binds' => @config['docker']['volumes'],
          },
        )

        state['container'] = container.id
        state.save
      end

      container.start
    end
end

#convergeObject



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/navo/suite.rb', line 90

def converge
  create

  sandbox.update_chef_config

  _, _, status = exec(%W[
    /opt/chef/embedded/bin/chef-solo
    --config=#{File.join(chef_config_dir, 'solo.rb')}
    --json-attributes=#{File.join(chef_config_dir, 'first-boot.json')}
    --force-formatter
  ])

  state['converged'] = status == 0
  state.save
  state['converged']
end

#copy(from:, to:) ⇒ Object

Copy file/directory from host to container.



37
38
39
# File 'lib/navo/suite.rb', line 37

def copy(from:, to:)
  system("docker cp #{from} #{container.id}:#{to}")
end

#createObject



86
87
88
# File 'lib/navo/suite.rb', line 86

def create
  container
end

#destroyObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/navo/suite.rb', line 121

def destroy
  if @config['docker']['stop-command']
    exec(@config['docker']['stop-command'])
    container.wait(@config['docker'].fetch('stop-timeout', 10))
  else
    container.stop
  end

  container.remove(force: true)

  state['converged'] = false
  state['container'] = nil
  state.save
end

#exec(args) ⇒ Object

Execte a command on the container.



47
48
49
50
51
# File 'lib/navo/suite.rb', line 47

def exec(args)
  container.exec(args) do |_stream, chunk|
    STDOUT.print chunk
  end
end

#exec!(args) ⇒ Object

Execute a command on the container, raising an error if it exists unsuccessfully.

Raises:

  • (Error::ExecutionError)


55
56
57
58
59
# File 'lib/navo/suite.rb', line 55

def exec!(args)
  out, err, status = exec(args)
  raise Error::ExecutionError, "STDOUT:#{out}\nSTDERR:#{err}" unless status == 0
  [out, err, status]
end

#fetch(key, *args) ⇒ Object



24
25
26
# File 'lib/navo/suite.rb', line 24

def fetch(key, *args)
  @config.fetch(key.to_s, *args)
end

#imageDocker::Image

Returns the Docker::Image used by this test suite, building it if necessary.

Returns:

  • (Docker::Image)


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/navo/suite.rb', line 140

def image
 @image ||=
   begin
     state['images'] ||= {}

     # Build directory is wherever the Dockerfile is located
     dockerfile = File.expand_path(@config['docker']['dockerfile'], repo_root)
     build_dir = File.dirname(dockerfile)

     dockerfile_hash = Digest::SHA256.new.hexdigest(File.read(dockerfile))
     image_id = state['images'][dockerfile_hash]

     if image_id && Docker::Image.exist?(image_id)
       Docker::Image.get(image_id)
     else
       Docker::Image.build_from_dir(build_dir) do |chunk|
         if (log = JSON.parse(chunk)) && log.has_key?('stream')
           STDOUT.print log['stream']
         end
       end.tap do |image|
         state['images'][dockerfile_hash] = image.id
         state.save
       end
     end
   end
end

#loginObject



61
62
63
# File 'lib/navo/suite.rb', line 61

def 
  Kernel.exec('docker', 'exec', '-it', container.id, *@config['docker']['shell-command'])
end

#node_attributesObject



79
80
81
82
83
84
# File 'lib/navo/suite.rb', line 79

def node_attributes
  suite_config = @config['suites'][name]
  @config['chef']['attributes']
    .merge(suite_config.fetch('attributes', {}))
    .merge(run_list: suite_config['run-list'])
end

#repo_rootObject



16
17
18
# File 'lib/navo/suite.rb', line 16

def repo_root
  @config.repo_root
end

#sandboxObject



201
202
203
# File 'lib/navo/suite.rb', line 201

def sandbox
  @sandbox ||= Sandbox.new(suite: self)
end

#stateObject



226
227
228
# File 'lib/navo/suite.rb', line 226

def state
  @state ||= SuiteState.new(suite: self).tap(&:load)
end

#storage_directoryObject



205
206
207
# File 'lib/navo/suite.rb', line 205

def storage_directory
  File.join(repo_root, '.navo', 'suites', name)
end

#testObject



116
117
118
119
# File 'lib/navo/suite.rb', line 116

def test
  return false unless converge
  verify
end

#verifyObject



107
108
109
110
111
112
113
114
# File 'lib/navo/suite.rb', line 107

def verify
  create

  sandbox.update_test_config

  _, _, status = exec(['/usr/bin/env'] + busser_env + %W[#{busser_bin} test])
  status == 0
end

#write(file:, content:) ⇒ Object

Write contents to a file on the container.



42
43
44
# File 'lib/navo/suite.rb', line 42

def write(file:, content:)
  container.exec(%w[bash -c] + ["cat > #{file}"], stdin: StringIO.new(content))
end