smart_proxy_omaha 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3153cfdac54b440908156dbada4b9af261c53cd5
4
- data.tar.gz: f35c54968e7f16b970c8e34434dfef41afa8bebb
3
+ metadata.gz: 4a6708a296e67836993cc8002728a630e3eb1aa3
4
+ data.tar.gz: 2d1a008d27a6500d01cf0f286d2de64c0a87ed47
5
5
  SHA512:
6
- metadata.gz: 126dc938473bd16fb5c3cdf64d4fef7800610f22ac86473d989b654b75126c2ccb1c8bd0f05b28183de756668f156ba21bb298a3dd083bb52d5b199d9e0ebd9f
7
- data.tar.gz: e40cc9ad0d51dcaf14fdc61a2769daa70bf4da74ce8c676208c55c0b6d6c134a91b8d9723ab83345bf49650649d20c9e825351359d043091b706052a20227e9c
6
+ metadata.gz: 1242a70477fb372e90003aae5d9b1efdc0cb081da403e1ef95f03d58cbe447c0b494ca0eff0829d2ad6ac6a6906ff4e599a236f719cdf9caa7777f4440db382b
7
+ data.tar.gz: ccd486cf864f01b2ac4d76c72ea901de33399a03fd6aeb1a0b8fc6cff8125da562b4aa2c48d44f9c47caeb51ac0c17d615a2f33e030f79cd51d3d2eb20930022
data/README.md CHANGED
@@ -28,17 +28,25 @@ Do not forget to register the smart proxy in Foreman via the user interface.
28
28
  ## Host Configuration
29
29
 
30
30
  You need to configure your CoreOS hosts to connect to the Omaha smart-proxy for updates. You can either configure your servers manually or use cloud-config.
31
+ If your smart-proxy uses a self-signed ssl certificate, you have to add the CA certificate to the CoreOS truststore. By default, the smart-proxys uses a PuppetCA certificate. To print the PuppetCA certificate, issue `cat $(puppet config print localcacert)` on any puppet enabled node.
31
32
 
32
33
  ### Using Config File
33
34
 
34
- Edit `/etc/coreos/update.conf`
35
+ To add a custom CA certificate to CoreOS's truststore:
36
+
37
+ ```bash
38
+ vim /etc/ssl/certs/customCA_root.pem
39
+ sudo /usr/sbin/update-ca-certificates
40
+ ```
41
+
42
+ To configure CoreOS to connect to the Omaha smart-proxy for updates, edit `/etc/coreos/update.conf`:
35
43
 
36
44
  ```
37
45
  GROUP=stable
38
46
  SERVER=https://omahaproxy.example.com:8443/omaha/v1/update
39
47
  ```
40
48
 
41
- Restart update engine
49
+ Restart update engine:
42
50
 
43
51
  ```bash
44
52
  sudo systemctl restart update-engine
@@ -46,6 +54,8 @@ sudo systemctl restart update-engine
46
54
 
47
55
  ### Using Cloud-Config
48
56
 
57
+ Configure CoreOS to connect to the Omaha smart-proxy for updates:
58
+
49
59
  ```yaml
50
60
  #cloud-config
51
61
  coreos:
@@ -54,6 +64,35 @@ coreos:
54
64
  server: "https://omahaproxy.example.com:8443/omaha/v1/update"
