zypper-upgraderepo 1.0.2 → 1.5.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 +4 -4
- data/.github/FUNDING.yml +13 -0
- data/Gemfile.lock +19 -19
- data/README.md +7 -3
- data/lib/zypper/upgraderepo.rb +72 -24
- data/lib/zypper/upgraderepo/cli.rb +82 -15
- data/lib/zypper/upgraderepo/os_release.rb +5 -2
- data/lib/zypper/upgraderepo/repository.rb +119 -77
- data/lib/zypper/upgraderepo/request.rb +42 -0
- data/lib/zypper/upgraderepo/requests/http.rb +131 -0
- data/lib/zypper/upgraderepo/requests/local.rb +94 -0
- data/lib/zypper/upgraderepo/traversable.rb +93 -0
- data/lib/zypper/upgraderepo/utils.rb +78 -27
- data/lib/zypper/upgraderepo/version.rb +1 -1
- data/lib/zypper/upgraderepo/view.rb +249 -0
- data/zypper-upgraderepo.gemspec +19 -19
- metadata +16 -10
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require_relative 'traversable.rb'
|
3
|
+
require_relative 'requests/local.rb'
|
4
|
+
require_relative 'requests/http.rb'
|
5
|
+
|
6
|
+
module Zypper
|
7
|
+
module Upgraderepo
|
8
|
+
|
9
|
+
|
10
|
+
class Request
|
11
|
+
|
12
|
+
def self.build(repo, timeout)
|
13
|
+
@@registry ||= self.load_requests
|
14
|
+
|
15
|
+
raise InvalidProtocol, repo unless @@registry.include? repo.protocol
|
16
|
+
|
17
|
+
Object.const_get(@@registry[repo.protocol]).new(repo, timeout)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.protocols
|
21
|
+
self.load_requests.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.load_requests
|
27
|
+
res = {}
|
28
|
+
Requests.constants.each do |klass|
|
29
|
+
Object.const_get("Zypper::Upgraderepo::Requests::#{klass}").register_protocol.each do |protocol|
|
30
|
+
res[protocol] = "Zypper::Upgraderepo::Requests::#{klass}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
res
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Zypper
|
5
|
+
module Upgraderepo
|
6
|
+
|
7
|
+
class PageRequest < SimpleDelegator
|
8
|
+
|
9
|
+
attr_reader :page
|
10
|
+
|
11
|
+
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0'
|
12
|
+
|
13
|
+
def initialize(obj, timeout = 60)
|
14
|
+
super obj
|
15
|
+
@timeout = timeout
|
16
|
+
end
|
17
|
+
|
18
|
+
def available?
|
19
|
+
ping.is_a?(Net::HTTPSuccess)
|
20
|
+
end
|
21
|
+
|
22
|
+
def redirected?
|
23
|
+
ping.is_a?(Net::HTTPRedirection)
|
24
|
+
end
|
25
|
+
|
26
|
+
def redirected_to
|
27
|
+
ping['location']
|
28
|
+
end
|
29
|
+
|
30
|
+
def not_found?
|
31
|
+
ping.is_a?(Net::HTTPNotFound)
|
32
|
+
end
|
33
|
+
|
34
|
+
def forbidden?
|
35
|
+
ping.is_a?(Net::HTTPForbidden)
|
36
|
+
end
|
37
|
+
|
38
|
+
def timeout?
|
39
|
+
ping.is_a?(Net::HTTPRequestTimeOut)
|
40
|
+
end
|
41
|
+
|
42
|
+
def status
|
43
|
+
ping.class.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache!
|
47
|
+
@page = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_request(uri, head)
|
54
|
+
|
55
|
+
if head
|
56
|
+
request = Net::HTTP::Head.new(uri.request_uri)
|
57
|
+
else
|
58
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
59
|
+
end
|
60
|
+
|
61
|
+
request['User-Agent'] = USER_AGENT
|
62
|
+
|
63
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
64
|
+
http.use_ssl = (uri.scheme == 'https')
|
65
|
+
http.open_timeout = @timeout
|
66
|
+
|
67
|
+
http.request(request)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ping(uri = nil, head = true)
|
71
|
+
begin
|
72
|
+
if @page.nil? || uri
|
73
|
+
@page = get_request(uri, head)
|
74
|
+
end
|
75
|
+
rescue SocketError
|
76
|
+
raise NoConnection
|
77
|
+
rescue Net::OpenTimeout
|
78
|
+
@page = Net::HTTPRequestTimeOut.new('1.1', '', '')
|
79
|
+
end
|
80
|
+
@page
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
module Requests
|
87
|
+
|
88
|
+
class HttpRequest < PageRequest
|
89
|
+
|
90
|
+
include Traversable
|
91
|
+
|
92
|
+
def max_drop_back; 0; end
|
93
|
+
|
94
|
+
def self.register_protocol; ['https', 'http'] end
|
95
|
+
|
96
|
+
def evaluate_alternative(version)
|
97
|
+
if not_found?
|
98
|
+
return traverse_url(URI(url), version)
|
99
|
+
elsif redirected?
|
100
|
+
return { url: redirected_to, message: 'Redirected to:' }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def get_request(uri, head)
|
108
|
+
#super uri || URI(url), head
|
109
|
+
super uri || repodata_uri, head
|
110
|
+
end
|
111
|
+
|
112
|
+
def has_repodata?(uri)
|
113
|
+
ping(repodata_uri(uri))
|
114
|
+
available?
|
115
|
+
end
|
116
|
+
|
117
|
+
def subfolders
|
118
|
+
res = ping.body.to_s.scan(Regexp.new('href=[\'\"][^\/\"]+\/[\'\"]')).delete_if do |x|
|
119
|
+
x =~ /^\// || x =~ /^\.\./ || x =~ /\:\/\// || x =~ /href=[\"\'](media\.1|boot|EFI)\/[\"\']/
|
120
|
+
end.uniq.map do |d|
|
121
|
+
d.scan(/href=[\"\']([^"]+)[\'\"]/).pop.pop
|
122
|
+
end
|
123
|
+
|
124
|
+
res
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Zypper
|
4
|
+
module Upgraderepo
|
5
|
+
|
6
|
+
|
7
|
+
class DirRequest < SimpleDelegator
|
8
|
+
|
9
|
+
attr_reader :dir_path
|
10
|
+
|
11
|
+
def initialize(obj, timeout)
|
12
|
+
super obj
|
13
|
+
end
|
14
|
+
|
15
|
+
def available?
|
16
|
+
Dir.exist? ping
|
17
|
+
end
|
18
|
+
|
19
|
+
def redirected?
|
20
|
+
File.symlink? ping
|
21
|
+
end
|
22
|
+
|
23
|
+
def redirected_to
|
24
|
+
File.realpath ping
|
25
|
+
end
|
26
|
+
|
27
|
+
def not_found?
|
28
|
+
!available?
|
29
|
+
end
|
30
|
+
|
31
|
+
def forbidden?
|
32
|
+
File.readable? ping
|
33
|
+
end
|
34
|
+
|
35
|
+
def timeout?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
File.stat ping
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache!
|
44
|
+
@dir_path = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def ping(uri = nil, head = true)
|
51
|
+
@dir_path ||= URI(url).path
|
52
|
+
|
53
|
+
@dir_path = uri.to_s =~ /^\// ? uri.to_s : URI(uri.to_s).path if uri
|
54
|
+
|
55
|
+
URI.unescape(@dir_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
module Requests
|
62
|
+
|
63
|
+
class LocalRequest < DirRequest
|
64
|
+
|
65
|
+
include Traversable
|
66
|
+
|
67
|
+
def max_drop_back; 1 end
|
68
|
+
|
69
|
+
def self.register_protocol; ['dir'] end
|
70
|
+
|
71
|
+
def evaluate_alternative(version)
|
72
|
+
if not_found?
|
73
|
+
return traverse_url(URI(url), version)
|
74
|
+
elsif redirected?
|
75
|
+
return { url: redirected_to, message: 'Linked to' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def has_repodata?(uri)
|
83
|
+
File.exist? URI.unescape(repodata_uri(uri).path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def subfolders
|
87
|
+
Dir.glob(ping.gsub(/\/$/, '') + '/*/').map { |x| URI.escape(x.gsub(/\/$/, '').gsub(ping, '').gsub(/^\//, '')) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Zypper
|
2
|
+
module Upgraderepo
|
3
|
+
|
4
|
+
module Traversable
|
5
|
+
|
6
|
+
def traverse_url(uri, version)
|
7
|
+
ping(uri)
|
8
|
+
|
9
|
+
if forbidden?
|
10
|
+
res = { url: url, message: 'Can\'t navigate through the repository!' }
|
11
|
+
elsif available? && uri.to_s =~ /#{version}/
|
12
|
+
res = traverse_url_forward(uri, version)
|
13
|
+
else
|
14
|
+
res = traverse_url_backward(uri, version)
|
15
|
+
end
|
16
|
+
|
17
|
+
res || { url: '', message: 'Can\'t find a valid alternative, try manually!' }
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def traverse_url_backward(uri, version)
|
24
|
+
uri.path = File.dirname(uri.path)
|
25
|
+
|
26
|
+
return nil if uri.path == '/' || uri.path == '.' || (versioned? && (drop_back_level(uri) > max_drop_back))
|
27
|
+
|
28
|
+
uri.path += '/' if uri.path[-1] != '/'
|
29
|
+
ping(uri, false)
|
30
|
+
|
31
|
+
if not_found?
|
32
|
+
return traverse_url_backward(uri, version)
|
33
|
+
elsif available?
|
34
|
+
if res = traverse_url_forward(uri, version)
|
35
|
+
return res
|
36
|
+
else
|
37
|
+
return traverse_url_backward(uri, version)
|
38
|
+
end
|
39
|
+
elsif forbidden?
|
40
|
+
return { url: uri.to_s, message: 'Try to replace with this one' } if has_repodata?(uri)
|
41
|
+
|
42
|
+
return traverse_url_backward(uri, version)
|
43
|
+
end
|
44
|
+
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def traverse_url_forward(uri, version)
|
49
|
+
uri.path += '/' if uri.path[-1] != '/'
|
50
|
+
ping(uri, false)
|
51
|
+
|
52
|
+
subfolders.each do |dir|
|
53
|
+
u = URI(uri.to_s)
|
54
|
+
u.path += dir
|
55
|
+
|
56
|
+
if has_repodata?(u)
|
57
|
+
if (versioned?) && (u.to_s =~ /#{version}/)
|
58
|
+
return { url: u.to_s, message: 'Override with this one' }
|
59
|
+
end
|
60
|
+
else
|
61
|
+
res = traverse_url_forward(u, version)
|
62
|
+
return res if res.class == Hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def repodata_uri(uri = nil)
|
70
|
+
if uri
|
71
|
+
uri = URI(uri.to_s)
|
72
|
+
else
|
73
|
+
uri = URI(url)
|
74
|
+
end
|
75
|
+
|
76
|
+
uri.path = uri.path.gsub(/\/$/, '') + '/repodata/repomd.xml'
|
77
|
+
|
78
|
+
uri
|
79
|
+
end
|
80
|
+
|
81
|
+
def drop_back_level(uri)
|
82
|
+
URI(url).path.split('/').index { |x| x =~ /\d\d.\d/ } - uri.path.split('/').count
|
83
|
+
end
|
84
|
+
|
85
|
+
# to implement on each repository type class
|
86
|
+
#
|
87
|
+
# def has_repodata?(uri)
|
88
|
+
#
|
89
|
+
# def subfolders
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -25,12 +25,19 @@ module Zypper
|
|
25
25
|
def new_line; "\n#{self}" end
|
26
26
|
end
|
27
27
|
|
28
|
+
class ::StandardError
|
29
|
+
def error_code
|
30
|
+
1
|
31
|
+
end
|
32
|
+
end
|
28
33
|
|
29
34
|
class Messages
|
30
35
|
|
31
36
|
def self.error(e)
|
32
37
|
if e.class == String
|
33
38
|
puts ' [E] '.bold.red + e
|
39
|
+
elsif e.class == Interruption
|
40
|
+
STDERR.puts e.message =~ /\(/ ? e.message.gsub(/.*\((.*)\).*/, '\1').green : e.message.green
|
34
41
|
else
|
35
42
|
STDERR.puts 'Error! '.bold.red + e.message
|
36
43
|
end
|
@@ -44,59 +51,89 @@ module Zypper
|
|
44
51
|
puts ' [W] '.bold.yellow + m
|
45
52
|
end
|
46
53
|
|
47
|
-
|
48
|
-
|
54
|
+
end
|
55
|
+
|
56
|
+
class FileNotFound < StandardError
|
57
|
+
def initialize(filename)
|
58
|
+
super "The File #{filename} doesn't exist."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ReleaseFileNotFound < StandardError
|
63
|
+
def initialize
|
64
|
+
super 'The release file is not found.'
|
49
65
|
end
|
66
|
+
end
|
50
67
|
|
51
|
-
|
52
|
-
|
53
|
-
|
68
|
+
class InvalidProtocol < StandardError
|
69
|
+
def initialize(repo)
|
70
|
+
super "The repository #{repo.name} has an unknown protocol: #{repo.protocol}; disable it to continue."
|
54
71
|
end
|
72
|
+
end
|
55
73
|
|
56
|
-
|
57
|
-
|
74
|
+
class InvalidVersion < StandardError
|
75
|
+
def initialize(version)
|
76
|
+
super "The version #{version} is not valid"
|
58
77
|
end
|
78
|
+
end
|
59
79
|
|
60
|
-
|
61
|
-
|
62
|
-
|
80
|
+
class InvalidWritePermissions < StandardError
|
81
|
+
def initialize(filename)
|
82
|
+
super "Don't have the right permission to write #{filename}"
|
63
83
|
end
|
64
84
|
|
65
|
-
def
|
66
|
-
|
85
|
+
def error_code
|
86
|
+
4
|
67
87
|
end
|
88
|
+
end
|
68
89
|
|
69
|
-
|
70
|
-
|
90
|
+
class SystemUpdateRunning < StandardError
|
91
|
+
def initialize(args)
|
92
|
+
super "The application #{args[:process].bold} with pid #{args[:pid].bold} is running a system update!"
|
71
93
|
end
|
72
94
|
|
73
|
-
def
|
74
|
-
|
95
|
+
def error_code
|
96
|
+
5
|
75
97
|
end
|
76
98
|
end
|
77
99
|
|
100
|
+
class UnableToUpgrade < StandardError
|
101
|
+
def initialize(args)
|
102
|
+
super "The repository n.#{args[:num].to_s.bold.red} named #{args[:repo].name.bold.red} can't be upgraded, a manual check is required!"
|
103
|
+
end
|
78
104
|
|
79
|
-
|
80
|
-
|
81
|
-
super 'The release file is not found.'
|
105
|
+
def error_code
|
106
|
+
7
|
82
107
|
end
|
83
108
|
end
|
84
|
-
|
85
|
-
class
|
86
|
-
def initialize(
|
87
|
-
super "The
|
109
|
+
|
110
|
+
class MissingOverride < StandardError
|
111
|
+
def initialize(args)
|
112
|
+
super "The repository n.#{args[:num].to_s.bold.red} named #{args[:ini]['name'].bold.red} doesn't contain the URL key!"
|
113
|
+
end
|
114
|
+
|
115
|
+
def error_code
|
116
|
+
8
|
88
117
|
end
|
89
118
|
end
|
90
119
|
|
91
|
-
class
|
92
|
-
def initialize(
|
93
|
-
super "
|
120
|
+
class UnmatchingOverrides < StandardError
|
121
|
+
def initialize(args)
|
122
|
+
super "The repository n.#{args[:num]} named #{args[:repo].name.bold.red} doesn't match with the repository named #{args[:ini]['name'].bold.red} in the ini file"
|
123
|
+
end
|
124
|
+
|
125
|
+
def error_code
|
126
|
+
9
|
94
127
|
end
|
95
128
|
end
|
96
129
|
|
97
130
|
class AlreadyUpgraded < StandardError
|
98
131
|
def initialize(version)
|
99
|
-
super "The system is already upgraded to #{version}"
|
132
|
+
super "The system is already upgraded to the #{version} version"
|
133
|
+
end
|
134
|
+
|
135
|
+
def error_code
|
136
|
+
2
|
100
137
|
end
|
101
138
|
end
|
102
139
|
|
@@ -104,6 +141,20 @@ module Zypper
|
|
104
141
|
def initialize
|
105
142
|
super 'Internet connection has some trouble'
|
106
143
|
end
|
144
|
+
|
145
|
+
def error_code
|
146
|
+
6
|
147
|
+
end
|
107
148
|
end
|
149
|
+
|
150
|
+
class Interruption < StandardError
|
151
|
+
def initialize
|
152
|
+
super 'Ok ok... Exiting!'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Signal.trap('INT') { raise Interruption }
|
157
|
+
|
158
|
+
Signal.trap('TERM') { raise Interruption }
|
108
159
|
end
|
109
160
|
end
|