dpkg-s3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "tempfile"
3
+
4
+ class Dpkg::S3::Release
5
+ include Dpkg::S3::Utils
6
+
7
+ attr_accessor :codename
8
+ attr_accessor :origin
9
+ attr_accessor :suite
10
+ attr_accessor :architectures
11
+ attr_accessor :components
12
+ attr_accessor :cache_control
13
+
14
+ attr_accessor :files
15
+ attr_accessor :policy
16
+
17
+ def initialize
18
+ @origin = nil
19
+ @suite = nil
20
+ @codename = nil
21
+ @architectures = []
22
+ @components = []
23
+ @cache_control = ""
24
+ @files = {}
25
+ @policy = :public_read
26
+ end
27
+
28
+ class << self
29
+ def retrieve(codename, origin=nil, suite=nil, cache_control=nil)
30
+ if s = Dpkg::S3::Utils.s3_read("dists/#{codename}/Release")
31
+ rel = self.parse_release(s)
32
+ else
33
+ rel = self.new
34
+ end
35
+ rel.codename = codename
36
+ rel.origin = origin unless origin.nil?
37
+ rel.suite = suite unless suite.nil?
38
+ rel.cache_control = cache_control
39
+ rel
40
+ end
41
+
42
+ def parse_release(str)
43
+ rel = self.new
44
+ rel.parse(str)
45
+ rel
46
+ end
47
+ end
48
+
49
+ def filename
50
+ "dists/#{@codename}/Release"
51
+ end
52
+
53
+ def parse(str)
54
+ parse = lambda do |field|
55
+ value = str[/^#{field}: .*/]
56
+ if value.nil?
57
+ return nil
58
+ else
59
+ return value.split(": ",2).last
60
+ end
61
+ end
62
+
63
+ # grab basic fields
64
+ self.codename = parse.call("Codename")
65
+ self.origin = parse.call("Origin") || nil
66
+ self.suite = parse.call("Suite") || nil
67
+ self.architectures = (parse.call("Architectures") || "").split(/\s+/)
68
+ self.components = (parse.call("Components") || "").split(/\s+/)
69
+
70
+ # find all the hashes
71
+ str.scan(/^\s+([^\s]+)\s+(\d+)\s+(.+)$/).each do |(hash,size,name)|
72
+ self.files[name] ||= { :size => size.to_i }
73
+ case hash.length
74
+ when 32
75
+ self.files[name][:md5] = hash
76
+ when 40
77
+ self.files[name][:sha1] = hash
78
+ when 64
79
+ self.files[name][:sha256] = hash
80
+ end
81
+ end
82
+ end
83
+
84
+ def generate
85
+ template("release.erb").result(binding)
86
+ end
87
+
88
+ def write_to_s3
89
+ # validate some other files are present
90
+ if block_given?
91
+ self.validate_others { |f| yield f }
92
+ else
93
+ self.validate_others
94
+ end
95
+
96
+ # generate the Release file
97
+ release_tmp = Tempfile.new("Release")
98
+ release_tmp.puts self.generate
99
+ release_tmp.close
100
+ yield self.filename if block_given?
101
+ s3_store(release_tmp.path, self.filename, 'text/plain; charset=utf-8', self.cache_control)
102
+
103
+ # sign the file, if necessary
104
+ if Dpkg::S3::Utils.signing_key
105
+ key_param = Dpkg::S3::Utils.signing_key != "" ? "--default-key=#{Dpkg::S3::Utils.signing_key}" : ""
106
+ if system("gpg -a #{key_param} --digest-algo SHA256 #{Dpkg::S3::Utils.gpg_options} -s --clearsign #{release_tmp.path}")
107
+ local_file = release_tmp.path+".asc"
108
+ remote_file = "dists/#{@codename}/InRelease"
109
+ yield remote_file if block_given?
110
+ raise "Unable to locate InRelease file" unless File.exists?(local_file)
111
+ s3_store(local_file, remote_file, 'application/pgp-signature; charset=us-ascii', self.cache_control)
112
+ File.unlink(local_file)
113
+ else
114
+ raise "Signing the InRelease file failed."
115
+ end
116
+ if system("gpg -a #{key_param} --digest-algo SHA256 #{Dpkg::S3::Utils.gpg_options} -b #{release_tmp.path}")
117
+ local_file = release_tmp.path+".asc"
118
+ remote_file = self.filename+".gpg"
119
+ yield remote_file if block_given?
120
+ raise "Unable to locate Release signature file" unless File.exists?(local_file)
121
+ s3_store(local_file, remote_file, 'application/pgp-signature; charset=us-ascii', self.cache_control)
122
+ File.unlink(local_file)
123
+ else
124
+ raise "Signing the Release file failed."
125
+ end
126
+ else
127
+ # remove an existing Release.gpg, if it was there
128
+ s3_remove(self.filename+".gpg")
129
+ end
130
+
131
+ release_tmp.unlink
132
+ end
133
+
134
+ def update_manifest(manifest)
135
+ self.components << manifest.component unless self.components.include?(manifest.component)
136
+ self.architectures << manifest.architecture unless self.architectures.include?(manifest.architecture)
137
+ self.files.merge!(manifest.files)
138
+ end
139
+
140
+ def validate_others
141
+ to_apply = []
142
+ self.components.each do |comp|
143
+ %w(amd64 i386 armhf).each do |arch|
144
+ next if self.files.has_key?("#{comp}/binary-#{arch}/Packages")
145
+
146
+ m = Dpkg::S3::Manifest.new
147
+ m.codename = self.codename
148
+ m.component = comp
149
+ m.architecture = arch
150
+ if block_given?
151
+ m.write_to_s3 { |f| yield f }
152
+ else
153
+ m.write_to_s3
154
+ end
155
+ to_apply << m
156
+ end
157
+ end
158
+
159
+ to_apply.each { |m| self.update_manifest(m) }
160
+ end
161
+ end
@@ -0,0 +1,66 @@
1
+ Package: <%= name %>
2
+ Version: <%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>
3
+ License: <%= license %>
4
+ Vendor: <%= vendor %>
5
+ Architecture: <%= architecture %>
6
+ Maintainer: <%= maintainer %>
7
+ Installed-Size: <%= attributes[:deb_installed_size] %>
8
+ <% if !dependencies.empty? and !attributes[:no_depends?] -%>
9
+ Depends: <%= dependencies.collect { |d| fix_dependency(d) }.flatten.join(", ") %>
10
+ <% end -%>
11
+ <% if attributes[:deb_conflicts] -%>
12
+ Conflicts: <%= attributes[:deb_conflicts] %>
13
+ <% end -%>
14
+ <% if attributes[:deb_breaks] -%>
15
+ Breaks: <%= attributes[:deb_breaks] %>
16
+ <% end -%>
17
+ <% if attributes[:deb_pre_depends] -%>
18
+ Pre-Depends: <%= attributes[:deb_pre_depends] %>
19
+ <% end -%>
20
+ <% if attributes[:deb_provides] -%>
21
+ <%# Turn each provides from 'foo = 123' to simply 'foo' because Debian :\ -%>
22
+ <%# http://www.debian.org/doc/debian-policy/ch-relationships.html -%>
23
+ Provides: <%= attributes[:deb_provides] %>
24
+ <% end -%>
25
+ <% if attributes[:deb_replaces] -%>
26
+ Replaces: <%= attributes[:deb_replaces] %>
27
+ <% end -%>
28
+ <% if attributes[:deb_recommends] -%>
29
+ Recommends: <%= attributes[:deb_recommends] %>
30
+ <% end -%>
31
+ <% if attributes[:deb_suggests] -%>
32
+ Suggests: <%= attributes[:deb_suggests] %>
33
+ <% end -%>
34
+ <% if attributes[:deb_enhances] -%>
35
+ Enhances: <%= attributes[:deb_enhances] %>
36
+ <% end -%>
37
+ Section: <%= category %>
38
+ <% if attributes[:deb_origin] -%>
39
+ Origin: <%= attributes[:deb_origin] %>
40
+ <% end -%>
41
+ Priority: <%= attributes[:deb_priority] %>
42
+ Homepage: <%= url or "http://nourlgiven.example.com/" %>
43
+ Filename: <%= url_filename_encoded(codename) %>
44
+ <% if size -%>
45
+ Size: <%= size %>
46
+ <% end -%>
47
+ <% if sha1 -%>
48
+ SHA1: <%= sha1 %>
49
+ <% end -%>
50
+ <% if sha256 -%>
51
+ SHA256: <%= sha256 %>
52
+ <% end -%>
53
+ <% if md5 -%>
54
+ MD5sum: <%= md5 %>
55
+ <% end -%>
56
+ <% lines = (description or "no description given").split("\n") -%>
57
+ <% firstline, *remainder = lines -%>
58
+ Description: <%= firstline %>
59
+ <% if remainder.any? -%>
60
+ <%= remainder.collect { |l| l =~ /^ *$/ ? " ." : " #{l}" }.join("\n") %>
61
+ <% end -%>
62
+ <% if attributes[:deb_field] -%>
63
+ <% attributes[:deb_field].each do |field, value| -%>
64
+ <%= field %>: <%= value %>
65
+ <% end -%>
66
+ <% end -%>
@@ -0,0 +1,20 @@
1
+ <% if origin -%>
2
+ Origin: <%= origin %>
3
+ <% end -%>
4
+ Codename: <%= codename %>
5
+ Date: <%= Time.now.utc.strftime("%a, %d %b %Y %T %Z") %>
6
+ Architectures: <%= architectures.join(" ") %>
7
+ Components: <%= components.join(" ") %>
8
+ Suite: <%= suite %>
9
+ MD5Sum:
10
+ <% files.sort.each do |f,p| -%>
11
+ <%= p[:md5] %> <%= p[:size].to_s.rjust(16) %> <%= f %>
12
+ <% end -%>
13
+ SHA1:
14
+ <% files.sort.each do |f,p| -%>
15
+ <%= p[:sha1] %> <%= p[:size].to_s.rjust(16) %> <%= f %>
16
+ <% end -%>
17
+ SHA256:
18
+ <% files.sort.each do |f,p| -%>
19
+ <%= p[:sha256] %> <%= p[:size].to_s.rjust(16) %> <%= f %>
20
+ <% end -%>
@@ -0,0 +1,117 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "base64"
3
+ require "digest/md5"
4
+ require "erb"
5
+ require "tmpdir"
6
+
7
+ module Dpkg::S3::Utils
8
+ module_function
9
+ def s3; @s3 end
10
+ def s3= v; @s3 = v end
11
+ def bucket; @bucket end
12
+ def bucket= v; @bucket = v end
13
+ def access_policy; @access_policy end
14
+ def access_policy= v; @access_policy = v end
15
+ def signing_key; @signing_key end
16
+ def signing_key= v; @signing_key = v end
17
+ def gpg_options; @gpg_options end
18
+ def gpg_options= v; @gpg_options = v end
19
+ def prefix; @prefix end
20
+ def prefix= v; @prefix = v end
21
+ def encryption; @encryption end
22
+ def encryption= v; @encryption = v end
23
+
24
+ class SafeSystemError < RuntimeError; end
25
+ class AlreadyExistsError < RuntimeError; end
26
+
27
+ def safesystem(*args)
28
+ success = system(*args)
29
+ if !success
30
+ raise SafeSystemError, "'system(#{args.inspect})' failed with error code: #{$?.exitstatus}"
31
+ end
32
+ return success
33
+ end
34
+
35
+ def debianize_op(op)
36
+ # Operators in debian packaging are <<, <=, =, >= and >>
37
+ # So any operator like < or > must be replaced
38
+ {:< => "<<", :> => ">>"}[op.to_sym] or op
39
+ end
40
+
41
+ def template(path)
42
+ template_file = File.join(File.dirname(__FILE__), "templates", path)
43
+ template_code = File.read(template_file)
44
+ ERB.new(template_code, nil, "-")
45
+ end
46
+
47
+ def s3_path(path)
48
+ File.join(*[Dpkg::S3::Utils.prefix, path].compact)
49
+ end
50
+
51
+ # from fog, Fog::AWS.escape
52
+ def s3_escape(string)
53
+ string.gsub(/([^a-zA-Z0-9_.\-~+]+)/) {
54
+ "%" + $1.unpack("H2" * $1.bytesize).join("%").upcase
55
+ }
56
+ end
57
+
58
+ def s3_exists?(path)
59
+ Dpkg::S3::Utils.s3.head_object(
60
+ :bucket => Dpkg::S3::Utils.bucket,
61
+ :key => s3_path(path),
62
+ )
63
+ rescue Aws::S3::Errors::NotFound
64
+ false
65
+ end
66
+
67
+ def s3_read(path)
68
+ Dpkg::S3::Utils.s3.get_object(
69
+ :bucket => Dpkg::S3::Utils.bucket,
70
+ :key => s3_path(path),
71
+ )[:body].read
72
+ rescue Aws::S3::Errors::NoSuchKey
73
+ false
74
+ end
75
+
76
+ def s3_store(path, filename=nil, content_type='application/octet-stream; charset=binary', cache_control=nil, fail_if_exists=false)
77
+ filename = File.basename(path) unless filename
78
+ obj = s3_exists?(filename)
79
+
80
+ file_md5 = Digest::MD5.file(path)
81
+
82
+ # check if the object already exists
83
+ if obj != false
84
+ return if (file_md5.to_s == obj[:etag].gsub('"', '') or file_md5.to_s == obj[:metadata]['md5'])
85
+ raise AlreadyExistsError, "file #{filename} already exists with different contents" if fail_if_exists
86
+ end
87
+
88
+ options = {
89
+ :bucket => Dpkg::S3::Utils.bucket,
90
+ :key => s3_path(filename),
91
+ :acl => Dpkg::S3::Utils.access_policy,
92
+ :content_type => content_type,
93
+ :metadata => { "md5" => file_md5.to_s },
94
+ }
95
+ if !cache_control.nil?
96
+ options[:cache_control] = cache_control
97
+ end
98
+
99
+ # specify if encryption is required
100
+ options[:server_side_encryption] = "AES256" if Dpkg::S3::Utils.encryption
101
+
102
+ # upload the file
103
+ File.open(path) do |f|
104
+ options[:body] = f
105
+ Dpkg::S3::Utils.s3.put_object(options)
106
+ end
107
+ end
108
+
109
+ def s3_remove(path)
110
+ if s3_exists?(path)
111
+ Dpkg::S3::Utils.s3.delete_object(
112
+ :bucket =>Dpkg::S3::Utils.bucket,
113
+ :key => s3_path(path),
114
+ )
115
+ end
116
+ end
117
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dpkg-s3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gamunu Balagalla
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.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.19.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11'
69
+ description: Easily create and manage an APT repository on S3.
70
+ email: gamunu.balagalla@outlook.com
71
+ executables:
72
+ - dpkg-s3
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - bin/dpkg-s3
78
+ - lib/dpkg/s3.rb
79
+ - lib/dpkg/s3/cli.rb
80
+ - lib/dpkg/s3/lock.rb
81
+ - lib/dpkg/s3/manifest.rb
82
+ - lib/dpkg/s3/package.rb
83
+ - lib/dpkg/s3/release.rb
84
+ - lib/dpkg/s3/templates/package.erb
85
+ - lib/dpkg/s3/templates/release.erb
86
+ - lib/dpkg/s3/utils.rb
87
+ homepage: https://github.com/gamunu/dpkg-s3
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 1.9.3
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Easily create and manage an APT repository on S3.
110
+ test_files: []