buildpack-support 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/LICENSE +202 -0
  2. data/NOTICE +2 -0
  3. data/docs/cache.md +77 -0
  4. data/docs/component.md +1 -0
  5. data/docs/configuration.md +27 -0
  6. data/docs/logging.md +54 -0
  7. data/docs/other.md +1 -0
  8. data/docs/rake.md +1 -0
  9. data/docs/repository.md +116 -0
  10. data/docs/test.md +1 -0
  11. data/lib/buildpack_support.rb +18 -0
  12. data/lib/buildpack_support/base_buildpack.rb +166 -0
  13. data/lib/buildpack_support/buildpack_version.rb +124 -0
  14. data/lib/buildpack_support/cache.rb +24 -0
  15. data/lib/buildpack_support/cache/application_cache.rb +41 -0
  16. data/lib/buildpack_support/cache/cached_file.rb +103 -0
  17. data/lib/buildpack_support/cache/download_cache.rb +280 -0
  18. data/lib/buildpack_support/cache/inferred_network_failure.rb +26 -0
  19. data/lib/buildpack_support/cache/internet_availability.rb +64 -0
  20. data/lib/buildpack_support/component.rb +24 -0
  21. data/lib/buildpack_support/component/application.rb +76 -0
  22. data/lib/buildpack_support/component/base_component.rb +78 -0
  23. data/lib/buildpack_support/component/base_droplet.rb +96 -0
  24. data/lib/buildpack_support/component/downloads.rb +88 -0
  25. data/lib/buildpack_support/component/services.rb +84 -0
  26. data/lib/buildpack_support/component/versioned_dependency_component.rb +71 -0
  27. data/lib/buildpack_support/component/versioned_downloads.rb +57 -0
  28. data/lib/buildpack_support/component/with_timing.rb +40 -0
  29. data/lib/buildpack_support/configuration_utils.rb +58 -0
  30. data/lib/buildpack_support/constantize.rb +46 -0
  31. data/lib/buildpack_support/dash_case.rb +29 -0
  32. data/lib/buildpack_support/directory_finder.rb +45 -0
  33. data/lib/buildpack_support/filtering_pathname.rb +227 -0
  34. data/lib/buildpack_support/format_duration.rb +57 -0
  35. data/lib/buildpack_support/logging.rb +22 -0
  36. data/lib/buildpack_support/logging/delegating_logger.rb +48 -0
  37. data/lib/buildpack_support/logging/logger_factory.rb +148 -0
  38. data/lib/buildpack_support/qualify_path.rb +36 -0
  39. data/lib/buildpack_support/rake.rb +22 -0
  40. data/lib/buildpack_support/rake/buildpack_stage_task.rb +86 -0
  41. data/lib/buildpack_support/rake/cached_artifact_finder.rb +99 -0
  42. data/lib/buildpack_support/rake/check_api_doc_task.rb +70 -0
  43. data/lib/buildpack_support/rake/dependency_cache_task.rb +87 -0
  44. data/lib/buildpack_support/rake/disable_remote_downloads_task.rb +80 -0
  45. data/lib/buildpack_support/rake/package_task.rb +133 -0
  46. data/lib/buildpack_support/rake/package_zip_task.rb +80 -0
  47. data/lib/buildpack_support/rake/repository_configuration_finder.rb +66 -0
  48. data/lib/buildpack_support/rake/write_version_file_task.rb +82 -0
  49. data/lib/buildpack_support/repository.rb +24 -0
  50. data/lib/buildpack_support/repository/configured_item.rb +81 -0
  51. data/lib/buildpack_support/repository/repository_index.rb +98 -0
  52. data/lib/buildpack_support/repository/wildcard_version_resolver.rb +75 -0
  53. data/lib/buildpack_support/shell.rb +41 -0
  54. data/lib/buildpack_support/snake_case.rb +30 -0
  55. data/lib/buildpack_support/space_case.rb +29 -0
  56. data/lib/buildpack_support/test/application_helper.rb +41 -0
  57. data/lib/buildpack_support/test/base_component_helper.rb +59 -0
  58. data/lib/buildpack_support/test/base_droplet_helper.rb +36 -0
  59. data/lib/buildpack_support/test/console_helper.rb +57 -0
  60. data/lib/buildpack_support/test/environment_helper.rb +32 -0
  61. data/lib/buildpack_support/test/internet_availability_helper.rb +29 -0
  62. data/lib/buildpack_support/test/logging_helper.rb +50 -0
  63. data/lib/buildpack_support/test/scratch_helper.rb +32 -0
  64. data/lib/buildpack_support/test/versioned_dependency_component_helper.rb +32 -0
  65. data/lib/buildpack_support/test/with_load_path_helper.rb +27 -0
  66. data/lib/buildpack_support/to_b.rb +38 -0
  67. data/lib/buildpack_support/tokenized_version.rb +157 -0
  68. data/lib/buildpack_support/version.rb +23 -0
  69. data/spec/buildpack_support/base_buildpack_spec.rb +112 -0
  70. data/spec/buildpack_support/buildpack_version_spec.rb +122 -0
  71. data/spec/buildpack_support/cache/application_cache_spec.rb +56 -0
  72. data/spec/buildpack_support/cache/cached_file_spec.rb +94 -0
  73. data/spec/buildpack_support/cache/download_cache_spec.rb +293 -0
  74. data/spec/buildpack_support/cache/internet_availability_spec.rb +57 -0
  75. data/spec/buildpack_support/cache/yield_file_with_content.rb +30 -0
  76. data/spec/buildpack_support/component/application_spec.rb +81 -0
  77. data/spec/buildpack_support/component/base_component_spec.rb +81 -0
  78. data/spec/buildpack_support/component/base_droplet_spec.rb +72 -0
  79. data/spec/buildpack_support/component/downloads_spec.rb +63 -0
  80. data/spec/buildpack_support/component/services_spec.rb +80 -0
  81. data/spec/buildpack_support/component/versioned_dependency_component_spec.rb +58 -0
  82. data/spec/buildpack_support/component/versioned_downloads_spec.rb +58 -0
  83. data/spec/buildpack_support/component/with_timing_spec.rb +30 -0
  84. data/spec/buildpack_support/configuration_utils_spec.rb +39 -0
  85. data/spec/buildpack_support/constantize_spec.rb +34 -0
  86. data/spec/buildpack_support/dash_case_spec.rb +41 -0
  87. data/spec/buildpack_support/directory_finder_spec.rb +41 -0
  88. data/spec/buildpack_support/filtering_pathname_spec.rb +443 -0
  89. data/spec/buildpack_support/format_duration_spec.rb +60 -0
  90. data/spec/buildpack_support/logging/delegating_logger_spec.rb +62 -0
  91. data/spec/buildpack_support/logging/logger_factory_spec.rb +262 -0
  92. data/spec/buildpack_support/qualify_path_spec.rb +42 -0
  93. data/spec/buildpack_support/rake/buildpack_stage_task_spec.rb +88 -0
  94. data/spec/buildpack_support/rake/cached_artifact_finder_spec.rb +73 -0
  95. data/spec/buildpack_support/rake/check_api_doc_task_spec.rb +69 -0
  96. data/spec/buildpack_support/rake/dependency_cache_task_spec.rb +133 -0
  97. data/spec/buildpack_support/rake/disable_remote_downloads_task_spec.rb +91 -0
  98. data/spec/buildpack_support/rake/package_task_spec.rb +335 -0
  99. data/spec/buildpack_support/rake/package_zip_task_spec.rb +91 -0
  100. data/spec/buildpack_support/rake/repository_configuration_finder_spec.rb +61 -0
  101. data/spec/buildpack_support/rake/write_version_file_task_spec.rb +96 -0
  102. data/spec/buildpack_support/repository/configured_item_spec.rb +78 -0
  103. data/spec/buildpack_support/repository/repository_index_spec.rb +118 -0
  104. data/spec/buildpack_support/repository/wildcard_version_resolver_spec.rb +73 -0
  105. data/spec/buildpack_support/shell_spec.rb +32 -0
  106. data/spec/buildpack_support/snake_case_spec.rb +45 -0
  107. data/spec/buildpack_support/space_case_spec.rb +41 -0
  108. data/spec/buildpack_support/to_b_spec.rb +41 -0
  109. data/spec/buildpack_support/tokenized_version_spec.rb +132 -0
  110. data/spec/fixtures/application/test-file +0 -0
  111. data/spec/fixtures/config/found-config.yml +2 -0
  112. data/spec/fixtures/droplet-resources/droplet-resource +0 -0
  113. data/spec/fixtures/stub-download-with-top-level.zip +0 -0
  114. data/spec/fixtures/stub-download.tar.gz +0 -0
  115. data/spec/fixtures/stub-download.zip +0 -0
  116. data/spec/fixtures/test-cache.yml +18 -0
  117. data/spec/fixtures/test-index.yml +2 -0
  118. data/spec/fixtures/test_component.rb +0 -0
  119. data/spec/fixtures/zip-contents/test-directory/test-deep-file +0 -0
  120. data/spec/fixtures/zip-contents/test-file +0 -0
  121. data/spec/spec_helper.rb +30 -0
  122. metadata +416 -0
