librarian 0.0.23 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
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