itamae-mitsurin 0.24 → 0.26

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baac90f0f5aa705c0c2d1e3a9bdd222b1ce47e3b
4
- data.tar.gz: 1d112209337e4cb63cd2f6d00962db1d78e28b10
3
+ metadata.gz: 3aa5cbb917a9a7142ee4869e8e37db1afa8a2faa
4
+ data.tar.gz: c1907411f6c07ad790e73826d0e5febacb3ee530
5
5
  SHA512:
6
- metadata.gz: 5b0823ffe5231992c98be804f49a5b0f1c667855a747867a2088151ae58a359916d5e6989dad0eeb8bdafd3169e7009d0f80b273fc90fa831cfdb54f10f7a44b
7
- data.tar.gz: 4ee53138a35fb70339e09d5675c37f9baeca35c6d7dd08a7951ee0d1aaedf83d43820bc9c7b0c1b2c99e9aadd99226b386598de83c1b176e525b429b05853be8
6
+ metadata.gz: 9615f64653565c2c8b5684de05797d28c4f13667ec41a27ffdca70af1a24e4aa2ede985d268fad6d588bb5068f64ee7df8a91ef4e714135e7ebdc38647410357
7
+ data.tar.gz: 14e0d357b6985d32919b4887213f298ecd251c995e41ea00756477e4be4fd562ad0e39081ba3d33cb9732edd5264f676ea4c6170dedecab8633e81a1eb655306
@@ -23,6 +23,9 @@ module ItamaeMitsurin
23
23
  option :shell, type: :string, default: "/bin/sh"
24
24
  option :ohai, type: :boolean, default: false, desc: "This option is DEPRECATED and will be unavailable."
25
25
  option :profile, type: :string, desc: "[EXPERIMENTAL] Save profiling data", banner: "PATH"
26
+ option :detailed_exitcode, type: :boolean, default: false, desc: "exit code 0 - The run succeeded with no changes or failures, exit code 1 - The run failed, exit code 2 - The run succeeded, and some resources were changed"
27
+
28
+
26
29
  end
27
30
 
28
31
  desc "local RECIPE [RECIPE...]", "Run Itamae locally"
@@ -32,7 +35,7 @@ module ItamaeMitsurin
32
35
  raise "Please specify recipe files."
33
36
  end
34
37
 
35
- Runner.run(recipe_files, :local, options)
38
+ run(recipe_files, :local, options)
36
39
  end
37
40
 
38
41
  desc "ssh RECIPE [RECIPE...]", "Run Itamae via ssh"
@@ -53,7 +56,7 @@ module ItamaeMitsurin
53
56
  raise "Please set '-h <hostname>' or '--vagrant'"
54
57
  end
55
58
 
56
- Runner.run(recipe_files, :ssh, options)
59
+ run(recipe_files, :local, options)
57
60
  end
58
61
 
59
62
  desc "docker RECIPE [RECIPE...]", "Create Docker image"
@@ -66,7 +69,7 @@ module ItamaeMitsurin
66
69
  raise "Please specify recipe files."
67
70
  end
68
71
 
69
- Runner.run(recipe_files, :docker, options)
72
+ run(recipe_files, :local, options)
70
73
  end
71
74
 
72
75
  desc "version", "Print version"
@@ -82,5 +85,12 @@ module ItamaeMitsurin
82
85
  end
83
86
  end
84
87
  end
88
+
89
+ def run(recipe_files, backend_type, options)
90
+ runner = Runner.run(recipe_files, backend_type, options)
91
+ if options[:detailed_exitcode] && runner.diff?
92
+ exit 2
93
+ end
94
+ end
85
95
  end
86
96
  end
@@ -9,7 +9,7 @@ module ItamaeMitsurin
9
9
 
10
10
  def event(type, payload = {})
11
11
  super
12
- @f.puts({'time' => Time.now.iso8601, 'event' => type, 'payload' => payload}.to_json)
12
+ @f.puts({'time' => Time.now.iso8601, 'event' => type, 'payload' => payload}.to_s.encode.to_json)
13
13
  end
14
14
 
15
15
  private
