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