librarian 0.0.23 → 0.0.24

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.0.24
4
+
5
+ * \#15. A remembered configuration system.
6
+
7
+ * \#16. Configure, and remember configuration for, chef install paths.
8
+
9
+ * \#38. Configure, and remember configuration for, stripping out `.git`
10
+ directories from git-sources chef dependencies.
11
+
12
+ * \#76. Support git annotated tags.
13
+
14
+ * \#80. Ignore directories in the `PATH` named `git` when looking for the `git`
15
+ bin.
16
+
17
+ * \#85. Provide a helpful message when running the `show` command without a
18
+ lockfile present.
19
+
3
20
  ## 0.0.23
4
21
 
5
22
  * \#41. Build gems with a built gemspec.
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in librarian.gemspec
4
4
  gemspec
5
+
6
+ gem "fakefs", :require => "fakefs/safe"
data/README.md CHANGED
@@ -12,33 +12,49 @@ A bundler written with Librarian will expect you to provide a specfile listing
12
12
  your project's declared dependencies, including any version constraints and
13
13
  including the upstream sources for finding them. Librarian can resolve the spec,
14
14
  write a lockfile listing the full resolution, fetch the resolved dependencies,
15
- install them, and isolate them in your project. This is what Bundler does for
16
- projects that depend on Ruby gems.
15
+ install them, and isolate them in your project.
16
+
17
+ A bundler written with Librarian will be similar in kind to [Bundler](http://gembundler.com),
18
+ the bundler for Ruby gems that many modern Rails applications use.
17
19
 
18
20
  Librarian-Chef
19
21
  ---------------
20
22
 
23
+ Librarian-Chef is a tool that helps you manage the cookbooks that your chef-repo
24
+ depends on. Here are some more details.
25
+
21
26
  Librarian-Chef is a bundler for infrastructure repositories using Chef. You can
22
- use Librarian-Chef to resolve your infrastructure's cookbook dependencies,
23
- fetch them and install them into your infrastructure.
27
+ use Librarian-Chef to resolve your infrastructure's cookbook dependencies, fetch
28
+ them, and install them into your infrastructure repository.
29
+
30
+ Librarian-Chef can resolve and fetch third-party, publicly-released cookbooks,
31
+ and install them into your infrastructure repository. It can also source
32
+ cookbooks directly from their own source control repositories.
33
+
34
+ Librarian-Chef can also deal with cookbooks you may actively be working on
35
+ outside your infrastructure repository. For example, it can deal with cookbooks
36
+ directly from their own private source control repositories, whether they are
37
+ remote or local to your machine, and it can deal with cookbooks released to and
38
+ hosted on a private cookbooks server.
24
39
 
25
- Librarian-Chef is for resolving and fetching third-party, publicly-released
26
- cookbooks, and installing them into your infrastructure repository. It is not
27
- for dealing with the cookbooks you're actively working on within your
28
- infrastructure repository.
40
+ Librarian-Chef is not primarily intended for dealing with the cookbooks you are
41
+ actively working on *within* your infrastructure repository. In such a case, you
42
+ can still use Librarian-Chef, but it is likely unnecessary.
29
43
 
30
44
  Librarian-Chef *takes over* your `cookbooks/` directory and manages it for you
31
45
  based on your `Cheffile`. Your `Cheffile` becomes the authoritative source for
32
46
  the cookbooks your infrastructure repository depends on. You should not modify
33
47
  the contents of your `cookbooks/` directory when using Librarian-Chef. If you
34
- have custom cookbooks which are specific to your infrastructure repository,
35
- they should go in your `site-cookbooks/` directory.
48
+ have cookbooks which are, rather than being separate projects, inherently part
49
+ of your infrastructure repository, then they should go in a separate directory,
50
+ like your `site-cookbooks/` directory, and you do not need to use Librarian-Chef
51
+ to manage them.
36
52
 
37
53
  ### The Cheffile
38
54
 
39
55
  Every infrastruture repository that uses Librarian-Chef will have a file named
40
56
  `Cheffile` in the root directory of that repository. The full specification for
41
- which third-party, publicly-rleased cookbooks your infrastructure repository
57
+ which third-party, publicly-released cookbooks your infrastructure repository
42
58
  depends will go here.
43
59
 
44
60
  Here's an example `Cheffile`:
@@ -103,7 +119,7 @@ most recent version of the cookbook from that same branch.
103
119
 
104
120
  The Git source also supports a `:path =>` option. If we use the path option,
105
121
  Librarian-Chef will navigate down into the Git repository and only use the
106
- specified subdirectory. Many people have the havit of having a single repository
122
+ specified subdirectory. Many people have the habit of having a single repository
107
123
  with many cookbooks in it. If we need a cookbook from such a repository, we can
108
124
  use the `:path =>` option here to help Librarian-Chef drill down and find the
109
125
  cookbook subdirectory.
@@ -240,6 +256,75 @@ Upload the cookbooks to your chef-server:
240
256
 
241
257
  $ knife cookbook upload --all
242
258
 
259
+ ### Configuration
260
+
261
+ Configuration comes from three sources with the following highest-to-lowest
262
+ precedence:
263
+
264
+ * The local config (`./.librarian/chef/config`)
265
+ * The environment
266
+ * The global config (`~/.librarian/chef/config`)
267
+
268
+ You can inspect the final configuration with:
269
+
270
+ $ librarian-chef config
271
+
272
+ You can find out where a particular key is set with:
273
+
274
+ $ librarian-chef config KEY
275
+
276
+ You can set a key at the global level with:
277
+
278
+ $ librarian-chef config KEY VALUE --global
279
+
280
+ And remove it with:
281
+
282
+ $ librarian-chef config KEY --global --delete
283
+
284
+ You can set a key at the local level with:
285
+
286
+ $ librarian-chef config KEY VALUE --local
287
+
288
+ And remove it with:
289
+
290
+ $ librarian-chef config KEY --local --delete
291
+
292
+ You cannot set or delete environment-level config keys with the CLI.
293
+
294
+ Configuration set at either the global or local level will affect subsequent
295
+ invocations of `librarian-chef`. Configurations set at the environment level are
296
+ not saved and will not affect subsequent invocations of `librarian-chef`.
297
+
298
+ You can pass a config at the environment level by taking the original config key
299
+ and transforming it: replace hyphens (`-`) with underscores (`_`) and periods
300
+ (`.`) with doubled underscores (`__`), uppercase, and finally prefix with
301
+ `LIBRARIAN_CHEF_`. For example, to pass a config in the environment for the key
302
+ `part-one.part-two`, set the environment variable
303
+ `LIBRARIAN_CHEF_PART_ONE__PART_TWO`.
304
+
305
+ Configuration affects how various commands operate.
306
+
307
+ * The `path` config sets the cookbooks directory to install to. If a relative
308
+ path, it is relative to the directory containing the `Cheffile`. The
309
+ equivalent environment variable is `LIBRARIAN_CHEF_PATH`.
310
+
311
+ * The `install.strip-dot-git` config causes the `.git/` directory to be stripped
312
+ out when installing cookbooks from a git source. This must be set to exactly
313
+ "1" to cause this behavior. The equivalent environment variable is
314
+ `LIBRARIAN_CHEF_INSTALL__STRIP_DOT_GIT`.
315
+
316
+ Configuration can be set by passing specific options to other commands.
317
+
318
+ * The `path` config can be set at the local level by passing the `--path` option
319
+ to the `install` command. It can be unset at the local level by passing the
320
+ `--no-path` option to the `install` command. Note that if this is set at the
321
+ environment or global level then, even if `--no-path` is given as an option,
322
+ the environment or global config will be used.
323
+
324
+ * The `install.strip-dot-git` config can be set at the local level by passing
325
+ the `--strip-dot-git` option to the `install` command. It can be unset at the
326
+ local level by passing the `--no-strip-dot-git` option.
327
+
243
328
  ### Knife Integration
244
329
 
245
330
  You can integrate your `knife.rb` with Librarian-Chef.
@@ -4,6 +4,7 @@ Feature: cli/init
4
4
 
5
5
  Scenario: Initing a directory
6
6
  When I run `librarian-chef init`
7
+ Then the exit status should be 0
7
8
  Then a file named "Cheffile" should exist
8
9
 
9
10
 
@@ -25,6 +25,18 @@ Feature: cli/show
25
25
 
26
26
 
27
27
 
28
+ Scenario: Showing al without a lockfile
29
+ Given I remove the file "Cheffile.lock"
30
+ When I run `librarian-chef show`
31
+ Then the exit status should be 1
32
+ Then the output should contain exactly:
33
+ """
34
+ Be sure to install first!
35
+
36
+ """
37
+
38
+
39
+
28
40
  Scenario: Showing all
29
41
  When I run `librarian-chef show`
30
42
  Then the exit status should be 0
@@ -22,6 +22,26 @@ module Librarian
22
22
  copy_file environment.specfile_name
23
23
  end
24
24
 
25
+ desc "install", "Resolves and installs all of the dependencies you specify."
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
+
25
45
  end
26
46
  end
27
47
  end
@@ -11,7 +11,15 @@ module Librarian
11
11
  end
12
12
 
13
13
  def install_path
14
- project_path.join("cookbooks")
14
+ part = config_db["path"] || "cookbooks"
15
+ project_path.join(part)
16
+ end
17
+
18
+ def config_keys
19
+ super + %w[
20
+ install.strip-dot-git
21
+ path
22
+ ]
15
23
  end
16
24
 
17
25
  end
@@ -6,6 +6,19 @@ module Librarian
6
6
  module Source
7
7
  class Git < Librarian::Source::Git
8
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
+
9
22
  end
10
23
  end
11
24
  end
@@ -19,8 +19,7 @@ module Librarian
19
19
  install_path.rmtree
20
20
  end
21
21
 
22
- debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
23
- FileUtils.cp_r(found_path, install_path)
22
+ install_perform_step_copy!(found_path, install_path)
24
23
  end
25
24
 
26
25
  def fetch_version(name, extra)
@@ -33,6 +32,11 @@ module Librarian
33
32
 
34
33
  private
35
34
 
35
+ def install_perform_step_copy!(found_path, install_path)
36
+ debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
37
+ FileUtils.cp_r(found_path, install_path)
38
+ end
39
+
36
40
  def manifest_data(name)
37
41
  @manifest_data ||= { }
38
42
  @manifest_data[name] ||= fetch_manifest_data(name)
data/lib/librarian/cli.rb CHANGED
@@ -57,28 +57,49 @@ module Librarian
57
57
  say "librarian-#{root_module.version}"
58
58
  end
59
59
 
60
+ desc "config", "Show or edit the config."
61
+ option "verbose", :type => :boolean, :default => false
62
+ option "line-numbers", :type => :boolean, :default => false
63
+ option "global", :type => :boolean, :default => false
64
+ option "local", :type => :boolean, :default => false
65
+ option "delete", :type => :boolean, :default => false
66
+ def config(key = nil, value = nil)
67
+ if key
68
+ raise Error, "cannot set both value and delete" if value && options["delete"]
69
+ if options["delete"]
70
+ raise Error, "must set either global or local" unless options["global"] ^ options["local"]
71
+ scope = options["global"] ? :global : options["local"] ? :local : nil
72
+ environment.config_db[key, scope] = nil
73
+ elsif value
74
+ raise Error, "must set either global or local" unless options["global"] ^ options["local"]
75
+ scope = options["global"] ? :global : options["local"] ? :local : nil
76
+ environment.config_db[key, scope] = value
77
+ else
78
+ raise Error, "cannot set both global and local" if options["global"] && options["local"]
79
+ scope = options["global"] ? :global : options["local"] ? :local : nil
80
+ if value = environment.config_db[key, scope]
81
+ prefix = scope ? "#{key} (#{scope})" : key
82
+ say "#{prefix}: #{value}"
83
+ end
84
+ end
85
+ else
86
+ environment.config_db.keys.each do |key|
87
+ say "#{key}: #{environment.config_db[key]}"
88
+ end
89
+ end
90
+ end
91
+
60
92
  desc "clean", "Cleans out the cache and install paths."
61
- option "verbose"
62
- option "line-numbers"
93
+ option "verbose", :type => :boolean, :default => false
94
+ option "line-numbers", :type => :boolean, :default => false
63
95
  def clean
64
96
  ensure!
65
97
  clean!
66
98
  end
67
99
 
68
- desc "install", "Resolves and installs all of the dependencies you specify."
69
- option "verbose"
70
- option "line-numbers"
71
- option "clean"
72
- def install
73
- ensure!
74
- clean! if options["clean"]
75
- resolve!
76
- install!
77
- end
78
-
79
100
  desc "update", "Updates and installs the dependencies you specify."
80
- option "verbose"
81
- option "line-numbers"
101
+ option "verbose", :type => :boolean, :default => false
102
+ option "line-numbers", :type => :boolean, :default => false
82
103
  def update(*names)
83
104
  ensure!
84
105
  if names.empty?
@@ -90,8 +111,8 @@ module Librarian
90
111
  end
91
112
 
92
113
  desc "outdated", "Lists outdated dependencies."
93
- option "verbose"
94
- option "line-numbers"
114
+ option "verbose", :type => :boolean, :default => false
115
+ option "line-numbers", :type => :boolean, :default => false
95
116
  def outdated
96
117
  ensure!
97
118
  resolution = environment.lock
@@ -104,12 +125,16 @@ module Librarian
104
125
  end
105
126
 
106
127
  desc "show", "Shows dependencies"
107
- option "verbose"
108
- option "line-numbers"
128
+ option "verbose", :type => :boolean, :default => false
129
+ option "line-numbers", :type => :boolean, :default => false
109
130
  option "detailed", :type => :boolean
110
131
  def show(*names)
111
132
  ensure!
112
- manifest_presenter.present(names, :detailed => options["detailed"])
133
+ if environment.lockfile_path.file?
134
+ manifest_presenter.present(names, :detailed => options["detailed"])
135
+ else
136
+ raise Error, "Be sure to install first!"
137
+ end
113
138
  end
114
139
 
115
140
  desc "init", "Initializes the current directory."
@@ -0,0 +1,7 @@
1
+ require "librarian/config/database"
2
+
3
+ module Librarian
4
+ module Config
5
+
6
+ end
7
+ end
@@ -0,0 +1,205 @@
1
+ require "pathname"
2
+
3
+ require "librarian/config/file_source"
4
+ require "librarian/config/hash_source"
5
+
6
+ module Librarian
7
+ module Config
8
+ class Database
9
+
10
+ class << self
11
+ def library
12
+ name.split("::").first.downcase
13
+ end
14
+ end
15
+
16
+ attr_accessor :adapter_name
17
+ private :adapter_name=
18
+
19
+ attr_accessor :root, :assigned_specfile_name
20
+ private :root=, :assigned_specfile_name=
21
+
22
+ attr_accessor :underlying_env, :underlying_pwd, :underlying_home
23
+ private :underlying_env=, :underlying_pwd=, :underlying_home=
24
+
25
+ def initialize(adapter_name, options = { })
26
+ self.adapter_name = adapter_name or raise ArgumentError, "must provide adapter_name"
27
+
28
+ options[:project_path] || options[:pwd] or raise ArgumentError, "must provide project_path or pwd"
29
+
30
+ self.root = options[:project_path] && Pathname(options[:project_path])
31
+ self.assigned_specfile_name = options[:specfile_name]
32
+ self.underlying_env = options[:env] or raise ArgumentError, "must provide env"
33
+ self.underlying_pwd = options[:pwd] && Pathname(options[:pwd])
34
+ self.underlying_home = options[:home] && Pathname(options[:home])
35
+ end
36
+
37
+ def global
38
+ memo(__method__) { new_file_source(global_config_path) }
39
+ end
40
+
41
+ def env
42
+ memo(__method__) { HashSource.new(adapter_name, :name => "environment", :raw => env_source_data) }
43
+ end
44
+
45
+ def local
46
+ memo(__method__) { new_file_source(local_config_path) }
47
+ end
48
+
49
+ def [](key, scope = nil)
50
+ case scope
51
+ when "local", :local then local[key]
52
+ when "env", :env then env[key]
53
+ when "global", :global then global[key]
54
+ when nil then local[key] || env[key] || global[key]
55
+ else raise Error, "bad scope"
56
+ end
57
+ end
58
+
59
+ def []=(key, scope, value)
60
+ case scope
61
+ when "local", :local then local[key] = value
62
+ when "global", :global then global[key] = value
63
+ else raise Error, "bad scope"
64
+ end
65
+ end
66
+
67
+ def keys
68
+ [local, env, global].inject([]){|a, e| a.concat(e.keys) ; a}.sort.uniq
69
+ end
70
+
71
+ def project_path
72
+ root || specfile_path.dirname
73
+ end
74
+
75
+ def specfile_path
76
+ if root
77
+ root + (assigned_specfile_name || default_specfile_name)
78
+ else
79
+ env_specfile_path || default_specfile_path
80
+ end
81
+ end
82
+
83
+ def specfile_name
84
+ specfile_path.basename.to_s
85
+ end
86
+
87
+ def lockfile_path
88
+ project_path + lockfile_name
89
+ end
90
+
91
+ def lockfile_name
92
+ "#{specfile_name}.lock"
93
+ end
94
+
95
+ private
96
+
97
+ def new_file_source(config_path)
98
+ return unless config_path
99
+
100
+ FileSource.new(adapter_name,
101
+ :config_path => config_path,
102
+ :forbidden_keys => [config_key, specfile_key]
103
+ )
104
+ end
105
+
106
+ def global_config_path
107
+ env_global_config_path || default_global_config_path
108
+ end
109
+
110
+ def env_global_config_path
111
+ memo(__method__) { env[config_key] }
112
+ end
113
+
114
+ def default_global_config_path
115
+ underlying_home && underlying_home + config_name
116
+ end
117
+
118
+ def local_config_path
119
+ root_local_config_path || env_local_config_path || default_local_config_path
120
+ end
121
+
122
+ def root_local_config_path
123
+ root && root + config_name
124
+ end
125
+
126
+ def env_specfile_path
127
+ memo(__method__) do
128
+ path = env[specfile_key]
129
+ path && Pathname(path)
130
+ end
131
+ end
132
+
133
+ def default_specfile_path
134
+ default_project_root_path + (assigned_specfile_name || default_specfile_name)
135
+ end
136
+
137
+ def env_local_config_path
138
+ return unless env_specfile_path
139
+
140
+ env_specfile_path.dirname + config_name
141
+ end
142
+
143
+ def default_local_config_path
144
+ default_project_root_path + config_name
145
+ end
146
+
147
+ def default_project_root_path
148
+ if root
149
+ root
150
+ else
151
+ path = underlying_pwd
152
+ path = path.dirname until project_root_path?(path) || path.dirname == path
153
+ project_root_path?(path) ? path : underlying_pwd
154
+ end
155
+ end
156
+
157
+ def project_root_path?(path)
158
+ File.file?(path + default_specfile_name)
159
+ end
160
+
161
+ def config_key
162
+ "config"
163
+ end
164
+
165
+ def specfile_key
166
+ "#{adapter_name}file"
167
+ end
168
+
169
+ def default_specfile_name
170
+ "#{adapter_name.capitalize}file"
171
+ end
172
+
173
+ def library
174
+ self.class.library
175
+ end
176
+
177
+ def config_name_prefix
178
+ ".#{library}"
179
+ end
180
+
181
+ def config_name
182
+ File.join(*[config_name_prefix, adapter_name, "config"])
183
+ end
184
+
185
+ def raw_key_prefix
186
+ "#{library.upcase}_#{adapter_name.upcase}_"
187
+ end
188
+
189
+ def env_source_data
190
+ prefix = raw_key_prefix
191
+
192
+ data = underlying_env.dup
193
+ data.reject!{|k, _| !k.start_with?(prefix) || k.size <= prefix.size}
194
+ data
195
+ end
196
+
197
+ def memo(key)
198
+ key = "@#{key}"
199
+ instance_variable_set(key, yield) unless instance_variable_defined?(key)
200
+ instance_variable_get(key)
201
+ end
202
+
203
+ end
204
+ end
205
+ end