@@ -73,12 +73,9 @@ module ItamaeMitsurin
73
73
  attr_accessor :colored
74
74
 
75
75
  def call(severity, datetime, progname, msg)
76
- log = "%s : %s\n" % ["%5s" % severity, msg2str(msg)]
77
- if colored
78
- colorize(log, severity)
79
- else
80
- log
81
- end
76
+ log = "%s : %s" % ["%5s" % severity, msg2str(msg)]
77
+
78
+ (colored ? colorize(log, severity) : log) + "\n"
82
79
  end
83
80
 
84
81
  def color(code)
@@ -31,3 +31,5 @@ options[:port] = ENV['SSH_PORT']
31
31
  set :host, options[:host_name] || host
32
32
  set :shell, '/bin/bash'
33
33
  set :ssh_options, options
34
+
35
+ set :request_pty, true
@@ -119,7 +119,13 @@ module ItamaeMitsurin
119
119
 
120
120
  puts TaskBase.hl.color(%!Run Itamae to \"#{bname}\"!, :red)
121
121
  run_list_noti = []
122
- command_recipe.each {|c_recipe| run_list_noti << c_recipe.split("/") [2]}
122
+ command_recipe.each { |c_recipe|
123
+ unless c_recipe.split("/")[4].split(".")[0] == 'default'
124
+ run_list_noti << c_recipe.split("/")[2] + "::#{c_recipe.split("/")[4].split(".")[0]}"
125
+ else
126
+ run_list_noti << c_recipe.split("/")[2]
127
+ end
128
+ }
123
129
  puts TaskBase.hl.color(%!Run List to \"#{run_list_noti.uniq.join(", ")}\"!, :green)
124
130
  puts TaskBase.hl.color(%!#{command}!, :white)
125
131
  st = system command
@@ -136,7 +136,13 @@ module ItamaeMitsurin
136
136
 
137
137
  puts TaskBase.hl.color(%!Run Itamae to \"#{bname}\"!, :red)
138
138
  run_list_noti = []
139
- command_recipe.each {|c_recipe| run_list_noti << c_recipe.split("/") [2]}
139
+ command_recipe.each { |c_recipe|
140
+ unless c_recipe.split("/")[4].split(".")[0] == 'default'
141
+ run_list_noti << c_recipe.split("/")[2] + "::#{c_recipe.split("/")[4].split(".")[0]}"
142
+ else
143
+ run_list_noti << c_recipe.split("/")[2]
144
+ end
145
+ }
140
146
  puts TaskBase.hl.color(%!Run List to \"#{run_list_noti.uniq.join(", ")}\"!, :green)
141
147
  puts TaskBase.hl.color(%!#{command}!, :white)
142
148
  st = system command
@@ -114,7 +114,13 @@ module ItamaeMitsurin
114
114
 
115
115
  puts TaskBase.hl.color(%!Run Itamae to \"#{bname}\"!, :red)
116
116
  run_list_noti = []
117
- command_recipe.each {|c_recipe| run_list_noti << c_recipe.split("/") [2]}
117
+ command_recipe.each { |c_recipe|
118
+ unless c_recipe.split("/")[4].split(".")[0] == 'default'
119
+ run_list_noti << c_recipe.split("/")[2] + "::#{c_recipe.split("/")[4].split(".")[0]}"
120
+ else
121
+ run_list_noti << c_recipe.split("/")[2]
122
+ end
123
+ }
118
124
  puts TaskBase.hl.color(%!Run List to \"#{run_list_noti.uniq.join(", ")}\"!, :green)
119
125
  puts TaskBase.hl.color(%!#{command}!, :white)
120
126
  begin
@@ -72,7 +72,13 @@ module Itamae
72
72
  spec_pattern.sort_by! {|item| File.dirname(item)}
73
73
  specs << spec_pattern.join
74
74
  run_list_noti = []
75
- spec_pattern.each {|c_spec| run_list_noti << c_spec.split("/") [2]}
75
+ spec_pattern.each { |c_spec|
76
+ unless c_spec.split("/")[4].split(".")[0] == 'default'
77
+ run_list_noti << c_spec.split("/")[2] + "::#{c_spec.split("/")[4].split(".")[0]}"
78
+ else
79
+ run_list_noti << c_spec.split("/")[2]
80
+ end
81
+ }
76
82
  puts TaskBase.hl.color(%!Run Serverspec to \"#{node_name}\"!, :red)
77
83
  puts TaskBase.hl.color(%!Run List to \"#{run_list_noti.uniq.join(", ")}\"!, :green)
78
84
  st = system specs
@@ -139,6 +139,7 @@ module ItamaeMitsurin
139
139
 
140
140
  verify unless runner.dry_run?
141
141
  if updated?
142
+ runner.diff_found!
142
143
  notify
143
144
  runner.handler.event(:resource_updated)
144
145
  end
@@ -0,0 +1,185 @@
1
+ require 'itamae-mitsurin'
2
+
3
+ module ItamaeMitsurin
4
+ module Resource
5
+ class File < Base
6
+ define_attribute :action, default: :create
7
+ define_attribute :path, type: String, default_name: true
8
+ define_attribute :content, type: String, default: nil
9
+ define_attribute :mode, type: String
10
+ define_attribute :owner, type: String
11
+ define_attribute :group, type: String
12
+ define_attribute :block, type: Proc, default: proc {}
13
+
14
+ def pre_action
15
+ case @current_action
16
+ when :create
17
+ attributes.exist = true
18
+ when :delete
19
+ attributes.exist = false
20
+ when :edit
21
+ attributes.exist = true
22
+
23
+ unless runner.dry_run?
24
+ content = backend.receive_file(attributes.path)
25
+ attributes.block.call(content)
26
+ attributes.content = content
27
+ end
28
+ end
29
+
30
+ send_tempfile
31
+ end
32
+
33
+ def set_current_attributes
34
+ current.exist = run_specinfra(:check_file_is_file, attributes.path)
35
+
36
+ if current.exist
37
+ current.mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp
38
+ current.owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp
39
+ current.group = run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp
40
+ else
41
+ current.mode = nil
42
+ current.owner = nil
43
+ current.group = nil
44
+ end
45
+ end
46
+
47
+ def show_differences
48
+ current.mode = current.mode.rjust(4, '0') if current.mode
49
+ attributes.mode = attributes.mode.rjust(4, '0') if attributes.mode
50
+
51
+ super
52
+
53
+ if @temppath && @current_action != :delete
54
+ compare_file
55
+ end
56
+ end
57
+
58
+ def action_create(options)
59
+ if !current.exist && !@temppath
60
+ run_command(["touch", attributes.path])
61
+ end
62
+
63
+ if @temppath
64
+ if run_specinfra(:check_file_is_file, attributes.path)
65
+ unless check_command(["diff", "-q", @temppath, attributes.path])
66
+ # the file is modified
67
+ updated!
68
+ end
69
+ else
70
+ # new file
71
+ updated!
72
+ end
73
+ end
74
+
75
+ change_target = @temppath && updated? ? @temppath : attributes.path
76
+
77
+ if attributes.mode
78
+ run_specinfra(:change_file_mode, change_target, attributes.mode)
79
+ end
80
+
81
+ if attributes.owner || attributes.group
82
+ run_specinfra(:change_file_owner, change_target, attributes.owner, attributes.group)
83
+ end
84
+
85
+ if @temppath && updated?
86
+ run_specinfra(:move_file, @temppath, attributes.path)
87
+ end
88
+ end
89
+
90
+ def action_delete(options)
91
+ if run_specinfra(:check_file_is_file, attributes.path)
92
+ run_specinfra(:remove_file, attributes.path)
93
+ end
94
+ end
95
+
96
+ def action_edit(options)
97
+ if attributes.mode
98
+ run_specinfra(:change_file_mode, @temppath, attributes.mode)
99
+ else
100
+ mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp
101
+ run_specinfra(:change_file_mode, @temppath, mode)
102
+ end
103
+
104
+ if attributes.owner || attributes.group
105
+ run_specinfra(:change_file_owner, @temppath, attributes.owner, attributes.group)
106
+ else
107
+ owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp
108
+ group = run_specinfra(:get_file_owner_group, attributes.path).stdout.chomp
109
+ run_specinfra(:change_file_owner, @temppath, owner)
110
+ run_specinfra(:change_file_group, @temppath, group)
111
+ end
112
+
113
+ unless check_command(["diff", "-q", @temppath, attributes.path])
114
+ # the file is modified
115
+ updated!
116
+ end
117
+
118
+ run_specinfra(:move_file, @temppath, attributes.path)
119
+ end
120
+
121
+ private
122
+
123
+ def compare_file
124
+ compare_to = if current.exist
125
+ attributes.path
126
+ else
127
+ '/dev/null'
128
+ end
129
+
130
+ diff = run_command(["diff", "-u", compare_to, @temppath], error: false)
131
+ if diff.exit_status == 0
132
+ # no change
133
+ ItamaeMitsurin.logger.debug "file content will not change"
134
+ else
135
+ ItamaeMitsurin.logger.info "diff:"
136
+ diff.stdout.each_line do |line|
137
+ color = if line.start_with?('+')
138
+ :green
139
+ elsif line.start_with?('-')
140
+ :red
141
+ else
142
+ :clear
143
+ end
144
+ ItamaeMitsurin.logger.color(color) do
145
+ ItamaeMitsurin.logger.info line.chomp
146
+ end
147
+ end
148
+ runner.handler.event(:file_content_changed, diff: diff.stdout)
149
+ end
150
+ end
151
+
152
+ # will be overridden
153
+ def content_file
154
+ nil
155
+ end
156
+
157
+ def send_tempfile
158
+ if !attributes.content && !content_file
159
+ @temppath = nil
160
+ return
161
+ end
162
+
163
+ begin
164
+ src = if content_file
165
+ content_file
166
+ else
167
+ f = Tempfile.open('itamae')
168
+ f.write(attributes.content)
169
+ f.close
170
+ f.path
171
+ end
172
+
173
+ @temppath = ::File.join(runner.tmpdir, Time.now.to_f.to_s)
174
+
175
+ run_command(["touch", @temppath])
176
+ run_specinfra(:change_file_mode, @temppath, '0600')
177
+ backend.send_file(src, @temppath)
178
+ run_specinfra(:change_file_mode, @temppath, '0600')
179
+ ensure
180
+ f.unlink if f
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -12,6 +12,8 @@ module ItamaeMitsurin
12
12
  define_attribute :block, type: Proc, default: proc {}
13
13
 
14
14
  def pre_action
15
+ current.exist = run_specinfra(:check_file_is_file, attributes.path)
16
+
15
17
  case @current_action
16
18
  when :create
17
19
  attributes.exist = true
@@ -20,7 +22,7 @@ module ItamaeMitsurin
20
22
  when :edit
21
23
  attributes.exist = true
22
24
 
23
- unless runner.dry_run?
25
+ if !runner.dry_run? || current.exist
24
26
  content = backend.receive_file(attributes.path)
25
27
  attributes.block.call(content)
26
28
  attributes.content = content
@@ -28,11 +30,11 @@ module ItamaeMitsurin
28
30
  end
29
31
 
30
32
  send_tempfile
33
+ compare_file
31
34
  end
32
35
 
33
36
  def set_current_attributes
34
- current.exist = run_specinfra(:check_file_is_file, attributes.path)
35
-
37
+ current.modified = false
36
38
  if current.exist
37
39
  current.mode = run_specinfra(:get_file_mode, attributes.path).stdout.chomp
38
40
  current.owner = run_specinfra(:get_file_owner_user, attributes.path).stdout.chomp
@@ -51,7 +53,7 @@ module ItamaeMitsurin
51
53
  super
52
54
 
53
55
  if @temppath && @current_action != :delete
54
- compare_file
56
+ show_content_diff
55
57
  end
56
58
  end
57
59
 
@@ -60,19 +62,7 @@ module ItamaeMitsurin
60
62
  run_command(["touch", attributes.path])
61
63
  end
62
64
 
63
- if @temppath
64
- if run_specinfra(:check_file_is_file, attributes.path)
65
- unless check_command(["diff", "-q", @temppath, attributes.path])
66
- # the file is modified
67
- updated!
68
- end
69
- else
70
- # new file
71
- updated!
72
- end
73
- end
74
-
75
- change_target = @temppath && updated? ? @temppath : attributes.path
65
+ change_target = attributes.modified ? @temppath : attributes.path
76
66
 
77
67
  if attributes.mode
78
68
  run_specinfra(:change_file_mode, change_target, attributes.mode)
@@ -82,7 +72,7 @@ module ItamaeMitsurin
82
72
  run_specinfra(:change_file_owner, change_target, attributes.owner, attributes.group)
83
73
  end
84
74
 
85
- if @temppath && updated?
75
+ if attributes.modified
86
76
  run_specinfra(:move_file, @temppath, attributes.path)
87
77
  end
88
78
  end
@@ -110,29 +100,39 @@ module ItamaeMitsurin
110
100
  run_specinfra(:change_file_group, @temppath, group)
111
101
  end
112
102
 
113
- unless check_command(["diff", "-q", @temppath, attributes.path])
114
- # the file is modified
115
- updated!
116
- end
117
-
118
103
  run_specinfra(:move_file, @temppath, attributes.path)
119
104
  end
120
105
 
121
106
  private
122
107
 
123
- def compare_file
124
- compare_to = if current.exist
125
- attributes.path
126
- else
127
- '/dev/null'
128
- end
129
-
130
- diff = run_command(["diff", "-u", compare_to, @temppath], error: false)
131
- if diff.exit_status == 0
132
- # no change
133
- ItamaeMitsurin.logger.debug "file content will not change"
108
+ def compare_to
109
+ if current.exist
110
+ attributes.path
134
111
  else
112
+ '/dev/null'
113
+ end
114
+ end
115
+
116
+ def compare_file
117
+ attributes.modified = false
118
+ unless @temppath
119
+ return
120
+ end
121
+
122
+ case run_command(["diff", "-q", compare_to, @temppath], error: false).exit_status
123
+ when 1
124
+ # diff found
125
+ attributes.modified = true
126
+ when 2
127
+ # error
128
+ raise ItamaeMitsurin::Backend::CommandExecutionError, "diff command exited with 2"
129
+ end
130
+ end
131
+
132
+ def show_content_diff
133
+ if attributes.modified
135
134
  ItamaeMitsurin.logger.info "diff:"
135
+ diff = run_command(["diff", "-u", compare_to, @temppath], error: false)
136
136
  diff.stdout.each_line do |line|
137
137
  color = if line.start_with?('+')
138
138
  :green
@@ -146,6 +146,9 @@ module ItamaeMitsurin
146
146
  end
147
147
  end
148
148
  runner.handler.event(:file_content_changed, diff: diff.stdout)
149
+ else
150
+ # no change
151
+ ItamaeMitsurin.logger.debug "file content will not change"
149
152
  end
150
153
  end
151
154
 
@@ -72,7 +72,11 @@ module ItamaeMitsurin
72
72
  end
73
73
 
74
74
  def run_command_in_repo(*args)
75
- run_command(*args, cwd: attributes.destination)
75
+ unless args.last.is_a?(Hash)
76
+ args << {}
77
+ end
78
+ args.last[:cwd] = attributes.destination
79
+ run_command(*args)
76
80
  end
77
81
 
78
82
  def current_branch
@@ -80,7 +84,11 @@ module ItamaeMitsurin
80
84
  end
81
85
 
82
86
  def get_revision(branch)
83
- run_command_in_repo("git rev-list #{shell_escape(branch)} | head -n1").stdout.strip
87
+ result = run_command_in_repo("git rev-list #{shell_escape(branch)}", error: false)
88
+ unless result.exit_status == 0
89
+ fetch_origin!
90
+ end
91
+ run_command_in_repo("git rev-list #{shell_escape(branch)}").stdout.lines.first.strip
84
92
  end
85
93
 
86
94
  def fetch_origin!
@@ -7,6 +7,9 @@ module ItamaeMitsurin
7
7
  class HttpRequest < File
8
8
  RedirectLimitExceeded = Class.new(StandardError)
9
9
 
10
+ alias_method :_action_create, :action_create
11
+ undef_method :action_create, :action_delete, :action_edit
12
+
10
13
  define_attribute :action, default: :get
11
14
  define_attribute :headers, type: Hash, default: {}
12
15
  define_attribute :message, type: String, default: ""
@@ -14,6 +17,23 @@ module ItamaeMitsurin
14
17
  define_attribute :url, type: String, required: true
15
18
 
16
19
  def pre_action
20
+ attributes.exist = true
21
+ attributes.content = fetch_content
22
+
23
+ send_tempfile
24
+ compare_file
25
+ end
26
+
27
+ def show_differences
28
+ current.mode = current.mode.rjust(4, '0') if current.mode
29
+ attributes.mode = attributes.mode.rjust(4, '0') if attributes.mode
30
+
31
+ super
32
+
33
+ show_content_diff
34
+ end
35
+
36
+ def fetch_content
17
37
  uri = URI.parse(attributes.url)
18
38
  response = nil
19
39
  redirects_followed = 0
@@ -45,26 +65,27 @@ module ItamaeMitsurin
45
65
  attributes.content = response.body
46
66
 
47
67
  super
68
+ response.body
48
69
  end
49
70
 
50
71
  def action_delete(options)
51
- action_create(options)
72
+ _action_create(options)
52
73
  end
53
74
 
54
75
  def action_get(options)
55
- action_create(options)
76
+ _action_create(options)
56
77
  end
57
78
 
58
79
  def action_options(options)
59
- action_create(options)
80
+ _action_create(options)
60
81
  end
61
82
 
62
83
  def action_post(options)
63
- action_create(options)
84
+ _action_create(options)
64
85
  end
65
86
 
66
87
  def action_put(options)
67
- action_create(options)
88
+ _action_create(options)
68
89
  end
69
90
  end
70
91
  end
@@ -18,6 +18,11 @@ module ItamaeMitsurin
18
18
  when :create
19
19
  attributes.exist = true
20
20
  end
21
+
22
+ if attributes.gid.is_a?(String)
23
+ # convert name to gid
24
+ attributes.gid = run_specinfra(:get_group_gid, attributes.gid).stdout.to_i
25
+ end
21
26
  end
22
27
 
23
28
  def set_current_attributes
@@ -12,6 +12,8 @@ module ItamaeMitsurin
12
12
  runner = self.new(backend, options)
13
13
  runner.load_recipes(recipe_files)
14
14
  runner.run
15
+
16
+ runner
15
17
  end
16
18
  end
17
19
 
@@ -31,6 +33,7 @@ module ItamaeMitsurin
31
33
  @node = create_node
32
34
  @tmpdir = "/tmp/itamae_tmp"
33
35
  @children = RecipeChildren.new
36
+ @diff = false
34
37
 
35
38
  @backend.run_command(["mkdir", "-p", @tmpdir])
36
39
  @backend.run_command(["chmod", "777", @tmpdir])
@@ -80,6 +83,14 @@ module ItamaeMitsurin
80
83
  end
81
84
  end
82
85
 
86
+ def diff?
87
+ @diff
88
+ end
89
+
90
+ def diff_found!
91
+ @diff = true
92
+ end
93
+
83
94
  private
84
95
  def create_node
85
96
  hash = {}
@@ -1 +1 @@
1
- 0.24
1
+ 0.26
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itamae-mitsurin
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.24'
4
+ version: '0.26'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akihiro Kamiyama
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-06 00:00:00.000000000 Z
11
+ date: 2016-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -347,6 +347,7 @@ files:
347
347
  - lib/itamae-mitsurin/resource/base.rb
348
348
  - lib/itamae-mitsurin/resource/directory.rb
349
349
  - lib/itamae-mitsurin/resource/execute.rb
350
+ - lib/itamae-mitsurin/resource/file.org
350
351
  - lib/itamae-mitsurin/resource/file.rb
351
352
  - lib/itamae-mitsurin/resource/gem_package.rb
352
353
  - lib/itamae-mitsurin/resource/git.rb