modulesync 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +10 -0
- data/LICENSE +17 -0
- data/README.md +220 -0
- data/bin/msync +8 -0
- data/lib/modulesync/cli.rb +81 -0
- data/lib/modulesync/constants.rb +11 -0
- data/lib/modulesync/git.rb +81 -0
- data/lib/modulesync/hook.rb +36 -0
- data/lib/modulesync/renderer.rb +41 -0
- data/lib/modulesync/util.rb +23 -0
- data/lib/modulesync.rb +86 -0
- data/modulesync.gemspec +23 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b33cdfbc0df1b30570ffadbd78e6bbcec9ba8b6
|
4
|
+
data.tar.gz: 5044a6754a320ec00e0ebf4bb7cba71087da9e02
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 529d0ed35ec73c722cb607980133637851e1cfda52d564542ec3d0393bae5bcce62a5c72dff96b430654c2fff69ee263c17f4d3a9572924d3cd1342f89aacf74
|
7
|
+
data.tar.gz: 563cf2c60a67b435a9ff7481f37288236166ea1fa66d30087becf15ef932335ceda65685aecfd8d8625172a4594e8265a34ece2c2ab4d6fe555cf85099d78ddf
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Puppet Module Sync
|
2
|
+
|
3
|
+
Copyright (C) 2014 Puppet Labs Inc
|
4
|
+
|
5
|
+
Puppet Labs can be contacted at: info@puppetlabs.com
|
6
|
+
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
you may not use this file except in compliance with the License.
|
9
|
+
You may obtain a copy of the License at
|
10
|
+
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
See the License for the specific language governing permissions and
|
17
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
ModuleSync
|
2
|
+
===========
|
3
|
+
|
4
|
+
Table of Contents
|
5
|
+
-----------------
|
6
|
+
|
7
|
+
1. [Usage TLDR](#usage-tldr)
|
8
|
+
2. [Overview](#overview)
|
9
|
+
3. [How it works](#how-it-works)
|
10
|
+
4. [Installing](#installing)
|
11
|
+
5. [Workflow](#workflow)
|
12
|
+
6. [The Templates](#the-templates)
|
13
|
+
|
14
|
+
Usage TLDR
|
15
|
+
----------
|
16
|
+
|
17
|
+
```
|
18
|
+
gem install modulesync
|
19
|
+
msync --help
|
20
|
+
```
|
21
|
+
|
22
|
+
Overview
|
23
|
+
--------
|
24
|
+
|
25
|
+
ModuleSync was written as a simple script with ERB templates to help the
|
26
|
+
Puppet Labs module engineers manage the zoo of Puppet modules on GitHub, and
|
27
|
+
has now been restructured and generalized to be used within other
|
28
|
+
organizations. Puppet modules within an organization tend to have a number of
|
29
|
+
meta-files that are identical or very similar between modules, such as the
|
30
|
+
Gemfile, .travis.yml, .gitignore, or spec\_helper.rb. If a file needs to
|
31
|
+
change in one module, it likely needs to change in the same way in every other
|
32
|
+
module that the organization manages.
|
33
|
+
|
34
|
+
One approach to this problem is to use sed in a bash for loop on the modules to
|
35
|
+
make a single change across every module. This approach falls short if there is
|
36
|
+
a single file that is purposefully different than the others, but still needs
|
37
|
+
to be managed. Moreover, this approach does not help if two files are
|
38
|
+
structured differently but need to be changed with the same meaning; for
|
39
|
+
instance, if the .travis.yml of one module uses a list of environments to
|
40
|
+
include, and another uses a matrix of includes with a list of excludes, adding
|
41
|
+
a test environment to both modules will require entirely different approaches.
|
42
|
+
|
43
|
+
ModuleSync provides the advantage of defining a standard template for each
|
44
|
+
file to follow, so it becomes clear what a file is supposed to look like. Two
|
45
|
+
files with the same semantics should also have the same syntax. A difference
|
46
|
+
between two files should have clear reasons, and old cruft should not be left
|
47
|
+
in files of one module as the files of another module march forward.
|
48
|
+
|
49
|
+
Another advantage of ModuleSync is the ability to run in no-op mode, which
|
50
|
+
makes local changes and shows the diffs, but does not make permanent changes in
|
51
|
+
the remote repository.
|
52
|
+
|
53
|
+
How It Works
|
54
|
+
------------
|
55
|
+
|
56
|
+
ModuleSync is a gem that uses the GitHub workflow to clone, update, and push module
|
57
|
+
repositories. It expects to be activated from a directory containing
|
58
|
+
configuration for modulesync and the modules, or you can pass it the location
|
59
|
+
of this configuration directory. [The configuration for the Puppet Labs
|
60
|
+
modules](https://github.com/puppetlabs/modulesync\_configs), can be used as an
|
61
|
+
example for your own configuration. The configuration directory contains a
|
62
|
+
directory called moduleroot which mirrors the structure of a module. The files
|
63
|
+
in the moduleroot could be flat files or ERB templates. The templates are
|
64
|
+
rendered using values from a file called config\_defaults.yml in the root (not
|
65
|
+
moduleroot) of the configuration directory. The default values can be
|
66
|
+
overridden or extended by adding a file called .sync.yml to the module itself.
|
67
|
+
This allows us to, for example, have a set of "required" gems that are added
|
68
|
+
to all Gemfiles, and a set of "optional" gems that a single module might add.
|
69
|
+
|
70
|
+
The list of modules to manage is in managed\_modules.yml in the configuration
|
71
|
+
directory. This lists just the GitHub names of the modules to be managed.
|
72
|
+
|
73
|
+
ModuleSync can be called from the command line with parameters to change the
|
74
|
+
branch you're working on or the remote to clone from and push to. You can also
|
75
|
+
define these parameters in a file named modulesync.yml in the configuration
|
76
|
+
directory.
|
77
|
+
|
78
|
+
Installing
|
79
|
+
----------
|
80
|
+
|
81
|
+
```
|
82
|
+
gem install modulesync
|
83
|
+
```
|
84
|
+
|
85
|
+
For developers:
|
86
|
+
|
87
|
+
```
|
88
|
+
gem build modulesync.gemspec
|
89
|
+
gem install modulesync-*.gem
|
90
|
+
```
|
91
|
+
|
92
|
+
Workflow
|
93
|
+
--------
|
94
|
+
|
95
|
+
### Default mode
|
96
|
+
|
97
|
+
With no additional arguments, ModuleSync clones modules from the puppetlabs
|
98
|
+
github organization and pushes to the master branch.
|
99
|
+
|
100
|
+
#### Make changes
|
101
|
+
|
102
|
+
Make changes to a file in the moduleroot. For sanity's sake you should commit
|
103
|
+
and push these changes, but in this mode the update will be rendered from the
|
104
|
+
state of the files locally. Run `msync update` from the root of the
|
105
|
+
configuration directory (not moduleroot), or use -c <relative path> to point
|
106
|
+
it to the location of the configuration directory.
|
107
|
+
|
108
|
+
#### Dry-run
|
109
|
+
|
110
|
+
Do a dry-run to see what files will be changed, added and removed. This clones
|
111
|
+
the modules to `modules/<namespace>-<modulename>` in the current working, or if
|
112
|
+
the modules are already cloned, does an effective `git fetch origin; git
|
113
|
+
checkout master; git reset --hard origin/master` on the modules. Don't run
|
114
|
+
modulesync if the current working directory contains a modules/ directory with
|
115
|
+
changes you want to keep. The dry-run makes local changes there, but does not
|
116
|
+
commit or push changes. It is still destructive in that it overwrites local
|
117
|
+
changes.
|
118
|
+
|
119
|
+
```
|
120
|
+
msync update --noop
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Damage mode
|
124
|
+
|
125
|
+
Make changes for real and push them back to master. This operates on the
|
126
|
+
pre-cloned modules from the dry-run or clones them fresh if the modules aren't
|
127
|
+
found.
|
128
|
+
|
129
|
+
```
|
130
|
+
msync update
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Automating Updates
|
134
|
+
|
135
|
+
You can install a pre-push git hook to automatically clone, update, and push
|
136
|
+
modules upon pushing changes to the configuration directory. This does not
|
137
|
+
include a noop mode.
|
138
|
+
|
139
|
+
```
|
140
|
+
msync hook activate
|
141
|
+
```
|
142
|
+
|
143
|
+
If you have activated the hook but want to make changes to the configuration
|
144
|
+
directory (such as changes to managed_modules.yml or modulesync.yml) without
|
145
|
+
touching the modules, you can deactivate the hook.
|
146
|
+
|
147
|
+
```
|
148
|
+
msync hook deactivate
|
149
|
+
```
|
150
|
+
|
151
|
+
### Using Forks and Non-master branches
|
152
|
+
|
153
|
+
The default functionality is to run ModuleSync on the puppetlabs modules, but
|
154
|
+
you can use this on your own organization's modules. This functionality also
|
155
|
+
applies if you want to work on a fork of the puppetlabs modules or work on a
|
156
|
+
non-master branch of any organization's modules. ModuleSync does not support
|
157
|
+
cloning from one remote and pushing to another, you are expected to fork
|
158
|
+
manually. It does not yet support automating pull requests (coming soon).
|
159
|
+
|
160
|
+
#### Dry-run
|
161
|
+
|
162
|
+
If you dry-run before doing the live update, you need to specify what namespace
|
163
|
+
to clone from because the live update will not re-clone if the modules are
|
164
|
+
already cloned. The namespace should be your fork, not the upstream module. The
|
165
|
+
format should be the SSH or HTTP prefix of the full URL minus the module name
|
166
|
+
itself.
|
167
|
+
|
168
|
+
```
|
169
|
+
msync update -n git@github.com:puppetlabs --noop
|
170
|
+
```
|
171
|
+
|
172
|
+
#### Damage mode
|
173
|
+
|
174
|
+
You don't technically need to specify the namespace if the modules are already
|
175
|
+
cloned from the dry-run, but it doesn't hurt. You do need to specify the
|
176
|
+
namespace if the modules are not pre-cloned. You need to specify a branch to
|
177
|
+
push to if you are not pushing to master.
|
178
|
+
|
179
|
+
```
|
180
|
+
msync update -n git@github.com:puppetlabs -b sync_branch
|
181
|
+
```
|
182
|
+
|
183
|
+
#### Configuring ModuleSync defaults
|
184
|
+
|
185
|
+
If you're not using the puppetlabs modules or only ever pushing to a fork of
|
186
|
+
them, then specifying the namespace and branch every time you use ModuleSync
|
187
|
+
probably seems excessive. You can create a file called modulesync.yml in the
|
188
|
+
configuration directory that provides these arguments automatically. This file
|
189
|
+
has a form such as:
|
190
|
+
|
191
|
+
```
|
192
|
+
---
|
193
|
+
namespace: mygithubusername
|
194
|
+
branch: modulesyncbranch
|
195
|
+
```
|
196
|
+
|
197
|
+
Then you can run ModuleSync without extra arguments:
|
198
|
+
|
199
|
+
```
|
200
|
+
msync update --noop
|
201
|
+
msync update
|
202
|
+
```
|
203
|
+
|
204
|
+
#### Automating updates
|
205
|
+
|
206
|
+
If you install a git hook, you need to tell it what remote and branch to push
|
207
|
+
to. This may not work properly if you already have the modules cloned from a
|
208
|
+
different remote. The hook will also look in modulesync.yml for default
|
209
|
+
arguments.
|
210
|
+
|
211
|
+
```
|
212
|
+
msync hook activate -n git@github.com:puppetlabs -b sync_branch
|
213
|
+
```
|
214
|
+
|
215
|
+
The Templates
|
216
|
+
-------------
|
217
|
+
|
218
|
+
See the [modulesync\_configs](https://github.com/puppetlabs/modulesync_configs)
|
219
|
+
repository for an explanation of the templates that Puppet Labs uses on its
|
220
|
+
modules.
|
data/bin/msync
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'modulesync/constants'
|
3
|
+
require 'modulesync/util'
|
4
|
+
|
5
|
+
module ModuleSync
|
6
|
+
class CLI
|
7
|
+
include Constants
|
8
|
+
|
9
|
+
def defaults
|
10
|
+
{
|
11
|
+
:namespace => 'puppetlabs',
|
12
|
+
:branch => 'master',
|
13
|
+
:managed_modules_conf => 'managed_modules.yml',
|
14
|
+
:configs => '.',
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def commands_available
|
19
|
+
[
|
20
|
+
'update',
|
21
|
+
'hook',
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
def fail(message)
|
26
|
+
puts @options[:help]
|
27
|
+
puts message
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_opts(args)
|
32
|
+
@options = defaults
|
33
|
+
@options.merge!(Hash.transform_keys_to_symbols(Util.parse_config(MODULESYNC_CONF_FILE)))
|
34
|
+
@options[:command] = args[0] if commands_available.include?(args[0])
|
35
|
+
opt_parser = OptionParser.new do |opts|
|
36
|
+
opts.banner = "Usage: msync update [-m <commit message>] [-c <directory> ] [--noop] [-n <namespace>] [-b <branch>] | hook activate|deactivate [-c <directory> ] [-n <namespace>] [-b <branch>]"
|
37
|
+
opts.on('-m', '--message <msg>',
|
38
|
+
'Commit message to apply to updated modules') do |msg|
|
39
|
+
@options[:message] = msg
|
40
|
+
end
|
41
|
+
opts.on('-n', '--namespace <url>',
|
42
|
+
'Remote github namespace (user or organization) to clone from and push to. Defaults to puppetlabs') do |namespace|
|
43
|
+
@options[:namespace] = namespace
|
44
|
+
end
|
45
|
+
opts.on('-c', '--configs <directory>',
|
46
|
+
'The local directory or remote repository to define the list of managed modules, the file templates, and the default values for template variables.') do |configs|
|
47
|
+
@options[:configs] = configs
|
48
|
+
end
|
49
|
+
opts.on('-b', '--branch <branch>',
|
50
|
+
'Branch name to make the changes in. Defaults to "master"') do |branch|
|
51
|
+
@options[:branch] = branch
|
52
|
+
end
|
53
|
+
opts.on('--noop',
|
54
|
+
'No-op mode') do |msg|
|
55
|
+
@options[:noop] = true
|
56
|
+
end
|
57
|
+
@options[:help] = opts.help
|
58
|
+
end.parse!
|
59
|
+
|
60
|
+
@options.fetch(:message) do
|
61
|
+
if @options[:command] == 'update' && ! @options[:noop]
|
62
|
+
fail("A commit message is required unless using noop.")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
@options.fetch(:command) do
|
67
|
+
fail("A command is required.")
|
68
|
+
end
|
69
|
+
|
70
|
+
if @options[:command] == 'hook' &&
|
71
|
+
(! args.include?('activate') && ! args.include?('deactivate'))
|
72
|
+
fail("You must activate or deactivate the hook.")
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def options
|
78
|
+
@options
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ModuleSync
|
2
|
+
module Constants
|
3
|
+
MODULE_FILES_DIR = 'moduleroot/'
|
4
|
+
CONF_FILE = 'config_defaults.yml'
|
5
|
+
MODULE_CONF_FILE = '.sync.yml'
|
6
|
+
MODULESYNC_CONF_FILE = 'modulesync.yml'
|
7
|
+
PROJ_ROOT = './modules'
|
8
|
+
ENDPOINT = 'git@github.com'
|
9
|
+
HOOK_FILE = '.git/hooks/pre-push'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module ModuleSync
|
4
|
+
module Git
|
5
|
+
include Constants
|
6
|
+
|
7
|
+
def self.pull(org, name)
|
8
|
+
if ! Dir.exists?(PROJ_ROOT)
|
9
|
+
Dir.mkdir(PROJ_ROOT)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Repo needs to be cloned in the cwd
|
13
|
+
if ! Dir.exists?("#{PROJ_ROOT}/#{name}") || ! Dir.exists?("#{PROJ_ROOT}/#{name}/.git")
|
14
|
+
puts "Cloning repository fresh"
|
15
|
+
remote = "#{ENDPOINT}:#{org}/#{name}.git"
|
16
|
+
local = "#{PROJ_ROOT}/#{name}"
|
17
|
+
repo = ::Git.clone(remote, local)
|
18
|
+
|
19
|
+
# Repo already cloned, check out master and override local changes
|
20
|
+
else
|
21
|
+
puts "Overriding any local changes to repositories in #{PROJ_ROOT}"
|
22
|
+
repo = ::Git.open("#{PROJ_ROOT}/#{name}")
|
23
|
+
repo.branch('master').checkout
|
24
|
+
repo.reset_hard
|
25
|
+
repo.pull
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Git add/rm, git commit, git push
|
30
|
+
def self.update(name, files, message, branch)
|
31
|
+
repo = ::Git.open("#{PROJ_ROOT}/#{name}")
|
32
|
+
repo.branch(branch).checkout
|
33
|
+
files.each do |file|
|
34
|
+
if repo.status.deleted.include?(file)
|
35
|
+
repo.remove(file)
|
36
|
+
else
|
37
|
+
repo.add(file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
begin
|
41
|
+
repo.commit(message)
|
42
|
+
#repo.push
|
43
|
+
rescue ::Git::GitExecuteError => git_error
|
44
|
+
if git_error.message.include? "nothing to commit, working directory clean"
|
45
|
+
puts "There were no files to update in #{name}. Not committing."
|
46
|
+
else
|
47
|
+
puts git_error
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Needed because of a bug in the git gem that lists ignored files as
|
54
|
+
# untracked under some circumstances
|
55
|
+
# https://github.com/schacon/ruby-git/issues/130
|
56
|
+
def self.untracked_unignored_files(repo)
|
57
|
+
ignored = File.open("#{repo.dir.path}/.gitignore").read.split
|
58
|
+
repo.status.untracked.keep_if{|f,_| !ignored.any?{|i| f.match(/#{i}/)}}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.update_noop(name, branch)
|
62
|
+
puts "Using no-op. Files in #{name} may be changed but will not be committed."
|
63
|
+
|
64
|
+
repo = ::Git.open("#{PROJ_ROOT}/#{name}")
|
65
|
+
repo.branch(branch).checkout
|
66
|
+
|
67
|
+
puts "Files changed: "
|
68
|
+
repo.diff('HEAD', '--').each do |diff|
|
69
|
+
puts diff.patch
|
70
|
+
end
|
71
|
+
|
72
|
+
puts "Files added: "
|
73
|
+
untracked_unignored_files(repo).each do |file,_|
|
74
|
+
puts file
|
75
|
+
end
|
76
|
+
|
77
|
+
puts "\n\n"
|
78
|
+
puts '--------------------------------'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ModuleSync
|
2
|
+
module Hook
|
3
|
+
include Constants
|
4
|
+
|
5
|
+
def self.activate(args)
|
6
|
+
repo = args[:configs]
|
7
|
+
hook_args = ''
|
8
|
+
hook_args <<= " -n #{args[:namespace]}" if args[:namespace]
|
9
|
+
hook_args <<= " -b #{args[:branch]}" if args[:branch]
|
10
|
+
hook = <<EOF
|
11
|
+
#!/usr/bin/env bash
|
12
|
+
|
13
|
+
current_branch=\`git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,'\`
|
14
|
+
git_dir=\`git rev-parse --show-toplevel\`
|
15
|
+
message=\`git log -1 --format=%B\`
|
16
|
+
msync -m "\$message"#{hook_args}
|
17
|
+
EOF
|
18
|
+
File.open("#{repo}/#{HOOK_FILE}", 'w') do |file|
|
19
|
+
file.write(hook)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.deactivate(repo)
|
24
|
+
hook_path = "#{repo}/#{HOOK_FILE}"
|
25
|
+
File.delete(hook_path) if File.exists?(hook_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.hook(command, args)
|
29
|
+
if (command == 'activate')
|
30
|
+
activate(args)
|
31
|
+
else
|
32
|
+
deactivate(args[:configs])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'find'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module Renderer
|
6
|
+
|
7
|
+
class ForgeModuleFile
|
8
|
+
def initialize(configs= {})
|
9
|
+
@configs = configs
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build(from_erb_template)
|
14
|
+
erb_obj = ERB.new(File.read(from_erb_template), nil, '-')
|
15
|
+
erb_obj.filename = from_erb_template.chomp('.erb')
|
16
|
+
erb_obj.def_method(ForgeModuleFile, 'render()')
|
17
|
+
erb_obj
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.remove(file)
|
21
|
+
if File.exists?(file)
|
22
|
+
File.delete(file)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.render(template, configs = {})
|
27
|
+
ForgeModuleFile.new(configs).render()
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.sync(template, to_file)
|
31
|
+
path = to_file.rpartition('/').first
|
32
|
+
if(! path.empty?)
|
33
|
+
FileUtils.mkdir_p(path)
|
34
|
+
end
|
35
|
+
File.open(to_file, 'w') do |file|
|
36
|
+
file.write(template)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module ModuleSync
|
4
|
+
module Util
|
5
|
+
|
6
|
+
def self.parse_config(config_file)
|
7
|
+
if File.exist?(config_file)
|
8
|
+
YAML.load_file(config_file) || {}
|
9
|
+
else
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Hash
|
17
|
+
#take keys of hash and transform those to a symbols
|
18
|
+
def self.transform_keys_to_symbols(value)
|
19
|
+
return value if not value.is_a?(Hash)
|
20
|
+
hash = value.inject({}){|memo,(k,v)| memo[k.to_sym] = Hash.transform_keys_to_symbols(v); memo}
|
21
|
+
return hash
|
22
|
+
end
|
23
|
+
end
|
data/lib/modulesync.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'modulesync/cli'
|
3
|
+
require 'modulesync/constants'
|
4
|
+
require 'modulesync/git'
|
5
|
+
require 'modulesync/hook'
|
6
|
+
require 'modulesync/renderer'
|
7
|
+
require 'modulesync/util'
|
8
|
+
|
9
|
+
module ModuleSync
|
10
|
+
include Constants
|
11
|
+
|
12
|
+
def self.local_file(config_path, file)
|
13
|
+
"#{config_path}/#{MODULE_FILES_DIR}/#{file}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.module_file(puppet_module, file)
|
17
|
+
"#{PROJ_ROOT}/#{puppet_module}/#{file}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.local_files(path)
|
21
|
+
if File.exists?(path)
|
22
|
+
local_files = Find.find(path).collect { |file| file if !File.directory?(file) }.compact
|
23
|
+
else
|
24
|
+
puts "#{path} does not exist. Check that you are working in your module configs directory or that you have passed in the correct directory with -c."
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.module_files(local_files, path)
|
30
|
+
local_files.map { |file| file.sub(/#{path}/, '') }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.managed_modules(path)
|
34
|
+
managed_modules = Util.parse_config(path)
|
35
|
+
if managed_modules.empty?
|
36
|
+
puts "No modules found at #{path}. Check that you specified the right configs directory containing managed_modules.yml."
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
managed_modules
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.run(args)
|
43
|
+
cli = CLI.new
|
44
|
+
cli.parse_opts(args)
|
45
|
+
options = cli.options
|
46
|
+
if options[:command] == 'update'
|
47
|
+
defaults = Util.parse_config("#{options[:configs]}/#{CONF_FILE}")
|
48
|
+
|
49
|
+
path = "#{options[:configs]}/#{MODULE_FILES_DIR}"
|
50
|
+
local_files = self.local_files(path)
|
51
|
+
module_files = self.module_files(local_files, path)
|
52
|
+
|
53
|
+
managed_modules = self.managed_modules("#{options[:configs]}/managed_modules.yml")
|
54
|
+
|
55
|
+
managed_modules.each do |puppet_module|
|
56
|
+
puts "Syncing #{puppet_module}"
|
57
|
+
Git.pull(options[:namespace], puppet_module)
|
58
|
+
module_configs = Util.parse_config("#{PROJ_ROOT}/#{puppet_module}/#{MODULE_CONF_FILE}")
|
59
|
+
files_to_manage = module_files | defaults.keys | module_configs.keys
|
60
|
+
files_to_delete = []
|
61
|
+
files_to_manage.each do |file|
|
62
|
+
file_configs = (defaults[file] || {}).merge(module_configs[file] || {})
|
63
|
+
if file_configs['unmanaged']
|
64
|
+
puts "Not managing #{file} in #{puppet_module}"
|
65
|
+
files_to_delete << file
|
66
|
+
elsif file_configs['delete']
|
67
|
+
Renderer.remove(module_file(puppet_module, file))
|
68
|
+
else
|
69
|
+
erb = Renderer.build(local_file(options[:configs], file))
|
70
|
+
template = Renderer.render(erb, file_configs)
|
71
|
+
Renderer.sync(template, "#{PROJ_ROOT}/#{puppet_module}/#{file}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
files_to_manage -= files_to_delete
|
75
|
+
if options[:noop]
|
76
|
+
Git.update_noop(puppet_module, options[:branch])
|
77
|
+
else
|
78
|
+
Git.update(puppet_module, files_to_manage, options[:message], options[:branch])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
elsif options[:command] == 'hook'
|
82
|
+
Hook.hook(args[1], options)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/modulesync.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'modulesync'
|
7
|
+
spec.version = '0.0.1'
|
8
|
+
spec.authors = ['Colleen Murphy']
|
9
|
+
spec.email = ['colleen@puppetlabs.com']
|
10
|
+
spec.summary = %q{Puppet Module Synchronizer}
|
11
|
+
spec.description = %q{Utility to synchronize common files across puppet modules in Github.}
|
12
|
+
spec.homepage = "http://github.com/puppetlabs/modulesync"
|
13
|
+
spec.license = "Apache 2"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'git', '~>1.2'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modulesync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colleen Murphy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: git
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
description: Utility to synchronize common files across puppet modules in Github.
|
42
|
+
email:
|
43
|
+
- colleen@puppetlabs.com
|
44
|
+
executables:
|
45
|
+
- msync
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
52
|
+
- LICENSE
|
53
|
+
- README.md
|
54
|
+
- bin/msync
|
55
|
+
- lib/modulesync.rb
|
56
|
+
- lib/modulesync/cli.rb
|
57
|
+
- lib/modulesync/constants.rb
|
58
|
+
- lib/modulesync/git.rb
|
59
|
+
- lib/modulesync/hook.rb
|
60
|
+
- lib/modulesync/renderer.rb
|
61
|
+
- lib/modulesync/util.rb
|
62
|
+
- modulesync.gemspec
|
63
|
+
homepage: http://github.com/puppetlabs/modulesync
|
64
|
+
licenses:
|
65
|
+
- Apache 2
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.2.2
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Puppet Module Synchronizer
|
87
|
+
test_files: []
|
88
|
+
has_rdoc:
|