gitchefsync 0.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d42fe53a3355fac66554627bcc30fedc5a28d9a
4
+ data.tar.gz: 6a277f678f793afdf12308510e8be5466711068a
5
+ SHA512:
6
+ metadata.gz: c6c0d391f1033f08f0e919c38be05033e057b1195d6db064d2033abae366eaf1c1c62156f6a0873f9b8e15509c443c2354e0d39a46dba7e9fcc6969e1b8cd590
7
+ data.tar.gz: b1f724fa77ee87ab65efa1bd48234fa34bacc96026222e96a25deced9cebee57469ee0207eaa0321295885651bab3018874bb3c35e73f59f0d46750831676486
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ one.sh
24
+ .buildpath
25
+ .project
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2014 Blackberry
2
+
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # GitChefSync
2
+
3
+ This is a Git -> Chef synchronization toolset.
4
+
5
+ ### Requirements:
6
+ - Git (1.8.x)
7
+ - Chef
8
+ - Berkshelf
9
+
10
+ ### Gem dependencies:
11
+ - gitlab + transitive dependencies
12
+
13
+
14
+ To install (from source):
15
+ - git clone [Mandolin GitChefSync](http://gitlab.rim.net/chef/gitchefsync.git)
16
+ - cd gitchefsync && rake install
17
+
18
+ ### Running:
19
+
20
+ - Typical "Master" options are:
21
+ - Cookbook sync: gitchefsync syncCookbooks\[Local\] -c /path_to_config -t xxxyyy
22
+ - Environment sync: gitchefsync syncEnv -c /path_to_config -t xxxyyy
23
+ - "Sous" operation:
24
+ - stage upload: gitchefsync stagedUpload -c /path_to_config -t xxxyyy
25
+
26
+
27
+ ### Configurtion
28
+ - VM configuration of gitchefsync [chefsync](https://gitlab.rim.net/mand-common/chefsync)
29
+ - sync-config.json is the central config file as is required for all operations
30
+ - relies on knife and chef in general it's best to create a .chef directory in the working directory and fill it with appropriate values, if not a .chef directory and it's corresponding knife.rb file will be created
31
+ - The script relies on 3 important pieces: Git, Chef and Berks. Installation options: see chefsync (https://gitlab.rim.net/mand-common/chefsync).
32
+ - There are 2 modes of operation: pulling from master node in git and pushing those changes into chef server, OR using the working directory (which is a git repository). Running locally can be achieved with the "sync_local flag".
33
+ - syncCookbooks still however depends on berks upload, which for all intents and purposes requires access to both git and the internet for getting and installing cookbook dependencies.
34
+
35
+ ### Sample sync-config.json
36
+
37
+ {
38
+ "knife":"/usr/local/bin/knife",
39
+ "git":"/usr/bin/git",
40
+ "berks":"/usr/local/bin/berks",
41
+ "working_directory":"/working_directory",
42
+ "knife_file":"path_to_knife/knife.rb",
43
+ "gitlab_group_names": [MAND_IEMS,MAND_BBM],
44
+ "git_env_repo": ""https://gitlab.rim.net/mand-common/global_chef_env.git",
45
+ "cookbook_repo_list" : ["https://gitlab.rim.net/mand-ems/iems.git"],
46
+ "sync_local" : true,
47
+ "stage_dir": "path_to_stage_",
48
+ "tmp_dir" : "path_to_store_tmp_data"
49
+ }
50
+
51
+ ### Dependencies:
52
+ - Berks: likely requires you to disable SSL if running through a proxy
53
+ - Git - goes without saying, need git
54
+ - Gitlab - https://gitlab.rim.net is hardcoded currently
55
+ - knife.rb file
56
+
57
+ Example of knife.rb :
58
+ node_name 'admin'
59
+ client_key '/etc/chef-server/admin.pem'
60
+ chef_server_url 'https://yourserver'
61
+
62
+ ### Cookbook synchronization
63
+ - the current "gitlab_group_names" are the gitlab groups that are the target set of repositories for synchronization
64
+ - this can work concurrently with "cookbook_repo_list" an explicit set of git urls for those cookbooks
65
+ - cookbooks are vetted with berks and knife
66
+ - requires appropriate knife configuration
67
+ - requires that your gitlab token is set correctly
68
+
69
+ ### Environment synchronization
70
+
71
+ - the "git_env_group" is the repository that houses the standard configuration for environment, data bags and roles
72
+ - chef-repo
73
+ - environments/
74
+ env.json
75
+ - data_bags/
76
+ - db_name1/
77
+ databag.json
78
+ - db_name2/
79
+ -roles/
80
+
81
+ OR structure:
82
+
83
+ - chef-repo
84
+ - env_identifier1/
85
+ - environments/
86
+ env.json
87
+ - data_bags/
88
+ - db_name1/
89
+ databag.json
90
+ - db_name2/
91
+ -roles/
92
+ -env_identifier2/
93
+ - environments/
94
+ env.json
95
+ - data_bags/
96
+ - db_name1/
97
+ databag.json
98
+ - db_name2/
99
+ -roles/
100
+
101
+ See (http://gitlab.rim.net/mand-common/global_chef_env) for example of such
102
+
103
+ ### Audit
104
+ - An audit file will be generated for each run of the synchronization, where each cookbook will generate a json structure of which cookbooks were deployed or staged
105
+ - 2 sets of audit files will be generated, one for the cookbook related synchronization and another for
106
+ - Audit will be in a parsable json format stored in the staging directory
107
+ [
108
+ {
109
+ "name": "epagent",
110
+ "ts": 1400702312,
111
+ "version": "1.0.3"
112
+ },
113
+ {
114
+ "name": "epagent",
115
+ "ts": 1400702315,
116
+ "version": "1.0.4"
117
+ },
118
+
119
+ {
120
+ "name": "bb_jboss-cookbook",
121
+ "ts": 1400702327,
122
+ "version": "1.0.0"
123
+ },
124
+ {
125
+ "name": "relay_node-cookbook",
126
+ "ts": 1400702329,
127
+ "exception": "Some Exception msg",
128
+ "version": "1.0.0"
129
+ },
130
+ {
131
+ "name": "bb_jssecert-cookbook",
132
+ "ts": 1400702333,
133
+ "version": "1.0.0"
134
+ },
135
+ {
136
+ "name": "nsupdate",
137
+ "ts": 1400702336,
138
+ "version": "1.0.0"
139
+ },
140
+ {
141
+ "name": "jdk-certicom",
142
+ "ts": 1400702339
143
+ },
144
+ {
145
+ "name": "bb_apt-cookbook",
146
+ "ts": 1400702343,
147
+ "version": "1.0.0"
148
+ }
149
+
150
+ ]
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/gitchefsync ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gitchefsync'
4
+
5
+ if ARGV.length == 0
6
+ Gitchefsync.help
7
+ else
8
+ begin
9
+ method = Gitchefsync.method( ARGV[0] )
10
+ #find method opts: stop when encountering arguments --x -y
11
+
12
+ options = Gitchefsync.parseAndConfigure( ARGV )
13
+ method.call
14
+ rescue Gitchefsync::ConfigError => e
15
+ puts e.backtrace
16
+ Gitchefsync.help
17
+ end
18
+ end
data/build.sh ADDED
@@ -0,0 +1,20 @@
1
+ #sudo rake install
2
+ mkdir -p /opt/gitchefsync/working_dir
3
+ mkdir -p /opt/gitchefsync/staging
4
+
5
+ #
6
+ #export PATH=/opt/chefdk/embedded/bin:~/.chefdk/gem/ruby/2.1.0/bin
7
+
8
+ CONF=/opt/gitchefsync/config/sync-config.json
9
+ rm pkg/*gem
10
+ rake build
11
+ wd=`pwd`
12
+ gem install pkg/*gem
13
+ cd bin
14
+ #
15
+ gitchefsync syncCookbooks --private_token=LQv1BEwgo3Z4RkuKpYyb --config=$CONF
16
+ #gitchefsync syncCookbooksLocal --private_token=LQv1BEwgo3Z4RkuKpYyb --config=$CONF
17
+ gitchefsync syncEnv --private_token=LQv1BEwgo3Z4RkuKpYyb --config=$CONF
18
+
19
+ gitchefsync stagedUpload --private_token=LQv1BEwgo3Z4RkuKpYyb --config=$CONF
20
+ cd $wd
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gitchefsync/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gitchefsync"
8
+ spec.version = Gitchefsync::VERSION
9
+ spec.authors = ["Marcus Simonsen"]
10
+ spec.email = ["msimonsen@blackberry.com"]
11
+ spec.summary = "Git to Chef sync"
12
+ spec.description = "Tool(s) to help synchronize Git -> Chef server"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ ##Need to figure out the right dependency versions first, otherwise errors out
21
+ #spec.add_runtime_dependency 'chef'
22
+ #spec.add_runtime_dependency 'berkshelf'
23
+ spec.add_runtime_dependency "gitlab", '~> 3.0.0'
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake"
26
+ spec.homepage = "https://gitlab.rim.net/mandolin/gitchefsync/tree/master"
27
+
28
+
29
+ end
@@ -0,0 +1,219 @@
1
+ require 'json'
2
+ require 'gitchefsync/config'
3
+ require 'gitchefsync/io_util'
4
+ require 'gitchefsync/knife_util'
5
+ module Gitchefsync
6
+
7
+ class Audit
8
+
9
+ def initialize (fileLocation, type = 'cb')
10
+ @fileLocation = fileLocation
11
+ @ts = Time.now.to_i
12
+ @list = Array.new
13
+ @type = type
14
+ end
15
+
16
+ #adds an audit item
17
+ def add ( item )
18
+ @list << item.to_hash
19
+ end
20
+
21
+ def addItem(name, version)
22
+ item = AuditItem.new(name,version)
23
+ add(item)
24
+ end
25
+
26
+ def addCookbook(cookbook,action="UPDATE",exception=nil)
27
+ item = AuditItem.new(cookbook.name,cookbook.version,exception,action)
28
+ item.setCookbook(cookbook)
29
+ add(item)
30
+ end
31
+
32
+ #This gives enough information
33
+ def addEnv(name,action='UPDATE',exception=nil)
34
+ cb = Cookbook.new(name,'','mandolin','mandolin@blackberry.com')
35
+ addCookbook(cb,action,exception)
36
+ end
37
+
38
+ #writes the audit out
39
+ #write out a current file audit-type-current.json
40
+ #and an audit-type-timestamp.json
41
+ def write
42
+ begin
43
+ fileLoc = @fileLocation + "/audit-" +@type+ @ts.to_s + ".json"
44
+ #fileCurrent = @fileLocation + "/audit-" +@type+ "-current" + ".json"
45
+ if @list.length > 0
46
+ Gitchefsync.logger.debug "event_id=write_audit:file_loc=#{fileLoc}"
47
+ file = File.open(fileLoc, "w")
48
+ json = JSON.generate(@list)
49
+ file.write(json)
50
+ #create sym link to this file
51
+ latest = @fileLocation+ "/audit_" + @type + "_latest.json"
52
+ if File.exists? latest
53
+ File.delete latest
54
+ end
55
+ File.symlink(file,latest)
56
+ else
57
+ Gitchefsync.logger.debug "event_id=no_write_audit"
58
+ end
59
+ rescue IOError => e
60
+ raise e
61
+ ensure
62
+ file.close unless file.nil?
63
+ end
64
+ end
65
+
66
+ #returns the latest audit file
67
+ def latest
68
+ entries = Dir.glob(@fileLocation + "/audit-#{@type}*")
69
+ file = nil
70
+ if entries != nil
71
+ file = entries.sort[entries.length-1]
72
+ end
73
+ file
74
+ end
75
+
76
+ #trims the oldest number of audit files to a max to to_num
77
+ #Additionally archives the audit file in an audit-archive.tar.gz
78
+ # in the fileLocation directory
79
+ #
80
+ #@param suffix - the audit suffix
81
+ #@param to_num
82
+ def trim(to_num)
83
+ entries = Dir.glob(@fileLocation + "/audit-#{@type}*")
84
+ Gitchefsync.logger.debug "#{@fileLocation}:#{@type} num audit files: #{entries.length}, keep=#{to_num}"
85
+ files_trimmed = 0
86
+ files_to_archive = Array.new
87
+ if entries != nil
88
+ #sorted in descending order (timestamp)
89
+ sorted = entries.sort
90
+ sl = sorted.length - to_num
91
+ if sl > 0
92
+ for i in 0..(sl-1)
93
+ #File.delete sorted[i]
94
+ files_to_archive << sorted[i]
95
+ end
96
+ files_trimmed = sl
97
+ end
98
+ end
99
+ #Archiving and cleanup
100
+ begin
101
+ if files_to_archive.length > 0
102
+ Gitchefsync.logger.debug "executing: tar uf #{@fileLocation}/audit-archive.tar.gz on #{flatten(files_to_archive)}"
103
+ FS.cmd("cd #{@fileLocation} && tar uf audit-archive.tar.gz #{flatten(files_to_archive)}")
104
+ Gitchefsync.logger.info "event_id=audit_archive:files=#{files_to_archive}"
105
+ #delete them
106
+ for f in files_to_archive
107
+ File.delete(f)
108
+ end
109
+ end
110
+ rescue Exception => e
111
+ Gitchefsync.logger.error "event_id=no_write_audit:msg=#{e.message}:trace=#{e.backtrace}"
112
+ end
113
+ Gitchefsync.logger.debug("files trimmed:#{files_trimmed}")
114
+ files_trimmed
115
+ end
116
+
117
+ def flatten(files_array)
118
+ str = ""
119
+ for f in files_array
120
+ str << File.basename(f) << " "
121
+ end
122
+ str
123
+ end
124
+
125
+ #returns json structure of the latest audit
126
+ def parseLatest
127
+ file = latest
128
+ if file != nil
129
+ json = File.read(file)
130
+ json = JSON.parse(json)
131
+ end
132
+ json
133
+ end
134
+
135
+ def latestAuditItems
136
+ json = parseLatest
137
+ if json.nil?
138
+ raise AuditError, "No json available"
139
+ end
140
+ audit_list = Array.new
141
+ json.each do |audit_item|
142
+ audit_list << AuditItem.new(nil,nil).from_hash(audit_item)
143
+ end
144
+ audit_list
145
+ end
146
+
147
+ #if the json has exceptions
148
+ def hasError (json_obj)
149
+ ret = false
150
+ json_obj.each do |item|
151
+ if item['exception'] != nil then ret = true end
152
+ end
153
+ ret
154
+ end
155
+ end
156
+
157
+ #contains functionality associated to audit information gathering
158
+ #TODO: just reference Cookbook clas in knife_util
159
+ class AuditItem
160
+
161
+ def initialize(name, version, exception = nil, action = 'UPDATE', ts = Time.now)
162
+ @name = name
163
+ @version = version
164
+ @ts = ts.to_i
165
+ @exception = exception
166
+ @action = action
167
+ end
168
+
169
+ def name
170
+ @name
171
+ end
172
+ def ex
173
+ @exception
174
+ end
175
+
176
+ #types are CB and ENV
177
+ def setType type
178
+ @type = type
179
+ end
180
+
181
+ def setCookbook(cb)
182
+ @cookbook = cb
183
+ end
184
+
185
+ #TODO action should be an enumeration
186
+ def setAction action
187
+ @action = action
188
+ end
189
+
190
+ #this method doesn't work when called when exception is created from json (from_hash)
191
+ def to_hash
192
+ h = Hash.new
193
+ h[:name] = @name
194
+ h[:ts] = @ts
195
+ if @exception.is_a? Exception
196
+ h[:exception] = @exception.message unless @exception == nil
197
+ else
198
+ h[:exception] = @exception unless @exception == nil
199
+ end
200
+ h[:version] = @version unless @version == nil
201
+ h[:type] = @type unless @type == nil
202
+ h[:action] = @action unless @action == nil
203
+ h[:maintainer] = @cookbook.maintainer unless @cookbook == nil
204
+ h[:maintainer_email] = @cookbook.maintainer_email unless @cookbook == nil
205
+ h
206
+ end
207
+
208
+ def from_hash(h)
209
+ @name = h['name']
210
+ @ts = h['ts']
211
+ @exception = h['exception']
212
+ @version = h['version']
213
+ @type = h['type']
214
+ @action = h['action']
215
+ @cookbook = Cookbook.new(@name,@version,h['maintainer'],h['maintainer_email'])
216
+ return self
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,178 @@
1
+ module Gitchefsync
2
+
3
+ extend Gitchefsync::Configuration
4
+ def self.included base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ #git installed?
9
+ def self.checkGit
10
+ include Git
11
+ if Git.hasGit == false
12
+ logger.error "event_id=git_error:msg=Git was not found on the path"
13
+ raise GitError, "Git was not detected"
14
+ end
15
+ end
16
+
17
+ def self.gitDelta(path, remote_ref)
18
+ include Git
19
+ env_repo = @config['git_env_repo']
20
+
21
+ local = Git.cmd "cd #{path} && #{@git_bin} rev-parse HEAD"
22
+ #logger.debug "local #{local}: path=#{path}"
23
+ remote = Git.cmd "cd #{path} && #{@git_bin} ls-remote origin #{remote_ref}"
24
+ return false if remote.empty?
25
+ remote = remote.split(/\s/)[0]
26
+
27
+ delta = (local.chomp != remote.chomp)
28
+ logger.debug "event_id=gitDelta:local=#{local.chomp}:remote=#{remote.chomp}:delta=#{delta}"
29
+ delta
30
+ end
31
+
32
+ # Verify .gitchefsync at HEAD of default_branch
33
+ def self.checkProjectConfig(cmd, log)
34
+ begin
35
+ proc_cmd, cmd_line = cmd
36
+ return proc_cmd.call(cmd_line)
37
+ rescue GitError, CmdError
38
+ proc_log, msg = log
39
+ proc_log.call(msg)
40
+ end
41
+ return
42
+ end
43
+
44
+ #@param project - Gitlab project object
45
+ def self.pullProject(project, verify_yml=false)
46
+ p_name = project['path_with_namespace'].split('/').join('_')
47
+ project_path = File.join(@git_local, p_name)
48
+
49
+ default_branch = project['default_branch']
50
+
51
+ url_type = @config['gitlab_url_type']
52
+ # "http" is default if @config['gitlab_url_type'] not configured
53
+ url_type ||= "http"
54
+
55
+ if url_type.eql?("http")
56
+ project_url = project['http_url_to_repo']
57
+ # Verify .gitchefsync at HEAD of default_branch using `wget -O - url`
58
+ cmd_line = "wget -qO - #{project['web_url']}/raw/#{default_branch}/.gitchefsync.yml"
59
+ cmd = [ Proc.new{ |cmd_line| FS.cmd cmd_line }, cmd_line ]
60
+
61
+ elsif url_type.eql?("ssh")
62
+ project_url = project['ssh_url_to_repo']
63
+ # Verify .gitchefsync at HEAD of default_branch using `git archive` (not currently supported/enabled using https protocol)
64
+ cmd_line = "git archive --remote=#{project_url} #{default_branch}: .gitchefsync.yml"
65
+ cmd = [ Proc.new { |cmd_line| Git.cmd("#{cmd_line}") }, cmd_line ]
66
+ end
67
+
68
+ msg = "event_id=project_missing_.gitchefsync.yml:project_url=#{project_url}:default_branch=#{default_branch}"
69
+ proc_log = Proc.new { |msg| logger.info "#{msg}" }
70
+ log = [ proc_log, msg ]
71
+
72
+ if verify_yml
73
+ check = checkProjectConfig(cmd, log)
74
+ if check.nil? || check.empty?
75
+ proc_log.call(msg)
76
+ return
77
+ end
78
+ end
79
+
80
+ begin
81
+ self.updateGit(project_path, project_url)
82
+ rescue GitError => e
83
+ logger.error "event_id=git_error:msg=#{e.message}:trace=#{e.backtrace}"
84
+ logger.error "event_id=remove_project_path: #{project_path}"
85
+ FS.cmd "rm -rf #{project_path}"
86
+ end
87
+ end
88
+
89
+ #central place to get cookbooks from server
90
+ #will be determined once - and will be (eventually) thread safe
91
+ def self.serverCookbooks
92
+ if @serverCookbooks.nil?
93
+ knifeUtil = KnifeUtil.new(@knife, @git_local)
94
+ @serverCookbooks = knifeUtil.listCookbooks()
95
+ end
96
+ @serverCookbooks
97
+ end
98
+
99
+ # Pulls all known projects (determined by configured gitlab-token)
100
+ def self.pullAllProjects
101
+ all_projects = []
102
+ done = false
103
+ page, per_page = 1, 100
104
+ while !done do
105
+ page_opts = { :per_page => per_page, :page => page}
106
+ repos = Gitlab.projects(page_opts)
107
+ if (repos.length == 0)
108
+ done = true
109
+ else
110
+ page += 1
111
+ end
112
+ repos.each do |project|
113
+ pullProject(project.to_hash, true)
114
+ end
115
+ end
116
+ end
117
+
118
+ # Get subset of known groups (determined by configured gitlab-token)
119
+ def self.getAllGroupIDs(group_names=[], group_ids=[])
120
+ groups = Array.new
121
+ done = false
122
+ page, per_page = 1, 100
123
+ while !done do
124
+ page_opts = { :per_page => per_page, :page => page}
125
+ known_groups = Gitlab.groups(page_opts)
126
+ if (known_groups.length == 0)
127
+ done = true
128
+ else
129
+ page += 1
130
+ end
131
+ known_groups.each do |group|
132
+ name = group.to_hash['name']
133
+ id = group.to_hash['id']
134
+ if (group_names.include? name) || (group_ids.include? id)
135
+ groups << id
136
+ end
137
+ end
138
+ end
139
+ groups.uniq!
140
+ groups
141
+ end
142
+
143
+ #TODO: change from git to git with path
144
+ def self.updateGit (project_path, git_path)
145
+ include Git
146
+ begin
147
+ branch = @rel_branch
148
+ logger.debug "using release branch: #{branch}"
149
+ _git = @git_bin
150
+ if !Git.gitInit(project_path)
151
+ FS.cmd "mkdir #{project_path}"
152
+ logger.debug "event_id=git_int:path=#{git_path}:project_path=#{project_path}"
153
+ Git.cmd "cd #{project_path} && #{_git} init"
154
+ Git.cmd "cd #{project_path} && #{_git} remote add origin #{git_path}"
155
+ Git.cmd "cd #{project_path} && #{_git} pull origin #{branch}"
156
+ else
157
+ logger.info "event_id=git_repo_exists:project_path=#{project_path}"
158
+ #wipe tags and re-fetch them
159
+ tags = Git.cmd "cd #{project_path} && git tag"
160
+ arr_tags = tags.split(/\n/)
161
+ arr_tags.each do |tag|
162
+ Git.cmd "cd #{project_path} && git tag -d #{tag}"
163
+ end
164
+ end
165
+
166
+ #get all commits and tags
167
+ Git.cmd "cd #{project_path} && #{_git} clean -xdf"
168
+ Git.cmd "cd #{project_path} && #{_git} checkout #{branch}"
169
+ Git.cmd "cd #{project_path} && #{_git} pull origin #{branch}"
170
+
171
+ git_tags = Git.cmd "cd #{project_path} && #{_git} fetch --tags"
172
+
173
+ rescue CmdError => e
174
+ raise GitError, "An error occurred synchronizing cookbooks #{e.message}"
175
+ end
176
+ end
177
+
178
+ end