55
65
  ```
56
66
 
67
+ Add a custom CA certificate to CoreOS's truststore:
68
+ ```yaml
69
+ #cloud-config
70
+ write-files:
71
+ - path: /etc/ssl/certs/customCA_root.pem
72
+ permissions: 0644
73
+ content: |
74
+ -----BEGIN CERTIFICATE-----
75
+ YOUR-BASE64-ENCODED-CERTIFICATE
76
+ -----END CERTIFICATE-----
77
+ units:
78
+ - name: update-ca-certificates.service
79
+ command: start
80
+ content: |
81
+ [Unit]
82
+ Description=Force Update CA bundle at /etc/ssl/certs/ca-certificates.crt
83
+ # Since other services depend on the certificate store run this early
84
+ DefaultDependencies=no
85
+ Wants=systemd-tmpfiles-setup.service clean-ca-certificates.service
86
+ After=systemd-tmpfiles-setup.service clean-ca-certificates.service
87
+ Before=sysinit.target
88
+ ConditionPathIsReadWrite=/etc/ssl/certs
89
+
90
+ [Service]
91
+ Type=oneshot
92
+ ExecStart=/usr/sbin/update-ca-certificates
93
+ ```
94
+
95
+
57
96
  ### Release channels
58
97
 
59
98
  All three default release channels (alpha, beta, stable) are supported. You cannot define custom channels right now.
@@ -15,6 +15,9 @@ module Proxy::LogBuffer
15
15
  end
16
16
  include Proxy::Log
17
17
 
18
+ # Unload all other plugins
19
+ ::Proxy::Plugins.instance.loaded.delete_if { |plugin| plugin[:name] != :omaha }
20
+
18
21
  ::Proxy::PluginInitializer.new(::Proxy::Plugins.instance).initialize_plugins
19
22
 
20
23
  unless ::Proxy::Plugins.instance.plugin_enabled?(:omaha)
@@ -1,16 +1,19 @@
1
1
  require 'thread'
2
+ require 'base64'
2
3
  require 'smart_proxy_omaha/http_shared'
4
+ require 'smart_proxy_omaha/http_verify'
3
5
 
4
6
  module Proxy::Omaha
5
7
  class HttpDownload
6
8
  include Proxy::Log
7
9
  include HttpShared
8
10
 
9
- attr_accessor :dst, :src, :result
11
+ attr_accessor :dst, :src, :tmp, :result, :http_response
10
12
 
11
13
  def initialize(src, dst)
12
14
  @src = src
13
15
  @dst = dst
16
+ @tmp = Tempfile.new('download', File.dirname(dst))
14
17
  end
15
18
 
16
19
  def start
@@ -22,44 +25,71 @@ module Proxy::Omaha
22
25
  end
23
26
 
24
27
  def run
25
- with_filelock do
26
- logger.info "Downloading #{src} to #{dst}."
27
- res = download
28
- logger.info "Finished downloading #{dst}."
29
- res
28
+ logger.info "#{filename}: Downloading #{src} to #{dst}."
29
+ unless download
30
+ logger.error "#{filename} failed to download."
31
+ return false
30
32
  end
33
+ logger.info "#{filename}: Finished downloading #{dst}."
34
+ unless valid?
35
+ logger.error "#{filename} is not valid. Deleting corrupt file."
36
+ File.unlink(tmp)
37
+ return false
38
+ end
39
+ # no DIGESTS file is provided for update.gz
40
+ # so we need to generate our own based on the
41
+ # http headers
42
+ write_digest if filename == 'update.gz'
43
+ finish
44
+ ensure
45
+ tmp.unlink
46
+ true
31
47
  end
32
48
 
33
49
  def join
34
50
  @task.join
35
51
  end
36
52
 
53
+ def valid?
54
+ verifier.valid?
55
+ end
56
+
57
+ def finish
58
+ File.rename(tmp, dst)
59
+ true
60
+ end
61
+
62
+ def write_digest
63
+ hexdigest = Digest.hexencode(Base64.decode64(verifier.local_md5))
64
+ File.open("#{dst}.DIGESTS", 'w') { |file| file.write("#{hexdigest} #{filename}\n") }
65
+ end
66
+
37
67
  private
38
68
 
69
+ def verifier
70
+ @verifier ||= HttpVerify.new(
71
+ :local_file => tmp,
72
+ :http_request => http_response,
73
+ :filename => filename,
74
+ )
75
+ end
76
+
77
+ def filename
78
+ File.basename(dst)
79
+ end
80
+
39
81
  def download
40
82
  http, request = connection_factory(src)
41
83
 
42
- http.request(request) do |response|
43
- open(dst, 'w') do |io|
84
+ self.http_response = http.request(request) do |response|
85
+ open(tmp, 'w') do |io|
44
86
  response.read_body do |chunk|
45
87
  io.write chunk
46
88
  end
47
89
  end
48
90
  end
49
- true
50
- end
51
91
 
52
- def with_filelock
53
- lock = Proxy::FileLock.try_locking(dst)
54
- if lock.nil?
55
- false
56
- else
57
- begin
58
- yield
59
- ensure
60
- Proxy::FileLock.unlock(lock)
61
- end
62
- end
92
+ true
63
93
  end
64
94
  end
65
95
  end
@@ -16,5 +16,17 @@ module Proxy::Omaha
16
16
  response.body
17
17
  end
18
18
  end
19
+
20
+ def head(url)
21
+ http, request = connection_factory(url, :method => :head)
22
+
23
+ Timeout::timeout(10) do
24
+ response = http.request(request)
25
+
26
+ raise "Error retrieving from #{url}: #{response.class}" unless ["200", "201"].include?(response.code)
27
+
28
+ response
29
+ end
30
+ end
19
31
  end
20
32
  end
@@ -4,7 +4,8 @@ require 'uri'
4
4
 
5
5
  module Proxy::Omaha
6
6
  module HttpShared
7
- def connection_factory(url)
7
+ def connection_factory(url, opts = {})
8
+ method = opts.fetch(:method, :get)
8
9
  uri = URI.parse(url)
9
10
 
10
11
  if Proxy::Omaha::Plugin.settings.proxy.to_s.empty?
@@ -22,7 +23,15 @@ module Proxy::Omaha
22
23
  http.use_ssl = true
23
24
  end
24
25
 
25
- request = Net::HTTP::Get.new(uri.request_uri)
26
+ request_class = case method
27
+ when :get
28
+ Net::HTTP::Get
29
+ when :head
30
+ Net::HTTP::Head
31
+ else
32
+ raise "Unknown request class"
33
+ end
34
+ request = request_class.new(uri.request_uri)
26
35
 
27
36
  [http, request]
28
37
  end
@@ -0,0 +1,73 @@
1
+ module Proxy::Omaha
2
+ class HttpVerify
3
+ include Proxy::Log
4
+
5
+ attr_accessor :http_request, :local_file, :filename
6
+
7
+ def initialize(opts = {})
8
+ self.http_request = opts.fetch(:http_request)
9
+ self.local_file = opts.fetch(:local_file)
10
+ self.filename = opts.fetch(:filename, File.basename(local_file))
11
+ end
12
+
13
+ def valid?
14
+ logger.debug "#{filename}: Verifying if file is valid."
15
+ return false unless file_size_valid?
16
+ return false if headers['x-goog-hash'] && !md5_hash_valid?
17
+ true
18
+ end
19
+
20
+ def file_size_valid?
21
+ unless remote_size
22
+ logger.error "#{filename}: Cannot determine remote file size."
23
+ return false
24
+ end
25
+ unless local_size == remote_size
26
+ logger.debug "#{filename}: File sizes do not match. Remote: #{remote_size}, Local: #{local_size}"
27
+ return false
28
+ end
29
+ true
30
+ end
31
+
32
+ def local_size
33
+ File.size(local_file)
34
+ end
35
+
36
+ def remote_size
37
+ @remote_size ||= content_length
38
+ end
39
+
40
+ def md5_hash_valid?
41
+ unless local_md5 == remote_md5
42
+ logger.debug "#{filename}: MD5 checksums do not match. Remote: #{remote_md5}, Local: #{local_md5}"
43
+ return false
44
+ end
45
+ true
46
+ end
47
+
48
+ def remote_hashes
49
+ headers['x-goog-hash'].inject({}) do |hsh, header|
50
+ key, value = header.split('=', 2)
51
+ hsh[key] = value
52
+ hsh
53
+ end
54
+ end
55
+
56
+ def local_md5
57
+ @local_md5 ||= Digest::MD5.file(local_file).base64digest
58
+ end
59
+
60
+ def remote_md5
61
+ remote_hashes['md5']
62
+ end
63
+
64
+ def content_length
65
+ length = headers['content-length'] || headers['x-goog-stored-content-length']
66
+ length.first.to_i if length
67
+ end
68
+
69
+ def headers
70
+ @headers ||= http_request.to_hash
71
+ end
72
+ end
73
+ end
@@ -1,5 +1,6 @@
1
1
  require 'sinatra'
2
2
  require 'smart_proxy_omaha/omaha_protocol'
3
+ require 'smart_proxy_omaha/release_repository'
3
4
 
4
5
  module Proxy::Omaha
5
6
 
@@ -27,7 +28,27 @@ module Proxy::Omaha
27
28
  :metadata_provider => metadata_provider
28
29
  )
29
30
  response = omaha_handler.handle
31
+ status response.http_status
30
32
  response.to_xml
31
33
  end
34
+
35
+ get '/tracks' do
36
+ release_repository.tracks.map do |track|
37
+ {
38
+ :name => track,
39
+ :architectures => release_repository.architectures(track)
40
+ }
41
+ end.to_json
42
+ end
43
+
44
+ get '/tracks/:track/:architecture' do |track, architecture|
45
+ not_found unless release_repository.tracks.include?(track)
46
+ not_found unless release_repository.architectures(track).include?(architecture)
47
+ release_repository.releases(track, architecture).map do |release|
48
+ release.to_h.merge(
49
+ :file_urls => release.file_urls(request.base_url)
50
+ )
51
+ end.to_json
52
+ end
32
53
  end
33
54
  end
@@ -0,0 +1,12 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Errorinternalresponse < Response
3
+
4
+ def http_status
5
+ 500
6
+ end
7
+
8
+ protected
9
+
10
+ def xml_response(xml); end
11
+ end
12
+ end
@@ -1,3 +1,5 @@
1
+ require 'smart_proxy_omaha/track'
2
+
1
3
  module Proxy::Omaha::OmahaProtocol
2
4
  class Handler
3
5
  include ::Proxy::Log
@@ -22,7 +24,7 @@ module Proxy::Omaha::OmahaProtocol
22
24
  )
23
25
  end
24
26
 
25
- unless ['stable', 'beta', 'alpha'].include?(request.track)
27
+ unless Proxy::Omaha::Track.valid?(request.track)
26
28
  logger.error "Unknown track requested. Aborting Omaha request."
27
29
  return Proxy::Omaha::OmahaProtocol::Eventacknowledgeresponse.new(
28
30
  :appid => request.appid,
@@ -32,13 +34,21 @@ module Proxy::Omaha::OmahaProtocol
32
34
  end
33
35
 
34
36
  upload_facts
35
- process_report
37
+ process_report if request.event?
36
38
 
37
- if request.updatecheck
39
+ if request.updatecheck?
38
40
  handle_update
39
- else
41
+ elsif request.event?
40
42
  handle_event
43
+ elsif request.ping?
44
+ handle_ping
45
+ else
46
+ logger.info "OmahaHandler: Unknown request."
47
+ handle_error
41
48
  end
49
+ rescue StandardError => e
50
+ logger.error("OmahaHandler: Aw, Snap! Error: #{e}", e.backtrace)
51
+ handle_error
42
52
  end
43
53
 
44
54
  private
@@ -48,12 +58,29 @@ module Proxy::Omaha::OmahaProtocol
48
58
  end
49
59
 
50
60
  def handle_event
61
+ logger.info "OmahaHandler: Processing event."
51
62
  Proxy::Omaha::OmahaProtocol::Eventacknowledgeresponse.new(
52
63
  :appid => request.appid,
53
64
  :base_url => request.base_url
54
65
  )
55
66
  end
56
67
 
68
+ def handle_ping
69
+ logger.info "OmahaHandler: Processing ping."
70
+ Proxy::Omaha::OmahaProtocol::Pingresponse.new(
71
+ :appid => request.appid,
72
+ :base_url => request.base_url
73
+ )
74
+ end
75
+
76
+ def handle_error
77
+ Proxy::Omaha::OmahaProtocol::Errorinternalresponse.new(
78
+ :appid => request.appid,
79
+ :base_url => request.base_url,
80
+ :status => 'error-internal',
81
+ )
82
+ end
83
+
57
84
  def process_report
58
85
  report = {
59
86
  'host' => request.hostname,
@@ -66,7 +93,8 @@ module Proxy::Omaha::OmahaProtocol
66
93
 
67
94
  def handle_update
68
95
  latest_os = repository.latest_os(request.track, request.board)
69
- if !latest_os.nil? && latest_os > Gem::Version.new(request.version)
96
+ if !latest_os.nil? && latest_os.version > Gem::Version.new(request.version)
97
+ logger.info "OmahaHandler: Offering update from #{request.version} to #{latest_os.version}"
70
98
  Proxy::Omaha::OmahaProtocol::Updateresponse.new(
71
99
  :appid => request.appid,
72
100
  :metadata => metadata_provider.get(request.track, latest_os, request.board),
@@ -74,6 +102,7 @@ module Proxy::Omaha::OmahaProtocol
74
102
  :base_url => request.base_url
75
103
  )
76
104
  else
105
+ logger.info "OmahaHandler: No update."
77
106
  Proxy::Omaha::OmahaProtocol::Noupdateresponse.new(
78
107
  :appid => request.appid,
79
108
  :base_url => request.base_url
@@ -0,0 +1,9 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Pingresponse < Response
3
+ protected
4
+
5
+ def xml_response(xml)
6
+ xml.ping(:status => 'ok')
7
+ end
8
+ end
9
+ end
@@ -6,7 +6,7 @@ module Proxy::Omaha::OmahaProtocol
6
6
  attr_reader :appid, :version, :track, :updatecheck, :eventtype, :eventresult, :board,
7
7
  :alephversion, :oemversion, :oem,
8
8
  :platform, :osmajor, :osminor, :hostname, :ipaddress, :ipaddress6,
9
- :body, :ip, :base_url
9
+ :body, :ip, :base_url, :ping
10
10
 
11
11
  def initialize(body, options)
12
12
  @body = body
@@ -46,6 +46,18 @@ module Proxy::Omaha::OmahaProtocol
46
46
  appid == Proxy::Omaha::OmahaProtocol::COREOS_APPID
47
47
  end
48
48
 
49
+ def updatecheck?
50
+ !@updatecheck.empty?
51
+ end
52
+
53
+ def ping?
54
+ !@ping.empty?
55
+ end
56
+
57
+ def event?
58
+ !@event.empty?
59
+ end
60
+
49
61
  private
50
62
 
51
63
  def parse_request
@@ -61,7 +73,9 @@ module Proxy::Omaha::OmahaProtocol
61
73
  @oem = xml_request.xpath('/request/app/@oem').to_s
62
74
  @platform = xml_request.xpath('/request/os/@platform').to_s
63
75
  @platform = 'CoreOS' if @platform.empty?
64
- @updatecheck = !xml_request.xpath('/request/app/updatecheck').to_s.empty?
76
+ @updatecheck = xml_request.xpath('/request/app/updatecheck').to_s
77
+ @ping = xml_request.xpath('/request/app/ping').to_s
78
+ @event = xml_request.xpath('/request/app/event').to_s
65
79
  @eventtype = xml_request.xpath('/request/app/event/@eventtype').to_s.to_i
66
80
  @eventresult = xml_request.xpath('/request/app/event/@eventresult').to_s.to_i
67
81
  end
@@ -17,6 +17,10 @@ module Proxy::Omaha::OmahaProtocol
17
17
  xml.to_xml
18
18
  end
19
19
 
20
+ def http_status
21
+ 200
22
+ end
23
+
20
24
  protected
21
25
 
22
26
  def xml
@@ -7,6 +7,8 @@ require 'smart_proxy_omaha/omaha_protocol/request'
7
7
  require 'smart_proxy_omaha/omaha_protocol/eventacknowledgeresponse'
8
8
  require 'smart_proxy_omaha/omaha_protocol/noupdateresponse'
9
9
  require 'smart_proxy_omaha/omaha_protocol/updateresponse'
10
+ require 'smart_proxy_omaha/omaha_protocol/pingresponse'
11
+ require 'smart_proxy_omaha/omaha_protocol/errorinternalresponse'
10
12
  require 'smart_proxy_omaha/omaha_protocol/handler'
11
13
 
12
14
  module Proxy::Omaha::OmahaProtocol
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'digest/md5'
2
3
  require 'smart_proxy_omaha/http_download'
3
4
  require 'smart_proxy_omaha/metadata_provider'
4
5
 
@@ -7,6 +8,7 @@ module Proxy::Omaha
7
8
  include Proxy::Log
8
9
 
9
10
  attr_accessor :track, :version, :architecture
11
+ attr_writer :digests
10
12
 
11
13
  def initialize(options)
12
14
  @track = options.fetch(:track).to_s
@@ -14,8 +16,16 @@ module Proxy::Omaha
14
16
  @version = Gem::Version.new(options.fetch(:version))
15
17
  end
16
18
 
19
+ def base_path
20
+ @base_path ||= File.join(Proxy::Omaha::Plugin.settings.contentpath, track, architecture)
21
+ end
22
+
17
23
  def path
18
- @path ||= File.join(Proxy::Omaha::Plugin.settings.contentpath, track, architecture, version.to_s)
24
+ @path ||= File.join(base_path, version.to_s)
25
+ end
26
+
27
+ def current_path
28
+ @current_path ||= File.join(base_path, 'current')
19
29
  end
20
30
 
21
31
  def metadata
@@ -27,7 +37,10 @@ module Proxy::Omaha
27
37
  end
28
38
 
29
39
  def valid?
30
- expected_files_exist?
40
+ existing_files.select { |file| file !~ /\.(DIGESTS|sig)$/ }.map do |file|
41
+ next unless digests[file]
42
+ digests[file].include?(Digest::MD5.file(File.join(path, file)).hexdigest)
43
+ end.compact.all?
31
44
  end
32
45
 
33
46
  def create
@@ -38,6 +51,17 @@ module Proxy::Omaha
38
51
  true
39
52
  end
40
53
 
54
+ def current?
55
+ return false unless File.symlink?(current_path)
56
+ File.readlink(current_path) == path
57
+ end
58
+
59
+ def mark_as_current!
60
+ return true if current?
61
+ File.unlink(current_path) if File.symlink?(current_path)
62
+ FileUtils.ln_s(path, current_path)
63
+ end
64
+
41
65
  def <=>(other)
42
66
  return unless self.class === other
43
67
  version.<=>(other.version)
@@ -54,12 +78,13 @@ module Proxy::Omaha
54
78
  def download
55
79
  sources.map do |url|
56
80
  file = URI.parse(url).path.split('/').last
81
+ next if file_exists?(file)
57
82
  dst = File.join(path, file)
58
83
  logger.debug "Downloading file #{url} to #{dst}"
59
84
  task = ::Proxy::Omaha::HttpDownload.new(url, dst)
60
85
  task.start
61
86
  task
62
- end.each(&:join).map(&:result).all?
87
+ end.compact.each(&:join).map(&:result).all?
63
88
  end
64
89
 
65
90
  def create_metadata
@@ -91,9 +116,17 @@ module Proxy::Omaha
91
116
  upstream = "https://#{track}.release.core-os.net/#{architecture}/#{version}"
92
117
  [
93
118
  "#{upstream}/coreos_production_pxe.vmlinuz",
119
+ "#{upstream}/coreos_production_pxe.DIGESTS",
94
120
  "#{upstream}/coreos_production_image.bin.bz2",
95
121
  "#{upstream}/coreos_production_image.bin.bz2.sig",
122
+ "#{upstream}/coreos_production_image.bin.bz2.DIGESTS",
96
123
  "#{upstream}/coreos_production_pxe_image.cpio.gz",
124
+ "#{upstream}/coreos_production_pxe_image.cpio.gz.DIGESTS",
125
+ "#{upstream}/coreos_production_vmware_raw_image.bin.bz2",
126
+ "#{upstream}/coreos_production_vmware_raw_image.bin.bz2.sig",
127
+ "#{upstream}/coreos_production_vmware_raw_image.bin.bz2.DIGESTS",
128
+ "#{upstream}/version.txt",
129
+ "#{upstream}/version.txt.DIGESTS",
97
130
  "https://update.release.core-os.net/#{architecture}/#{version}/update.gz"
98
131
  ]
99
132
  end
@@ -102,8 +135,24 @@ module Proxy::Omaha
102
135
  sources.map { |source| File.basename(source) }
103
136
  end
104
137
 
105
- def expected_files_exist?
106
- expected_files.map {|file| File.file?(File.join(path, file)) }.all?
138
+ def existing_files
139
+ Dir.glob(File.join(path, '*')).map { |file| File.basename(file) }
140
+ end
141
+
142
+ def missing_files
143
+ expected_files - existing_files
144
+ end
145
+
146
+ def digest_files
147
+ Dir.glob(File.join(path, '*.DIGESTS')).map { |file| File.basename(file) }
148
+ end
149
+
150
+ def complete?
151
+ missing_files.empty?
152
+ end
153
+
154
+ def file_exists?(file)
155
+ File.file?(File.join(path, file))
107
156
  end
108
157
 
109
158
  def purge
@@ -114,6 +163,45 @@ module Proxy::Omaha
114
163
  false
115
164
  end
116
165
 
166
+ def digests
167
+ @digests ||= load_digests!
168
+ end
169
+
170
+ def load_digests!
171
+ self.digests = {}
172
+ digest_files.each do |digest_file|
173
+ file = File.basename(digest_file, '.DIGESTS')
174
+ File.readlines(File.join(path, digest_file)).each do |line|
175
+ next unless line =~ /^\w+[ ]+\S+$/
176
+ hexdigest = line.split(/[ ]+/).first
177
+ self.digests[file] ||= []
178
+ self.digests[file] << hexdigest
179
+ end
180
+ end
181
+ self.digests
182
+ end
183
+
184
+ def to_h
185
+ {
186
+ :name => to_s,
187
+ :current => current?,
188
+ :architecture => architecture,
189
+ :track => track,
190
+ :complete => complete?,
191
+ :files => existing_files
192
+ }
193
+ end
194
+
195
+ def to_json
196
+ to_h.to_json
197
+ end
198
+
199
+ def file_urls(base_url)
200
+ existing_files.map do |file|
201
+ [base_url, 'omahareleases', track, architecture, self, file].join('/')
202
+ end
203
+ end
204
+
117
205
  private
118
206
 
119
207
  def metadata_provider
@@ -1,7 +1,25 @@
1
+ require 'smart_proxy_omaha/release'
2
+
1
3
  module Proxy::Omaha
2
4
  class ReleaseRepository
3
5
  def releases(track, architecture)
4
- Dir.glob(File.join(Proxy::Omaha::Plugin.settings.contentpath, track, architecture, '*')).select {|f| File.directory? f }.map { |f| Gem::Version.new(File.basename(f)) }
6
+ Dir.glob(File.join(Proxy::Omaha::Plugin.settings.contentpath, track, architecture, '*')).select do |f|
7
+ File.directory?(f) && ! File.symlink?(f)
8
+ end.map do |f|
9
+ Proxy::Omaha::Release.new(
10
+ :track => track,
11
+ :architecture => architecture,
12
+ :version => File.basename(f)
13
+ )
14
+ end
15
+ end
16
+
17
+ def tracks
18
+ Dir.glob(File.join(Proxy::Omaha::Plugin.settings.contentpath, '*')).select {|f| File.directory? f }.map { |f| File.basename(f) }
19
+ end
20
+
21
+ def architectures(track)
22
+ Dir.glob(File.join(Proxy::Omaha::Plugin.settings.contentpath, track, '*')).select {|f| File.directory? f }.map { |f| File.basename(f) }
5
23
  end
6
24
 
7
25
  def latest_os(track, architecture)
@@ -1,4 +1,5 @@
1
1
  require 'smart_proxy_omaha/release'
2
+ require 'smart_proxy_omaha/track'
2
3
  require 'smart_proxy_omaha/release_provider'
3
4
 
4
5
  module Proxy::Omaha
@@ -11,25 +12,32 @@ module Proxy::Omaha
11
12
  return
12
13
  end
13
14
 
14
- ['alpha', 'beta', 'stable'].each do |track|
15
+ Proxy::Omaha::Track.all.each do |track|
15
16
  logger.debug "Syncing track: #{track}..."
16
- sync_track(track)
17
+ releases = release_provider(track).releases
18
+ releases.last(sync_count).each do |release|
19
+ sync_release(track, release)
20
+ end
21
+ update_current_release(track, releases.last) if releases.any?
17
22
  end
18
23
  end
19
24
 
20
- def sync_track(track)
21
- release_provider(track).releases.last(sync_count).each do |release|
22
- if release.exists?
23
- if release.valid?
24
- logger.info "#{track} release #{release} already exists and is valid."
25
- next
26
- else
27
- logger.info "#{track} release #{release} is invalid. Purging."
28
- release.purge
29
- end
25
+ def sync_release(track, release)
26
+ if release.exists?
27
+ if !release.valid?
28
+ logger.info "#{track} release #{release} is invalid. Purging."
29
+ release.purge
30
+ elsif release.complete?
31
+ logger.info "#{track} release #{release} exists, is complete and valid. Skipping sync."
32
+ return
30
33
  end
31
- release.create
32
34
  end
35
+ release.create
36
+ end
37
+
38
+ def update_current_release(track, release)
39
+ logger.debug "#{track}: Updating current release to #{release}"
40
+ release.mark_as_current!
33
41
  end
34
42
 
35
43
  private
@@ -0,0 +1,15 @@
1
+ module Proxy
2
+ module Omaha
3
+ module Track
4
+ TRACKS = ['alpha', 'beta', 'stable'].freeze
5
+
6
+ def self.all
7
+ TRACKS
8
+ end
9
+
10
+ def self.valid?(track)
11
+ all.include?(track)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module Omaha
3
- VERSION = '0.0.2'
3
+ VERSION = '0.0.3'
4
4
  end
5
5
  end
@@ -0,0 +1,6 @@
1
+ <request protocol="3.0" version="update_engine-0.4.2" updaterversion="update_engine-0.4.2" installsource="scheduler" ismachine="1">
2
+ <os version="Chateau" platform="CoreOS" sp="1409.7.0_x86_64"></os>
3
+ <app appid="{e96281a6-d1af-4bde-9a0a-97b76e56dc57}" version="1409.7.0" track="stable" bootid="{9f849398-6c5d-4ad2-af52-07fd168ff548}" oem="vmware" oemversion="10.1.5" alephversion="1409.7.0" machineid="904694c1f8e148f0a77ef0884711da1a" lang="en-US" board="amd64-usr" hardware_class="" delta_okay="false">
4
+ <ping active="1"></ping>
5
+ </app>
6
+ </request>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <response protocol="3.0" server="example.org">
3
+ <daystart elapsed_seconds="0"/>
4
+ <app app_id="e96281a6-d1af-4bde-9a0a-97b76e56dc57" status="error-internal" />
5
+ </response>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <response protocol="3.0" server="example.org">
3
+ <daystart elapsed_seconds="0"/>
4
+ <app app_id="e96281a6-d1af-4bde-9a0a-97b76e56dc57" status="ok">
5
+ <ping status="ok"/>
6
+ </app>
7
+ </response>
@@ -16,7 +16,14 @@ class HttpDownloadTest < Test::Unit::TestCase
16
16
  end
17
17
 
18
18
  def test_downloads_file
19
- stub_request(:get, @source_url).to_return(:status => [200, 'OK'], :body => "body")
19
+ stub_request(:get, @source_url).to_return(
20
+ :status => [200, 'OK'],
21
+ :body => 'body',
22
+ headers: {
23
+ 'Content-Length' => 4,
24
+ 'x-goog-hash' => 'md5=hBotaJrYa9FhFEdFPCLG/A=='
25
+ }
26
+ )
20
27
 
21
28
  http_download = ::Proxy::Omaha::HttpDownload.new(@source_url, @destination_path)
22
29
  task = http_download.start
@@ -24,11 +31,4 @@ class HttpDownloadTest < Test::Unit::TestCase
24
31
  assert_equal 'body', File.read(@destination_path)
25
32
  assert_equal true, http_download.result
26
33
  end
27
-
28
- def test_should_skip_download_if_one_is_in_progress
29
- locked = Proxy::FileLock.try_locking(@destination_path)
30
- http_download = ::Proxy::Omaha::HttpDownload.new(@source_url, locked.path)
31
- http_download.start.join
32
- assert_equal false, http_download.result
33
- end
34
34
  end
@@ -11,7 +11,21 @@ end
11
11
 
12
12
  class TestReleaseRepository
13
13
  def releases(track, architecture)
14
- ['1068.9.0', '1122.2.0'].map { |release| Gem::Version.new(release) }
14
+ ['1068.9.0', '1122.2.0'].map do |release|
15
+ Proxy::Omaha::Release.new(
16
+ :track => 'alpha',
17
+ :architecture => 'amd64-usr',
18
+ :version => release
19
+ )
20
+ end
21
+ end
22
+
23
+ def tracks
24
+ ['alpha', 'beta', 'stable']
25
+ end
26
+
27
+ def architectures(track)
28
+ ['amd64-usr']
15
29
  end
16
30
 
17
31
  def latest_os(track, architecture)
@@ -73,4 +87,33 @@ class OmahaApiTest < Test::Unit::TestCase
73
87
  assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
74
88
  assert_xml_equal xml_fixture('response_update_complete_error'), last_response.body
75
89
  end
90
+
91
+ def test_processes_ping
92
+ post "/v1/update", xml_fixture('request_ping')
93
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
94
+ assert_xml_equal xml_fixture('response_ping'), last_response.body
95
+ end
96
+
97
+ def test_processes_internalerror
98
+ TestForemanClient.any_instance.stubs(:post_facts).raises(StandardError.new('Test Error'))
99
+ post "/v1/update", xml_fixture('request_update_complete_update')
100
+ refute last_response.ok?
101
+ assert_xml_equal xml_fixture('response_errorinternal'), last_response.body
102
+ end
103
+
104
+ def test_get_tracks
105
+ get "/tracks"
106
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
107
+ parsed = JSON.parse(last_response.body)
108
+ assert_kind_of Array, parsed
109
+ assert_equal ['alpha', 'beta', 'stable'], parsed.map { |track| track['name'] }
110
+ end
111
+
112
+ def test_get_releases
113
+ get "/tracks/alpha/amd64-usr"
114
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
115
+ parsed = JSON.parse(last_response.body)
116
+ assert_kind_of Array, parsed
117
+ assert_equal ['1068.9.0', '1122.2.0'], parsed.map { |track| track['name'] }
118
+ end
76
119
  end
@@ -25,6 +25,10 @@ class RequestTest < Test::Unit::TestCase
25
25
  assert_equal 3, @request.eventtype
26
26
  assert_equal 2, @request.eventresult
27
27
  assert @request.updatecheck
28
+ assert @request.ping
29
+ assert_equal true, @request.ping?
30
+ assert_equal true, @request.event?
31
+ assert_equal true, @request.updatecheck?
28
32
  end
29
33
 
30
34
  def test_from_coreos
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+ require 'smart_proxy_omaha/release_repository'
4
+
5
+ class ReleaseRepositoryTest < Test::Unit::TestCase
6
+ def setup
7
+ @contentpath = Dir.mktmpdir
8
+ Proxy::Omaha::Plugin.load_test_settings(
9
+ {
10
+ :contentpath => @contentpath
11
+ }
12
+ )
13
+ @repository = Proxy::Omaha::ReleaseRepository.new
14
+ end
15
+
16
+ def teardown
17
+ FileUtils.rm_rf(@contentpath)
18
+ end
19
+
20
+ def test_releases
21
+ test_releases = [
22
+ '197.0.0', '206.0.0', '206.1.0', '225.1.0', '225.2.0', '226.0.0'
23
+ ]
24
+ test_releases.each do |test_release|
25
+ FileUtils.mkdir_p(release_path(test_release))
26
+ end
27
+
28
+ FileUtils.ln_s(release_path('current'), release_path('226.0.0'))
29
+
30
+ releases = @repository.releases('alpha', 'amd64-usr')
31
+ assert_equal test_releases.sort, releases.map(&:to_s).sort
32
+ assert_equal '226.0.0', @repository.latest_os('alpha', 'amd64-usr').to_s
33
+ end
34
+
35
+ private
36
+
37
+ def release_path(version)
38
+ File.join(@contentpath, 'alpha', 'amd64-usr', version)
39
+ end
40
+ end
@@ -23,8 +23,10 @@ class ReleaseTest < Test::Unit::TestCase
23
23
  FileUtils.rm_rf(@contentpath)
24
24
  end
25
25
 
26
- def test_path
26
+ def test_paths
27
+ assert_equal "#{@contentpath}/stable/amd64-usr", @release.base_path
27
28
  assert_equal "#{@contentpath}/stable/amd64-usr/1068.9.0", @release.path
29
+ assert_equal "#{@contentpath}/stable/amd64-usr/current", @release.current_path
28
30
  end
29
31
 
30
32
  def test_exists?
@@ -84,21 +86,22 @@ class ReleaseTest < Test::Unit::TestCase
84
86
  end
85
87
 
86
88
  def test_download
87
- stub_request(:get, /.*release\.core-os.*/).to_return(:status => [200, 'OK'], :body => "body")
88
- expected = [
89
- 'coreos_production_pxe.vmlinuz',
90
- 'coreos_production_pxe_image.cpio.gz',
91
- 'coreos_production_image.bin.bz2',
92
- 'coreos_production_image.bin.bz2.sig',
93
- 'update.gz'
94
- ]
89
+ stub_request(:get, /.*release\.core-os.*/).to_return(
90
+ :status => [200, 'OK'],
91
+ :body => 'body',
92
+ headers: {
93
+ 'Content-Length' => 4,
94
+ 'x-goog-hash' => 'md5=hBotaJrYa9FhFEdFPCLG/A=='
95
+ }
96
+ )
95
97
 
96
98
  assert_equal true, @release.create_path
97
99
  assert_equal true, @release.download
98
100
 
99
101
  existing_files = Dir.entries(@release.path) - ['.', '..']
100
102
 
101
- assert_equal expected.sort, existing_files.sort
103
+ assert_equal (expected_release_files + ['update.gz.DIGESTS']).sort, existing_files.sort
104
+ assert_equal "841a2d689ad86bd1611447453c22c6fc update.gz\n", File.read(File.join(@release.path, 'update.gz.DIGESTS'))
102
105
  end
103
106
 
104
107
  def test_create_metadata
@@ -117,4 +120,86 @@ class ReleaseTest < Test::Unit::TestCase
117
120
  assert File.exist?(file)
118
121
  assert_equal JSON.parse(expected), JSON.parse(File.read(file))
119
122
  end
123
+
124
+ def test_missing_existing_files
125
+ FileUtils.mkdir_p(@release.path)
126
+ refute @release.complete?
127
+ assert_empty @release.existing_files
128
+ assert_equal expected_release_files.sort, @release.missing_files.sort
129
+
130
+ expected_release_files.each do |file|
131
+ FileUtils.touch(File.join(@release.path, file))
132
+ end
133
+
134
+ assert @release.complete?
135
+ assert_empty @release.missing_files
136
+ assert_equal expected_release_files.sort, @release.existing_files.sort
137
+ end
138
+
139
+ def test_current_release_idempotent
140
+ FileUtils.mkdir_p(@release.path)
141
+ refute @release.current?
142
+ @release.mark_as_current!
143
+ assert @release.current?
144
+ symlinks = Dir.glob("#{@contentpath}/**/*").select { |f| File.symlink?(f) }
145
+ assert_equal 1, symlinks.count
146
+ @release.mark_as_current!
147
+ assert @release.current?
148
+ assert_equal symlinks, Dir.glob("#{@contentpath}/**/*").select { |f| File.symlink?(f) }
149
+ assert_equal @release.path, File.readlink(@release.current_path)
150
+ end
151
+
152
+ def test_current_release_update
153
+ FileUtils.mkdir_p(@release.path)
154
+ older = Proxy::Omaha::Release.new(
155
+ :track => :stable,
156
+ :architecture => 'amd64-usr',
157
+ :version => '100.0.0'
158
+ )
159
+ FileUtils.mkdir_p(older.path)
160
+ older.mark_as_current!
161
+ assert older.current?
162
+ refute @release.current?
163
+ @release.mark_as_current!
164
+ assert @release.current?
165
+ refute older.current?
166
+ end
167
+
168
+ def test_digests_valid
169
+ FileUtils.mkdir_p(@release.path)
170
+ File.open(File.join(@release.path, "update.gz"), 'w') { |file| file.write('body') }
171
+ File.open(File.join(@release.path, "update.gz.DIGESTS"), 'w') { |file| file.write("841a2d689ad86bd1611447453c22c6fc update.gz\n") }
172
+ expected = {
173
+ 'update.gz' => ['841a2d689ad86bd1611447453c22c6fc']
174
+ }
175
+ assert_equal expected, @release.digests
176
+ assert_equal true, @release.valid?
177
+ end
178
+
179
+ def test_digests_invalid
180
+ FileUtils.mkdir_p(@release.path)
181
+ File.open(File.join(@release.path, "update.gz"), 'w') { |file| file.write('invalid') }
182
+ File.open(File.join(@release.path, "update.gz.DIGESTS"), 'w') { |file| file.write("841a2d689ad86bd1611447453c22c6fc update.gz\n") }
183
+ assert_equal false, @release.valid?
184
+ end
185
+
186
+ private
187
+
188
+ def expected_release_files
189
+ [
190
+ 'coreos_production_pxe.vmlinuz',
191
+ 'coreos_production_pxe.DIGESTS',
192
+ 'coreos_production_pxe_image.cpio.gz',
193
+ 'coreos_production_pxe_image.cpio.gz.DIGESTS',
194
+ 'coreos_production_image.bin.bz2',
195
+ 'coreos_production_image.bin.bz2.sig',
196
+ 'coreos_production_image.bin.bz2.DIGESTS',
197
+ 'coreos_production_vmware_raw_image.bin.bz2',
198
+ 'coreos_production_vmware_raw_image.bin.bz2.sig',
199
+ 'coreos_production_vmware_raw_image.bin.bz2.DIGESTS',
200
+ 'update.gz',
201
+ 'version.txt',
202
+ 'version.txt.DIGESTS'
203
+ ]
204
+ end
120
205
  end
@@ -12,7 +12,12 @@ class SyncerTest < Test::Unit::TestCase
12
12
  true
13
13
  end
14
14
 
15
+ def complete?
16
+ true
17
+ end
18
+
15
19
  def create; end
20
+ def mark_as_current!; end
16
21
  end
17
22
 
18
23
  class FakeReleaseProvider
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+ require 'smart_proxy_omaha/track'
3
+
4
+ class TrackTest < Test::Unit::TestCase
5
+
6
+ def test_valid
7
+ assert_equal true, Proxy::Omaha::Track.valid?('alpha')
8
+ end
9
+
10
+ def test_invalid
11
+ assert_equal false, Proxy::Omaha::Track.valid?('bär')
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_omaha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timo Goebel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-24 00:00:00.000000000 Z
11
+ date: 2017-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -131,15 +131,18 @@ files:
131
131
  - lib/smart_proxy_omaha/http_download.rb
132
132
  - lib/smart_proxy_omaha/http_request.rb
133
133
  - lib/smart_proxy_omaha/http_shared.rb
134
+ - lib/smart_proxy_omaha/http_verify.rb
134
135
  - lib/smart_proxy_omaha/metadata.rb
135
136
  - lib/smart_proxy_omaha/metadata_provider.rb
136
137
  - lib/smart_proxy_omaha/omaha_api.rb
137
138
  - lib/smart_proxy_omaha/omaha_http_config.ru
138
139
  - lib/smart_proxy_omaha/omaha_plugin.rb
139
140
  - lib/smart_proxy_omaha/omaha_protocol.rb
141
+ - lib/smart_proxy_omaha/omaha_protocol/errorinternalresponse.rb
140
142
  - lib/smart_proxy_omaha/omaha_protocol/eventacknowledgeresponse.rb
141
143
  - lib/smart_proxy_omaha/omaha_protocol/handler.rb
142
144
  - lib/smart_proxy_omaha/omaha_protocol/noupdateresponse.rb
145
+ - lib/smart_proxy_omaha/omaha_protocol/pingresponse.rb
143
146
  - lib/smart_proxy_omaha/omaha_protocol/request.rb
144
147
  - lib/smart_proxy_omaha/omaha_protocol/response.rb
145
148
  - lib/smart_proxy_omaha/omaha_protocol/updateresponse.rb
@@ -147,13 +150,17 @@ files:
147
150
  - lib/smart_proxy_omaha/release_provider.rb
148
151
  - lib/smart_proxy_omaha/release_repository.rb
149
152
  - lib/smart_proxy_omaha/syncer.rb
153
+ - lib/smart_proxy_omaha/track.rb
150
154
  - lib/smart_proxy_omaha/version.rb
151
155
  - settings.d/omaha.yml.example
152
156
  - smart_proxy_omaha.gemspec
157
+ - test/fixtures/request_ping.xml
153
158
  - test/fixtures/request_update_complete_error.xml
154
159
  - test/fixtures/request_update_complete_noupdate.xml
155
160
  - test/fixtures/request_update_complete_update.xml
156
161
  - test/fixtures/request_update_download_started.xml
162
+ - test/fixtures/response_errorinternal.xml
163
+ - test/fixtures/response_ping.xml
157
164
  - test/fixtures/response_update_complete_error.xml
158
165
  - test/fixtures/response_update_complete_noupdate.xml
159
166
  - test/fixtures/response_update_complete_update.xml
@@ -164,8 +171,10 @@ files:
164
171
  - test/omaha/omaha_api_test.rb
165
172
  - test/omaha/omaha_protocol/request_test.rb
166
173
  - test/omaha/release_provider_test.rb
174
+ - test/omaha/release_repository_test.rb
167
175
  - test/omaha/release_test.rb
168
176
  - test/omaha/syncer_test.rb
177
+ - test/omaha/track_test.rb
169
178
  - test/test_helper.rb
170
179
  homepage: http://github.com/theforeman/smart_proxy_omaha
171
180
  licenses: