Class: CookbookOmnifetch::StagingArea
- Inherits:
-
Object
- Object
- CookbookOmnifetch::StagingArea
- Defined in:
- lib/cookbook-omnifetch/staging_area.rb
Overview
A staging area in which the caller can stage files and publish them to a local directory.
When performing long operations such as installing or updating a cookbook from the web, StagingArea allows you to minimize the risk that a process running in parallel might retrieve an incomplete cookbook from the local cache before it is completely installed. (See #publish! for details.)
StagingArea allocates temporary directories on the local file system. It is the caller’s responsibility to use #discard! when it is done to remove those directories. The StagingArea.stage method handles directory cleanup for the staging area it creates before returning.
Class Method Summary collapse
-
.stage(target_path) {|staging_path| ... } ⇒ Object
Creates a staging area, calls a block to populate it, then publishes it.
Instance Method Summary collapse
-
#discard! ⇒ Object
Removes the staging area and its contents from the file system.
-
#empty? ⇒ Boolean
Returns true if the staging area is empty.
-
#match?(compare_path) ⇒ Boolean
Returns true if the staging area’s contents match those of a given path.
-
#path ⇒ Pathname
Path to the staging folder on the file system.
-
#publish!(install_path) ⇒ Object
Replaces
install_path
with the contents of the staging area. -
#unavailable? ⇒ Boolean
Returns true if the staging area is no longer available for use.
Class Method Details
.stage(target_path) {|staging_path| ... } ⇒ Object
Creates a staging area, calls a block to populate it, then publishes it.
stage creates a staging area and calls the provided block to populate it with files. If the staging area does not contain any changes for target_path
(see #match?), it cleans up the staging area without modifying target_path
. Otherwise, it publishes its contents to target_path
and deletes the staging area. As a safety measure, stage will not publish an empty staging area.
42 43 44 45 46 47 48 49 50 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 42 def self.stage(target_path) sa = new begin yield(sa.path) sa.publish!(target_path) unless sa.empty? || sa.match?(target_path) ensure sa.discard! end end |
Instance Method Details
#discard! ⇒ Object
Removes the staging area and its contents from the file system.
The staging area is no longer available once #discard! removes it from the file system. Future attempts to use it will raise CookbookOmnifetch::StagingAreaNotAvailable.
133 134 135 136 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 133 def discard! FileUtils.rm_rf(@stage_tmp) unless @stage_tmp.nil? @unavailable = true end |
#empty? ⇒ Boolean
Returns true if the staging area is empty.
A staging area is considered empty when it has no files or directories in its path or the staging directory does not exist.
71 72 73 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 71 def empty? !path.exist? || path.empty? end |
#match?(compare_path) ⇒ Boolean
Returns true if the staging area’s contents match those of a given path.
#match? compares the contents of the staging area with the contents of the compare_path
. It considers the staging area to match if it contains all of and nothing more than the files and directories present in compare_path
and the content of each file is the same as that of its corresponding file in compare_path
. #match? does not compare file metadata or the contents of special files.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 91 def match?(compare_path) raise StagingAreaNotAvailable if unavailable? target = Pathname(compare_path) return false unless target.exist? files = Dir.glob("**/*", File::FNM_DOTMATCH, base: path) target_files = Dir.glob("**/*", File::FNM_DOTMATCH, base: target) return false unless files.sort == target_files.sort files.each do |subpath| return false if files_different?(path, target, subpath) end true end |
#path ⇒ Pathname
Path to the staging folder on the file system.
114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 114 def path raise StagingAreaNotAvailable if unavailable? return @path unless @path.nil? # Dir.mktmpdir returns a directory with restrictive permissions that it # doesn't support modifying, so create a subdirectory under it with # regular permissions for staging. @stage_tmp = Dir.mktmpdir @path = Pathname.new(File.join(@stage_tmp, "staging")) FileUtils.mkdir(@path) @path end |
#publish!(install_path) ⇒ Object
Replaces install_path
with the contents of the staging area.
#publish! removes the target and copies the new content into place using two atomic file system operations. This eliminates much of the risk associated with updating the target in a multiprocess environment by ensuring that another process does not see a partially removed or populated directory at the target_path
while this operation is being performed.
Note that it is still possible for the #publish! to interrupt another process performing a long operation, such as creating a recursive copy of the target. In this situation, the other process may create a copy that consists of a combination of content from the old target directory and the newly staged files. The other process may also raise an exception should it try to access the target during a small window in the #publish! operation where the target directory does not exist, or tries to open a file that is no longer part of the target tree after #publish! completes. The other process can detect this situation by verifying that the content of its copy matches the content of target_path
after its copy is complete.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 164 def publish!(install_path) target = Pathname(install_path) cache_dir = target.parent cache_dir.mkpath Dir.mktmpdir("_STAGING_TMP_", cache_dir) do |tmpdir| newtmp = File.join(tmpdir, "new_cookbook") oldtmp = File.join(tmpdir, "old_cookbook") FileUtils.cp_r(path, newtmp) # We could achieve an atomic replace using symbolic links, if they are # supported on all platforms. File.rename(target, oldtmp) if target.exist? File.rename(newtmp, target) end end |
#unavailable? ⇒ Boolean
Returns true if the staging area is no longer available for use.
The staging area is no longer available once #discard! removes it from the file system.
58 59 60 |
# File 'lib/cookbook-omnifetch/staging_area.rb', line 58 def unavailable? !!@unavailable end |