@@ -0,0 +1,280 @@
1
+ # Encoding: utf-8
2
+ # Copyright 2013-2014 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'buildpack_support/cache'
17
+ require 'buildpack_support/cache/cached_file'
18
+ require 'buildpack_support/cache/inferred_network_failure'
19
+ require 'buildpack_support/cache/internet_availability'
20
+ require 'buildpack_support/directory_finder'
21
+ require 'buildpack_support/logging/logger_factory'
22
+ require 'monitor'
23
+ require 'net/http'
24
+ require 'tmpdir'
25
+ require 'uri'
26
+
27
+ module BuildpackSupport
28
+ module Cache
29
+
30
+ # A cache for downloaded files that is configured to use a filesystem as the backing store.
31
+ #
32
+ # Note: this class is thread-safe, however access to the cached files is not
33
+ #
34
+ # References:
35
+ # * {https://en.wikipedia.org/wiki/HTTP_ETag ETag Wikipedia Definition}
36
+ # * {http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html HTTP/1.1 Header Field Definitions}
37
+ class DownloadCache
38
+ extend DirectoryFinder
39
+
40
+ # The location to find cached resources in the buildpack
41
+ CACHED_RESOURCES_DIRECTORY = DownloadCache.load_path_peer('resources/cache').freeze
42
+
43
+ # Creates an instance of the cache that is backed by a number of filesystem locations. The first argument
44
+ # (+mutable_cache_root+) is the only location that downloaded files will be stored in.
45
+ #
46
+ # @param [Pathname] mutable_cache_root the filesystem location in which find cached files in. This will also be
47
+ # the location that all downloaded files are written to.
48
+ # @param [Pathname] immutable_cache_roots other filesystem locations to find cached files in. No files will be
49
+ # written to these locations.
50
+ def initialize(mutable_cache_root = Pathname.new(Dir.tmpdir), *immutable_cache_roots)
51
+ @logger = BuildpackSupport::Logging::LoggerFactory.instance.get_logger DownloadCache
52
+ @mutable_cache_root = mutable_cache_root
53
+ @immutable_cache_roots = immutable_cache_roots.unshift mutable_cache_root
54
+ end
55
+
56
+ # Retrieves an item from the cache. Yields an open file containing the item's content or raises an exception if
57
+ # the item cannot be retrieved.
58
+ #
59
+ # @param [String] uri the URI of the item
60
+ # @yield [file, downloaded] the file representing the cached item and whether the file was downloaded or was
61
+ # already in the cache
62
+ # @return [Void]
63
+ def get(uri, &block)
64
+ cached_file, downloaded = nil, nil
65
+ cached_file, downloaded = from_mutable_cache uri if InternetAvailability.instance.available?
66
+ cached_file, downloaded = from_immutable_caches(uri), false unless cached_file
67
+
68
+ fail "Unable to find cached file for #{uri}" unless cached_file
69
+ cached_file.cached(File::RDONLY, downloaded, &block)
70
+ end
71
+
72
+ # Removes an item from the mutable cache.
73
+ #
74
+ # @param [String] uri the URI of the item
75
+ # @return [Void]
76
+ def evict(uri)
77
+ CachedFile.new(@mutable_cache_root, uri).destroy
78
+ end
79
+
80
+ private
81
+
82
+ FAILURE_LIMIT = 5.freeze
83
+
84
+ HTTP_ERRORS = [
85
+ EOFError,
86
+ Errno::ECONNABORTED,
87
+ Errno::ECONNREFUSED,
88
+ Errno::ECONNRESET,
89
+ Errno::EHOSTDOWN,
90
+ Errno::EHOSTUNREACH,
91
+ Errno::EINVAL,
92
+ Errno::ENETDOWN,
93
+ Errno::ENETRESET,
94
+ Errno::ENETUNREACH,
95
+ Errno::ENONET,
96
+ Errno::ENOTCONN,
97
+ Errno::EPIPE,
98
+ Errno::ETIMEDOUT,
99
+ Net::HTTPBadResponse,
100
+ Net::HTTPHeaderSyntaxError,
101
+ Net::ProtocolError,
102
+ SocketError,
103
+ Timeout::Error
104
+ ].freeze
105
+
106
+ REDIRECT_TYPES = [
107
+ Net::HTTPMovedPermanently,
108
+ Net::HTTPFound,
109
+ Net::HTTPSeeOther,
110
+ Net::HTTPTemporaryRedirect
111
+ ].freeze
112
+
113
+ TIMEOUT_SECONDS = 10.freeze
114
+
115
+ private_constant :FAILURE_LIMIT, :HTTP_ERRORS, :REDIRECT_TYPES, :TIMEOUT_SECONDS
116
+
117
+ def attempt(http, request, cached_file)
118
+ downloaded = false
119
+
120
+ http.request request do |response|
121
+ @logger.debug { "Status: #{response.code}" }
122
+
123
+ if response.is_a? Net::HTTPOK
124
+ cache_etag response, cached_file
125
+ cache_last_modified response, cached_file
126
+ cache_content response, cached_file
127
+ downloaded = true
128
+ elsif response.is_a? Net::HTTPNotModified
129
+ @logger.debug { 'Cached copy up to date' }
130
+ elsif redirect?(response)
131
+ downloaded = update URI(response['Location']), cached_file
132
+ else
133
+ fail InferredNetworkFailure, "Bad response: #{response}"
134
+ end
135
+ end
136
+
137
+ downloaded
138
+ end
139
+
140
+ def cache_content(response, cached_file)
141
+ cached_file.cached(File::CREAT | File::WRONLY) do |f|
142
+ @logger.debug { "Persisting content to #{f.path}" }
143
+
144
+ f.truncate(0)
145
+ response.read_body { |chunk| f.write chunk }
146
+ f.fsync
147
+ end
148
+
149
+ validate_size response['Content-Length'], cached_file
150
+ end
151
+
152
+ def cache_etag(response, cached_file)
153
+ etag = response['Etag']
154
+
155
+ return unless etag
156
+
157
+ @logger.debug { "Persisting etag: #{etag}" }
158
+
159
+ cached_file.etag(File::CREAT | File::WRONLY) do |f|
160
+ f.truncate(0)
161
+ f.write etag
162
+ f.fsync
163
+ end
164
+ end
165
+
166
+ def cache_last_modified(response, cached_file)
167
+ last_modified = response['Last-Modified']
168
+
169
+ return unless last_modified
170
+
171
+ @logger.debug { "Persisting last-modified: #{last_modified}" }
172
+
173
+ cached_file.last_modified(File::CREAT | File::WRONLY) do |f|
174
+ f.truncate(0)
175
+ f.write last_modified
176
+ f.fsync
177
+ end
178
+ end
179
+
180
+ def from_mutable_cache(uri)
181
+ cached_file = CachedFile.new(@mutable_cache_root, uri)
182
+ cached = update URI(uri), cached_file
183
+ [cached_file, cached]
184
+ rescue => e
185
+ @logger.warn { "Unable to download #{uri} into cache #{@mutable_cache_root}: #{e.message}" }
186
+ nil
187
+ end
188
+
189
+ def from_immutable_caches(uri)
190
+ @immutable_cache_roots.each do |cache_root|
191
+ candidate = CachedFile.new cache_root, uri
192
+
193
+ next unless candidate.cached?
194
+
195
+ @logger.debug { "#{uri} found in cache #{cache_root}" }
196
+ return candidate
197
+ end
198
+
199
+ nil
200
+ end
201
+
202
+ # Beware known problems with timeouts: https://www.ruby-forum.com/topic/143840
203
+ def http_options(rich_uri)
204
+ { read_timeout: TIMEOUT_SECONDS,
205
+ connect_timeout: TIMEOUT_SECONDS,
206
+ open_timeout: TIMEOUT_SECONDS,
207
+ use_ssl: secure?(rich_uri) }
208
+ end
209
+
210
+ def proxy(uri)
211
+ proxy_uri = if secure?(uri)
212
+ URI.parse(ENV['https_proxy'] || ENV['HTTPS_PROXY'] || '')
213
+ else
214
+ URI.parse(ENV['http_proxy'] || ENV['HTTP_PROXY'] || '')
215
+ end
216
+
217
+ @logger.debug { "Proxy: #{proxy_uri.host}, #{proxy_uri.port}, #{proxy_uri.user}, #{proxy_uri.password}" }
218
+ Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
219
+ end
220
+
221
+ def redirect?(response)
222
+ REDIRECT_TYPES.any? { |t| response.is_a? t }
223
+ end
224
+
225
+ def request(uri, cached_file)
226
+ request = Net::HTTP::Get.new(uri.request_uri)
227
+
228
+ if cached_file.etag?
229
+ cached_file.etag(File::RDONLY) { |f| request['If-None-Match'] = File.read(f) }
230
+ end
231
+
232
+ if cached_file.last_modified?
233
+ cached_file.last_modified(File::RDONLY) { |f| request['If-Modified-Since'] = File.read(f) }
234
+ end
235
+
236
+ @logger.debug { "Request: #{request.path}, #{request.to_hash}" }
237
+ request
238
+ end
239
+
240
+ def secure?(uri)
241
+ uri.scheme == 'https'
242
+ end
243
+
244
+ def update(uri, cached_file)
245
+ proxy(uri).start(uri.host, uri.port, http_options(uri)) do |http|
246
+ @logger.debug { "HTTP: #{http.address}, #{http.port}, #{http_options(uri)}" }
247
+ request = request uri, cached_file
248
+ request.basic_auth uri.user, uri.password
249
+
250
+ failures = 0
251
+ begin
252
+ attempt http, request, cached_file
253
+ rescue InferredNetworkFailure, *HTTP_ERRORS => e
254
+ if (failures += 1) > FAILURE_LIMIT
255
+ InternetAvailability.instance.available false, "Request failed: #{e.message}"
256
+ raise e
257
+ else
258
+ @logger.warn { "Request failure #{failures}, retrying: #{e.message}" }
259
+ retry
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ def validate_size(expected_size, cached_file)
266
+ return unless expected_size
267
+
268
+ actual_size = cached_file.cached(File::RDONLY) { |f| f.size }
269
+ @logger.debug { "Validated content size #{actual_size} is #{expected_size}" }
270
+
271
+ return if expected_size.to_i == actual_size
272
+
273
+ cached_file.destroy
274
+ fail InferredNetworkFailure, "Content has invalid size. Was #{actual_size}, should be #{expected_size}."
275
+ end
276
+
277
+ end
278
+
279
+ end
280
+ end
@@ -0,0 +1,26 @@
1
+ # Encoding: utf-8
2
+ # Copyright 2013-2014 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'buildpack_support/cache'
17
+
18
+ module BuildpackSupport
19
+ module Cache
20
+
21
+ # An error thrown when a we infer that an error has occurred (rather than receiving an explicit indication)
22
+ class InferredNetworkFailure < StandardError
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ # Encoding: utf-8
2
+ # Copyright 2013-2014 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'buildpack_support/cache'
17
+ require 'buildpack_support/configuration_utils'
18
+ require 'buildpack_support/logging/logger_factory'
19
+ require 'monitor'
20
+ require 'singleton'
21
+
22
+ module BuildpackSupport
23
+ module Cache
24
+
25
+ # Maintains the current state of internet availability.
26
+ class InternetAvailability
27
+ include ::Singleton
28
+
29
+ # Creates a new instance. Availability is assumed to be +true+ unless +remote_downloads+ is set to +disabled+
30
+ # in +config/cache.yml+.
31
+ def initialize
32
+ @logger = BuildpackSupport::Logging::LoggerFactory.instance.get_logger InternetAvailability
33
+ @monitor = Monitor.new
34
+ @monitor.synchronize { @available = remote_downloads? }
35
+ end
36
+
37
+ # Returns whether the internet is available
38
+ #
39
+ # @return [Boolean] +true+ if the internet is available, +false+ otherwise
40
+ def available?
41
+ @monitor.synchronize { @available }
42
+ end
43
+
44
+ # Sets whether the internet is available
45
+ #
46
+ # @param [Boolean] available whether the internet is available
47
+ # @param [String, nil] message an optional message to be printed when the availability is set
48
+ def available(available, message = nil)
49
+ @monitor.synchronize do
50
+ @available = available
51
+ @logger.warn { "Internet availability set to #{available}: #{message}" } if message
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def remote_downloads?
58
+ BuildpackSupport::ConfigurationUtils.new.load('cache')['remote_downloads'] != 'disabled'
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,24 @@
1
+ # Encoding: utf-8
2
+ # Copyright 2013-2014 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'buildpack_support'
17
+
18
+ module BuildpackSupport
19
+
20
+ # A module encapsulating all of the code for the component utilities
21
+ module Component
22
+ end
23
+
24
+ end
@@ -0,0 +1,76 @@
1
+ # Encoding: utf-8
2
+ # Copyright 2013-2014 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'buildpack_support/component'
17
+ require 'buildpack_support/component/services'
18
+ require 'buildpack_support/filtering_pathname'
19
+ require 'yaml'
20
+
21
+ module BuildpackSupport
22
+ module Component
23
+
24
+ # An abstraction around the application as uploaded by the user. This abstraction is intended to hide any
25
+ # modifications made to the filesystem by other components. Think of this as an immutable representation of the
26
+ # application as it was uploaded.
27
+ #
28
+ # A new instance of this type should be created once for the application.
29
+ class Application
30
+
31
+ # @!attribute [r] details
32
+ # @return [Hash] the parsed contents of the +VCAP_APPLICATION+ environment variable
33
+ attr_reader :details
34
+
35
+ # @!attribute [r] environment
36
+ # @return [Hash] all environment variables except +VCAP_APPLICATION+ and +VCAP_SERVICES+. Those values are
37
+ # available separately in parsed form.
38
+ attr_reader :environment
39
+
40
+ # @!attribute [r] root
41
+ # @return [BuildpackSupport::FilteringPathname] the root of the application's fileystem filtered so that it only
42
+ # shows files that have been uploaded by the user
43
+ attr_reader :root
44
+
45
+ # @!attribute [r] services
46
+ # @return [Hash] the parsed contents of the +VCAP_SERVICES+ environment variable
47
+ attr_reader :services
48
+
49
+ # Create a new instance of the application abstraction
50
+ #
51
+ # @param [Pathname] root the root of the application
52
+ def initialize(root)
53
+ initial = children(root)
54
+ @root = BuildpackSupport::FilteringPathname.new(root, ->(path) { initial.member? path }, false)
55
+
56
+ @environment = ENV.to_hash
57
+ @details = parse(@environment.delete('VCAP_APPLICATION'))
58
+ @services = Services.new(parse(@environment.delete('VCAP_SERVICES')))
59
+ end
60
+
61
+ private
62
+
63
+ def children(root, s = Set.new)
64
+ s << root
65
+ root.children.each { |child| children(child, s) } if root.directory?
66
+ s
67
+ end
68
+
69
+ def parse(input)
70
+ input ? YAML.load(input) : {}
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end