binpkgbot 0.1.0

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