binpkgbot 0.1.0

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: f2648e9c2366abff1f8551eb3c8f7c112f577345
4
+ data.tar.gz: 2e4e135f112ac49dd4d9427d765c902061f362cf
5
+ SHA512:
6
+ metadata.gz: 3407003e296eb9a3be40cfe9b295d2c0e5a128978b55f81009af033e6eff9f8dd212fc8fe8dbc011d772adac7e1e22dda3e00c2629e9d8537c03a3c1e5603da4
7
+ data.tar.gz: 721140a0c13b88a03a50f018adf074aa7de6b4d9316185a9a83242a6431c1c26cc484fab5d397b8eadc3845025532151670c03b478529db9c8fbb4088f0066d9
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ config.yml
11
+ binpkgbot.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.13.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in binpkgbot.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sorah Fukumori
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,161 @@
1
+ # binpkgbot: Robot builds Gentoo binpkg in clean environment, automatically and continuously.
2
+
3
+ When you manage many Gentoo servers, I guess you're tired to run `emerge -uDN @world` and wait for all servers, or to install same packages in your all servers. Portage has binpkg feature, which allows to re-use prebuilt binaries when `emerge`ing packages.
4
+
5
+ Binpkgbot allows you to build binpkg in clean environment, automatically and continuously! Continuous building of the latest packages also allows you to run `-uDN` anytime you want.
6
+
7
+ ## Features
8
+
9
+ - Build in clean environment
10
+ - Runs emerge on clean stage3 using systemd-nspawn every run
11
+ - Fetch and install existing binpkg of build dependencies
12
+ - Builds binpkg of new dependencies
13
+ - 2 build modes:
14
+ - Attempt to upgrade existing stage3 directory
15
+ - Just install additional packages
16
+
17
+ ## Prerequisites
18
+
19
+ - systemd based Linux box
20
+ - systemd-nspawn
21
+ - with btrfs filesystem
22
+
23
+ ## Installation
24
+
25
+ $ gem install binpkgbot
26
+
27
+ ## Usage
28
+
29
+ ### Set up
30
+
31
+ 1. Prepare stage3 (or 4) directory (e.g. `/mnt/vol/stage`)
32
+ - binpkgbot adds modification when running upgrades. Otherwise it's used as base of ephemeral container
33
+ - this directory should be in a btrfs filesystem
34
+ 2. `/etc/portage` directory outside of (1) (e.g. `/mnt/vol/etc-portage`)
35
+ - binpkgbot always copies the entire directory into a container
36
+ 3. gentoo portage repository (e.g. `/usr/portage`)
37
+ - binpkgbot syncs the specified directory; if this behavior is acceptable, you can specify `/usr/portage`.
38
+
39
+ then write some yml:
40
+
41
+ ``` yaml
42
+ # binpkgbot.yml
43
+ stage: /mnt/vol/stage
44
+ etc_portage: /mnt/vol/etc-portage
45
+ portage_repo: /usr/portage
46
+
47
+ # use_sudo_for_nspawn: true
48
+
49
+ # emerge_options:
50
+ # - '-v'
51
+ # config_protect_mask: false
52
+
53
+ ## bind mounts for a container
54
+ # binds:
55
+ # - /opt/my-overlay # default to read-only
56
+ # - rw: /tmp/something_writable # or 'ro:'
57
+
58
+ # - from: /mnt/vol/packages
59
+ # to: /usr/portage/packages
60
+ # writable: true
61
+ # - from: /mnt/vol/elog
62
+ # to: /var/log/portage/elog
63
+ # writable: true
64
+
65
+ tasks:
66
+ # Upgrade the stage (-uDN)
67
+ - upgrade: '@world' # emerge -uDN @world
68
+ - install:
69
+ atom: '@preserve-rebuild'
70
+ persist: true # Run sd-nspawn without --ephemeral option, default to false
71
+
72
+
73
+ # Simple way
74
+ - install: 'sys-apps/dstat'
75
+ # Complex way
76
+ - install:
77
+ atom: 'media-apps/ffmpeg'
78
+ use: x265
79
+ ## or
80
+ # use:
81
+ # - x265
82
+ # # to other packages
83
+ # - media-libs/x265 numa
84
+ accept_keywords: true # default to ~*
85
+ # accept_keywords:
86
+ # - true
87
+ # # - "~amd64"
88
+ # ## to other packages
89
+ # - "media-libs/x265 ~amd64"
90
+ # - media-libs/x265 # or default: ~*
91
+ unmask: true # unmask a specified atom itself
92
+ # unmask:
93
+ # - true
94
+ # - media-libs/x265
95
+ # mask:
96
+ # - media-libs/x264
97
+
98
+ - include: ./task.yml # use file instead
99
+ - include: ./task.d/* # or glob files and run all of them (order by filename)
100
+
101
+ ## run something inside or outside a container
102
+ # - run:
103
+ # ## run outside of container? (default to false)
104
+ # # host: true
105
+ # ## persist change in a container (default to true)
106
+ # # persist: false
107
+ # script:
108
+ # - emerge-webrsync
109
+ ```
110
+
111
+ ```
112
+ # ./task.yml
113
+ - install: ...
114
+ ```
115
+
116
+ ### Running
117
+
118
+ ```
119
+ $ binpkgbot
120
+ $ binpkgbot -c path/to/binpkgbot.yml
121
+
122
+ $ binpkgbot --help
123
+ $ binpkgbot --version
124
+ ```
125
+
126
+ ## Recommended practices
127
+
128
+ - Turn on `binpkg-multi-instance` and `getbinpkg` in `$FEATURES`
129
+ - These are optional. Put them by yourself into `make.conf`.
130
+ - Maintain different `/etc/portage` directory then put all common use flags, keywords, and unmaskings in the directory.
131
+ - minimize USE flag difference. Use `use:` `accept_keywords:` `unmask:` for a case needs to make a variant of builds.
132
+
133
+ (e.g. `app-analyzer/zabbix` has a flag to build `server` or go only with `agent`. Server needs both, but most servers are okay with having `agent` only. Then we need 2 variants in `zabbix` binpkg, so use `use:` configuration to build variants.)
134
+
135
+ ``` yaml
136
+ - install:
137
+ atom: app-analyzer/zabbix
138
+ use:
139
+ - agent
140
+ - server
141
+ - install:
142
+ atom: app-analyzer/zabbix
143
+ use:
144
+ - agent
145
+ ```
146
+
147
+ ## Development
148
+
149
+ 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.
150
+
151
+ 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).
152
+
153
+ ## Contributing
154
+
155
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sorah/binpkgbot.
156
+
157
+
158
+ ## License
159
+
160
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
161
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/binpkgbot ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'binpkgbot'
3
+
4
+ Binpkgbot::Cli.new(ARGV).run
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "binpkgbot"
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/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/binpkgbot.gemspec 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 'binpkgbot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "binpkgbot"
8
+ spec.version = Binpkgbot::VERSION
9
+ spec.authors = ["Sorah Fukumori"]
10
+ spec.email = ["her@sorah.jp"]
11
+
12
+ spec.summary = %q{Your robot to build Gentoo binpkgs in clean environment, continously}
13
+ spec.homepage = "https://github.com/sorah/binpkgbot"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.13"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
data/lib/binpkgbot.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'binpkgbot/version'
2
+ require 'binpkgbot/cli'
@@ -0,0 +1,59 @@
1
+ require 'optparse'
2
+ require 'binpkgbot/config'
3
+ require 'binpkgbot/version'
4
+
5
+ module Binpkgbot
6
+ class Cli
7
+ def initialize(argv)
8
+ @argv = argv.dup
9
+ end
10
+
11
+ def run
12
+ optparse.parse!(@argv)
13
+ case options[:mode]
14
+ when :version
15
+ do_version
16
+ when :run
17
+ do_run
18
+ end
19
+ end
20
+
21
+ def do_version
22
+ puts "binpkgbot #{Binpkgbot::VERSION}"
23
+ 0
24
+ end
25
+
26
+ def do_run
27
+ config.tasks.each do |task|
28
+ task.execute
29
+ end
30
+ 0
31
+ end
32
+
33
+ def options
34
+ @options ||= {
35
+ config: nil,
36
+ mode: :run,
37
+ debug: false,
38
+ }
39
+ end
40
+
41
+ def optparse
42
+ @optparse ||= OptionParser.new do |opt|
43
+ opt.on('-v', '--version') { options[:mode] = :version }
44
+
45
+ opt.on('-c PATH', '--config PATH', 'config file to use (default: ./binpkgbot.yml)') do |file|
46
+ options[:config] = file
47
+ end
48
+ end
49
+ end
50
+
51
+ def config_path
52
+ options[:config] || './binpkgbot.yml'
53
+ end
54
+
55
+ def config
56
+ @config ||= Config.load_yaml(config_path)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,58 @@
1
+ require 'binpkgbot/utils'
2
+ require 'binpkgbot/tasks'
3
+ require 'yaml'
4
+
5
+ module Binpkgbot
6
+ class Config
7
+ def self.from_yaml(yaml)
8
+ new(YAML.load(yaml))
9
+ end
10
+
11
+ def self.load_yaml(path)
12
+ new(YAML.load_file(path))
13
+ end
14
+
15
+ def initialize(doc={})
16
+ @doc = Utils.symbolize_keys(doc)
17
+ end
18
+
19
+ def stage
20
+ @doc[:stage]
21
+ end
22
+
23
+ def etc_portage
24
+ @doc[:etc_portage]
25
+ end
26
+
27
+ def portage_repo
28
+ @doc[:portage_repo]
29
+ end
30
+
31
+ def emerge_options
32
+ @doc[:emerge_options]
33
+ end
34
+
35
+ def binds
36
+ @doc[:binds]
37
+ end
38
+
39
+ def tasks
40
+ (@doc[:tasks] || []).map do |defi|
41
+ Tasks.from_definition(defi, config: self)
42
+ end
43
+ end
44
+
45
+ def config_protect_mask?
46
+ @doc.key?(:config_protect_mask)
47
+ end
48
+
49
+ def config_protect_mask
50
+ @doc[:config_protect_mask]
51
+ end
52
+
53
+ def use_sudo_for_nspawn?
54
+ @doc.fetch(:use_sudo_for_nspawn, false)
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,111 @@
1
+ require 'binpkgbot/utils'
2
+ require 'tmpdir'
3
+ require 'shellwords'
4
+ require 'pathname'
5
+
6
+ module Binpkgbot
7
+ class Container
8
+ class ContainerFailure < StandardError; end
9
+
10
+ COPY_TMPDIR = "/.binpkgbot.copies"
11
+ WORKDIR = "/.binpkgbot.work"
12
+ SHAREDIR = "/.binpkgbot.share"
13
+
14
+ SHAREDIR_SRC = File.expand_path("../../share", __dir__)
15
+ def initialize(directory, ephemeral: true, binds: [], copies: [], env: {}, script:, config: nil)
16
+ @directory = directory
17
+ @ephemeral = ephemeral
18
+ @env = env
19
+ @binds = normalize_binds(binds)
20
+ @copies = normalize_copies(copies)
21
+ @script = script
22
+ @config = config
23
+ end
24
+
25
+ def workdir
26
+ @workdir ||= Pathname.new(Dir.mktmpdir)
27
+ end
28
+
29
+ def script
30
+ parts = ['set -e']
31
+ @copies.each do |copy|
32
+ parts.push <<-EOF
33
+ if [ -e #{copy[:to].shellescape} ]; then
34
+ rm -rf #{copy[:to].shellescape}
35
+ fi
36
+ cp -pr #{COPY_TMPDIR}-#{copy[:id]} #{copy[:to].shellescape}
37
+ EOF
38
+ end
39
+ parts.push @env.map { |k, v| "export #{k}=#{v.shellescape}" }.join("\n")
40
+ parts << @script
41
+ parts.join("\n\n")
42
+ end
43
+
44
+ def binds
45
+ @binds + \
46
+ (@config&.binds || []) + \
47
+ @copies.map { |copy| {from: copy[:from], to: "/#{COPY_TMPDIR}-#{copy[:id]}", writable: false} } + \
48
+ [
49
+ {from: workdir, to: WORKDIR, writable: true},
50
+ {from: SHAREDIR_SRC, to: SHAREDIR, writable: true},
51
+ ]
52
+ end
53
+
54
+ def command_line
55
+ [
56
+ @config.use_sudo_for_nspawn? ? 'sudo' : nil,
57
+ 'systemd-nspawn',
58
+ "--directory=#{@directory}",
59
+ @ephemeral ? "--ephemeral" : nil,
60
+ binds.map { |_| "--bind#{_[:writable] ? nil : '-ro'}=#{_[:from]}:#{_[:to]}" },
61
+ '/bin/bash'
62
+ ].flatten.compact
63
+ end
64
+
65
+ def run(error: true)
66
+ puts script.each_line.map.with_index { |_,i| _.strip.empty? ? nil : "#{(i.zero? ? "$ " : " ")}#{_.chomp}" }.compact.join(?\n)
67
+ r,w = IO.pipe
68
+ w.puts script
69
+ pid = spawn(*command_line, in: r)
70
+ r.close
71
+ w.close
72
+ _, status = Process.waitpid2(pid)
73
+ if error && !status.success?
74
+ raise ContainerFailure, "container failed #{status.inspect}, #{command_line.inspect}"
75
+ end
76
+ status
77
+ end
78
+
79
+ private
80
+
81
+ def normalize_binds(binds)
82
+ Utils.symbolize_keys(binds || []).map do |bind|
83
+ case
84
+ when bind.kind_of?(String)
85
+ {from: bind, to: bind, writable: false}
86
+ when bind[:ro]
87
+ {from: bind[:ro], to: bind[:ro], writable: false}
88
+ when bind[:rw]
89
+ {from: bind[:rw], to: bind[:rw], writable: true}
90
+ when bind.kind_of?(Hash)
91
+ bind
92
+ else
93
+ raise ArgumentError, "Unknown --bind specification: #{bind.inspect}"
94
+ end
95
+ end
96
+ end
97
+
98
+ def normalize_copies(copies)
99
+ Utils.symbolize_keys(copies || []).map.with_index do |copy, idx|
100
+ case
101
+ when copy.kind_of?(String)
102
+ {id: idx, from: copy, to: copy}
103
+ when copy.kind_of?(Hash)
104
+ copy.merge(id: idx)
105
+ else
106
+ raise ArgumentError, "Unknown copy specification: #{copy.inspect}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,137 @@
1
+ require 'binpkgbot/container'
2
+ require 'shellwords'
3
+
4
+ module Binpkgbot
5
+ class EmergeRunner
6
+ def initialize(atom, *args, config:, ephemeral: true, use: [], accept_keywords: [], unmasks: [], masks: [], script: nil)
7
+ @atom = atom
8
+ @args = args
9
+ @config = config
10
+ @ephemeral = ephemeral
11
+ @use = use
12
+ @accept_keywords = accept_keywords
13
+ @unmasks = unmasks
14
+ @masks = masks
15
+ @script = script
16
+ end
17
+
18
+ def binds
19
+ [
20
+ {from: @config.portage_repo, to: '/usr/portage', writable: true},
21
+ *@config.binds
22
+ ]
23
+ end
24
+
25
+ def copies
26
+ [
27
+ {from: @config.etc_portage, to: '/etc/portage'},
28
+ ]
29
+ end
30
+
31
+ def env
32
+ {
33
+ FEATURES: "buildpkg",
34
+ PORTAGE_ELOG_CLASSES: 'log warn error qa',
35
+ PORTAGE_ELOG_SYSTEM: 'save save_summary',
36
+ CONFIG_PROTECT_MASK: @config.config_protect_mask? ? @config.config_protect_mask : '/etc',
37
+ }
38
+ end
39
+
40
+ def script
41
+ @script || <<-EOF
42
+ set -x
43
+ /.binpkgbot.share/libexec/modify-etc-portage /.binpkgbot.work/package.use /etc/portage/package.use
44
+ /.binpkgbot.share/libexec/modify-etc-portage /.binpkgbot.work/package.accept_keywords /etc/portage/package.accept_keywords
45
+ /.binpkgbot.share/libexec/modify-etc-portage /.binpkgbot.work/package.unmasks /etc/portage/package.unmasks
46
+ /.binpkgbot.share/libexec/modify-etc-portage /.binpkgbot.work/package.masks /etc/portage/package.masks
47
+ emerge #{@config.emerge_options.shelljoin} #{@args.shelljoin} #{@atom.shellescape}
48
+ EOF
49
+ end
50
+
51
+ def container
52
+ @container ||= Container.new(
53
+ @config.stage,
54
+ ephemeral: @ephemeral,
55
+ binds: binds,
56
+ copies: copies,
57
+ env: env,
58
+ script: script,
59
+ config: @config,
60
+ )
61
+ end
62
+
63
+ def prepare_etc_portage_overrides
64
+ {masks: @masks, unmasks: @unmasks}.each do |k,v|
65
+ file = container.workdir.join("package.#{k}")
66
+ content = []
67
+ [v].flatten.each do |x|
68
+ case x
69
+ when true
70
+ content << @atom
71
+ when false
72
+ # do nothing
73
+ when String
74
+ content << x
75
+ when nil
76
+ else
77
+ raise TypeError, "Invalid type for #{k.inspect} specification on #{@atom}, it should be true or false or string: #{x.inspect}"
78
+ end
79
+ end
80
+ File.write file, "#{content.join(?\n)}\n"
81
+ end
82
+
83
+ begin
84
+ file = container.workdir.join("package.accept_keywords")
85
+ content = []
86
+ [@accept_keywords].flatten.each do |x|
87
+ case x
88
+ when true
89
+ content << "#{@atom} ~*"
90
+ when false
91
+ # do nothing
92
+ when String
93
+ if x.split(/\s+/, 2).size > 1
94
+ content << x
95
+ else
96
+ content << "#{x} ~*"
97
+ end
98
+ when nil
99
+ else
100
+ raise TypeError, "Invalid type for accept_keywords specification on #{@atom}, it should be true or false or string: #{x.inspect}"
101
+ end
102
+ end
103
+ File.write file, "#{content.join(?\n)}\n"
104
+ end
105
+
106
+ begin
107
+ file = container.workdir.join("package.use")
108
+ content = []
109
+ [@use].flatten.each do |x|
110
+ case x
111
+ when true
112
+ content << "#{@atom} ~*"
113
+ when false
114
+ # do nothing
115
+ when String
116
+ if x.split(/\s+/, 2).size > 1
117
+ content << x
118
+ else
119
+ content << "#{@atom} #{x}"
120
+ end
121
+ when nil
122
+ else
123
+ raise TypeError, "Invalid type for :use specification on #{@atom}, it should be true or false or string: #{x.inspect}"
124
+ end
125
+ end
126
+ File.write file, "#{content.join(?\n)}\n"
127
+ end
128
+ end
129
+
130
+ def run
131
+ prepare_etc_portage_overrides
132
+ container.run
133
+ end
134
+
135
+ end
136
+ end
137
+
@@ -0,0 +1,42 @@
1
+ require 'binpkgbot/utils'
2
+
3
+ module Binpkgbot
4
+ module Tasks
5
+ def self.from_definition(defi, config: nil)
6
+ case defi
7
+ when String, Symbol
8
+ self.find(defi).new(config: config)
9
+ when Hash
10
+ raise ArgumentError, "task defiification should not have more than 2 keys when it's a Hash" if defi.size > 1
11
+ kind = defi.keys.first
12
+ options = defi.values.first
13
+ options = {name: options} unless options.kind_of?(Hash)
14
+ self.find(kind).new(config: config, **Utils.symbolize_keys(options))
15
+ end
16
+ end
17
+
18
+ def self.find(name)
19
+ const = Binpkgbot::Tasks
20
+ prefix = 'binpkgbot/tasks'
21
+
22
+ retried = false
23
+ constant_name = name.to_s.gsub(/\A.|_./) { |s| s[-1].upcase }
24
+
25
+ begin
26
+ const.const_get constant_name, false
27
+ rescue NameError
28
+ unless retried
29
+ begin
30
+ require "#{prefix}/#{name}"
31
+ rescue LoadError
32
+ end
33
+
34
+ retried = true
35
+ retry
36
+ end
37
+
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module Binpkgbot
2
+ module Tasks
3
+ class Base
4
+ def initialize(config: nil, **options)
5
+ @config = config
6
+ @options = options
7
+ end
8
+
9
+ attr_reader :config, :options
10
+
11
+ def execute
12
+ puts "==> #{self.class}: #{options.inspect}"
13
+ run
14
+ end
15
+
16
+ private
17
+
18
+ def run
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require 'binpkgbot/emerge_runner'
2
+
3
+ module Binpkgbot
4
+ module Tasks
5
+ module Concern
6
+ module Emerge
7
+ def emerge_runner(script, **options)
8
+ emerge(nil, script: script, **options)
9
+ end
10
+
11
+ def emerge(atom, *args, ephemeral: !@options[:persist], use: @options[:use], accept_keywords: @options[:accept_keywords], unmasks: @options[:unmasks], masks: @options[:masks], script: nil)
12
+ EmergeRunner.new(
13
+ atom, *args,
14
+ ephemeral: ephemeral,
15
+ use: use,
16
+ accept_keywords: accept_keywords,
17
+ unmasks: unmasks,
18
+ masks: masks,
19
+ config: @config,
20
+ script: script,
21
+ ).run
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require 'binpkgbot/tasks/base'
2
+ require 'binpkgbot/tasks'
3
+
4
+ module Binpkgbot
5
+ module Tasks
6
+ class Include < Base
7
+ def run
8
+ name = @options[:name]
9
+ raise ArgumentError, "include task is missing :name -- what to include?" unless name
10
+ Dir.glob[name].sort.each do |path|
11
+ yaml = YAML.load_file(path)
12
+ if yaml.kind_of?(Array)
13
+ raise ArgumentError, "#{path} should be an array of task definitions"
14
+ end
15
+ yaml.each do |task_def|
16
+ Tasks.from_definition(task_def, config: @config).execute
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require 'binpkgbot/tasks/base'
2
+ require 'binpkgbot/tasks/concern/emerge'
3
+
4
+ module Binpkgbot
5
+ module Tasks
6
+ class Install < Base
7
+ include Concern::Emerge
8
+
9
+ def run
10
+ name = @options[:name] || @options[:atom]
11
+ unless name
12
+ raise ArgumentError, "install task is missing :name and :atom -- what to install?"
13
+ end
14
+
15
+ emerge(name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ require 'binpkgbot/tasks/base'
2
+ require 'binpkgbot/tasks/concern/emerge'
3
+
4
+ module Binpkgbot
5
+ module Tasks
6
+ class Run < Base
7
+ include Concern::Emerge
8
+
9
+ def run
10
+ script = [*@options[:script],*@options[:scripts]].join("\n")
11
+ if @options[:host]
12
+ puts script.each_line.map.with_index { |_,i| "#{(i.zero? ? "$ " : " ")}#{_}" }.join(?\n)
13
+ r,w = IO.pipe
14
+ w.puts script
15
+ pid = spawn('bash', in: r)
16
+ r.close
17
+ w.close
18
+ _, status = Process.waitpid2(pid)
19
+ if !status.success?
20
+ raise "host run failed #{status.inspect}, #{command_line.inspect}"
21
+ end
22
+ else
23
+ emerge_runner(script, ephemeral: @options.key?(:persist) ? !@options[:persist] : false)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ require 'binpkgbot/tasks/base'
2
+ require 'binpkgbot/tasks/concern/emerge'
3
+
4
+ module Binpkgbot
5
+ module Tasks
6
+ class Upgrade < Base
7
+ include Concern::Emerge
8
+
9
+ def run
10
+ name = @options[:name] || @options[:atom]
11
+ unless name
12
+ raise ArgumentError, "upgrade task is missing :name -- what to upgrade? e.g. @world"
13
+ end
14
+
15
+ emerge(name, '-uDN', ephemeral: @options.key?(:persist) ? !@options[:persist] : false)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Binpkgbot
2
+ module Utils
3
+ def self.symbolize_keys(obj)
4
+ case obj
5
+ when Hash
6
+ Hash[obj.map { |k, v| [k.is_a?(String) ? k.to_sym : k, symbolize_keys(v)] }]
7
+ when Array
8
+ obj.map { |v| symbolize_keys(v) }
9
+ else
10
+ obj
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Binpkgbot
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/python3
2
+ import re
3
+ import sys
4
+ import os
5
+ import glob
6
+
7
+ if len(sys.argv) != 3:
8
+ print("Usage: modify-etc-portage file-to-add target", file=sys.stderr)
9
+ sys.exit(1)
10
+
11
+ source = sys.argv[1]
12
+ target = sys.argv[2]
13
+
14
+ if not os.path.exists(source):
15
+ sys.exit(0)
16
+
17
+ source_content = []
18
+ with open(source, 'r') as io:
19
+ for line in io:
20
+ if not re.match("^#", line):
21
+ source_content.append(re.split("\s+", line.rstrip()))
22
+
23
+ target_is_directory = os.path.isdir(target)
24
+
25
+ target_content = []
26
+ if target_is_directory:
27
+ paths = sorted(glob.glob(os.path.join(target, "*")))
28
+ for path in paths:
29
+ with open(path, 'r') as io:
30
+ for line in io:
31
+ if not re.match("^#", line):
32
+ target_content.append(re.split("\s+", line.rstrip()))
33
+ elif os.path.exists(target):
34
+ with open(target, 'r') as io:
35
+ for line in io:
36
+ if not re.match("^#", line):
37
+ target_content.append(re.split("\s+", line.rstrip()))
38
+
39
+
40
+ source_atoms = [x[0] for x in source_content]
41
+ filtered_target_content = [x for x in target_content if not x[0] in source_atoms]
42
+
43
+ new_content = filtered_target_content + source_content
44
+ new_content_str = "\n".join([" ".join(x) for x in new_content]) + "\n"
45
+
46
+ if target_is_directory:
47
+ os.rename(target, target + "." + str(os.getpid()))
48
+
49
+ with open(target, 'w') as io:
50
+ io.write(new_content_str)
51
+
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ if [ $# -lt 2 ]; then
3
+ echo "usage: $0 file-to-add target" 1>&2
4
+ exit 1
5
+ fi
6
+ set -e
7
+
8
+ file_to_add="$1"
9
+ target="$2"
10
+
11
+ if [ ! -e "${file_to_add}" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ if [ -d "${target}" ]; then
16
+ target="${target}/binpkgbot"
17
+ fi
18
+
19
+ echo "modify-etc-portage: modifying ${target}"
20
+
21
+ temp="$(mktemp)"
22
+ if [ -f "${target}" ]; then
23
+ cat "${target}" > "${temp}"
24
+ echo >> "${temp}"
25
+ elif [ -d "${target}" ]; then
26
+ fi
27
+ cat "${file_to_add}" >> "${temp}"
28
+
29
+ mv "${temp}" "${target}"
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binpkgbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-26 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.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - her@sorah.jp
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/binpkgbot
70
+ - bin/console
71
+ - bin/setup
72
+ - binpkgbot.gemspec
73
+ - lib/binpkgbot.rb
74
+ - lib/binpkgbot/cli.rb
75
+ - lib/binpkgbot/config.rb
76
+ - lib/binpkgbot/container.rb
77
+ - lib/binpkgbot/emerge_runner.rb
78
+ - lib/binpkgbot/tasks.rb
79
+ - lib/binpkgbot/tasks/base.rb
80
+ - lib/binpkgbot/tasks/concern/emerge.rb
81
+ - lib/binpkgbot/tasks/include.rb
82
+ - lib/binpkgbot/tasks/install.rb
83
+ - lib/binpkgbot/tasks/run.rb
84
+ - lib/binpkgbot/tasks/upgrade.rb
85
+ - lib/binpkgbot/utils.rb
86
+ - lib/binpkgbot/version.rb
87
+ - share/libexec/modify-etc-portage
88
+ - share/libexec/modify-etc-portage.sh
89
+ homepage: https://github.com/sorah/binpkgbot
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.6.8
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Your robot to build Gentoo binpkgs in clean environment, continously
113
+ test_files: []