chake 0.0.1 → 0.1.0
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 +2 -0
- data/Rakefile +6 -0
- data/chake.gemspec +1 -0
- data/examples/test/Rakefile +2 -6
- data/examples/test/cookbooks/example/files/default/test.asc +18 -0
- data/examples/test/cookbooks/example/recipes/default.rb +6 -0
- data/lib/chake.rb +48 -43
- data/lib/chake/backend.rb +46 -0
- data/lib/chake/backend/local.rb +15 -0
- data/lib/chake/backend/ssh.rb +25 -0
- data/lib/chake/node.rb +44 -0
- data/lib/chake/version.rb +1 -1
- data/spec/chake/backend/local_spec.rb +12 -0
- data/spec/chake/backend/ssh_spec.rb +13 -0
- data/spec/chake/backend_spec.rb +2 -0
- data/spec/chake/node_spec.rb +50 -0
- data/spec/spec_helper.rb +29 -0
- metadata +35 -6
- data/examples/test/.tmp/lvh.me.plain.sha1sum +0 -4
- data/examples/test/cookbooks/myhost/recipes/default.rb +0 -1
- data/examples/test/nodes.yaml +0 -3
data/.gitignore
CHANGED
data/Rakefile
CHANGED
data/chake.gemspec
CHANGED
data/examples/test/Rakefile
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
-----BEGIN PGP MESSAGE-----
|
2
|
+
Version: GnuPG v1
|
3
|
+
|
4
|
+
hQIMA5A8ZkAWdYz7AQ//dgQhKXuGS0dY04TXa1cXEtOHYC64LLnoo+UZT/KgnkzI
|
5
|
+
/IAgLFbAV0Fd9DGLK857qv9fWf/FB5b6loYLPOVEys0mb5aQrqPPFeKwTTXxTJdk
|
6
|
+
Ts2NvSVfNZx95Igotm/vkKW6/dbGlDfQSOVQMzhvmKOMMpRP+ixqn+G/nwE/0wQG
|
7
|
+
QXc3UPAe9gBFlI9GWLTafhftwrxXiYeNF8N03W9BUk1OiQqJUmDRK9ZjVe/vbEiZ
|
8
|
+
iQ689MnF5l+/6gptU0j77QIqk5vEItkl7RISxOS8PDFI+926NV3fZUrRAlu2eDIy
|
9
|
+
HNMGkcKTjGoPNrj/UzzmqjP3uNQtKoK559cul7uY44QmsEINoFP3HoAOHeVxm1pG
|
10
|
+
6IG5EY6znwVEYTIVPK21NAPFpfk9yAB53sv6GrtOaYp8FFp+lLHJlPQcOOP51UV9
|
11
|
+
GqiAuec7Lgr3iy1yaXIBHFlLG0lmZ1OI41zwqh+Z8EF1NC0gFPhGuOJqBGmBbOxy
|
12
|
+
Wt1IFx1JUHi0f277us9pFbQlcUb8tgFZ0J69epBrod+xWaZQKFwLWsCTN66fKJfp
|
13
|
+
rqBzRBcNDJwsKOd54v/Cmrws8bwnfB8iNZYuEQdEt9u5TGIZFl4R9k+/pydAQouP
|
14
|
+
Z95g4vh2ST2ZEeblnbCc2TFY/7j2O+aXyBzX/4+/bM0kZbFjxCNuefU/v3ZnDwXS
|
15
|
+
QAH+NPQ31IiqMmoETUqHzo1UI+hV9em81Llt2bsbgWULp84LEmzczbjAcMi2EWNt
|
16
|
+
SzPyJzOLrqEvEYg2O/+bGho=
|
17
|
+
=Y2iZ
|
18
|
+
-----END PGP MESSAGE-----
|
data/lib/chake.rb
CHANGED
@@ -5,21 +5,23 @@ require 'json'
|
|
5
5
|
require 'tmpdir'
|
6
6
|
require 'readline'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
require 'chake/node'
|
9
|
+
|
10
|
+
nodes_file = ENV['NODES'] || 'nodes.yaml'
|
11
|
+
node_data = File.exists?(nodes_file) && YAML.load_file(nodes_file) || {}
|
12
|
+
$nodes = node_data.map { |node,data| Chake::Node.new(node, data) }
|
11
13
|
|
12
|
-
$sample_nodes = <<EOF
|
13
|
-
host1.my.tld:
|
14
|
-
run_list:
|
15
|
-
- recipe[myhost]
|
16
|
-
EOF
|
17
14
|
|
18
15
|
desc "Initializes current directory with sample structure"
|
19
16
|
task :init do
|
20
17
|
if !File.exists?('nodes.yaml')
|
21
18
|
File.open('nodes.yaml', 'w') do |f|
|
22
|
-
|
19
|
+
sample_nodes = <<EOF
|
20
|
+
host1.my.tld:
|
21
|
+
run_list:
|
22
|
+
- recipe[myhost]
|
23
|
+
EOF
|
24
|
+
f.write(sample_nodes)
|
23
25
|
puts "→ nodes.yaml"
|
24
26
|
end
|
25
27
|
end
|
@@ -45,7 +47,9 @@ end
|
|
45
47
|
|
46
48
|
desc 'list nodes'
|
47
49
|
task :nodes do
|
48
|
-
|
50
|
+
$nodes.each do |node|
|
51
|
+
puts "%-40s %-5s\n" % [node.hostname, node.backend]
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
def encrypted_for(node)
|
@@ -71,23 +75,25 @@ end
|
|
71
75
|
|
72
76
|
$nodes.each do |node|
|
73
77
|
|
74
|
-
|
75
|
-
|
78
|
+
hostname = node.hostname
|
79
|
+
|
80
|
+
desc "bootstrap #{hostname}"
|
81
|
+
task "bootstrap:#{hostname}" do
|
76
82
|
mkdir_p '.tmp', :verbose => false
|
77
|
-
config = '.tmp/' +
|
83
|
+
config = '.tmp/' + hostname + '.json'
|
78
84
|
|
79
85
|
seen_before = File.exists?(config)
|
80
86
|
|
81
87
|
# overwrite config with current contents
|
82
88
|
File.open(config, 'w') do |f|
|
83
|
-
json_data =
|
89
|
+
json_data = node.data
|
84
90
|
f.write(JSON.dump(json_data))
|
85
91
|
f.write("\n")
|
86
92
|
end
|
87
93
|
|
88
94
|
unless seen_before
|
89
95
|
begin
|
90
|
-
|
96
|
+
node.run_as_root('apt-get -q -y install rsync chef')
|
91
97
|
rescue
|
92
98
|
rm_f config
|
93
99
|
raise
|
@@ -96,41 +102,40 @@ $nodes.each do |node|
|
|
96
102
|
|
97
103
|
end
|
98
104
|
|
99
|
-
desc "upload data to #{
|
100
|
-
task "upload:#{
|
101
|
-
encrypted = encrypted_for(
|
102
|
-
|
105
|
+
desc "upload data to #{hostname}"
|
106
|
+
task "upload:#{hostname}" do
|
107
|
+
encrypted = encrypted_for(hostname)
|
108
|
+
rsync_excludes = (encrypted.values + encrypted.keys).map { |f| ["--exclude", f] }.flatten
|
103
109
|
|
104
|
-
rsync = "rsync -avp --
|
110
|
+
rsync = "rsync", "-avp", "--exclude", ".git/"
|
111
|
+
rsync_logging = Rake.application.options.silent && '--quiet' || '--verbose'
|
105
112
|
|
106
|
-
Dir.
|
107
|
-
|
108
|
-
|
109
|
-
if_files_changed(node, 'plain', files) do
|
110
|
-
rsync_logging = Rake.application.options.silent && '--quiet' || '--verbose'
|
111
|
-
sh "#{rsync} #{rsync_logging} ./ root@#{node}:/srv/chef/"
|
112
|
-
end
|
113
|
+
files = Dir.glob("**/*").select { |f| !File.directory?(f) } - encrypted.keys - encrypted.values
|
114
|
+
if_files_changed(hostname, 'plain', files) do
|
115
|
+
sh *rsync, '--delete', rsync_logging, *rsync_excludes, './', node.rsync_dest
|
113
116
|
end
|
114
117
|
|
115
|
-
if_files_changed(
|
116
|
-
|
117
|
-
|
118
|
+
if_files_changed(hostname, 'enc', encrypted.keys) do
|
119
|
+
Dir.mktmpdir do |tmpdir|
|
120
|
+
encrypted.each do |encrypted_file, target_file|
|
121
|
+
target = File.join(tmpdir, target_file)
|
122
|
+
mkdir_p(File.dirname(target))
|
123
|
+
sh 'gpg', '--quiet', '--batch', '--use-agent', '--output', target, '--decrypt', encrypted_file
|
124
|
+
end
|
125
|
+
sh *rsync, rsync_logging, tmpdir + '/', node.rsync_dest
|
118
126
|
end
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
122
|
-
desc "converge #{
|
123
|
-
task "converge:#{
|
130
|
+
desc "converge #{hostname}"
|
131
|
+
task "converge:#{hostname}" => ["bootstrap:#{hostname}", "upload:#{hostname}"] do
|
124
132
|
chef_logging = Rake.application.options.silent && '-l fatal' || ''
|
125
|
-
|
133
|
+
node.run_as_root "chef-solo -c #{node.path}/config.rb #{chef_logging} -j #{node.path}/.tmp/#{hostname}.json"
|
126
134
|
end
|
127
135
|
|
128
|
-
desc "run a command on #{
|
129
|
-
task "run:#{
|
130
|
-
|
131
|
-
IO.popen(cmd).lines.each do |line|
|
132
|
-
puts "#{node}: #{line}"
|
133
|
-
end
|
136
|
+
desc "run a command on #{hostname}"
|
137
|
+
task "run:#{hostname}" => 'run_input' do
|
138
|
+
node.run($cmd)
|
134
139
|
end
|
135
140
|
end
|
136
141
|
|
@@ -139,15 +144,15 @@ task :run_input do
|
|
139
144
|
end
|
140
145
|
|
141
146
|
desc "upload to all nodes"
|
142
|
-
task :upload => $nodes.map { |node| "upload:#{node}" }
|
147
|
+
task :upload => $nodes.map { |node| "upload:#{node.hostname}" }
|
143
148
|
|
144
149
|
desc "bootstrap all nodes"
|
145
|
-
task :bootstrap => $nodes.map { |node| "bootstrap:#{node}" }
|
150
|
+
task :bootstrap => $nodes.map { |node| "bootstrap:#{node.hostname}" }
|
146
151
|
|
147
152
|
desc "converge all nodes (default)"
|
148
|
-
task "converge" => $nodes.map { |node| "converge:#{node}" }
|
153
|
+
task "converge" => $nodes.map { |node| "converge:#{node.hostname}" }
|
149
154
|
|
150
155
|
task "run a command on all nodes"
|
151
|
-
task :run => $nodes.map { |node| "run:#{node}" }
|
156
|
+
task :run => $nodes.map { |node| "run:#{node.hostname}" }
|
152
157
|
|
153
158
|
task :default => :converge
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Chake
|
2
|
+
|
3
|
+
class Backend < Struct.new(:node)
|
4
|
+
|
5
|
+
def rsync_dest
|
6
|
+
node.path + '/'
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(cmd)
|
10
|
+
IO.popen(command_runner + [cmd]).lines.each do |line|
|
11
|
+
puts [node.hostname, line.strip].join(': ')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_as_root(cmd)
|
16
|
+
if node.username == 'root'
|
17
|
+
run(cmd)
|
18
|
+
else
|
19
|
+
run('sudo ' + cmd)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
self.class.backend_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.backend_name
|
28
|
+
name.split("::").last.downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.inherited(subclass)
|
32
|
+
@backends ||= []
|
33
|
+
@backends << subclass
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.get(name)
|
37
|
+
backend = @backends.find { |b| b.backend_name == name }
|
38
|
+
backend || raise(ArgumentError.new("Invalid backend name: #{name}"))
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
require 'chake/backend/ssh'
|
46
|
+
require 'chake/backend/local'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Chake
|
2
|
+
|
3
|
+
class Backend
|
4
|
+
|
5
|
+
class Ssh < Backend
|
6
|
+
|
7
|
+
def rsync_dest
|
8
|
+
[ssh_target, node.path + '/'].join(':')
|
9
|
+
end
|
10
|
+
|
11
|
+
def command_runner
|
12
|
+
['ssh', ssh_target]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def ssh_target
|
18
|
+
[node.username, node.hostname].compact.join('@')
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/chake/node.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'etc'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'chake/backend'
|
6
|
+
|
7
|
+
module Chake
|
8
|
+
|
9
|
+
class Node
|
10
|
+
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :hostname
|
14
|
+
attr_reader :username
|
15
|
+
attr_reader :path
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(hostname, data = {})
|
19
|
+
uri = URI.parse(hostname)
|
20
|
+
if !uri.scheme && !uri.host && uri.path
|
21
|
+
uri = URI.parse("ssh://#{hostname}")
|
22
|
+
end
|
23
|
+
if uri.path.empty?
|
24
|
+
uri.path = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
@backend_name = uri.scheme
|
28
|
+
|
29
|
+
@hostname = uri.hostname
|
30
|
+
@username = uri.user || Etc.getpwuid.name
|
31
|
+
@path = uri.path || "/tmp/chef.#{username}"
|
32
|
+
@data = data
|
33
|
+
end
|
34
|
+
|
35
|
+
def backend
|
36
|
+
@backend ||= Chake::Backend.get(@backend_name).new(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def_delegators :backend, :run, :run_as_root, :rsync_dest
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
data/lib/chake/version.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chake::Backend::Local do
|
4
|
+
|
5
|
+
include_examples "Chake::Backend", Chake::Backend::Local
|
6
|
+
|
7
|
+
let(:node) { Chake::Node.new('local://myhost/srv/chef') }
|
8
|
+
|
9
|
+
it('runs commands with sh -c') { expect(backend.command_runner).to eq(['sh', '-c']) }
|
10
|
+
|
11
|
+
it('rsyncs locally') { expect(backend.rsync_dest).to eq('/srv/chef/') }
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chake::Backend::Ssh do
|
4
|
+
|
5
|
+
include_examples "Chake::Backend", Chake::Backend::Ssh, ->() { }
|
6
|
+
|
7
|
+
let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chef') }
|
8
|
+
|
9
|
+
it('runs commands with ssh') { expect(backend.command_runner).to eq(['ssh', 'myuser@myhost']) }
|
10
|
+
|
11
|
+
it('rsyncs over ssh') { expect(backend.rsync_dest).to eq('myuser@myhost:/srv/chef/') }
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'chake/node'
|
2
|
+
|
3
|
+
describe Chake::Node do
|
4
|
+
|
5
|
+
before do
|
6
|
+
ent = double
|
7
|
+
ent.stub(:name).and_return('jonhdoe')
|
8
|
+
Etc.stub(:getpwuid).and_return(ent)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:simple) { Chake::Node.new('hostname') }
|
12
|
+
it('has a name') { expect(simple.hostname).to eq('hostname') }
|
13
|
+
it('uses ssh by default') { expect(simple.backend).to be_an_instance_of(Chake::Backend::Ssh) }
|
14
|
+
it('user current username by default') {
|
15
|
+
expect(simple.username).to eq('jonhdoe')
|
16
|
+
}
|
17
|
+
it('write to /tmp/chef.$username') {
|
18
|
+
expect(simple.path).to eq('/tmp/chef.jonhdoe')
|
19
|
+
}
|
20
|
+
|
21
|
+
let(:with_username) { Chake::Node.new('username@hostname') }
|
22
|
+
it('accepts username') { expect(with_username.username).to eq('username') }
|
23
|
+
it('uses ssh') { expect(with_username.backend).to be_an_instance_of(Chake::Backend::Ssh) }
|
24
|
+
|
25
|
+
let(:with_backend) { Chake::Node.new('local://hostname')}
|
26
|
+
it('accepts backend as URI scheme') { expect(with_backend.backend).to be_an_instance_of(Chake::Backend::Local) }
|
27
|
+
|
28
|
+
it('wont accept any backend') do
|
29
|
+
expect { Chake::Node.new('foobar://bazqux').backend }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:with_data) { Chake::Node.new('local://localhost', 'run_list' => ['recipe[common]']) }
|
33
|
+
it('takes data') do
|
34
|
+
expect(with_data.data).to be_a(Hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
[:run, :run_as_root, :rsync_dest].each do |method|
|
38
|
+
it("delegates #{method} to backend") do
|
39
|
+
node = simple
|
40
|
+
|
41
|
+
backend = double
|
42
|
+
args = Object.new
|
43
|
+
node.stub(:backend).and_return(backend)
|
44
|
+
|
45
|
+
backend.should_receive(method).with(args)
|
46
|
+
node.send(method, args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'chake/node'
|
2
|
+
require 'chake/backend'
|
3
|
+
|
4
|
+
|
5
|
+
shared_examples "Chake::Backend" do |backend_class|
|
6
|
+
|
7
|
+
let(:backend) { backend_class.new(node) }
|
8
|
+
|
9
|
+
it('runs commands') do
|
10
|
+
io = StringIO.new("line 1\nline 2\n")
|
11
|
+
IO.should_receive(:popen).with(backend.command_runner + ['something']).and_return(io)
|
12
|
+
backend.should_receive(:puts).with("myhost: line 1")
|
13
|
+
backend.should_receive(:puts).with("myhost: line 2")
|
14
|
+
backend.run('something')
|
15
|
+
end
|
16
|
+
|
17
|
+
it('runs as root') do
|
18
|
+
backend.should_receive(:run).with('sudo something')
|
19
|
+
backend.run_as_root('something')
|
20
|
+
end
|
21
|
+
|
22
|
+
it('does not use sudo if already root') do
|
23
|
+
backend.node.stub(:username).and_return('root')
|
24
|
+
backend.should_receive(:run).with('something')
|
25
|
+
backend.run_as_root('something')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chake
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-02-
|
12
|
+
date: 2014-02-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
63
|
name: rake
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,13 +90,21 @@ files:
|
|
74
90
|
- README.md
|
75
91
|
- Rakefile
|
76
92
|
- chake.gemspec
|
77
|
-
- examples/test/.tmp/lvh.me.plain.sha1sum
|
78
93
|
- examples/test/Rakefile
|
79
94
|
- examples/test/config.rb
|
80
|
-
- examples/test/cookbooks/
|
81
|
-
- examples/test/
|
95
|
+
- examples/test/cookbooks/example/files/default/test.asc
|
96
|
+
- examples/test/cookbooks/example/recipes/default.rb
|
82
97
|
- lib/chake.rb
|
98
|
+
- lib/chake/backend.rb
|
99
|
+
- lib/chake/backend/local.rb
|
100
|
+
- lib/chake/backend/ssh.rb
|
101
|
+
- lib/chake/node.rb
|
83
102
|
- lib/chake/version.rb
|
103
|
+
- spec/chake/backend/local_spec.rb
|
104
|
+
- spec/chake/backend/ssh_spec.rb
|
105
|
+
- spec/chake/backend_spec.rb
|
106
|
+
- spec/chake/node_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
84
108
|
homepage: https://github.com/terceiro/chake
|
85
109
|
licenses:
|
86
110
|
- MIT
|
@@ -106,4 +130,9 @@ rubygems_version: 1.8.23
|
|
106
130
|
signing_key:
|
107
131
|
specification_version: 3
|
108
132
|
summary: Simple host management with chef and rake. No chef server required.
|
109
|
-
test_files:
|
133
|
+
test_files:
|
134
|
+
- spec/chake/backend/local_spec.rb
|
135
|
+
- spec/chake/backend/ssh_spec.rb
|
136
|
+
- spec/chake/backend_spec.rb
|
137
|
+
- spec/chake/node_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
@@ -1 +0,0 @@
|
|
1
|
-
package 'openssh-server'
|
data/examples/test/nodes.yaml
DELETED