repomate 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.
@@ -0,0 +1,102 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for the component layer of the directory structure
5
+ class Component
6
+
7
+ # Init
8
+ def initialize(component, suitename, category)
9
+ @component = component
10
+ @suitename = suitename
11
+ @category = category
12
+ end
13
+
14
+ # Returns the given architecture name (eg. main, contrib, non-free)
15
+ def name
16
+ @component
17
+ end
18
+
19
+ # Returns the directory strcuture of the component including all lower layers
20
+ def directory
21
+ File.join(Cfg.rootdir, @category, @suitename, @component)
22
+ end
23
+
24
+ # Checks if the component directory exists
25
+ def exist?
26
+ Dir.exist?(directory)
27
+ end
28
+
29
+ # Checks if the component is allowed (See: configurationfile)
30
+ def is_allowed?
31
+ self.allowed.include?(@component)
32
+ end
33
+
34
+ # Checks if directory is unused
35
+ def is_unused?(dir)
36
+ status = true
37
+
38
+ path = Dir.glob(File.join(dir, "*"))
39
+ path.each do |dirorfile|
40
+ status = false if File.directory?(dirorfile)
41
+ status = false if File.basename(dirorfile) =~ /\.deb$/
42
+ end
43
+
44
+ status
45
+ end
46
+
47
+ # Creates the directory strcuture of the component including all lower layers
48
+ def create
49
+ FileUtils.mkdir_p(directory) unless exist?
50
+ end
51
+
52
+ # Deletes the components directory including all lower layers
53
+ def destroy
54
+ FileUtils.rm_r(directory) if exist?
55
+ end
56
+
57
+ # Returns a list of all debian files in the component directory
58
+ def files
59
+ Dir.glob(File.join(directory, "*.deb"))
60
+ end
61
+
62
+ # Returns a dataset including the name of the component, the fullpath recursive through all lower layers
63
+ def self.dataset(category=nil)
64
+ data = []
65
+ self.all.each do |entry|
66
+ parts = entry.split(/\//)
67
+ unless parts.length < 3
68
+ next unless parts[0].eql?(category) || category.eql?("all")
69
+ data << {
70
+ :category => parts[0],
71
+ :suitename => parts[1],
72
+ :component => parts[2],
73
+ :fullpath => File.join(Cfg.rootdir, entry)
74
+ }
75
+ end
76
+ end
77
+ data
78
+ end
79
+
80
+ # Returns all directories without @rootdir
81
+ def self.all
82
+ config = Configuration.new
83
+ suites = Suite.all
84
+ dirs = []
85
+ rootdir = Cfg.rootdir
86
+ suites.each do |suite|
87
+ components = Dir.glob(File.join(rootdir, suite, "*"))
88
+ components.each do |component|
89
+ dirs.push component.gsub(/#{rootdir}\//, '') if File.directory? component
90
+ end
91
+ end
92
+ return dirs
93
+ end
94
+
95
+ # Gets all configured architectures
96
+ def self.allowed
97
+ Cfg.components.uniq
98
+ end
99
+
100
+ end
101
+ end
102
+
@@ -0,0 +1,75 @@
1
+ require 'yaml'
2
+
3
+ # RepoMate module
4
+ module RepoMate
5
+
6
+ # Configuration class
7
+ class Configuration
8
+
9
+ # Init
10
+ def initialize
11
+ @configfile = File.join(ENV['HOME'], '.repomate')
12
+
13
+ configure(@configfile)
14
+ end
15
+
16
+ # Loads configfile
17
+ def configure(configfile)
18
+ filecontent = []
19
+
20
+ filecontent = YAML::load_file(configfile) if File.exists?(configfile)
21
+
22
+ merge(filecontent)
23
+ end
24
+
25
+ # Merges configfile content with defaults
26
+ def merge(filecontent=nil)
27
+ config = {}
28
+
29
+ defaults = {
30
+ :rootdir => '/var/lib/repomate/repository',
31
+ :logdir => '/var/log/repomate',
32
+ :dpkg => '/usr/bin/dpkg',
33
+ :suites => [ "lenny", "squeeze" ],
34
+ :components => [ "main", "contrib" ],
35
+ :architectures => [ "all", "amd64" ],
36
+ :origin => 'Repository',
37
+ :label => 'Repository',
38
+ :gpg_enable => 'yes',
39
+ :gpg_email => 'someone@example.net',
40
+ :gpg_password => 'secret',
41
+ }
42
+
43
+ if filecontent
44
+ defaults.each do |key, value|
45
+ keysymbol = key.to_sym
46
+ setter = "#{key}="
47
+
48
+ if filecontent[keysymbol]
49
+ config[keysymbol] = filecontent[keysymbol]
50
+ else
51
+ config[keysymbol] = value
52
+ end
53
+ end
54
+ else
55
+ config = defaults
56
+ end
57
+
58
+ config.each do |key, value|
59
+ setter = "#{key}="
60
+
61
+ self.class.send(:attr_accessor, key) unless respond_to?(setter)
62
+
63
+ send setter, value
64
+ end
65
+ end
66
+ end
67
+
68
+ # Returns
69
+ Cfg = Configuration.new
70
+
71
+ end
72
+
73
+
74
+
75
+
@@ -0,0 +1,30 @@
1
+ require 'sqlite3'
2
+
3
+ # RepoMate module
4
+ module RepoMate
5
+
6
+ # Class for the database
7
+ class Database
8
+
9
+ # Init
10
+ def initialize(dbfile)
11
+ @db = SQLite3::Database.new(dbfile)
12
+ end
13
+
14
+ # Checks if the database file already exists
15
+ def exists?
16
+ File.exists?(@dbfile)
17
+ end
18
+
19
+ # Executes a query
20
+ def query(sql)
21
+ @db.execute(sql)
22
+ end
23
+
24
+ # Deletes a categories directory
25
+ def destroy
26
+ FileUtils.rm_r(@dbfile) if exists?
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,137 @@
1
+ require 'erb'
2
+ require 'date'
3
+ require 'time'
4
+ require 'gpgme'
5
+ require 'digest/md5'
6
+ require 'digest/sha1'
7
+ require 'digest/sha2'
8
+
9
+ # RepoMate module
10
+ module RepoMate
11
+
12
+ # Class that can create and delete all metafiles like Packages, Packages.gz, Release and Release.gpg
13
+ class Metafile
14
+
15
+ # Init
16
+ def initialize
17
+ @repository = Repository.new
18
+ end
19
+
20
+ # Returns a lit of all existing metafiles as array
21
+ def all
22
+ rootdir = Cfg.rootdir
23
+ dirlist = ["#{rootdir}/*/*", "#{rootdir}/*/*/*/*"]
24
+ filelist = ["Packages", "Packages.gz", "Release", "Release.gpg" ]
25
+ files = []
26
+
27
+ dirlist.each do |dirs|
28
+ Dir.glob(dirs).each do |dir|
29
+ filelist.each do |file|
30
+ fullname = File.join(dir, file)
31
+ files << fullname if File.exists? fullname
32
+ end
33
+ end
34
+ end
35
+ return files
36
+ end
37
+
38
+ # Deletes all existing metafiles
39
+ def destroy
40
+ all.each { |file| FileUtils.rm_f(file) }
41
+ end
42
+
43
+ # Creates all metafiles
44
+ def create
45
+ destroy
46
+ create_packages
47
+
48
+ if Cfg.gpg_enable
49
+ if Cfg.gpg_password.nil? || Cfg.gpg_email.nil?
50
+ puts "Configure password and email for GPG!"
51
+ exit 1
52
+ else
53
+ create_release
54
+ end
55
+ end
56
+ end
57
+
58
+ # Create Packages* files
59
+ def create_packages
60
+ source_category = "dists"
61
+
62
+ packages_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/packages.erb")).read, nil, "%"
63
+
64
+ Architecture.dataset(source_category).each do |entry|
65
+ source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category)
66
+ source.files.each do |fullname|
67
+ package = Package.new(fullname, entry[:suitename], entry[:component])
68
+
69
+ checksums = package.get_checksums
70
+
71
+ packagesfile = File.join(entry[:fullpath], "Packages")
72
+ size = File.size(fullname)
73
+ path = File.join("dists", entry[:suitename], entry[:component], entry[:architecture_dir], package.newbasename)
74
+
75
+ File.open(packagesfile, 'a') do |file|
76
+ package.controlfile.each { |key, value| file.puts "#{key}: #{value}" if key }
77
+ file.puts packages_template.result(binding)
78
+ end
79
+ raise "Could not gzip" unless system "gzip -9 -c #{packagesfile} > #{packagesfile}.gz"
80
+ end
81
+ end
82
+ end
83
+
84
+ # Create Release* files
85
+ def create_release
86
+ source_category = "dists"
87
+ suites = []
88
+
89
+ archrelease_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/archrelease.erb")).read, nil, "%"
90
+ suiterelease_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/suiterelease.erb")).read, nil, "%"
91
+
92
+ now = Time.new.strftime("%a, %d %b %Y %H:%M:%S %Z")
93
+
94
+ Architecture.dataset(source_category).each do |entry|
95
+ releasefile = File.join(entry[:fullpath], "Release")
96
+
97
+ suites << entry[:suitename] unless suites.include?(entry[:suitename])
98
+
99
+ File.open(releasefile, 'w') { |file| file.puts archrelease_template.result(binding) }
100
+ end
101
+
102
+ suites.each do |suite|
103
+ architecture = []
104
+ component = []
105
+
106
+ Architecture.dataset(source_category).each do |entry|
107
+ if entry[:suitename].eql?(suite)
108
+ architecture << entry[:architecture] unless architecture.include?(entry[:architecture])
109
+ component << entry[:component] unless component.include?(entry[:component])
110
+ end
111
+ end
112
+
113
+ releasefile = File.join(Cfg.rootdir, source_category, suite, "Release")
114
+
115
+ File.open(releasefile, 'w') { |file| file.puts suiterelease_template.result(binding).gsub(/^\s+\n|^\n|^\s{3}/, '') }
116
+ begin
117
+ sign(releasefile)
118
+ rescue
119
+ destroy
120
+ create_packages
121
+ puts "GPG email/password incorrect"
122
+ return
123
+ end
124
+ end
125
+ end
126
+
127
+ # Sign a file
128
+ def sign(file)
129
+ crypto = GPGME::Crypto.new :password => Cfg.gpg_password
130
+ outfile = "#{file}.gpg"
131
+ output = File.open(outfile, 'w')
132
+ crypto.sign File.open(file, 'r'), :symmetric => false, :output => output, :signer => Cfg.gpg_email, :mode => GPGME::SIG_MODE_DETACH
133
+ end
134
+
135
+ end
136
+ end
137
+
@@ -0,0 +1,116 @@
1
+ require 'digest/md5'
2
+ require 'digest/sha1'
3
+ require 'digest/sha2'
4
+ require 'tempfile'
5
+ require 'date'
6
+ require 'time'
7
+
8
+ # RepoMate module
9
+ module RepoMate
10
+
11
+ # Class for reading debian packages
12
+ class Package
13
+
14
+ attr_reader :name, :basename, :newbasename, :controlfile, :architecture, :version, :suitename, :component, :fullname
15
+
16
+ # Init
17
+ def initialize(fullname, suitename, component)
18
+ @fullname = fullname
19
+ @suitename = suitename
20
+ @component = component
21
+ @basename = File.basename(fullname)
22
+ @mtime = File.mtime(fullname)
23
+ @pkgdbfile = File.join(Cfg.rootdir, "packages.db")
24
+ @pkgdb = Database.new(@pkgdbfile)
25
+
26
+ check_package
27
+ create_table
28
+
29
+ @controlfile = read_controlfile
30
+ @name = @controlfile['Package']
31
+ @version = @controlfile['Version']
32
+ @architecture = @controlfile['Architecture']
33
+ @newbasename = "#{@name}-#{@version}_#{@architecture}.deb"
34
+ end
35
+
36
+ # Create the package table
37
+ def create_table
38
+ sql = "create table if not exists checksums (
39
+ date varchar(25),
40
+ basename varchar(70),
41
+ mtime varchar(25),
42
+ md5 varchar(32),
43
+ sha1 varchar(40),
44
+ sha256 varchar(64)
45
+ )"
46
+ @pkgdb.query(sql)
47
+ end
48
+
49
+ # Gets checksums for the given package
50
+ def get_checksums
51
+ result = []
52
+
53
+ @pkgdb.query("select md5, sha1, sha256 from checksums where basename = '#{@basename}' and mtime = '#{@mtime.iso8601}'").each do |row|
54
+ result = row
55
+ # puts "Hit: #{@basename} #{result}"
56
+ end
57
+ result
58
+ end
59
+
60
+ # Creates the checksums for a package
61
+ def create_checksums
62
+ # puts "Ins: #{@basename}"
63
+ now = DateTime.now
64
+ md5 = Digest::MD5.file(@fullname).to_s
65
+ sha1 = Digest::SHA1.file(@fullname).to_s
66
+ sha256 = Digest::SHA256.new(256).file(@fullname).to_s
67
+ @pkgdb.query("insert into checksums values ( '#{now}', '#{@basename}', '#{@mtime.iso8601}', '#{md5}', '#{sha1}', '#{sha256}' )")
68
+ end
69
+
70
+ # Gets checksums for the given package
71
+ def delete_checksums
72
+ # puts "Del: #{@basename}"
73
+ @pkgdb.query("delete from checksums where basename = '#{@basename}'")
74
+ end
75
+
76
+ protected
77
+
78
+ # Checks if the given package is a debian package
79
+ def check_package
80
+ unless `file --dereference #{@fullname}` =~ /Debian binary package/i
81
+ puts "File does not exist or is not a Debian package!"
82
+ false
83
+ end
84
+ end
85
+
86
+ # Extracts the controlfile and returns is
87
+ def read_controlfile
88
+ gzbasename = "control.tar.gz"
89
+ basename = "control"
90
+ tmpdir = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
91
+ gzfullname = File.join(tmpdir, gzbasename)
92
+ fullname = File.join(tmpdir, basename)
93
+
94
+ controlfile = {}
95
+
96
+ FileUtils.mkdir_p(tmpdir)
97
+
98
+ begin
99
+ raise "Could not untar" unless system "ar -p #{@fullname} #{gzbasename} > #{gzfullname}"
100
+ raise Errno::ENOENT, "Package file does not exist" unless File.exists?(gzfullname)
101
+ raise "Could not untar" unless system "tar xfz #{gzfullname} -C #{tmpdir}"
102
+
103
+ File.open(fullname) do |file|
104
+ while(line = file.gets)
105
+ line =~ %r{([a-zA-Z]+):\s(.*)}
106
+ controlfile[$1] = $2
107
+ end
108
+ end
109
+ ensure
110
+ FileUtils.rm_rf(tmpdir)
111
+ end
112
+ controlfile
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,49 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for creating the repository structure
5
+ class Repository
6
+
7
+ attr_reader :categories
8
+
9
+ # Init
10
+ def initialize
11
+ @categories = ["stage", "pool", "dists"]
12
+ end
13
+
14
+ # Creates the base structure
15
+ def create(suitename=nil, component=nil, architecture=nil)
16
+ unless Suite.allowed.include?(suitename)
17
+ STDERR.puts "Suitename (#{suitename}) is not configured"
18
+ exit 1
19
+ end
20
+
21
+ unless Component.allowed.include?(component)
22
+ STDERR.puts "Component (#{component}) is not configured"
23
+ exit 1
24
+ end
25
+
26
+ unless architecture.nil?
27
+ unless Architecture.allowed.include?(architecture)
28
+ STDERR.puts "Architecture (#{architecture}) is not configured"
29
+ exit 1
30
+ end
31
+ end
32
+
33
+ @categories.each do |category|
34
+ if category.eql?("stage")
35
+ Component.new(component, suitename, category).create
36
+ else
37
+ if architecture && component && suitename
38
+ Architecture.new(architecture, component, suitename, category).create
39
+ elsif component && suitename
40
+ Component.new(component, suitename, category).create
41
+ elsif suitename.nil?
42
+ Suite.new(suitename, category).create
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,96 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for the suite layer of the directory structure
5
+ class Suite
6
+
7
+ # Init
8
+ def initialize(suitename, category)
9
+ @suitename = suitename
10
+ @category = category
11
+ end
12
+
13
+ # Returns the given suite name (eg. lenny, squeeze)
14
+ def name
15
+ @suitename
16
+ end
17
+
18
+ # Returns the directory strcuture of the suite including all lower layers
19
+ def directory
20
+ File.join(Cfg.rootdir, @category, @suitename)
21
+ end
22
+
23
+ # Checks if the suite directory exists
24
+ def exist?
25
+ Dir.exist?(directory)
26
+ end
27
+
28
+ # Checks if the suite is allowed (See: configurationfile)
29
+ def is_allowed?
30
+ self.allowed.include?(@suitename)
31
+ end
32
+
33
+ # Checks if directory is unused
34
+ def is_unused?(dir)
35
+ status = true
36
+
37
+ path = Dir.glob(File.join(dir, "*"))
38
+ path.each do |dirorfile|
39
+ status = false if File.directory?(dirorfile)
40
+ status = false if File.basename(dirorfile) =~ /\.deb$/
41
+ end
42
+
43
+ status
44
+ end
45
+
46
+ # Creates the directory strcuture of the suite including all lower layers
47
+ def create
48
+ FileUtils.mkdir_p(directory) unless exist?
49
+ end
50
+
51
+ # Deletes the suites directory including all lower layers
52
+ def destroy
53
+ FileUtils.rm_r(directory) if exist?
54
+ end
55
+
56
+ # Returns a dataset including the name of the suite, the fullpath recursive through all lower layers
57
+ def self.dataset(category=nil)
58
+ data = []
59
+ self.all.each do |entry|
60
+ # p entry
61
+ parts = entry.split(/\//)
62
+ unless parts.length < 2
63
+ next unless parts[0].eql?(category) || category.eql?("all")
64
+
65
+ data << {
66
+ :category => parts[0],
67
+ :suitename => parts[1],
68
+ :fullpath => File.join(Cfg.rootdir, entry),
69
+ }
70
+ end
71
+ end
72
+ data
73
+ end
74
+
75
+ # Returns all directories without @rootdir
76
+ def self.all
77
+ categories = Category.all
78
+ dirs = []
79
+ rootdir = Cfg.rootdir
80
+ categories.each do |category|
81
+ suites = Dir.glob(File.join(rootdir, category, "*"))
82
+ suites.each do |suite|
83
+ dirs.push suite.gsub(/#{rootdir}\//, '') if File.directory? suite
84
+ end
85
+ end
86
+ return dirs
87
+ end
88
+
89
+ # Gets all configured architectures
90
+ def self.allowed
91
+ Cfg.suites.uniq
92
+ end
93
+
94
+ end
95
+ end
96
+
@@ -0,0 +1,6 @@
1
+ Archive: stable
2
+ Component: <%= entry[:component] %>
3
+ Origin: <%= Cfg.origin %>
4
+ Label: <%= Cfg.label %>
5
+ Architecture: <%= entry[:architecture]%>
6
+ Description: Repository for debian <%= entry[:suitename]%>
@@ -0,0 +1,6 @@
1
+ Size: <%= size %>
2
+ Filename: <%= path %>
3
+ MD5sum: <%= checksums[0] %>
4
+ SHA1: <%= checksums[1] %>
5
+ SHA256: <%= checksums[2] %>
6
+
@@ -0,0 +1,41 @@
1
+ Origin: <%= Cfg.origin %>
2
+ Label: <%= Cfg.label %>
3
+ Suite: stable
4
+ Codename: <%= suite %>
5
+ Date: <%= now %>
6
+ Architectures: <%= architecture.join(', ') %>
7
+ Components: <%= component.join(', ') %>
8
+ Description: Repository for debian
9
+ MD5Sum:
10
+ <% Architecture.dataset(source_category).each do |entry| %>
11
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
12
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
13
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
14
+ <%= Digest::MD5.file(file) %> <%= File.size(file) %> <%= path %>
15
+ <% end %>
16
+ <% file = File.join(entry[:fullpath], "Release") %>
17
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
18
+ <%= Digest::MD5.file(file) %> <%= File.size(file) %> <%= path %>
19
+ <% end %>
20
+ SHA1:
21
+ <% Architecture.dataset(source_category).each do |entry| %>
22
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
23
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
24
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
25
+ <%= Digest::SHA1.file(file) %> <%= File.size(file) %> <%= path %>
26
+ <% end %>
27
+ <% file = File.join(entry[:fullpath], "Release") %>
28
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
29
+ <%= Digest::SHA1.file(file) %> <%= File.size(file) %> <%= path %>
30
+ <% end %>
31
+ SHA256:
32
+ <% Architecture.dataset(source_category).each do |entry| %>
33
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
34
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
35
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
36
+ <%= Digest::SHA256.new(256).file(file) %> <%= File.size(file) %> <%= path %>
37
+ <% end %>
38
+ <% file = File.join(entry[:fullpath], "Release") %>
39
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
40
+ <%= Digest::SHA256.new(256).file(file) %> <%= File.size(file) %> <%= path %>
41
+ <% end %>
data/lib/repomate.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'repomate/architecture'
4
+ require 'repomate/base'
5
+ require 'repomate/category'
6
+ require 'repomate/cli'
7
+ require 'repomate/component'
8
+ require 'repomate/configuration'
9
+ require 'repomate/database'
10
+ require 'repomate/metafile'
11
+ require 'repomate/package'
12
+ require 'repomate/repository'
13
+ require 'repomate/suite'
14
+