itamae-plugin-resource-portage 0.1.0.beta1
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 +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +96 -0
- data/Rakefile +6 -0
- data/itamae-plugin-resource-portage.gemspec +26 -0
- data/lib/itamae-plugin-resource-portage.rb +3 -0
- data/lib/itamae-plugin-resource-portage/portage_file_resource_base.rb +162 -0
- data/lib/itamae-plugin-resource-portage/resource_base.rb +53 -0
- data/lib/itamae-plugin-resource-portage/resources.rb +13 -0
- data/lib/itamae-plugin-resource-portage/resources/portage.rb +157 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_accept_keywords.rb +24 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_mask.rb +15 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_package.rb +138 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_pin.rb +37 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_unmask.rb +15 -0
- data/lib/itamae-plugin-resource-portage/resources/portage_use.rb +17 -0
- data/lib/itamae-plugin-resource-portage/version.rb +3 -0
- data/lib/itamae/plugin/resource/portage.rb +9 -0
- data/script/console +14 -0
- data/script/setup +7 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 6934173b643af32c0be76926c4b63be912edb7be
|
|
4
|
+
data.tar.gz: 5e966d20c26bf0d127baaf7c0c4a74e3c875df6a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bd3c0fc6d72224171c4a393cba286a036b3af539cd827cace9ddc474d1ec7ff77b9afa874975d70e653a90c29bf0bf4f4ebff4839b4303b29c437b52803a4a30
|
|
7
|
+
data.tar.gz: 4b5fb91c5858da0a0ef50d234de605edc470d871d6a9830b1c0606248ab9a024b20fe1cb2459a884dace3a9990faf8ed2390320488dee0dc261417050f79438f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Shota Fukumori (sora_h)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Itamae::Plugin::Resource::Portage
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
gem 'itamae-plugin-resource-portage'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## prerequisites
|
|
10
|
+
|
|
11
|
+
- eix
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Basic attributes
|
|
16
|
+
|
|
17
|
+
All resources take `name`, `slot`, `version`, and/or `atom` attribtues
|
|
18
|
+
|
|
19
|
+
- name (default_name): package name (category/name)
|
|
20
|
+
- version (+ slot) or atom:
|
|
21
|
+
- version: package version + operator (e.g. `1.0.0`, `>=1.0.0`, `<2.0.0`)
|
|
22
|
+
- slot: (optional)
|
|
23
|
+
- atom: if specified, version and slot are ignored
|
|
24
|
+
|
|
25
|
+
`portage_accept_keywords`, `portage_mask`, `portage_unmask`, `portage_use` resource take additionally:
|
|
26
|
+
|
|
27
|
+
- action: `:add` (default) or `:remove`
|
|
28
|
+
- target: when absolute path given, the resource will use that path to modify (e.g. `/etc/portage/package.use/foobar`). Otherwise, given name under appropriate directory will be used (e.g. `foobar` to `/etc/portage/package.XXX/foobar`); Default to `/etc/portage/package.XXX/itamae` or `/etc/portage/package.XXX` (only when it exists as file)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### `portage` resource
|
|
32
|
+
|
|
33
|
+
`portage` resource defines `portage_package`, `portage_unmask`, `portage_accept_keywords`, `portage_use`, and/or `portage_pin` resources based on given attributes.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
portage "www-servers/nginx" do
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
portage "www-servers/nginx" do
|
|
40
|
+
version "1.9.7"
|
|
41
|
+
|
|
42
|
+
flags ['nginx_modules_http_stub_status']
|
|
43
|
+
|
|
44
|
+
# accept keywords by default, but if you want to disable:
|
|
45
|
+
# keywords []
|
|
46
|
+
|
|
47
|
+
# Mask =version and unmask !=version
|
|
48
|
+
pin true
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- action: install (default), remove
|
|
53
|
+
- unmask (nil/bool):
|
|
54
|
+
- pin (bool):
|
|
55
|
+
- noreplace (bool, default=true):
|
|
56
|
+
- oneshot (bool, default=false):
|
|
57
|
+
- flags (array)
|
|
58
|
+
- keywords (array,default=[$ARCH])
|
|
59
|
+
- package_use_file
|
|
60
|
+
- package_mask_file
|
|
61
|
+
- package_unmask_file
|
|
62
|
+
- package_accept_keywords_file
|
|
63
|
+
- emerge_cmd:
|
|
64
|
+
- eix_cmd:
|
|
65
|
+
|
|
66
|
+
### `portage_accept_keywords` resource
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
portage_accept_keywords "www-servers/nginx" do
|
|
70
|
+
keywords ['~amd64']
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `keywords`: Default to `$ARCH` (`eix --print ARCH`)
|
|
75
|
+
|
|
76
|
+
### `portage_mask` resource
|
|
77
|
+
|
|
78
|
+
### `portage_pin` resource
|
|
79
|
+
|
|
80
|
+
### `portage_package` resource
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
85
|
+
|
|
86
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/itamae-plugin-resource-portage.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
96
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'itamae-plugin-resource-portage/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "itamae-plugin-resource-portage"
|
|
8
|
+
spec.version = ItamaePluginResourcePortage::VERSION
|
|
9
|
+
spec.authors = ["sorah (Shota Fukumori)"]
|
|
10
|
+
spec.email = ["her@sorah.jp"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Itamae resources for Gentoo Portage}
|
|
13
|
+
spec.homepage = "https://github.com/sorah/itamae-plugin-resource-portage"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = "bin"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "itamae"
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "bundler"
|
|
24
|
+
spec.add_development_dependency "rake"
|
|
25
|
+
spec.add_development_dependency "rspec"
|
|
26
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
class PortageFileResourceBase < ResourceBase
|
|
5
|
+
define_attribute :action, default: :add
|
|
6
|
+
define_attribute :target, type: String
|
|
7
|
+
|
|
8
|
+
def value
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def item_list?
|
|
13
|
+
!(value == true || value == false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def pre_action
|
|
17
|
+
case @current_action
|
|
18
|
+
when :add
|
|
19
|
+
attributes.value = value || (item_list? ? [] : false)
|
|
20
|
+
when :remove
|
|
21
|
+
attributes.value = item_list? ? [] : false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def action_add(options)
|
|
26
|
+
lines = current_content.lines.map(&:chomp)
|
|
27
|
+
new_line = if item_list?
|
|
28
|
+
"#{atom} #{value.kind_of?(Array) ? value.join(' ') : value}"
|
|
29
|
+
else
|
|
30
|
+
atom.to_s
|
|
31
|
+
end
|
|
32
|
+
modified = false
|
|
33
|
+
|
|
34
|
+
if current_value
|
|
35
|
+
if lines.grep(target_line_pattern).size > 1
|
|
36
|
+
modified = true
|
|
37
|
+
lines.reject! { |_| target_line_pattern === _ }
|
|
38
|
+
lines << new_line
|
|
39
|
+
else
|
|
40
|
+
lines.map! do |_|
|
|
41
|
+
if target_line_pattern === _
|
|
42
|
+
if _.chomp != new_line
|
|
43
|
+
modified = true
|
|
44
|
+
end
|
|
45
|
+
new_line
|
|
46
|
+
else
|
|
47
|
+
_
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
modified = true
|
|
53
|
+
lines << new_line
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if modified
|
|
57
|
+
send_file(lines.join(?\n))
|
|
58
|
+
updated!
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def action_remove(options)
|
|
63
|
+
lines = current_content.lines.map(&:chomp)
|
|
64
|
+
|
|
65
|
+
new_lines = lines.reject { |_| target_line_pattern === _ }
|
|
66
|
+
if lines.size != new_lines.size
|
|
67
|
+
send_file(new_lines.join(?\n))
|
|
68
|
+
updated!
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def send_file(content)
|
|
73
|
+
ensure_target_directory
|
|
74
|
+
|
|
75
|
+
tempfile = Tempfile.new('portage_file')
|
|
76
|
+
tempfile.puts content
|
|
77
|
+
tempfile.flush
|
|
78
|
+
|
|
79
|
+
remote_temppath = ::File.join(runner.tmpdir, "#{self.class.name.split('::').last}-#{Time.now.to_f.to_s}")
|
|
80
|
+
backend.send_file(tempfile.path, remote_temppath)
|
|
81
|
+
run_specinfra(:change_file_mode, remote_temppath, '0644')
|
|
82
|
+
run_specinfra(:move_file, remote_temppath, target)
|
|
83
|
+
ensure
|
|
84
|
+
tempfile.close if tempfile
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_current_attributes
|
|
88
|
+
current.value = current_value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def clear_current_attributes
|
|
92
|
+
super
|
|
93
|
+
@current_content = nil
|
|
94
|
+
@current_value = nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def current_content
|
|
98
|
+
@current_content ||= target_exist? ? backend.receive_file(target) : "\n"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_value
|
|
102
|
+
@current_value = begin
|
|
103
|
+
lines = current_content.each_line.grep(target_line_pattern)
|
|
104
|
+
|
|
105
|
+
# https://github.com/gentoo/portage/blob/1eb8e1e38c410a6b3792d005da7a75a87c811721/pym/portage/util/__init__.py
|
|
106
|
+
if item_list?
|
|
107
|
+
if lines.empty?
|
|
108
|
+
nil
|
|
109
|
+
else
|
|
110
|
+
lines.flat_map { |_| _.chomp.split(/\s+/, 2)[1].split(/\s+/) }
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
!lines.empty?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def target_default_directory
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ensure_target_directory
|
|
123
|
+
target
|
|
124
|
+
if @target_dir_unexist
|
|
125
|
+
run_command(['mkdir', '-p', target_default_directory])
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def target_line_pattern
|
|
130
|
+
item_list? ? /^#{Regexp.escape(atom)}\s/ : /^#{Regexp.escape(atom)}$/
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def target_exist?
|
|
134
|
+
run_command(['test', '-e', target], error: false).exit_status == 0
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def target
|
|
138
|
+
@target ||= begin
|
|
139
|
+
@target_dir_unexist = false
|
|
140
|
+
if attributes.target && attributes.target.start_with?('/') # absolute path
|
|
141
|
+
attributes.target
|
|
142
|
+
else # relative
|
|
143
|
+
stat = run_command(['stat', '-c', '%F' , target_default_directory], error: false)
|
|
144
|
+
exist = stat.exit_status.zero?
|
|
145
|
+
directory = exist && stat.stdout.chomp == 'directory'
|
|
146
|
+
file = exist && stat.stdout.chomp == 'regular file'
|
|
147
|
+
|
|
148
|
+
case
|
|
149
|
+
when directory
|
|
150
|
+
"#{target_default_directory}/#{attributes.target || 'itamae'}"
|
|
151
|
+
when file
|
|
152
|
+
target_default_directory
|
|
153
|
+
else # enoent
|
|
154
|
+
@target_dir_unexist = true
|
|
155
|
+
"#{target_default_directory}/#{attributes.target || 'itamae'}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'itamae'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
class ResourceBase < Itamae::Resource::Base
|
|
5
|
+
module Attributes
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.instance_eval do
|
|
8
|
+
define_attribute :name, type: String, default_name: true
|
|
9
|
+
|
|
10
|
+
define_attribute :version, type: String
|
|
11
|
+
define_attribute :slot, type: String
|
|
12
|
+
define_attribute :atom, type: String
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
include Attributes
|
|
18
|
+
|
|
19
|
+
def atom
|
|
20
|
+
@atom ||= attributes.atom or begin
|
|
21
|
+
case
|
|
22
|
+
when attributes.version
|
|
23
|
+
_, op, version, slot = attributes.version.match(/\A(!=|>=|>|=|<=|<|~)?(.+)(:.+)?\z/).to_a
|
|
24
|
+
|
|
25
|
+
# XXX:
|
|
26
|
+
if slot.nil? || slot.empty?
|
|
27
|
+
if attributes.slot
|
|
28
|
+
slot = ":#{attributes.slot}"
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
attributes.slot = slot[1..-1]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if op.nil? || op.empty?
|
|
35
|
+
op = '='
|
|
36
|
+
end
|
|
37
|
+
@op = op
|
|
38
|
+
|
|
39
|
+
"#{op}#{attributes.name}-#{version}#{slot}"
|
|
40
|
+
when attributes.name
|
|
41
|
+
@op = nil
|
|
42
|
+
attributes.name
|
|
43
|
+
else
|
|
44
|
+
raise ArgumentError, "Can't determine atom"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def atom_op
|
|
50
|
+
atom; @op
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/resource_base'
|
|
2
|
+
|
|
3
|
+
require 'itamae-plugin-resource-portage/portage_file_resource_base'
|
|
4
|
+
require 'itamae-plugin-resource-portage/resources/portage_use'
|
|
5
|
+
require 'itamae-plugin-resource-portage/resources/portage_accept_keywords'
|
|
6
|
+
require 'itamae-plugin-resource-portage/resources/portage_mask'
|
|
7
|
+
require 'itamae-plugin-resource-portage/resources/portage_unmask'
|
|
8
|
+
require 'itamae-plugin-resource-portage/resources/portage_pin'
|
|
9
|
+
# require 'itamae-plugin-resource-portage/resources/portage_env'
|
|
10
|
+
|
|
11
|
+
require 'itamae-plugin-resource-portage/resources/portage_package'
|
|
12
|
+
|
|
13
|
+
require 'itamae-plugin-resource-portage/resources/portage'
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require 'itamae/resource/base'
|
|
2
|
+
require 'itamae-plugin-resource-portage/resource_base'
|
|
3
|
+
require 'itamae-plugin-resource-portage/resources/portage_package'
|
|
4
|
+
require 'itamae-plugin-resource-portage/resources/portage_accept_keywords'
|
|
5
|
+
require 'itamae-plugin-resource-portage/resources/portage_mask'
|
|
6
|
+
require 'itamae-plugin-resource-portage/resources/portage_unmask'
|
|
7
|
+
require 'itamae-plugin-resource-portage/resources/portage_pin'
|
|
8
|
+
require 'itamae-plugin-resource-portage/resources/portage_use'
|
|
9
|
+
|
|
10
|
+
module ItamaePluginResourcePortage
|
|
11
|
+
module Resources
|
|
12
|
+
class Portage < Itamae::Resource::Base
|
|
13
|
+
include ResourceBase::Attributes
|
|
14
|
+
define_attribute :action, default: :install
|
|
15
|
+
|
|
16
|
+
define_attribute :emerge_cmd, type: String, default: '/usr/bin/emerge'
|
|
17
|
+
define_attribute :eix_cmd, type: String, default: '/usr/bin/eix'
|
|
18
|
+
# define_attribute :eix_update_cmd, type: String, default: '/usr/bin/eix-update'
|
|
19
|
+
|
|
20
|
+
define_attribute :package_use_file, type: String
|
|
21
|
+
define_attribute :package_mask_file, type: String
|
|
22
|
+
define_attribute :package_unmask_file, type: String
|
|
23
|
+
define_attribute :package_accept_keywords_file, type: String
|
|
24
|
+
|
|
25
|
+
define_attribute :unmask, type: [NilClass, TrueClass, FalseClass]
|
|
26
|
+
define_attribute :pin, type: [TrueClass, FalseClass], default: false
|
|
27
|
+
|
|
28
|
+
define_attribute :noreplace, type: [TrueClass, FalseClass], default: true
|
|
29
|
+
define_attribute :oneshot, type: [TrueClass, FalseClass], default: false
|
|
30
|
+
|
|
31
|
+
define_attribute :flags, type: [Array, String]
|
|
32
|
+
define_attribute :keywords, type: [Array, String]
|
|
33
|
+
|
|
34
|
+
def pre_action
|
|
35
|
+
if attributes.pin && attributes.unmask
|
|
36
|
+
raise ArgumentError, "pin and unmask can't be specified at once"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if attributes.pin && !attributes.version
|
|
40
|
+
raise ArgumentError, "Should specify version when you want to pin"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def action_install
|
|
45
|
+
recipes = []
|
|
46
|
+
recipes << recipe_use(:add) if attributes.flags
|
|
47
|
+
recipes << recipe_accept_keywords(:add) if accept_keywords?
|
|
48
|
+
case
|
|
49
|
+
when attributes.pin
|
|
50
|
+
recipes << recipe_pin(:pin)
|
|
51
|
+
when unmask?
|
|
52
|
+
recipes << recipe_unmask(:add)
|
|
53
|
+
end
|
|
54
|
+
recipes << recipe_package(:install)
|
|
55
|
+
Itamae::RecipeChildren.new(recipes).run
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def action_remove
|
|
59
|
+
recipes = []
|
|
60
|
+
recipes << recipe_use(:remove) if attributes.flags
|
|
61
|
+
recipes << recipe_accept_keywords(:remove) if accept_keywords?
|
|
62
|
+
case
|
|
63
|
+
when attributes.pin
|
|
64
|
+
recipes << recipe_pin(:unpin)
|
|
65
|
+
when unmask?
|
|
66
|
+
recipes << recipe_unmask(:remove)
|
|
67
|
+
end
|
|
68
|
+
recipes << recipe_package(:remove)
|
|
69
|
+
Itamae::RecipeChildren.new(recipes).run
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def unmask?
|
|
75
|
+
if attributes.unmask.nil?
|
|
76
|
+
!!attributes.version
|
|
77
|
+
else
|
|
78
|
+
attributes.unmask
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def accept_keywords?
|
|
83
|
+
if attributes.keywords.nil?
|
|
84
|
+
!!attributes.version
|
|
85
|
+
else
|
|
86
|
+
!(attributes.keywords ||[] ).empty?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def recipe_use(_action)
|
|
91
|
+
_name, _version, _slot, _atom = self.attributes.name, self.attributes.version, self.attributes.slot, self.attributes.atom
|
|
92
|
+
_target = self.attributes.package_use_file
|
|
93
|
+
_flags = self.attributes.flags
|
|
94
|
+
PortageUse.new(recipe, _name) do
|
|
95
|
+
action _action
|
|
96
|
+
target _target
|
|
97
|
+
flags _flags
|
|
98
|
+
version _version
|
|
99
|
+
slot _slot
|
|
100
|
+
atom _atom
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def recipe_accept_keywords(_action)
|
|
105
|
+
_name, _version, _slot, _atom = self.attributes.name, self.attributes.version, self.attributes.slot, self.attributes.atom
|
|
106
|
+
_target = self.attributes.package_accept_keywords_file
|
|
107
|
+
_keywords = self.attributes.keywords
|
|
108
|
+
PortageAcceptKeywords.new(recipe, _name) do
|
|
109
|
+
action _action
|
|
110
|
+
target _target
|
|
111
|
+
keywords _keywords if _keywords
|
|
112
|
+
version _version
|
|
113
|
+
slot _slot
|
|
114
|
+
atom _atom
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def recipe_pin(_action)
|
|
119
|
+
_name, _version = self.attributes.name, self.attributes.version
|
|
120
|
+
PortagePin.new(recipe, _name) do
|
|
121
|
+
action _action
|
|
122
|
+
version _version
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def recipe_unmask(_action)
|
|
127
|
+
_name, _version, _slot, _atom = self.attributes.name, self.attributes.version, self.attributes.slot, self.attributes.atom
|
|
128
|
+
_target = self.attributes.package_unmask_file
|
|
129
|
+
PortageUnmask.new(recipe, _name) do
|
|
130
|
+
action _action
|
|
131
|
+
target _target
|
|
132
|
+
version _version
|
|
133
|
+
slot _slot
|
|
134
|
+
atom _atom
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def recipe_package(_action)
|
|
139
|
+
_name, _version, _slot, _atom = self.attributes.name, self.attributes.version, self.attributes.slot, self.attributes.atom
|
|
140
|
+
_emerge_cmd, _eix_cmd = self.attributes.emerge_cmd, self.attributes.eix_cmd
|
|
141
|
+
_noreplace, _oneshot = self.attributes.noreplace, self.attributes.oneshot
|
|
142
|
+
PortagePackage.new(recipe, _name) do
|
|
143
|
+
action _action
|
|
144
|
+
version _version if _version
|
|
145
|
+
slot _slot
|
|
146
|
+
atom _atom
|
|
147
|
+
|
|
148
|
+
emerge_cmd _emerge_cmd
|
|
149
|
+
eix_cmd _eix_cmd
|
|
150
|
+
|
|
151
|
+
noreplace _noreplace
|
|
152
|
+
oneshot _oneshot
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/portage_file_resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
module Resources
|
|
5
|
+
class PortageAcceptKeywords < PortageFileResourceBase
|
|
6
|
+
define_attribute :keywords, type: [String, Array, NilClass]
|
|
7
|
+
|
|
8
|
+
def value
|
|
9
|
+
attributes.keywords || default_accept_keywords
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def default_accept_keywords
|
|
13
|
+
@default_accept_keywords ||= begin
|
|
14
|
+
arch = backend.run_command(%w(eix --print ARCH)).stdout.chomp
|
|
15
|
+
["~#{arch}"]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def target_default_directory
|
|
20
|
+
"/etc/portage/package.accept_keywords"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/portage_file_resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
module Resources
|
|
5
|
+
class PortageMask < PortageFileResourceBase
|
|
6
|
+
def value
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def target_default_directory
|
|
11
|
+
"/etc/portage/package.mask"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
module Resources
|
|
5
|
+
class PortagePackage < ResourceBase
|
|
6
|
+
define_attribute :action, default: :install
|
|
7
|
+
define_attribute :emerge_cmd, type: String, default: '/usr/bin/emerge'
|
|
8
|
+
define_attribute :eix_cmd, type: String, default: '/usr/bin/eix'
|
|
9
|
+
# define_attribute :eix_update_cmd, type: String, default: '/usr/bin/eix-update'
|
|
10
|
+
|
|
11
|
+
define_attribute :noreplace, type: [TrueClass, FalseClass], default: true
|
|
12
|
+
define_attribute :oneshot, type: [TrueClass, FalseClass], default: false
|
|
13
|
+
|
|
14
|
+
def pre_action
|
|
15
|
+
atom
|
|
16
|
+
case @current_action
|
|
17
|
+
when :unmerge, :remove
|
|
18
|
+
attributes.installed = false
|
|
19
|
+
when :update, :install
|
|
20
|
+
attributes.installed = true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set_current_attributes
|
|
25
|
+
check_installation
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def action_install
|
|
29
|
+
case
|
|
30
|
+
when !attributes.noreplace
|
|
31
|
+
emerge!
|
|
32
|
+
when !current.installed && attributes.installed
|
|
33
|
+
emerge!
|
|
34
|
+
when current.installed && (attributes.version.nil? || (attributes.version != current.version))
|
|
35
|
+
emerge!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
alias action_update action_install
|
|
39
|
+
|
|
40
|
+
def action_unmerge
|
|
41
|
+
case
|
|
42
|
+
when current.installed
|
|
43
|
+
unmerge!
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
alias action_remove action_unmerge
|
|
47
|
+
|
|
48
|
+
def unmerge!
|
|
49
|
+
run_command([attributes.emerge_cmd, '--nospinner', '--color=n', '--rage-clean', '-v', atom])
|
|
50
|
+
updated!
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def emerge!
|
|
54
|
+
update = @current_action == :update ? %w(-u) : []
|
|
55
|
+
noreplace = attributes.noreplace ? %w(--noreplace) : []
|
|
56
|
+
run_command([attributes.emerge_cmd, '--nospinner', '--color=n', '-v', *update, *noreplace, atom])
|
|
57
|
+
updated!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def eix_determinable?
|
|
63
|
+
!attributes.atom && (!attributes.version || atom_op == '=')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def check_installation
|
|
67
|
+
if eix_determinable?
|
|
68
|
+
result = eix(attributes.name).find {|_| _[:name] == attributes.name }
|
|
69
|
+
if !result
|
|
70
|
+
raise ArgumentError, "package #{attributes.name} not found on eix"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
installed_version = if attributes.slot
|
|
74
|
+
result[:installed_versions][attributes.slot]
|
|
75
|
+
else
|
|
76
|
+
result[:installed_version]
|
|
77
|
+
end
|
|
78
|
+
if installed_version
|
|
79
|
+
current.installed = true
|
|
80
|
+
current.version = result[:installed_version]
|
|
81
|
+
else
|
|
82
|
+
current.installed = false
|
|
83
|
+
current.version = nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if !attributes.version
|
|
87
|
+
attributes.version = if attributes.slot
|
|
88
|
+
result[:best_slot_versions][attributes.slot]
|
|
89
|
+
else
|
|
90
|
+
result[:best_version]
|
|
91
|
+
end
|
|
92
|
+
@atom = nil; atom
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
update = @current_action == :update ? %w(-u) : []
|
|
96
|
+
noreplace = attributes.noreplace ? %w(--noreplace) : []
|
|
97
|
+
pvn = run_command([attributes.emerge_cmd, '--nospinner', '--color=n', '-pv', *noreplace, *update, atom])
|
|
98
|
+
|
|
99
|
+
current.installed = /Total: 0 packages/ === pvn.stdout
|
|
100
|
+
if current.installed
|
|
101
|
+
current.version = attributes.version
|
|
102
|
+
else
|
|
103
|
+
current.version = nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def eix(*options)
|
|
109
|
+
result = run_command(['env', "MYVERSION=<version>:<slot>{!last} {}", attributes.eix_cmd, '--nocolor', '--pure-packages', '--format', "<category>/<name>|<installedversions:MYVERSION>|<bestslotversions:MYVERSION>|<bestversion:MYVERSION>\\n", *options], error: false)
|
|
110
|
+
|
|
111
|
+
unless result.exit_status.zero?
|
|
112
|
+
return []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result.stdout.each_line.map do |line|
|
|
116
|
+
name, installedversions, bestslotversions, bestversion = line.chomp.split(?|, 4)
|
|
117
|
+
|
|
118
|
+
best_version, best_version_slot = bestversion.split(?:,2)
|
|
119
|
+
|
|
120
|
+
installedversion = installedversions.split(/ /).last
|
|
121
|
+
if installedversion
|
|
122
|
+
installed_version, installed_version_slot = installedversion.split(?:,2)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
name: name,
|
|
127
|
+
installed_versions: Hash[installedversions.split(/ /).map{ |_| _.split(?:, 2) }.group_by(&:last).map { |k,v| [k, v.map(&:first).last] }],
|
|
128
|
+
best_slot_versions: Hash[bestslotversions.split(/ /).map{ |_| _.split(?:, 2) }.group_by(&:last).map { |k,v| [k, v.map(&:first).last] }],
|
|
129
|
+
best_version: best_version,
|
|
130
|
+
best_version_slot: best_version_slot,
|
|
131
|
+
installed_version: installed_version,
|
|
132
|
+
installed_version_slot: installed_version_slot,
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'itamae'
|
|
2
|
+
require 'itamae-plugin-resource-portage/resources/portage_mask'
|
|
3
|
+
require 'itamae-plugin-resource-portage/resources/portage_unmask'
|
|
4
|
+
|
|
5
|
+
module ItamaePluginResourcePortage
|
|
6
|
+
module Resources
|
|
7
|
+
class PortagePin < Itamae::Resource::Base
|
|
8
|
+
define_attribute :name, type: String, default_name: true
|
|
9
|
+
define_attribute :action, default: :pin
|
|
10
|
+
define_attribute :version, type: String, required: true
|
|
11
|
+
|
|
12
|
+
def action_pin
|
|
13
|
+
Itamae::RecipeChildren.new([mask(:add), unmask(:add)]).run
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def action_unpin
|
|
17
|
+
Itamae::RecipeChildren.new([mask(:remove), unmask(:remove)]).run
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def mask(_action)
|
|
23
|
+
PortageMask.new(recipe, self.attributes.name) do
|
|
24
|
+
action _action
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def unmask(_action)
|
|
29
|
+
_version = self.attributes.version
|
|
30
|
+
PortageUnmask.new(recipe, self.attributes.name) do
|
|
31
|
+
action _action
|
|
32
|
+
version "=#{_version}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/portage_file_resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
module Resources
|
|
5
|
+
class PortageUnmask < PortageFileResourceBase
|
|
6
|
+
def value
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def target_default_directory
|
|
11
|
+
"/etc/portage/package.unmask"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'itamae-plugin-resource-portage/portage_file_resource_base'
|
|
2
|
+
|
|
3
|
+
module ItamaePluginResourcePortage
|
|
4
|
+
module Resources
|
|
5
|
+
class PortageUse < PortageFileResourceBase
|
|
6
|
+
define_attribute :flags, type: Array
|
|
7
|
+
|
|
8
|
+
def value
|
|
9
|
+
attributes.flags
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def target_default_directory
|
|
13
|
+
"/etc/portage/package.use"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/script/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "itamae/plugin/resource/portage"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start
|
data/script/setup
ADDED
metadata
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: itamae-plugin-resource-portage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.beta1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- sorah (Shota Fukumori)
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-01-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: itamae
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description:
|
|
70
|
+
email:
|
|
71
|
+
- her@sorah.jp
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".rspec"
|
|
78
|
+
- ".travis.yml"
|
|
79
|
+
- Gemfile
|
|
80
|
+
- LICENSE.txt
|
|
81
|
+
- README.md
|
|
82
|
+
- Rakefile
|
|
83
|
+
- itamae-plugin-resource-portage.gemspec
|
|
84
|
+
- lib/itamae-plugin-resource-portage.rb
|
|
85
|
+
- lib/itamae-plugin-resource-portage/portage_file_resource_base.rb
|
|
86
|
+
- lib/itamae-plugin-resource-portage/resource_base.rb
|
|
87
|
+
- lib/itamae-plugin-resource-portage/resources.rb
|
|
88
|
+
- lib/itamae-plugin-resource-portage/resources/portage.rb
|
|
89
|
+
- lib/itamae-plugin-resource-portage/resources/portage_accept_keywords.rb
|
|
90
|
+
- lib/itamae-plugin-resource-portage/resources/portage_mask.rb
|
|
91
|
+
- lib/itamae-plugin-resource-portage/resources/portage_package.rb
|
|
92
|
+
- lib/itamae-plugin-resource-portage/resources/portage_pin.rb
|
|
93
|
+
- lib/itamae-plugin-resource-portage/resources/portage_unmask.rb
|
|
94
|
+
- lib/itamae-plugin-resource-portage/resources/portage_use.rb
|
|
95
|
+
- lib/itamae-plugin-resource-portage/version.rb
|
|
96
|
+
- lib/itamae/plugin/resource/portage.rb
|
|
97
|
+
- script/console
|
|
98
|
+
- script/setup
|
|
99
|
+
homepage: https://github.com/sorah/itamae-plugin-resource-portage
|
|
100
|
+
licenses:
|
|
101
|
+
- MIT
|
|
102
|
+
metadata: {}
|
|
103
|
+
post_install_message:
|
|
104
|
+
rdoc_options: []
|
|
105
|
+
require_paths:
|
|
106
|
+
- lib
|
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: 1.3.1
|
|
117
|
+
requirements: []
|
|
118
|
+
rubyforge_project:
|
|
119
|
+
rubygems_version: 2.5.1
|
|
120
|
+
signing_key:
|
|
121
|
+
specification_version: 4
|
|
122
|
+
summary: Itamae resources for Gentoo Portage
|
|
123
|
+
test_files: []
|