librarian-chef 0.0.1.beta.1
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.
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +386 -0
- data/Rakefile +1 -0
- data/lib/librarian-chef.rb +1 -0
- data/lib/librarian/chef.rb +1 -0
- data/lib/librarian/chef/cli.rb +47 -0
- data/lib/librarian/chef/dsl.rb +16 -0
- data/lib/librarian/chef/environment.rb +32 -0
- data/lib/librarian/chef/extension.rb +9 -0
- data/lib/librarian/chef/integration/knife.rb +46 -0
- data/lib/librarian/chef/manifest_reader.rb +59 -0
- data/lib/librarian/chef/source.rb +4 -0
- data/lib/librarian/chef/source/git.rb +25 -0
- data/lib/librarian/chef/source/github.rb +27 -0
- data/lib/librarian/chef/source/local.rb +74 -0
- data/lib/librarian/chef/source/path.rb +12 -0
- data/lib/librarian/chef/source/site.rb +442 -0
- data/lib/librarian/chef/templates/Cheffile +15 -0
- data/lib/librarian/chef/version.rb +5 -0
- data/librarian-chef.gemspec +27 -0
- data/spec/functional/chef/cli_spec.rb +195 -0
- data/spec/functional/chef/source/site_spec.rb +266 -0
- data/spec/integration/chef/source/git_spec.rb +451 -0
- data/spec/integration/chef/source/site_spec.rb +217 -0
- metadata +176 -0
@@ -0,0 +1 @@
|
|
1
|
+
require "librarian/chef"
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'librarian/chef/extension'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'librarian/helpers'
|
2
|
+
|
3
|
+
require 'librarian/cli'
|
4
|
+
require 'librarian/chef'
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
module Chef
|
8
|
+
class Cli < Librarian::Cli
|
9
|
+
|
10
|
+
module Particularity
|
11
|
+
def root_module
|
12
|
+
Chef
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend Particularity
|
17
|
+
|
18
|
+
source_root Pathname.new(__FILE__).dirname.join("templates")
|
19
|
+
|
20
|
+
def init
|
21
|
+
copy_file environment.specfile_name
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "install", "Resolves and installs all of the dependencies you specify."
|
25
|
+
option "quiet", :type => :boolean, :default => false
|
26
|
+
option "verbose", :type => :boolean, :default => false
|
27
|
+
option "line-numbers", :type => :boolean, :default => false
|
28
|
+
option "clean", :type => :boolean, :default => false
|
29
|
+
option "strip-dot-git", :type => :boolean
|
30
|
+
option "path", :type => :string
|
31
|
+
def install
|
32
|
+
ensure!
|
33
|
+
clean! if options["clean"]
|
34
|
+
if options.include?("strip-dot-git")
|
35
|
+
strip_dot_git_val = options["strip-dot-git"] ? "1" : nil
|
36
|
+
environment.config_db.local["install.strip-dot-git"] = strip_dot_git_val
|
37
|
+
end
|
38
|
+
if options.include?("path")
|
39
|
+
environment.config_db.local["path"] = options["path"]
|
40
|
+
end
|
41
|
+
resolve!
|
42
|
+
install!
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'librarian/dsl'
|
2
|
+
require 'librarian/chef/source'
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Chef
|
6
|
+
class Dsl < Librarian::Dsl
|
7
|
+
|
8
|
+
dependency :cookbook
|
9
|
+
|
10
|
+
source :site => Source::Site
|
11
|
+
source :git => Source::Git
|
12
|
+
source :github => Source::Github
|
13
|
+
source :path => Source::Path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "librarian/environment"
|
2
|
+
require "librarian/chef/dsl"
|
3
|
+
require "librarian/chef/source"
|
4
|
+
require "librarian/chef/version"
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
module Chef
|
8
|
+
class Environment < Environment
|
9
|
+
|
10
|
+
def adapter_name
|
11
|
+
"chef"
|
12
|
+
end
|
13
|
+
|
14
|
+
def adapter_version
|
15
|
+
VERSION
|
16
|
+
end
|
17
|
+
|
18
|
+
def install_path
|
19
|
+
part = config_db["path"] || "cookbooks"
|
20
|
+
project_path.join(part)
|
21
|
+
end
|
22
|
+
|
23
|
+
def config_keys
|
24
|
+
super + %w[
|
25
|
+
install.strip-dot-git
|
26
|
+
path
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'highline'
|
4
|
+
|
5
|
+
require 'librarian'
|
6
|
+
require 'librarian/action/install'
|
7
|
+
require 'librarian/chef'
|
8
|
+
|
9
|
+
module Librarian
|
10
|
+
module Chef
|
11
|
+
|
12
|
+
class Environment
|
13
|
+
def install_path
|
14
|
+
@install_path ||= begin
|
15
|
+
has_home = ENV["HOME"] && File.directory?(ENV["HOME"])
|
16
|
+
tmp_dir = Pathname.new(has_home ? "~/.librarian/tmp" : "/tmp/librarian").expand_path
|
17
|
+
enclosing = tmp_dir.join("chef/integration/knife/install")
|
18
|
+
enclosing.mkpath unless enclosing.exist?
|
19
|
+
dir = enclosing.join(SecureRandom.hex(16))
|
20
|
+
dir.mkpath
|
21
|
+
at_exit { dir.rmtree }
|
22
|
+
dir
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def environment
|
28
|
+
@environment ||= environment_class.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def install_path
|
32
|
+
environment.install_path
|
33
|
+
end
|
34
|
+
|
35
|
+
hl = HighLine.new
|
36
|
+
|
37
|
+
begin
|
38
|
+
Action::Install.new(environment).run
|
39
|
+
rescue Error => e
|
40
|
+
message = hl.color(e.message, HighLine::RED)
|
41
|
+
hl.say(message)
|
42
|
+
Process.exit!(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'librarian/manifest'
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
module Chef
|
8
|
+
module ManifestReader
|
9
|
+
extend self
|
10
|
+
|
11
|
+
MANIFESTS = %w(metadata.json metadata.yml metadata.yaml metadata.rb)
|
12
|
+
|
13
|
+
def manifest_path(path)
|
14
|
+
MANIFESTS.map{|s| path.join(s)}.find{|s| s.exist?}
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_manifest(name, manifest_path)
|
18
|
+
case manifest_path.extname
|
19
|
+
when ".json" then JSON.parse(binread(manifest_path))
|
20
|
+
when ".yml", ".yaml" then YAML.load(binread(manifest_path))
|
21
|
+
when ".rb" then compile_manifest(name, manifest_path.dirname)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def compile_manifest(name, path)
|
26
|
+
# Inefficient, if there are many cookbooks with uncompiled metadata.
|
27
|
+
require 'chef/json_compat'
|
28
|
+
require 'chef/cookbook/metadata'
|
29
|
+
md = ::Chef::Cookbook::Metadata.new
|
30
|
+
md.name(name)
|
31
|
+
md.from_file(path.join('metadata.rb').to_s)
|
32
|
+
{"name" => md.name, "version" => md.version, "dependencies" => md.dependencies}
|
33
|
+
end
|
34
|
+
|
35
|
+
def manifest?(name, path)
|
36
|
+
path = Pathname.new(path)
|
37
|
+
!!manifest_path(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_manifest(name, manifest_path)
|
41
|
+
manifest = read_manifest(name, manifest_path)
|
42
|
+
manifest["name"] == name
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
if IO.respond_to?(:binread)
|
48
|
+
def binread(path)
|
49
|
+
path.binread
|
50
|
+
end
|
51
|
+
else
|
52
|
+
def binread(path)
|
53
|
+
path.read
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'librarian/source/git'
|
2
|
+
require 'librarian/chef/source/local'
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Chef
|
6
|
+
module Source
|
7
|
+
class Git < Librarian::Source::Git
|
8
|
+
include Local
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def install_perform_step_copy!(found_path, install_path)
|
13
|
+
debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
|
14
|
+
FileUtils.cp_r(found_path, install_path)
|
15
|
+
|
16
|
+
if environment.config_db["install.strip-dot-git"] == "1"
|
17
|
+
dot_git = install_path.join(".git")
|
18
|
+
dot_git.rmtree if dot_git.directory?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'librarian/chef/source/git'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
module Chef
|
5
|
+
module Source
|
6
|
+
class Github
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def lock_name
|
11
|
+
Git.lock_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_lock_options(environment, options)
|
15
|
+
Git.from_lock_options(environment, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_spec_args(environment, uri, options)
|
19
|
+
Git.from_spec_args(environment, "https://github.com/#{uri}", options)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'librarian/chef/manifest_reader'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
module Chef
|
5
|
+
module Source
|
6
|
+
module Local
|
7
|
+
|
8
|
+
def install!(manifest)
|
9
|
+
manifest.source == self or raise ArgumentError
|
10
|
+
|
11
|
+
info { "Installing #{manifest.name} (#{manifest.version})" }
|
12
|
+
|
13
|
+
debug { "Installing #{manifest}" }
|
14
|
+
|
15
|
+
name, version = manifest.name, manifest.version
|
16
|
+
found_path = found_path(name)
|
17
|
+
|
18
|
+
install_path = environment.install_path.join(name)
|
19
|
+
if install_path.exist?
|
20
|
+
debug { "Deleting #{relative_path_to(install_path)}" }
|
21
|
+
install_path.rmtree
|
22
|
+
end
|
23
|
+
|
24
|
+
install_perform_step_copy!(found_path, install_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_version(name, extra)
|
28
|
+
manifest_data(name)["version"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch_dependencies(name, version, extra)
|
32
|
+
manifest_data(name)["dependencies"]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def install_perform_step_copy!(found_path, install_path)
|
38
|
+
debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
|
39
|
+
FileUtils.mkdir_p(install_path)
|
40
|
+
FileUtils.cp_r(filter_path(found_path), install_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def filter_path(path)
|
44
|
+
Dir.glob("#{path}/*").reject { |e| e == environment.install_path.to_s }
|
45
|
+
end
|
46
|
+
|
47
|
+
def manifest_data(name)
|
48
|
+
@manifest_data ||= { }
|
49
|
+
@manifest_data[name] ||= fetch_manifest_data(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_manifest_data(name)
|
53
|
+
expect_manifest!(name)
|
54
|
+
|
55
|
+
found_path = found_path(name)
|
56
|
+
manifest_path = ManifestReader.manifest_path(found_path)
|
57
|
+
ManifestReader.read_manifest(name, manifest_path)
|
58
|
+
end
|
59
|
+
|
60
|
+
def manifest?(name, path)
|
61
|
+
ManifestReader.manifest?(name, path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def expect_manifest!(name)
|
65
|
+
found_path = found_path(name)
|
66
|
+
return if found_path && ManifestReader.manifest_path(found_path)
|
67
|
+
|
68
|
+
raise Error, "No metadata file found for #{name} from #{self}! If this should be a cookbook, you might consider contributing a metadata file upstream or forking the cookbook to add your own metadata file."
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'json'
|
6
|
+
require 'digest'
|
7
|
+
require 'zlib'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'archive/tar/minitar'
|
10
|
+
|
11
|
+
require 'librarian/source/basic_api'
|
12
|
+
require 'librarian/chef/manifest_reader'
|
13
|
+
|
14
|
+
module Librarian
|
15
|
+
module Chef
|
16
|
+
module Source
|
17
|
+
class Site
|
18
|
+
|
19
|
+
class Line
|
20
|
+
|
21
|
+
attr_accessor :source, :name
|
22
|
+
private :source=, :name=
|
23
|
+
|
24
|
+
def initialize(source, name)
|
25
|
+
self.source = source
|
26
|
+
self.name = name
|
27
|
+
end
|
28
|
+
|
29
|
+
def install_version!(version, install_path)
|
30
|
+
cache_version_unpacked! version
|
31
|
+
|
32
|
+
if install_path.exist?
|
33
|
+
debug { "Deleting #{relative_path_to(install_path)}" }
|
34
|
+
install_path.rmtree
|
35
|
+
end
|
36
|
+
|
37
|
+
unpacked_path = version_unpacked_cache_path(version)
|
38
|
+
|
39
|
+
debug { "Copying #{relative_path_to(unpacked_path)} to #{relative_path_to(install_path)}" }
|
40
|
+
FileUtils.cp_r(unpacked_path, install_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def manifests
|
44
|
+
version_uris.map do |version_uri|
|
45
|
+
Manifest.new(source, name, version_uri)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_version(version_uri)
|
50
|
+
version_uri_metadata(version_uri)["version"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def version_dependencies(version)
|
54
|
+
version_manifest(version)["dependencies"]
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_accessor :metadata_cached
|
60
|
+
alias metadata_cached? metadata_cached
|
61
|
+
|
62
|
+
def environment
|
63
|
+
source.environment
|
64
|
+
end
|
65
|
+
|
66
|
+
def uri
|
67
|
+
@uri ||= URI.parse("#{source.uri}/cookbooks/#{name}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def version_uris
|
71
|
+
metadata["versions"]
|
72
|
+
end
|
73
|
+
|
74
|
+
def version_metadata(version)
|
75
|
+
version_uri = to_version_uri(version)
|
76
|
+
version_uri_metadata(version_uri)
|
77
|
+
end
|
78
|
+
|
79
|
+
def version_uri_metadata(version_uri)
|
80
|
+
memo(__method__, version_uri.to_s) do
|
81
|
+
cache_version_uri_metadata! version_uri
|
82
|
+
parse_local_json(version_uri_metadata_cache_path(version_uri))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def version_manifest(version)
|
87
|
+
version_uri = to_version_uri(version)
|
88
|
+
version_uri_manifest(version_uri)
|
89
|
+
end
|
90
|
+
|
91
|
+
def version_uri_manifest(version_uri)
|
92
|
+
memo(__method__, version_uri.to_s) do
|
93
|
+
cache_version_uri_unpacked! version_uri
|
94
|
+
unpacked_path = version_uri_unpacked_cache_path(version_uri)
|
95
|
+
manifest_path = ManifestReader.manifest_path(unpacked_path)
|
96
|
+
ManifestReader.read_manifest(name, manifest_path)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def metadata
|
101
|
+
@metadata ||= begin
|
102
|
+
cache_metadata!
|
103
|
+
parse_local_json(metadata_cache_path)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_version_uri(version)
|
108
|
+
memo(__method__, version.to_s) do
|
109
|
+
cache_version! version
|
110
|
+
version_cache_path(version).read
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def metadata_cached!
|
115
|
+
self.metadata_cached = true
|
116
|
+
end
|
117
|
+
|
118
|
+
def cache_path
|
119
|
+
@cache_path ||= source.cache_path.join(name)
|
120
|
+
end
|
121
|
+
|
122
|
+
def metadata_cache_path
|
123
|
+
@metadata_cache_path ||= cache_path.join("metadata.json")
|
124
|
+
end
|
125
|
+
|
126
|
+
def version_cache_path(version)
|
127
|
+
memo(__method__, version.to_s) do
|
128
|
+
cache_path.join("version").join(version.to_s)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def version_uri_cache_path(version_uri)
|
133
|
+
memo(__method__, version_uri.to_s) do
|
134
|
+
cache_path.join("version-uri").join(hexdigest(version_uri))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def version_metadata_cache_path(version)
|
139
|
+
version_uri = to_version_uri(version)
|
140
|
+
version_uri_metadata_cache_path(version_uri)
|
141
|
+
end
|
142
|
+
|
143
|
+
def version_uri_metadata_cache_path(version_uri)
|
144
|
+
memo(__method__, version_uri.to_s) do
|
145
|
+
version_uri_cache_path(version_uri).join("metadata.json")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def version_package_cache_path(version)
|
150
|
+
version_uri = to_version_uri(version)
|
151
|
+
version_uri_package_cache_path(version_uri)
|
152
|
+
end
|
153
|
+
|
154
|
+
def version_uri_package_cache_path(version_uri)
|
155
|
+
memo(__method__, version_uri.to_s) do
|
156
|
+
version_uri_cache_path(version_uri).join("package.tar.gz")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def version_unpacked_cache_path(version)
|
161
|
+
version_uri = to_version_uri(version)
|
162
|
+
version_uri_unpacked_cache_path(version_uri)
|
163
|
+
end
|
164
|
+
|
165
|
+
def version_uri_unpacked_cache_path(version_uri)
|
166
|
+
memo(__method__, version_uri.to_s) do
|
167
|
+
version_uri_cache_path(version_uri).join("package")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def cache_metadata!
|
172
|
+
metadata_cached? and return or metadata_cached!
|
173
|
+
cache_remote_json! metadata_cache_path, uri
|
174
|
+
end
|
175
|
+
|
176
|
+
def cache_version_uri_metadata!(version_uri)
|
177
|
+
path = version_uri_metadata_cache_path(version_uri)
|
178
|
+
path.file? and return
|
179
|
+
|
180
|
+
cache_remote_json! path, version_uri
|
181
|
+
end
|
182
|
+
|
183
|
+
def cache_version!(version)
|
184
|
+
path = version_cache_path(version)
|
185
|
+
path.file? and return
|
186
|
+
|
187
|
+
version_uris.each do |version_uri|
|
188
|
+
m = version_uri_metadata(version_uri)
|
189
|
+
v = m["version"]
|
190
|
+
if version.to_s == v
|
191
|
+
write! path, version_uri.to_s
|
192
|
+
break
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def cache_version_package!(version)
|
198
|
+
version_uri = to_version_uri(version)
|
199
|
+
cache_version_uri_package! version_uri
|
200
|
+
end
|
201
|
+
|
202
|
+
def cache_version_uri_package!(version_uri)
|
203
|
+
path = version_uri_package_cache_path(version_uri)
|
204
|
+
path.file? and return
|
205
|
+
|
206
|
+
file_uri = version_uri_metadata(version_uri)["file"]
|
207
|
+
cache_remote_object! path, file_uri
|
208
|
+
end
|
209
|
+
|
210
|
+
def cache_version_unpacked!(version)
|
211
|
+
version_uri = to_version_uri(version)
|
212
|
+
cache_version_uri_unpacked! version_uri
|
213
|
+
end
|
214
|
+
|
215
|
+
def cache_version_uri_unpacked!(version_uri)
|
216
|
+
cache_version_uri_package!(version_uri)
|
217
|
+
|
218
|
+
path = version_uri_unpacked_cache_path(version_uri)
|
219
|
+
path.directory? and return
|
220
|
+
|
221
|
+
package_path = version_uri_package_cache_path(version_uri)
|
222
|
+
unpacked_path = version_uri_unpacked_cache_path(version_uri)
|
223
|
+
|
224
|
+
unpack_package! unpacked_path, package_path
|
225
|
+
end
|
226
|
+
|
227
|
+
def cache_remote_json!(path, uri)
|
228
|
+
cache_remote_object!(path, uri, :type => :json)
|
229
|
+
end
|
230
|
+
|
231
|
+
def cache_remote_object!(path, uri, options = { })
|
232
|
+
path = Pathname(path)
|
233
|
+
uri = to_uri(uri)
|
234
|
+
type = options[:type]
|
235
|
+
|
236
|
+
debug { "Caching #{uri} to #{path}" }
|
237
|
+
|
238
|
+
response = http_get(uri)
|
239
|
+
|
240
|
+
object = response.body
|
241
|
+
case type
|
242
|
+
when :json
|
243
|
+
JSON.parse(object) # verify that it's really JSON.
|
244
|
+
end
|
245
|
+
write! path, object
|
246
|
+
end
|
247
|
+
|
248
|
+
def write!(path, bytes)
|
249
|
+
path.dirname.mkpath
|
250
|
+
path.open("wb"){|f| f.write(bytes)}
|
251
|
+
end
|
252
|
+
|
253
|
+
def unpack_package!(path, source)
|
254
|
+
path = Pathname(path)
|
255
|
+
source = Pathname(source)
|
256
|
+
|
257
|
+
temp = environment.scratch_path.join(SecureRandom.hex(16))
|
258
|
+
temp.mkpath
|
259
|
+
|
260
|
+
debug { "Unpacking #{relative_path_to(source)} to #{relative_path_to(temp)}" }
|
261
|
+
Zlib::GzipReader.open(source) do |input|
|
262
|
+
Archive::Tar::Minitar.unpack(input, temp.to_s)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Cookbook files, as pulled from Opscode Community Site API, are
|
266
|
+
# embedded in a subdirectory of the tarball. If created by git archive they
|
267
|
+
# can include the subfolder `pax_global_header`, which is ignored.
|
268
|
+
subtemps = temp.children
|
269
|
+
subtemps.empty? and raise "The package archive was empty!"
|
270
|
+
subtemps.delete_if{|pth| pth.to_s[/pax_global_header/]}
|
271
|
+
subtemps.size > 1 and raise "The package archive has too many children!"
|
272
|
+
subtemp = subtemps.first
|
273
|
+
debug { "Moving #{relative_path_to(subtemp)} to #{relative_path_to(path)}" }
|
274
|
+
FileUtils.mv(subtemp, path)
|
275
|
+
ensure
|
276
|
+
temp.rmtree if temp && temp.exist?
|
277
|
+
end
|
278
|
+
|
279
|
+
def parse_local_json(path)
|
280
|
+
JSON.parse(path.read)
|
281
|
+
end
|
282
|
+
|
283
|
+
def hexdigest(bytes)
|
284
|
+
Digest::MD5.hexdigest(bytes)[0..15]
|
285
|
+
end
|
286
|
+
|
287
|
+
def to_uri(uri)
|
288
|
+
uri = URI(uri) unless URI === uri
|
289
|
+
uri
|
290
|
+
end
|
291
|
+
|
292
|
+
def debug(*args, &block)
|
293
|
+
environment.logger.debug(*args, &block)
|
294
|
+
end
|
295
|
+
|
296
|
+
def relative_path_to(path)
|
297
|
+
environment.logger.relative_path_to(path)
|
298
|
+
end
|
299
|
+
|
300
|
+
def http(uri)
|
301
|
+
environment.net_http_class(uri.host).new(uri.host, uri.port)
|
302
|
+
end
|
303
|
+
|
304
|
+
def http_get(uri)
|
305
|
+
max_redirects = 10
|
306
|
+
redirects = []
|
307
|
+
|
308
|
+
loop do
|
309
|
+
debug { "Performing http-get for #{uri}" }
|
310
|
+
http = http(uri)
|
311
|
+
request = Net::HTTP::Get.new(uri.path)
|
312
|
+
response = http.start{|http| http.request(request)}
|
313
|
+
|
314
|
+
case response
|
315
|
+
when Net::HTTPSuccess
|
316
|
+
debug { "Responded with success" }
|
317
|
+
return response
|
318
|
+
when Net::HTTPRedirection
|
319
|
+
location = response["Location"]
|
320
|
+
debug { "Responded with redirect to #{uri}" }
|
321
|
+
redirects.size > max_redirects and raise Error,
|
322
|
+
"Could not get #{uri} because too many redirects!"
|
323
|
+
redirects.include?(location) and raise Error,
|
324
|
+
"Could not get #{uri} because redirect cycle!"
|
325
|
+
redirects << location
|
326
|
+
uri = URI.parse(location)
|
327
|
+
# continue the loop
|
328
|
+
else
|
329
|
+
raise Error, "Could not get #{uri} because #{response.code} #{response.message}!"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def memo(method, *path)
|
335
|
+
ivar = "@#{method}".to_sym
|
336
|
+
unless memo = instance_variable_get(ivar)
|
337
|
+
memo = instance_variable_set(ivar, { })
|
338
|
+
end
|
339
|
+
|
340
|
+
memo.key?(path) or memo[path] = yield
|
341
|
+
memo[path]
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
include Librarian::Source::BasicApi
|
347
|
+
|
348
|
+
lock_name 'SITE'
|
349
|
+
spec_options []
|
350
|
+
|
351
|
+
attr_accessor :environment, :uri
|
352
|
+
private :environment=, :uri=
|
353
|
+
|
354
|
+
def initialize(environment, uri, options = {})
|
355
|
+
self.environment = environment
|
356
|
+
self.uri = uri
|
357
|
+
end
|
358
|
+
|
359
|
+
def to_s
|
360
|
+
uri
|
361
|
+
end
|
362
|
+
|
363
|
+
def ==(other)
|
364
|
+
other &&
|
365
|
+
self.class == other.class &&
|
366
|
+
self.uri == other.uri
|
367
|
+
end
|
368
|
+
|
369
|
+
def to_spec_args
|
370
|
+
[uri, {}]
|
371
|
+
end
|
372
|
+
|
373
|
+
def to_lock_options
|
374
|
+
{:remote => uri}
|
375
|
+
end
|
376
|
+
|
377
|
+
def pinned?
|
378
|
+
false
|
379
|
+
end
|
380
|
+
|
381
|
+
def unpin!
|
382
|
+
end
|
383
|
+
|
384
|
+
def install!(manifest)
|
385
|
+
manifest.source == self or raise ArgumentError
|
386
|
+
|
387
|
+
name = manifest.name
|
388
|
+
version = manifest.version
|
389
|
+
install_path = install_path(name)
|
390
|
+
line = line(name)
|
391
|
+
|
392
|
+
info { "Installing #{manifest.name} (#{manifest.version})" }
|
393
|
+
|
394
|
+
debug { "Installing #{manifest}" }
|
395
|
+
|
396
|
+
line.install_version! version, install_path
|
397
|
+
end
|
398
|
+
|
399
|
+
# NOTE:
|
400
|
+
# Assumes the Opscode Site API responds with versions in reverse sorted order
|
401
|
+
def manifests(name)
|
402
|
+
line(name).manifests
|
403
|
+
end
|
404
|
+
|
405
|
+
def cache_path
|
406
|
+
@cache_path ||= begin
|
407
|
+
dir = Digest::MD5.hexdigest(uri)[0..15]
|
408
|
+
environment.cache_path.join("source/chef/site/#{dir}")
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def install_path(name)
|
413
|
+
environment.install_path.join(name)
|
414
|
+
end
|
415
|
+
|
416
|
+
def fetch_version(name, version_uri)
|
417
|
+
line(name).to_version(version_uri)
|
418
|
+
end
|
419
|
+
|
420
|
+
def fetch_dependencies(name, version, version_uri)
|
421
|
+
line(name).version_dependencies(version).map{|k, v| Dependency.new(k, v, nil)}
|
422
|
+
end
|
423
|
+
|
424
|
+
private
|
425
|
+
|
426
|
+
def line(name)
|
427
|
+
@line ||= { }
|
428
|
+
@line[name] ||= Line.new(self, name)
|
429
|
+
end
|
430
|
+
|
431
|
+
def info(*args, &block)
|
432
|
+
environment.logger.info(*args, &block)
|
433
|
+
end
|
434
|
+
|
435
|
+
def debug(*args, &block)
|
436
|
+
environment.logger.debug(*args, &block)
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|