aptly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d28012fc713f236f2439309dddc1d30fdb66338e
4
+ data.tar.gz: ee1285d3545517e13dd2dff95b3222b8a081babf
5
+ SHA512:
6
+ metadata.gz: 4d9e6934ea4a9b23b2c3028110d39b8bcbcd7e07dc25132e47854d2bdb570961e32e514147759dd7f5274c28f85ff6f041663960e405479329e5cf2960fb5953
7
+ data.tar.gz: 3b39146ff8e28f1af97c602c4415a48948a666c6a66d5660aa7d2fbc170f29812af166613188b19e180c3ad5a42d930d61496112b037b6132ef1a9349e4478f1
@@ -0,0 +1,102 @@
1
+ require 'aptly/version'
2
+ require 'aptly/mutex'
3
+ require 'aptly/error'
4
+ require 'aptly/mirror'
5
+ require 'aptly/repo'
6
+ require 'aptly/snapshot'
7
+ require 'aptly/publish'
8
+ require 'aptly/string'
9
+ require 'aptly/hash'
10
+
11
+ module Aptly
12
+
13
+ # runcmd handles running arbitrary commands. It is intended to run aptly-
14
+ # specific commands, and as such, it uses a mutex to take gurantee
15
+ # consistency and avoid multiple processes modifying aptly simultaneously.
16
+ #
17
+ # == Parameters:
18
+ # cmd::
19
+ # A string holding the command to run
20
+ #
21
+ # == Returns:
22
+ # The content of stdout
23
+ #
24
+ def runcmd cmd
25
+ Mutex.lock
26
+ at_exit { Mutex.unlock }
27
+
28
+ out = %x(#{cmd} 2>&1)
29
+ res = $?.exitstatus
30
+
31
+ Mutex.unlock
32
+
33
+ if res != 0
34
+ raise AptlyError.new "aptly: command failed: #{cmd}", out
35
+ end
36
+
37
+ return out
38
+ end
39
+
40
+ # Parses the output lines of aptly listing commands
41
+ #
42
+ # == Parameters:
43
+ # lines::
44
+ # An array of lines of string output from aptly list commands
45
+ #
46
+ # == Returns:
47
+ # An array of items
48
+ #
49
+ def parse_list lines
50
+ items = Array.new
51
+ lines.each do |line|
52
+ if line.start_with?(' * ')
53
+ parts = line.split(/\[|\]/, 3)
54
+ items << parts[1] if parts.length == 3
55
+ end
56
+ end
57
+ items
58
+ end
59
+
60
+ # Parses the output of aptly show commands.
61
+ #
62
+ # == Parameters:
63
+ # lines::
64
+ # An array of strings of aptly output
65
+ #
66
+ # == Returns:
67
+ # A hash of information
68
+ #
69
+ def parse_info lines
70
+ items = Hash.new
71
+ lines.reject{|l| l.empty?}.each do |line|
72
+ parts = line.split(/:\s/, 2)
73
+ items[parts[0]] = parts[1].strip if parts.length == 2
74
+ end
75
+
76
+ def multi items, key
77
+ items[key] = items[key].split(' ') if items.has_key? key
78
+ end
79
+
80
+ multi items, 'Components'
81
+ multi items, 'Architectures'
82
+
83
+ items
84
+ end
85
+
86
+ # Parses aptly output of double-space indented lists
87
+ #
88
+ # == Parameters:
89
+ # lines::
90
+ # Output lines from an aptly list command
91
+ #
92
+ # == Result:
93
+ # An array of items
94
+ #
95
+ def parse_indented_list lines
96
+ items = Array.new
97
+ lines.each do |line|
98
+ items << line.strip if line.start_with? ' '
99
+ end
100
+ items
101
+ end
102
+ end
@@ -0,0 +1,9 @@
1
+ class AptlyError < RuntimeError
2
+ attr_accessor :output
3
+ @output = nil
4
+
5
+ def initialize msg=nil, output=nil
6
+ @output = output
7
+ super msg
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def arg arg, default
3
+ if self.has_key? arg
4
+ return self[arg]
5
+ else
6
+ return default
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,150 @@
1
+ module Aptly
2
+ extend self
3
+
4
+ # Creates a new mirror in aptly. This simply creates the necessary records in
5
+ # leveldb and doesn't do any heavy lifting.
6
+ #
7
+ # == Parameters:
8
+ # name::
9
+ # The name of the new repository
10
+ # baseurl::
11
+ # The URL to the repository content
12
+ # dist::
13
+ # The distribution (e.g. precise, quantal)
14
+ # components::
15
+ # The repository components (e.g. main, stable)
16
+ # archlist::
17
+ # A list of architecture types to mirror
18
+ # ignoresigs::
19
+ # Ignore package signature mismatches
20
+ # source::
21
+ # Optionally mirror in source packages
22
+ #
23
+ # == Returns:
24
+ # An Aptly::Mirror object
25
+ #
26
+ def create_mirror(
27
+ name,
28
+ baseurl,
29
+ dist,
30
+ kwargs={}
31
+ )
32
+ components = kwargs.arg :components, []
33
+ archlist = kwargs.arg :archlist, []
34
+ ignoresigs = kwargs.arg :ignoresigs, false
35
+ source = kwargs.arg :source, false
36
+
37
+ if list_mirrors.include? name
38
+ raise AptlyError.new "Mirror '#{name}' already exists"
39
+ end
40
+
41
+ if components.length < 1
42
+ raise AptlyError.new "1 or more components are required"
43
+ end
44
+
45
+ cmd = 'aptly mirror create'
46
+ cmd += " -architectures #{archlist.join(',')}" if !archlist.empty?
47
+ cmd += ' -ignore-signatures' if ignoresigs
48
+ cmd += ' -with-sources' if source
49
+ cmd += " #{name.quote} #{baseurl.quote} #{dist.quote}"
50
+ cmd += " #{components.join(' ')}"
51
+
52
+ runcmd cmd
53
+ return Mirror.new name
54
+ end
55
+
56
+ # Returns a list of existing mirrors in aptly
57
+ #
58
+ # == Returns
59
+ # An array of mirror names
60
+ #
61
+ def list_mirrors
62
+ out = runcmd 'aptly mirror list'
63
+ parse_list out.lines
64
+ end
65
+
66
+ # Retrieves information about a mirror
67
+ #
68
+ # == Parameters:
69
+ # name::
70
+ # The name of the mirror to retrieve info for
71
+ #
72
+ # == Returns:
73
+ # A hash of mirror information
74
+ #
75
+ def mirror_info name
76
+ out = runcmd "aptly mirror show #{name.quote}"
77
+ parse_info out.lines
78
+ end
79
+
80
+ class Mirror
81
+ attr_accessor :name, :baseurl, :dist, :components, :archlist
82
+
83
+ @name = ''
84
+ @baseurl = ''
85
+ @dist = ''
86
+ @components = []
87
+ @archlist = []
88
+
89
+ # Instantiates a new Mirror object
90
+ #
91
+ # == Parameters:
92
+ # name::
93
+ # Then name associated with the mirror
94
+ #
95
+ def initialize name
96
+ if !Aptly::list_mirrors.include? name
97
+ raise AptlyError.new("Mirror '#{name}' does not exist")
98
+ end
99
+
100
+ info = Aptly::mirror_info name
101
+ @name = info['Name']
102
+ @baseurl = info['Archive Root URL']
103
+ @dist = info['Distribution']
104
+ @components = info['Components']
105
+ @archlist = info['Architectures']
106
+ end
107
+
108
+ # Drops an existing mirror from aptly's configuration
109
+ def drop
110
+ Aptly::runcmd "aptly mirror drop #{@name.quote}"
111
+ end
112
+
113
+ # List all packages contained in a mirror
114
+ #
115
+ # == Returns:
116
+ # An array of packages
117
+ #
118
+ def list_packages
119
+ res = []
120
+ out = Aptly::runcmd "aptly mirror show -with-packages #{@name.quote}"
121
+ Aptly::parse_indented_list out.lines
122
+ end
123
+
124
+ # Shortcut method to snapshot an Aptly::Mirror object
125
+ def snapshot name
126
+ Aptly::create_mirror_snapshot name, @name
127
+ end
128
+
129
+ # Updates a repository, syncing in all packages which have not already been
130
+ # downloaded and caches them locally.
131
+ #
132
+ # == Parameters:
133
+ # ignore_cksum::
134
+ # Ignore checksum mismatches
135
+ # ignore_sigs::
136
+ # Ignore author signature mismatches
137
+ #
138
+ def update kwargs={}
139
+ ignore_cksum = kwargs.arg :ignore_cksum, false
140
+ ignore_sigs = kwargs.arg :ignore_sigs, false
141
+
142
+ cmd = 'aptly mirror update'
143
+ cmd += ' -ignore-checksums' if ignore_cksum
144
+ cmd += ' -ignore-signatures' if ignore_sigs
145
+ cmd += " #{@name.quote}"
146
+
147
+ Aptly::runcmd cmd
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,78 @@
1
+ module Aptly
2
+ extend self
3
+
4
+ class Mutex
5
+ @@mutex_path = '/tmp/aptly.lock'
6
+
7
+ # Static attribute accessor for mutex path
8
+ #
9
+ # == Returns:
10
+ # The path to the aptly mutex file
11
+ #
12
+ def self.mutex_path
13
+ @@mutex_path
14
+ end
15
+
16
+ # Alter the mutex path. This should be set to the same path in all places
17
+ # where ruby-aptly will be used on the same host, since the mutex is meant
18
+ # to be system-wide.
19
+ #
20
+ # == Parameters:
21
+ # path::
22
+ # The desired path for the mutex
23
+ #
24
+ def self.mutex_path= path
25
+ @@mutex_path = path
26
+ end
27
+
28
+ # Attempts to acquire the aptly mutex. This method will wait if the mutex
29
+ # is already locked elsewhere, and check back every 5 seconds to see if it
30
+ # has been freed. On each check where the mutex is determined to be in use,
31
+ # we check if the process which originally acquired the lock is still
32
+ # running so we can acquire a stale mutex should we need to.
33
+ #
34
+ # == Returns:
35
+ # True, once the lock is acquired.
36
+ #
37
+ def self.lock
38
+ while self.locked?
39
+ self.running? && sleep(5) || self.unlock
40
+ end
41
+ File.open(@@mutex_path, 'w') {|f| f.write Process.pid}
42
+ end
43
+
44
+ # Checks if the mutex is in use. This is done via a simple file check.
45
+ #
46
+ # == Returns:
47
+ # True if locked, else false
48
+ #
49
+ def self.locked?
50
+ File.exist? @@mutex_path
51
+ end
52
+
53
+ # Checks if the process which originally acquired the mutex is still
54
+ # running. This is useful to determine staleness of a locked mutex.
55
+ #
56
+ # == Returns:
57
+ # True if running, else false
58
+ #
59
+ def self.running?
60
+ begin
61
+ pid = File.open(@@mutex_path).read.to_i
62
+ Process.kill(0, pid)
63
+ return true
64
+ rescue Errno::ESRCH, Errno::ENOENT
65
+ return false
66
+ end
67
+ end
68
+
69
+ # Unlocks the mutex so that other processes may acquire it.
70
+ #
71
+ # == Returns:
72
+ # True if unlock happens, else false
73
+ #
74
+ def self.unlock
75
+ File.delete @@mutex_path if self.locked?
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,147 @@
1
+ module Aptly
2
+ extend self
3
+
4
+ # Publish a snapshot or repo resource.
5
+ #
6
+ # == Parameters:
7
+ # type::
8
+ # The type of resource to publish. 'snapshot' and 'repo' supported.
9
+ # name::
10
+ # The name of the resource to publish.
11
+ # prefix::
12
+ # An optional prefix to publish to.
13
+ # component::
14
+ # The component to publish to. If empty, aptly will attempt to guess
15
+ # from the source, or use 'main' as the default.
16
+ # dist::
17
+ # The distribution name. If empty, aptly will try to guess.
18
+ # gpg_key::
19
+ # The gpg key to sign the repository. Uses default if not specified.
20
+ # keyring::
21
+ # GPG keyring to use
22
+ # label::
23
+ # An optional label to give the published resource
24
+ # origin::
25
+ # The value of the 'Origin' field
26
+ # secret_keyring::
27
+ # GPG secret keyring to use
28
+ # sign::
29
+ # When false, don't sign Release files. Defaults to true.
30
+ #
31
+ def publish(
32
+ type,
33
+ name,
34
+ kwargs={}
35
+ )
36
+ prefix = kwargs.arg :prefix, ''
37
+ component = kwargs.arg :component, ''
38
+ dist = kwargs.arg :dist, ''
39
+ gpg_key = kwargs.arg :gpg_key, ''
40
+ keyring = kwargs.arg :keyring, ''
41
+ label = kwargs.arg :label, ''
42
+ origin = kwargs.arg :origin, ''
43
+ secret_keyring = kwargs.arg :secret_keyring, ''
44
+ sign = kwargs.arg :sign, true
45
+
46
+ if type != 'repo' && type != 'snapshot'
47
+ raise AptlyError "Invalid publish type: #{type}"
48
+ end
49
+
50
+ cmd = "aptly publish #{type}"
51
+ cmd += ' -skip-signing' if !sign
52
+ cmd += " -component #{component.quote}" if !component.empty?
53
+ cmd += " -distribution #{dist.quote}" if !dist.empty?
54
+ cmd += " -gpg-key #{gpg_key.quote}" if !gpg_key.empty?
55
+ cmd += " -keyring #{keyring.quote}" if !keyring.empty?
56
+ cmd += " -label #{label.quote}" if !label.empty?
57
+ cmd += " -origin #{origin.quote}" if !origin.empty?
58
+ cmd += " #{name.quote}"
59
+ cmd += " #{prefix.quote}" if !prefix.empty?
60
+ if !secret_keyring.empty?
61
+ cmd += " -secret-keyring #{secret_keyring.quote}"
62
+ end
63
+
64
+ runcmd cmd
65
+ end
66
+
67
+ # List existing published resources.
68
+ #
69
+ # == Returns
70
+ # A hash of published resource information. The outer hash key is the
71
+ # published resource path, and its value is the resource metadata.
72
+ #
73
+ def list_published
74
+ res = Hash.new
75
+ out = runcmd 'aptly publish list'
76
+ out.lines.each do |line|
77
+ if line.start_with? ' * '
78
+ resource = {}
79
+ parts = line[3..-1].split(/\[|\]|\(|\)/)
80
+ resource['path'] = parts[0].strip
81
+ resource['component'] = parts[1].strip
82
+ resource['archlist'] = parts[3].split(', ')
83
+ resource['from_name'] = parts[5].strip
84
+ if parts[6].include? 'Snapshot'
85
+ resource['from_type'] = 'snapshot'
86
+ elsif parts[6].include? 'Repo'
87
+ resource['from_type'] = 'repo'
88
+ else
89
+ next
90
+ end
91
+ dist = line.split.last
92
+ res[dist] = resource
93
+ end
94
+ end
95
+ res
96
+ end
97
+
98
+ class PublishedResource
99
+ attr_accessor :path, :from_name, :from_type, :component
100
+ attr_accessor :archlist, :dist
101
+
102
+ @path = ''
103
+ @from_name = ''
104
+ @from_type = ''
105
+ @component = ''
106
+ @archlist = []
107
+ @dist = ''
108
+
109
+ # Instantiates a new published resource object from an existing published
110
+ # resource.
111
+ #
112
+ # == Parameters:
113
+ # dist::
114
+ # The distribution name
115
+ # prefix::
116
+ # An optional prefix
117
+ #
118
+ # == Returns:
119
+ # An Aptly::PublishedResource object
120
+ #
121
+ def initialize dist, prefix=''
122
+ idx = "#{prefix}#{prefix.empty? ? '' : '/'}#{dist}"
123
+
124
+ published = Aptly::list_published
125
+ if !published.include? idx
126
+ raise AptlyError.new "Published resource #{idx} does not exist"
127
+ end
128
+
129
+ @dist = dist
130
+ @prefix = prefix
131
+ @path = published[idx]['path']
132
+ @from_name = published[idx]['from_name']
133
+ @from_type = published[idx]['from_type']
134
+ @component = published[idx]['component']
135
+ @archlist = published[idx]['archlist']
136
+ end
137
+
138
+ # Unpublish a currently published resource.
139
+ def drop
140
+ cmd = 'aptly publish drop'
141
+ cmd += " #{@dist.quote}"
142
+ cmd += " #{@prefix.quote}" if !@prefix.empty?
143
+
144
+ Aptly::runcmd cmd
145
+ end
146
+ end
147
+ end