batali 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +112 -2
- data/batali.gemspec +3 -1
- data/bin/batali +2 -0
- data/lib/batali/b_file.rb +48 -28
- data/lib/batali/command/install.rb +22 -16
- data/lib/batali/command/resolve.rb +35 -21
- data/lib/batali/command.rb +13 -0
- data/lib/batali/git.rb +54 -0
- data/lib/batali/manifest.rb +2 -2
- data/lib/batali/origin/git.rb +54 -0
- data/lib/batali/origin/path.rb +79 -0
- data/lib/batali/origin/remote_site.rb +100 -0
- data/lib/batali/origin.rb +20 -0
- data/lib/batali/source/git.rb +36 -0
- data/lib/batali/source/site.rb +32 -21
- data/lib/batali/source.rb +15 -1
- data/lib/batali/unit_loader.rb +70 -0
- data/lib/batali/utility.rb +7 -0
- data/lib/batali/version.rb +1 -1
- data/lib/batali.rb +4 -1
- metadata +40 -5
- data/lib/batali/remote_site.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68737e2f016b576dbe9b038f1d6a3930b17fd8ee
|
4
|
+
data.tar.gz: 47a886779b940b36035dfa68d8cf07f4631bb24f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdd04d4d64c21b700d89b85403367badaa16d4168461e4b393105d39e4e2b243e79cde92b5dad431fc1a765f461bced6b341bb8cb63067c778dc1d83796e7a2a
|
7
|
+
data.tar.gz: c4beb1a4fe18118e09c53962f04603aeeb5246b8fcbf565577ccc773b1aeb03d11c9b5bbdc414ef704c3765ed423bd184b74ff4a373f7fc66e0ad5af2bcb2f5a
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -27,10 +27,120 @@ by default.
|
|
27
27
|
|
28
28
|
_IT WILL DESTROY YOUR COOKBOOKS DIRECTORY BY DEFAULT_
|
29
29
|
|
30
|
-
|
31
|
-
only site sources can be defined (no path, or git, or anything else).
|
30
|
+
## Commands
|
32
31
|
|
32
|
+
* `batali resolve` - Resolve dependencies and produce `batali.manifest`
|
33
|
+
* `batali install` - Install entries from the `batali.manifest`
|
34
|
+
* `batali update` - Perform `resolve` and then `install`
|
35
|
+
|
36
|
+
## Features
|
37
|
+
|
38
|
+
### Origins
|
39
|
+
|
40
|
+
Currently supported "origins":
|
41
|
+
|
42
|
+
* RemoteSite
|
43
|
+
* Path
|
44
|
+
* Git
|
45
|
+
|
46
|
+
#### RemoteSite
|
47
|
+
|
48
|
+
This is simply a supermarket endpoint:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
source 'https://supermarket.chef.io'
|
52
|
+
```
|
53
|
+
|
54
|
+
Multiple endpoints can be provided by specifying multiple
|
55
|
+
`source` lines. They can also be named:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
source 'https://supermarket.chef.io', :name => 'opscode'
|
59
|
+
source 'https://cookbooks.example.com', :name => 'example'
|
60
|
+
```
|
61
|
+
|
62
|
+
##### Path
|
63
|
+
|
64
|
+
Paths are defined via cookbook entries:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
cookbook 'example', path: '/path/to/example'
|
68
|
+
```
|
69
|
+
|
70
|
+
##### Git
|
71
|
+
|
72
|
+
Git sources are defined via cookbook entries:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
cookbook 'example', git: 'git://git.example.com/example-repo.git', ref: 'master'
|
76
|
+
```
|
77
|
+
|
78
|
+
### Least Impact Updates
|
79
|
+
|
80
|
+
After a `batali.manifest` file has been generated, subsequent `resolve` requests
|
81
|
+
will update cookbook versions using a "least impact" approach. This means that
|
82
|
+
by default if the `Batali` file has not changed, running a `batali resolve` will
|
83
|
+
be a noop, even if new versions of cookbooks may be available. This helps to reduce
|
84
|
+
unintended upgrades that may break things due to a required cookbook update. Allowing
|
85
|
+
a cookbook to be updated is done simply by adding it to the request:
|
86
|
+
|
87
|
+
```
|
88
|
+
$ batali resolve example
|
89
|
+
```
|
90
|
+
|
91
|
+
This will only update the version of the example cookbook, and any dependency cookbooks
|
92
|
+
that _must_ be updated to provide resolution. Multiple cookbooks can be listed:
|
93
|
+
|
94
|
+
```
|
95
|
+
$ batali resolve example ipsum lorem
|
96
|
+
```
|
97
|
+
|
98
|
+
or this feature can be disabled to allow everything to be updated to the latest
|
99
|
+
possible versions:
|
100
|
+
|
101
|
+
```
|
102
|
+
$ batali resolve --no-least-impact
|
103
|
+
```
|
104
|
+
|
105
|
+
### Light weight
|
106
|
+
|
107
|
+
One of the goals for batali was being light weight resolver, in the same vein as
|
108
|
+
the [librarian][1] project. This means it does nothing more than manage local cookbooks. This
|
109
|
+
includes dependency and constraint resolution, as well as providing a local installation
|
110
|
+
of assets defined within the generated manifest. It provides no extra features outside of
|
111
|
+
that scope.
|
112
|
+
|
113
|
+
### Multiple platform support
|
114
|
+
|
115
|
+
Batali does not rely on the [chef][2] gem to function. This removes any dependencies on
|
116
|
+
gems that may be incompatible outside the MRI platform.
|
117
|
+
|
118
|
+
### Isolated manifest files
|
119
|
+
|
120
|
+
Manifest files are fully isolated. The resolver does not need to perform any actions
|
121
|
+
for installing cookbooks defined within the manifest. This allows for easy transmission
|
122
|
+
and direct installation of a manifest without the requirement of re-pulling information
|
123
|
+
from sources.
|
124
|
+
|
125
|
+
### Infrastructure manifests
|
126
|
+
|
127
|
+
Batali aims to solve the issue of full infrastructure resolution: resolving dependencies
|
128
|
+
from an infrastructure repository. Resolving a single dependency path will not provide
|
129
|
+
a correct resolution. This is because environments or run lists can provide extra constraints
|
130
|
+
that will result in unsolvable resolutions on individual nodes. In this case we want
|
131
|
+
to know what cookbooks are _allowed_ within a solution, and ensure all those cookbooks
|
132
|
+
are available. Batali provides infrastructure level manifests by setting the `infrastructure`
|
133
|
+
flag:
|
134
|
+
|
135
|
+
```
|
136
|
+
$ batali resolve --infrastructure
|
137
|
+
```
|
138
|
+
|
139
|
+
_NOTE: Depending on constraints defined within the Batali file, this can be a very large manifest_
|
33
140
|
|
34
141
|
# Info
|
35
142
|
|
36
143
|
* Repository: https://github.com/hw-labs/batali
|
144
|
+
|
145
|
+
[1]: https://rubygems.org/gems/librarian "A Framework for Bundlers"
|
146
|
+
[2]: https://rubygems.org/gems/chef "A systems integration framework"
|
data/batali.gemspec
CHANGED
@@ -10,10 +10,12 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = 'Magic'
|
11
11
|
s.require_path = 'lib'
|
12
12
|
s.license = 'Apache 2.0'
|
13
|
-
s.add_runtime_dependency '
|
13
|
+
s.add_runtime_dependency 'attribute_struct', '>= 0.2.14'
|
14
|
+
s.add_runtime_dependency 'grimoire', '>= 0.1.4'
|
14
15
|
s.add_runtime_dependency 'bogo', '>= 0.1.12'
|
15
16
|
s.add_runtime_dependency 'bogo-cli', '>= 0.1.8'
|
16
17
|
s.add_runtime_dependency 'bogo-config', '>= 0.1.10'
|
18
|
+
s.add_runtime_dependency 'git'
|
17
19
|
s.add_runtime_dependency 'http'
|
18
20
|
s.add_development_dependency 'minitest'
|
19
21
|
s.add_development_dependency 'pry'
|
data/bin/batali
CHANGED
@@ -32,6 +32,7 @@ Bogo::Cli::Setup.define do
|
|
32
32
|
on :d, 'dry-run', 'Print changes'
|
33
33
|
on :l, 'least-impact', 'Update cookbooks with minimal version impact', :default => true
|
34
34
|
on :i, 'install', 'Install cookbooks after update', :default => true
|
35
|
+
on :I, 'infrastructure', 'Resolve infrastructure cookbooks'
|
35
36
|
|
36
37
|
run do |opts, args|
|
37
38
|
Batali::Command::Update.new({:update => opts.to_hash}, args).execute!
|
@@ -43,6 +44,7 @@ Bogo::Cli::Setup.define do
|
|
43
44
|
self.instance_exec(&global_opts)
|
44
45
|
on :d, 'dry-run', 'Print changes'
|
45
46
|
on :l, 'least-impact', 'Update cookbooks with minimal version impact', :default => true
|
47
|
+
on :I, 'infrastructure', 'Resolve infrastructure cookbooks'
|
46
48
|
|
47
49
|
run do |opts, args|
|
48
50
|
Batali::Command::Resolve.new({:resolve => opts.to_hash}, args).execute!
|
data/lib/batali/b_file.rb
CHANGED
@@ -4,8 +4,8 @@ module Batali
|
|
4
4
|
|
5
5
|
class Struct < AttributeStruct
|
6
6
|
|
7
|
-
def cookbook(*args)
|
8
|
-
set!(:cookbook, args)
|
7
|
+
def cookbook(*args, &block)
|
8
|
+
set!(:cookbook, args, &block)
|
9
9
|
self
|
10
10
|
end
|
11
11
|
|
@@ -24,38 +24,58 @@ module Batali
|
|
24
24
|
|
25
25
|
class BFile < Bogo::Config
|
26
26
|
|
27
|
-
|
27
|
+
# @return [Proc] cookbook convert
|
28
|
+
def self.cookbook_coerce
|
29
|
+
proc do |v|
|
30
|
+
case v
|
31
|
+
when Array
|
32
|
+
case v.last
|
33
|
+
when String
|
34
|
+
Cookbook.new(
|
35
|
+
:name => v.first,
|
36
|
+
:constraint => v.slice(1, v.size)
|
37
|
+
)
|
38
|
+
when Hash
|
39
|
+
c_name = v.first
|
40
|
+
Cookbook.new(
|
41
|
+
v.last.merge(
|
42
|
+
:name => c_name
|
43
|
+
)
|
44
|
+
)
|
45
|
+
else
|
46
|
+
raise ArgumentError.new "Unable to coerce given type `#{v.class}` to `Batali::BFile::Cookbook`!"
|
47
|
+
end
|
48
|
+
when String
|
49
|
+
Cookbook.new(:name => v)
|
50
|
+
else
|
51
|
+
raise ArgumentError.new "Unable to coerce given type `#{v.class}` to `Batali::BFile::Cookbook`!"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Cookbook < Utility
|
28
57
|
attribute :name, String, :required => true
|
29
58
|
attribute :constraint, String, :multiple => true
|
30
|
-
attribute :git,
|
59
|
+
attribute :git, String
|
60
|
+
attribute :ref, String
|
31
61
|
attribute :path, String
|
32
62
|
end
|
33
63
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:name => c_name,
|
49
|
-
:constraint => constraints
|
50
|
-
)
|
51
|
-
else
|
52
|
-
raise ArgumentError.new "Unable to coerce given type `#{v.class}` to `Batali::BFile::Cookbook`!"
|
53
|
-
end
|
54
|
-
}
|
64
|
+
class Restriction < Utility
|
65
|
+
attribute :cookbook, String, :required => true
|
66
|
+
attribute :source, String, :required => true
|
67
|
+
end
|
68
|
+
|
69
|
+
class Group < Utility
|
70
|
+
attribute :name, String, :required => true
|
71
|
+
attribute :cookbook, Cookbook, :multiple => true, :required => true, :coerce => BFile.cookbook_coerce
|
72
|
+
end
|
73
|
+
|
74
|
+
attribute :restrict, Restriction, :multiple => true, :coerce => lambda{|v| Restriction.new(:cookbook => v.first, :source => v.last)}
|
75
|
+
attribute :source, Origin::RemoteSite, :multiple => true, :coerce => lambda{|v| Origin::RemoteSite.new(:endpoint => v)}
|
76
|
+
attribute :group, Group, :multiple => true, :coerce => lambda{|v| Group.new()}
|
77
|
+
attribute :cookbook, Cookbook, :multiple => true, :coerce => BFile.cookbook_coerce
|
55
78
|
|
56
|
-
## TODO: supported values still required
|
57
|
-
# attribute :restrict -- restrict cookbooks of name `x` to source named `y`
|
58
|
-
# attribute :group -- cookbook grouping (i.e. :integration)
|
59
79
|
end
|
60
80
|
|
61
81
|
end
|
@@ -16,25 +16,31 @@ module Batali
|
|
16
16
|
FileUtils.mkdir_p(install_path)
|
17
17
|
nil
|
18
18
|
end
|
19
|
-
|
20
|
-
manifest.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
unit.name
|
27
|
-
),
|
28
|
-
File.join(
|
29
|
-
install_path,
|
30
|
-
unit.name
|
19
|
+
if(manifest.cookbook.nil? || manifest.cookbook.empty?)
|
20
|
+
ui.error "No cookbooks defined within manifest! Try resolving first. (`batali resolve`)"
|
21
|
+
else
|
22
|
+
run_action('Installing cookbooks') do
|
23
|
+
manifest.cookbook.each do |unit|
|
24
|
+
if(unit.source.respond_to?(:cache))
|
25
|
+
unit.source.cache = cache_directory(
|
26
|
+
Bogo::Utility.snake(unit.source.class.name.split('::').last)
|
31
27
|
)
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
end
|
29
|
+
asset_path = unit.source.asset
|
30
|
+
begin
|
31
|
+
FileUtils.cp_r(
|
32
|
+
File.join(asset_path, '.'),
|
33
|
+
File.join(
|
34
|
+
install_path,
|
35
|
+
"#{unit.name}-#{unit.version}"
|
36
|
+
)
|
37
|
+
)
|
38
|
+
ensure
|
39
|
+
unit.source.clean_asset(asset_path)
|
40
|
+
end
|
35
41
|
end
|
42
|
+
nil
|
36
43
|
end
|
37
|
-
nil
|
38
44
|
end
|
39
45
|
end
|
40
46
|
end
|
@@ -11,15 +11,17 @@ module Batali
|
|
11
11
|
def execute!
|
12
12
|
system = Grimoire::System.new
|
13
13
|
run_action 'Loading sources' do
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
UnitLoader.new(
|
15
|
+
:file => batali_file,
|
16
|
+
:system => system,
|
17
|
+
:cache => cache_directory(:git)
|
18
|
+
).populate!
|
17
19
|
nil
|
18
20
|
end
|
19
21
|
requirements = Grimoire::RequirementList.new(
|
20
22
|
:name => :batali_resolv,
|
21
23
|
:requirements => batali_file.cookbook.map{ |ckbk|
|
22
|
-
[ckbk.name, *(ckbk.constraint.empty? ? ['> 0'] : ckbk.constraint)]
|
24
|
+
[ckbk.name, *(ckbk.constraint.nil? || ckbk.constraint.empty? ? ['> 0'] : ckbk.constraint)]
|
23
25
|
}
|
24
26
|
)
|
25
27
|
solv = Grimoire::Solver.new(
|
@@ -27,27 +29,39 @@ module Batali
|
|
27
29
|
:system => system,
|
28
30
|
:score_keeper => score_keeper
|
29
31
|
)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
if(opts[:infrastructure])
|
33
|
+
ui.info 'Performing infrastructure path resolution.'
|
34
|
+
run_action 'Writing infrastructure manifest file' do
|
35
|
+
File.open('batali.manifest', 'w') do |file|
|
36
|
+
manifest = Manifest.new(:cookbook => solv.world.units.values.flatten)
|
37
|
+
file.write MultiJson.dump(manifest, :pretty => true)
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
37
41
|
else
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
ui.info 'Performing single path resolution.'
|
43
|
+
results = []
|
44
|
+
run_action 'Resolving dependency constraints' do
|
45
|
+
results = solv.generate!
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
if(results.empty?)
|
49
|
+
ui.error 'No solutions found defined requirements!'
|
50
|
+
else
|
51
|
+
ideal_solution = results.pop
|
52
|
+
dry_run('manifest file write') do
|
53
|
+
run_action 'Writing manifest' do
|
54
|
+
manifest = Manifest.new(:cookbook => ideal_solution.units)
|
55
|
+
File.open('batali.manifest', 'w') do |file|
|
56
|
+
file.write MultiJson.dump(manifest, :pretty => true)
|
57
|
+
end
|
58
|
+
nil
|
44
59
|
end
|
45
|
-
nil
|
46
60
|
end
|
61
|
+
ui.info "Number of solutions collected for defined requirements: #{results.size + 1}"
|
62
|
+
ui.info 'Ideal solution:'
|
63
|
+
ui.puts ideal_solution.units.sort_by(&:name).map{|u| "#{u.name}<#{u.version}>"}
|
47
64
|
end
|
48
|
-
ui.info "Found #{results.size} solutions for defined requirements."
|
49
|
-
ui.info 'Ideal solution:'
|
50
|
-
ui.puts ideal_solution.units.sort_by(&:name).map{|u| "#{u.name}<#{u.version}>"}
|
51
65
|
end
|
52
66
|
end
|
53
67
|
|
data/lib/batali/command.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'batali'
|
2
|
+
require 'fileutils'
|
2
3
|
|
3
4
|
module Batali
|
4
5
|
# Customized command base for Batali
|
@@ -32,6 +33,18 @@ module Batali
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
# @return [String] path to local cache
|
37
|
+
def cache_directory(*args)
|
38
|
+
memoize(['cache_directory', *args].join('_')) do
|
39
|
+
directory = opts.fetch(:cache_directory, '/tmp/batali-cache')
|
40
|
+
unless(args.empty?)
|
41
|
+
directory = File.join(directory, *args.map(&:to_s))
|
42
|
+
end
|
43
|
+
FileUtils.mkdir_p(directory)
|
44
|
+
directory
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
35
48
|
# Do not execute block if dry run
|
36
49
|
#
|
37
50
|
# @param action [String] action to be performed
|
data/lib/batali/git.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'git'
|
2
|
+
require 'batali'
|
3
|
+
|
4
|
+
module Batali
|
5
|
+
module Git
|
6
|
+
|
7
|
+
# @return [String] path to repository clone
|
8
|
+
def base_path
|
9
|
+
File.join(cache, Base64.urlsafe_encode64(url))
|
10
|
+
end
|
11
|
+
|
12
|
+
# Clone the repository to the local machine
|
13
|
+
#
|
14
|
+
# @return [TrueClass]
|
15
|
+
def clone_repository
|
16
|
+
if(File.directory?(base_path))
|
17
|
+
repo = ::Git.open(base_path)
|
18
|
+
repo.checkout('master')
|
19
|
+
repo.pull
|
20
|
+
repo.fetch
|
21
|
+
else
|
22
|
+
::Git.clone(url, base_path)
|
23
|
+
end
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Duplicate reference and store
|
28
|
+
#
|
29
|
+
# @return [String] commit SHA
|
30
|
+
# @note this will update ref to SHA
|
31
|
+
def ref_dup
|
32
|
+
git = ::Git.open(base_path)
|
33
|
+
git.checkout(ref)
|
34
|
+
self.ref = git.log.first.sha
|
35
|
+
self.path = File.join(cache, ref)
|
36
|
+
unless(File.directory?(path))
|
37
|
+
FileUtils.mkdir_p(path)
|
38
|
+
FileUtils.cp_r(File.join(base_path, '.'), path)
|
39
|
+
FileUtils.rm_rf(File.join(path, '.git'))
|
40
|
+
end
|
41
|
+
self.path
|
42
|
+
end
|
43
|
+
|
44
|
+
# Load attributes into class
|
45
|
+
def self.included(klass)
|
46
|
+
klass.class_eval do
|
47
|
+
attribute :url, String, :required => true
|
48
|
+
attribute :ref, String, :required => true
|
49
|
+
attribute :cache, String
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/batali/manifest.rb
CHANGED
@@ -2,11 +2,11 @@ require 'batali'
|
|
2
2
|
|
3
3
|
module Batali
|
4
4
|
# Collection of resolved units
|
5
|
-
class Manifest <
|
5
|
+
class Manifest < Utility
|
6
6
|
|
7
7
|
include Bogo::Memoization
|
8
8
|
|
9
|
-
attribute :cookbook, Unit, :multiple => true, :coerce => lambda{|v| Unit.new(v)}
|
9
|
+
attribute :cookbook, Unit, :multiple => true, :coerce => lambda{|v| Unit.new(v)}, :default => []
|
10
10
|
|
11
11
|
# Build manifest from given path. If no file exists, empty
|
12
12
|
# manifest will be provided.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'git'
|
2
|
+
require 'batali'
|
3
|
+
|
4
|
+
module Batali
|
5
|
+
class Origin
|
6
|
+
# Fetch unit from local path
|
7
|
+
class Git < Path
|
8
|
+
|
9
|
+
include Batali::Git
|
10
|
+
|
11
|
+
def initialize(args={})
|
12
|
+
unless(args[:path])
|
13
|
+
args[:path] = '/dev/null'
|
14
|
+
end
|
15
|
+
super
|
16
|
+
self.identifier = Smash.new(
|
17
|
+
:url => url,
|
18
|
+
:ref => ref
|
19
|
+
).checksum
|
20
|
+
unless(name?)
|
21
|
+
self.name = self.identifier
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array<Unit>]
|
26
|
+
def units
|
27
|
+
memoize(:g_units) do
|
28
|
+
items = super
|
29
|
+
items.first.source = Source::Git.new(
|
30
|
+
:url => url,
|
31
|
+
:ref => ref,
|
32
|
+
:path => path
|
33
|
+
)
|
34
|
+
items
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Smash] metadata information
|
39
|
+
def load_metadata
|
40
|
+
fetch_repo
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] path to repository
|
45
|
+
def fetch_repo
|
46
|
+
memoize(:fetch_repo) do
|
47
|
+
clone_repository
|
48
|
+
ref_dup
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
class Origin
|
5
|
+
# Fetch unit from local path
|
6
|
+
class Path < Origin
|
7
|
+
|
8
|
+
class Metadata < AttributeStruct
|
9
|
+
|
10
|
+
def depends(*args)
|
11
|
+
set!(:depends, args)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
include Bogo::Memoization
|
18
|
+
|
19
|
+
attribute :path, String, :required => true
|
20
|
+
|
21
|
+
def initialize(*_)
|
22
|
+
super
|
23
|
+
self.identifier = Smash.new(:path => path).checksum
|
24
|
+
unless(name?)
|
25
|
+
self.name = self.identifier
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Array<Unit>]
|
30
|
+
def units
|
31
|
+
memoize(:units) do
|
32
|
+
info = load_metadata
|
33
|
+
[
|
34
|
+
Unit.new(
|
35
|
+
:name => info[:name],
|
36
|
+
:version => info[:version],
|
37
|
+
:dependencies => info[:depends],
|
38
|
+
:source => Smash.new(
|
39
|
+
:type => :path,
|
40
|
+
:version => info[:version],
|
41
|
+
:path => path,
|
42
|
+
:dependencies => info[:depends].map{ |dep|
|
43
|
+
if(dep.size == 1)
|
44
|
+
dep.push('> 0')
|
45
|
+
else
|
46
|
+
dep.to_a
|
47
|
+
end
|
48
|
+
}
|
49
|
+
)
|
50
|
+
)
|
51
|
+
]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Smash] metadata information
|
56
|
+
def load_metadata
|
57
|
+
memoize(:metadata) do
|
58
|
+
if(File.exists?(json = File.join(path, 'metadata.json')))
|
59
|
+
MultiJson.load(json).to_smash
|
60
|
+
elsif(File.exists?(rb = File.join(path, 'metadata.rb')))
|
61
|
+
struct = Metadata.new
|
62
|
+
struct.set_state!(:value_collapse => true)
|
63
|
+
File.readlines(rb).find_all do |line|
|
64
|
+
line.start_with?('name') ||
|
65
|
+
line.start_with?('version') ||
|
66
|
+
line.start_with?('depends')
|
67
|
+
end.each do |line|
|
68
|
+
struct.instance_eval(line)
|
69
|
+
end
|
70
|
+
struct._dump.to_smash
|
71
|
+
else
|
72
|
+
raise Errno::ENOENT.new('No metadata file available to load!')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'http'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Batali
|
8
|
+
class Origin
|
9
|
+
# Fetch unit information from remote site
|
10
|
+
class RemoteSite < Origin
|
11
|
+
|
12
|
+
# Site suffix for API endpoint
|
13
|
+
COOKBOOK_API_SUFFIX = 'api/v1/cookbooks'
|
14
|
+
|
15
|
+
include Bogo::Memoization
|
16
|
+
|
17
|
+
attribute :name, String
|
18
|
+
attribute :identifier, String
|
19
|
+
attribute :endpoint, String, :required => true
|
20
|
+
attribute :force_update, [TrueClass, FalseClass], :required => true, :default => false
|
21
|
+
attribute :update_interval, Integer, :required => true, :default => 60
|
22
|
+
attribute :cache, String, :default => File.expand_path('~/.batali/cache/remote_site'), :required => true
|
23
|
+
|
24
|
+
def initialize(*_)
|
25
|
+
super
|
26
|
+
endpoint = URI.join(self.endpoint, COOKBOOK_API_SUFFIX).to_s
|
27
|
+
self.identifier = Digest::SHA256.hexdigest(endpoint)
|
28
|
+
unless(name?)
|
29
|
+
self.name = self.identifier
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] cache directory path
|
34
|
+
def cache_directory
|
35
|
+
memoize(:cache_directory) do
|
36
|
+
path = File.join(cache, identifier)
|
37
|
+
FileUtils.mkdir_p(path)
|
38
|
+
path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Array<Unit>] all units
|
43
|
+
def units
|
44
|
+
memoize(:units) do
|
45
|
+
items.map do |u_name, versions|
|
46
|
+
versions.map do |version, info|
|
47
|
+
Unit.new(
|
48
|
+
:name => u_name,
|
49
|
+
:version => version,
|
50
|
+
:dependencies => info[:dependencies].to_a,
|
51
|
+
:source => Smash.new(
|
52
|
+
:type => :site,
|
53
|
+
:url => info[:download_url],
|
54
|
+
:version => version,
|
55
|
+
:dependencies => info[:dependencies]
|
56
|
+
)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end.flatten
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
# @return [Smash] all info
|
66
|
+
def items
|
67
|
+
memoize(:items) do
|
68
|
+
MultiJson.load(File.read(fetch)).to_smash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Fetch the universe
|
73
|
+
#
|
74
|
+
# @return [String] path to universe file
|
75
|
+
def fetch
|
76
|
+
do_fetch = true
|
77
|
+
if(File.exists?(universe_path))
|
78
|
+
age = Time.now - File.mtime(universe_path)
|
79
|
+
if(age < update_interval)
|
80
|
+
do_fetch = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if(do_fetch)
|
84
|
+
t_uni = "#{universe_path}.#{SecureRandom.urlsafe_base64}"
|
85
|
+
File.open(t_uni, 'w') do |file|
|
86
|
+
file.write HTTP.get(URI.join(endpoint, 'universe')).body.to_s
|
87
|
+
end
|
88
|
+
FileUtils.mv(t_uni, universe_path)
|
89
|
+
end
|
90
|
+
universe_path
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [String] path to universe file
|
94
|
+
def universe_path
|
95
|
+
File.join(cache_directory, 'universe.json')
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Cookbook source origin
|
5
|
+
class Origin < Utility
|
6
|
+
|
7
|
+
autoload :RemoteSite, 'batali/origin/remote_site'
|
8
|
+
autoload :Git, 'batali/origin/git'
|
9
|
+
autoload :Path, 'batali/origin/path'
|
10
|
+
|
11
|
+
attribute :name, String, :required => true
|
12
|
+
attribute :identifier, String
|
13
|
+
|
14
|
+
# @return [Array<Unit>] all units
|
15
|
+
def units
|
16
|
+
raise NotImplementedError.new 'Abstract class'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module Batali
|
6
|
+
# Source of asset
|
7
|
+
class Source
|
8
|
+
# Path based source
|
9
|
+
class Git < Path
|
10
|
+
|
11
|
+
include Bogo::Memoization
|
12
|
+
include Batali::Git
|
13
|
+
|
14
|
+
attribute :path, String
|
15
|
+
|
16
|
+
# @return [String] directory containing contents
|
17
|
+
def asset
|
18
|
+
clone_repository
|
19
|
+
self.path = ref_dup
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# Overload to remove non-relevant attributes
|
24
|
+
def to_json(*args)
|
25
|
+
MultiJson.dump(
|
26
|
+
Smash.new(
|
27
|
+
:url => url,
|
28
|
+
:ref => ref,
|
29
|
+
:type => self.class.name
|
30
|
+
), *args
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/batali/source/site.rb
CHANGED
@@ -13,6 +13,8 @@ module Batali
|
|
13
13
|
attr_reader :dependencies
|
14
14
|
# @return [String] version
|
15
15
|
attr_reader :version
|
16
|
+
# @return [String] local cache path
|
17
|
+
attr_accessor :cache
|
16
18
|
|
17
19
|
attribute :url, String, :required => true
|
18
20
|
attribute :version, String, :required => true
|
@@ -38,31 +40,40 @@ module Batali
|
|
38
40
|
|
39
41
|
# @return [String] directory
|
40
42
|
def asset
|
41
|
-
path =
|
42
|
-
|
43
|
-
|
44
|
-
result = HTTP.get(
|
45
|
-
|
46
|
-
|
47
|
-
while(content = result.body.readpartial(2048))
|
48
|
-
file.write content
|
43
|
+
path = File.join(cache, Base64.urlsafe_encode64(url))
|
44
|
+
unless(File.directory?(path))
|
45
|
+
FileUtils.mkdir_p(path)
|
46
|
+
result = HTTP.get(url)
|
47
|
+
while(result.code == 302)
|
48
|
+
result = HTTP.get(result.headers['Location'])
|
49
49
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
File.open(a_path = File.join(path, 'asset'), 'w') do |file|
|
51
|
+
while(content = result.body.readpartial(2048))
|
52
|
+
file.write content
|
53
|
+
end
|
54
|
+
end
|
55
|
+
ext = Gem::Package::TarReader.new(
|
56
|
+
Zlib::GzipReader.open(a_path)
|
57
|
+
)
|
58
|
+
ext.rewind
|
59
|
+
ext.each do |entry|
|
60
|
+
next unless entry.file?
|
61
|
+
n_path = File.join(path, entry.full_name)
|
62
|
+
FileUtils.mkdir_p(File.dirname(n_path))
|
63
|
+
File.open(n_path, 'w') do |file|
|
64
|
+
while(content = entry.read(2048))
|
65
|
+
file.write(content)
|
66
|
+
end
|
62
67
|
end
|
63
68
|
end
|
69
|
+
FileUtils.rm(a_path)
|
64
70
|
end
|
65
|
-
path
|
71
|
+
Dir.glob(File.join(path, '*')).first
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [TrueClass, FalseClass]
|
75
|
+
def clean_asset(asset_path)
|
76
|
+
super File.dirname(asset_path)
|
66
77
|
end
|
67
78
|
|
68
79
|
end
|
data/lib/batali/source.rb
CHANGED
@@ -2,7 +2,7 @@ require 'batali'
|
|
2
2
|
|
3
3
|
module Batali
|
4
4
|
# Source of asset
|
5
|
-
class Source <
|
5
|
+
class Source < Utility
|
6
6
|
|
7
7
|
autoload :Path, 'batali/source/path'
|
8
8
|
autoload :Site, 'batali/source/site'
|
@@ -25,6 +25,20 @@ module Batali
|
|
25
25
|
raise NotImplementedError.new 'Abstract class'
|
26
26
|
end
|
27
27
|
|
28
|
+
# @return [TrueClass, FalseClass]
|
29
|
+
def clean_asset(asset_path)
|
30
|
+
if(self.respond_to?(:cache))
|
31
|
+
false
|
32
|
+
else
|
33
|
+
if(File.exists?(asset_path))
|
34
|
+
FileUtils.rm_rf(asset_path)
|
35
|
+
true
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
# Build a source
|
29
43
|
#
|
30
44
|
# @param args [Hash]
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
|
5
|
+
class UnitLoader < Utility
|
6
|
+
|
7
|
+
include Bogo::Memoization
|
8
|
+
|
9
|
+
attribute :file, BFile, :required => true
|
10
|
+
attribute :system, Grimoire::System, :required => true
|
11
|
+
attribute :cache, String, :required => true
|
12
|
+
|
13
|
+
# Populate the system with units
|
14
|
+
#
|
15
|
+
# @return [self]
|
16
|
+
def populate!
|
17
|
+
memoize(:populate) do
|
18
|
+
file.source.each do |src|
|
19
|
+
src.units.find_all do |unit|
|
20
|
+
if(restrictions[unit.name])
|
21
|
+
restrictions[unit.name] == src.identifier
|
22
|
+
else
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end.each do |unit|
|
26
|
+
system.add_unit(unit)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
file.cookbook.each do |ckbk|
|
30
|
+
if(ckbk.path)
|
31
|
+
source = Origin::Path.new(
|
32
|
+
:name => ckbk.name,
|
33
|
+
:path => ckbk.path
|
34
|
+
)
|
35
|
+
elsif(ckbk.git)
|
36
|
+
source = Origin::Git.new(
|
37
|
+
:name => ckbk.name,
|
38
|
+
:url => ckbk.git,
|
39
|
+
:ref => ckbk.ref || 'master',
|
40
|
+
:cache => cache
|
41
|
+
)
|
42
|
+
end
|
43
|
+
if(source)
|
44
|
+
system.add_unit(source.units.first)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Smash]
|
51
|
+
def restrictions
|
52
|
+
memoize(:restrictions) do
|
53
|
+
rest = (file.restrict || Smash.new).to_smash
|
54
|
+
file.cookbook.each do |ckbk|
|
55
|
+
if(ckbk.path)
|
56
|
+
rest[ckbk.name] = Smash.new(:path => ckbk.path).checksum
|
57
|
+
elsif(ckbk.git)
|
58
|
+
rest[ckbk.name] = Smash.new(
|
59
|
+
:url => ckbk.git,
|
60
|
+
:ref => ckbk.ref
|
61
|
+
).checksum
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rest
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/batali/version.rb
CHANGED
data/lib/batali.rb
CHANGED
@@ -5,11 +5,14 @@ module Batali
|
|
5
5
|
|
6
6
|
autoload :Command, 'batali/command'
|
7
7
|
autoload :Config, 'batali/config'
|
8
|
+
autoload :Git, 'batali/git'
|
8
9
|
autoload :Manifest, 'batali/manifest'
|
9
|
-
autoload :
|
10
|
+
autoload :Origin, 'batali/origin'
|
10
11
|
autoload :ScoreKeeper, 'batali/score_keeper'
|
11
12
|
autoload :Source, 'batali/source'
|
12
13
|
autoload :Unit, 'batali/unit'
|
14
|
+
autoload :UnitLoader, 'batali/unit_loader'
|
15
|
+
autoload :Utility, 'batali/utility'
|
13
16
|
|
14
17
|
end
|
15
18
|
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: batali
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Roberts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: attribute_struct
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.14
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.14
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: grimoire
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - ">="
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.
|
33
|
+
version: 0.1.4
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
38
|
- - ">="
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.
|
40
|
+
version: 0.1.4
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bogo
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: 0.1.10
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: git
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: http
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,14 +157,21 @@ files:
|
|
129
157
|
- lib/batali/command/resolve.rb
|
130
158
|
- lib/batali/command/update.rb
|
131
159
|
- lib/batali/config.rb
|
160
|
+
- lib/batali/git.rb
|
132
161
|
- lib/batali/manifest.rb
|
133
162
|
- lib/batali/monkey.rb
|
134
|
-
- lib/batali/
|
163
|
+
- lib/batali/origin.rb
|
164
|
+
- lib/batali/origin/git.rb
|
165
|
+
- lib/batali/origin/path.rb
|
166
|
+
- lib/batali/origin/remote_site.rb
|
135
167
|
- lib/batali/score_keeper.rb
|
136
168
|
- lib/batali/source.rb
|
169
|
+
- lib/batali/source/git.rb
|
137
170
|
- lib/batali/source/path.rb
|
138
171
|
- lib/batali/source/site.rb
|
139
172
|
- lib/batali/unit.rb
|
173
|
+
- lib/batali/unit_loader.rb
|
174
|
+
- lib/batali/utility.rb
|
140
175
|
- lib/batali/version.rb
|
141
176
|
homepage: https://github.com/hw-labs/batali
|
142
177
|
licenses:
|
data/lib/batali/remote_site.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
require 'batali'
|
2
|
-
require 'digest/sha2'
|
3
|
-
require 'securerandom'
|
4
|
-
require 'http'
|
5
|
-
require 'fileutils'
|
6
|
-
|
7
|
-
module Batali
|
8
|
-
# Fetch unit information from remote site
|
9
|
-
class RemoteSite < Grimoire::Utility
|
10
|
-
|
11
|
-
# Site suffix for API endpoint
|
12
|
-
COOKBOOK_API_SUFFIX = 'api/v1/cookbooks'
|
13
|
-
|
14
|
-
include Bogo::Memoization
|
15
|
-
|
16
|
-
attribute :name, String
|
17
|
-
attribute :identifier, String
|
18
|
-
attribute :endpoint, String, :required => true
|
19
|
-
attribute :force_update, [TrueClass, FalseClass], :required => true, :default => false
|
20
|
-
attribute :update_interval, Integer, :required => true, :default => 10000 # NOTE: reset this default to 60/120 when ready
|
21
|
-
attribute :cache, String, :default => File.expand_path('~/.batali/cache/remote_site'), :required => true
|
22
|
-
|
23
|
-
def initialize(*_)
|
24
|
-
super
|
25
|
-
endpoint = URI.join(self.endpoint, COOKBOOK_API_SUFFIX).to_s
|
26
|
-
self.identifier = Digest::SHA256.hexdigest(endpoint)
|
27
|
-
unless(name?)
|
28
|
-
self.name = self.identifier
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# @return [String] cache directory path
|
33
|
-
def cache_directory
|
34
|
-
memoize(:cache_directory) do
|
35
|
-
path = File.join(cache, identifier)
|
36
|
-
FileUtils.mkdir_p(path)
|
37
|
-
path
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# @return [Array<Unit>] all units
|
42
|
-
def units
|
43
|
-
memoize(:units) do
|
44
|
-
items.map do |u_name, versions|
|
45
|
-
versions.map do |version, info|
|
46
|
-
Unit.new(
|
47
|
-
:name => u_name,
|
48
|
-
:version => version,
|
49
|
-
:dependencies => info[:dependencies].to_a,
|
50
|
-
:source => Smash.new(
|
51
|
-
:type => :site,
|
52
|
-
:url => info[:download_url],
|
53
|
-
:version => version,
|
54
|
-
:dependencies => info[:dependencies]
|
55
|
-
)
|
56
|
-
)
|
57
|
-
end
|
58
|
-
end.flatten
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
protected
|
63
|
-
|
64
|
-
# @return [Smash] all info
|
65
|
-
def items
|
66
|
-
memoize(:items) do
|
67
|
-
MultiJson.load(File.read(fetch)).to_smash
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Fetch the universe
|
72
|
-
#
|
73
|
-
# @return [String] path to universe file
|
74
|
-
def fetch
|
75
|
-
do_fetch = true
|
76
|
-
if(File.exists?(universe_path))
|
77
|
-
age = Time.now - File.mtime(universe_path)
|
78
|
-
if(age < update_interval)
|
79
|
-
do_fetch = false
|
80
|
-
end
|
81
|
-
end
|
82
|
-
if(do_fetch)
|
83
|
-
t_uni = "#{universe_path}.#{SecureRandom.urlsafe_base64}"
|
84
|
-
File.open(t_uni, 'w') do |file|
|
85
|
-
file.write HTTP.get(URI.join(endpoint, 'universe')).body.to_s
|
86
|
-
end
|
87
|
-
FileUtils.mv(t_uni, universe_path)
|
88
|
-
end
|
89
|
-
universe_path
|
90
|
-
end
|
91
|
-
|
92
|
-
# @return [String] path to universe file
|
93
|
-
def universe_path
|
94
|
-
File.join(cache_directory, 'universe.json')
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|