smart_proxy_omaha 0.0.1

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +14 -0
  3. data/LICENSE +675 -0
  4. data/README.md +99 -0
  5. data/Rakefile +14 -0
  6. data/bin/smart-proxy-omaha-sync +35 -0
  7. data/bundler.d/omaha.rb +1 -0
  8. data/extra/foreman-proxy-omaha-sync.cron +2 -0
  9. data/lib/smart_proxy_omaha.rb +3 -0
  10. data/lib/smart_proxy_omaha/configuration_loader.rb +17 -0
  11. data/lib/smart_proxy_omaha/dependency_injection.rb +8 -0
  12. data/lib/smart_proxy_omaha/foreman_client.rb +11 -0
  13. data/lib/smart_proxy_omaha/http_download.rb +65 -0
  14. data/lib/smart_proxy_omaha/http_request.rb +20 -0
  15. data/lib/smart_proxy_omaha/http_shared.rb +30 -0
  16. data/lib/smart_proxy_omaha/metadata.rb +34 -0
  17. data/lib/smart_proxy_omaha/metadata_provider.rb +23 -0
  18. data/lib/smart_proxy_omaha/omaha_api.rb +33 -0
  19. data/lib/smart_proxy_omaha/omaha_http_config.ru +9 -0
  20. data/lib/smart_proxy_omaha/omaha_plugin.rb +18 -0
  21. data/lib/smart_proxy_omaha/omaha_protocol.rb +65 -0
  22. data/lib/smart_proxy_omaha/omaha_protocol/eventacknowledgeresponse.rb +9 -0
  23. data/lib/smart_proxy_omaha/omaha_protocol/handler.rb +84 -0
  24. data/lib/smart_proxy_omaha/omaha_protocol/noupdateresponse.rb +9 -0
  25. data/lib/smart_proxy_omaha/omaha_protocol/request.rb +101 -0
  26. data/lib/smart_proxy_omaha/omaha_protocol/response.rb +33 -0
  27. data/lib/smart_proxy_omaha/omaha_protocol/updateresponse.rb +35 -0
  28. data/lib/smart_proxy_omaha/release.rb +121 -0
  29. data/lib/smart_proxy_omaha/release_provider.rb +37 -0
  30. data/lib/smart_proxy_omaha/release_repository.rb +11 -0
  31. data/lib/smart_proxy_omaha/syncer.rb +48 -0
  32. data/lib/smart_proxy_omaha/version.rb +5 -0
  33. data/settings.d/omaha.yml.example +5 -0
  34. data/smart_proxy_omaha.gemspec +23 -0
  35. data/test/fixtures/request_update_complete_error.xml +7 -0
  36. data/test/fixtures/request_update_complete_noupdate.xml +9 -0
  37. data/test/fixtures/request_update_complete_update.xml +9 -0
  38. data/test/fixtures/request_update_download_started.xml +7 -0
  39. data/test/fixtures/response_update_complete_error.xml +5 -0
  40. data/test/fixtures/response_update_complete_noupdate.xml +7 -0
  41. data/test/fixtures/response_update_complete_update.xml +19 -0
  42. data/test/fixtures/stable.html +97 -0
  43. data/test/omaha/http_download_test.rb +34 -0
  44. data/test/omaha/http_request_test.rb +12 -0
  45. data/test/omaha/metadata_provider_test.rb +33 -0
  46. data/test/omaha/omaha_api_test.rb +75 -0
  47. data/test/omaha/omaha_protocol/request_test.rb +77 -0
  48. data/test/omaha/release_provider_test.rb +59 -0
  49. data/test/omaha/release_test.rb +110 -0
  50. data/test/omaha/syncer_test.rb +41 -0
  51. data/test/test_helper.rb +18 -0
  52. metadata +167 -0
@@ -0,0 +1,65 @@
1
+ require 'nokogiri'
2
+ require 'smart_proxy_omaha/release_repository'
3
+ require 'smart_proxy_omaha/metadata'
4
+ require 'smart_proxy_omaha/metadata_provider'
5
+ require 'smart_proxy_omaha/omaha_protocol/response'
6
+ require 'smart_proxy_omaha/omaha_protocol/request'
7
+ require 'smart_proxy_omaha/omaha_protocol/eventacknowledgeresponse'
8
+ require 'smart_proxy_omaha/omaha_protocol/noupdateresponse'
9
+ require 'smart_proxy_omaha/omaha_protocol/updateresponse'
10
+ require 'smart_proxy_omaha/omaha_protocol/handler'
11
+
12
+ module Proxy::Omaha::OmahaProtocol
13
+ COREOS_APPID = 'e96281a6-d1af-4bde-9a0a-97b76e56dc57'
14
+
15
+ EVENT_TYPES = {
16
+ 0 => 'unknown',
17
+ 1 => 'download complete',
18
+ 2 => 'install complete',
19
+ 3 => 'update complete',
20
+ 4 => 'uninstall',
21
+ 5 => 'download started',
22
+ 6 => 'install started',
23
+ 9 => 'new application install started',
24
+ 10 => 'setup started',
25
+ 11 => 'setup finished',
26
+ 12 => 'update application started',
27
+ 13 => 'update download started',
28
+ 14 => 'update download finished',
29
+ 15 => 'update installer started',
30
+ 16 => 'setup update begin',
31
+ 17 => 'setup update complete',
32
+ 20 => 'register product complete',
33
+ 30 => 'OEM install first check',
34
+ 40 => 'app-specific command started',
35
+ 41 => 'app-specific command ended',
36
+ 100 => 'setup failure',
37
+ 102 => 'COM server failure',
38
+ 103 => 'setup update failure',
39
+ 800 => 'ping'
40
+ }.freeze
41
+
42
+ EVENT_RESULTS = {
43
+ 0 => 'error',
44
+ 1 => 'success',
45
+ 2 => 'success reboot',
46
+ 3 => 'success restart browser',
47
+ 4 => 'cancelled',
48
+ 5 => 'error installer MSI',
49
+ 6 => 'error installer other',
50
+ 7 => 'noupdate',
51
+ 8 => 'error installer system',
52
+ 9 => 'update deferred',
53
+ 10 => 'handoff error'
54
+ }.freeze
55
+
56
+ def self.event_description(id)
57
+ id = 0 unless EVENT_TYPES.key?(id)
58
+ EVENT_TYPES[id]
59
+ end
60
+
61
+ def self.event_result(id)
62
+ id = 0 unless EVENT_RESULTS.key?(id)
63
+ EVENT_RESULTS[id]
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Eventacknowledgeresponse < Response
3
+ protected
4
+
5
+ def xml_response(xml)
6
+ # Intentionally left blank
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,84 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Handler
3
+ include ::Proxy::Log
4
+ attr_reader :request, :foreman_client, :repository, :metadata_provider
5
+
6
+ def initialize(options = {})
7
+ @request = options.fetch(:request)
8
+ @foreman_client = options.fetch(:foreman_client)
9
+ @repository = options.fetch(:repository)
10
+ @metadata_provider = options.fetch(:metadata_provider)
11
+ end
12
+
13
+ def handle
14
+ logger.info "OmahaHandler: Received #{request.event_description} event with result: #{request.event_result}"
15
+
16
+ unless request.from_coreos?
17
+ logger.error "Appid does not match CoreOS. Aborting Omaha request."
18
+ return Proxy::Omaha::OmahaProtocol::Eventacknowledgeresponse.new(
19
+ :appid => request.appid,
20
+ :base_url => request.base_url,
21
+ :status => 'error-unknownApplication'
22
+ )
23
+ end
24
+
25
+ unless ['stable', 'beta', 'alpha'].include?(request.track)
26
+ logger.error "Unknown track requested. Aborting Omaha request."
27
+ return Proxy::Omaha::OmahaProtocol::Eventacknowledgeresponse.new(
28
+ :appid => request.appid,
29
+ :base_url => request.base_url,
30
+ :status => 'error-unknownApplication'
31
+ )
32
+ end
33
+
34
+ upload_facts
35
+ process_report
36
+
37
+ if request.updatecheck
38
+ handle_update
39
+ else
40
+ handle_event
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def upload_facts
47
+ foreman_client.post_facts(request.facts_data.to_json)
48
+ end
49
+
50
+ def handle_event
51
+ Proxy::Omaha::OmahaProtocol::Eventacknowledgeresponse.new(
52
+ :appid => request.appid,
53
+ :base_url => request.base_url
54
+ )
55
+ end
56
+
57
+ def process_report
58
+ report = {
59
+ 'host' => request.hostname,
60
+ 'status' => request.to_status.to_s,
61
+ 'omaha_version' => request.version,
62
+ 'reported_at' => Time.now.getutc.to_s
63
+ }
64
+ foreman_client.post_report({'omaha_report' => report}.to_json)
65
+ end
66
+
67
+ def handle_update
68
+ latest_os = repository.latest_os(request.track)
69
+ if !latest_os.nil? && latest_os > Gem::Version.new(request.version)
70
+ Proxy::Omaha::OmahaProtocol::Updateresponse.new(
71
+ :appid => request.appid,
72
+ :metadata => metadata_provider.get(request.track, latest_os),
73
+ :board => request.board,
74
+ :base_url => request.base_url
75
+ )
76
+ else
77
+ Proxy::Omaha::OmahaProtocol::Noupdateresponse.new(
78
+ :appid => request.appid,
79
+ :base_url => request.base_url
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,9 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Noupdateresponse < Response
3
+ protected
4
+
5
+ def xml_response(xml)
6
+ xml.updatecheck(:status => 'noupdate')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,101 @@
1
+ require 'resolv'
2
+ require 'ipaddr'
3
+
4
+ module Proxy::Omaha::OmahaProtocol
5
+ class Request
6
+ attr_reader :appid, :version, :track, :updatecheck, :eventtype, :eventresult, :board,
7
+ :alephversion, :oemversion, :oem,
8
+ :platform, :osmajor, :osminor, :hostname, :ipaddress, :ipaddress6,
9
+ :body, :ip, :base_url
10
+
11
+ def initialize(body, options)
12
+ @body = body
13
+ @ip = options.fetch(:ip)
14
+ @base_url = options.fetch(:base_url)
15
+ parse_request
16
+ parse_ipaddress
17
+ raise "Could not determine request hostname." if hostname.nil?
18
+ end
19
+
20
+ def facts_data
21
+ {
22
+ :name => hostname,
23
+ :facts => to_facts.merge({:_type => :foreman_omaha, :_timestamp => Time.now})
24
+ }
25
+ end
26
+
27
+ def to_status
28
+ return :downloading if eventtype == 13 && eventresult == 1
29
+ return :downloaded if eventtype == 14 && eventresult == 1
30
+ return :installed if eventtype == 3 && eventresult == 1
31
+ return :instance_hold if eventtype == 800 && eventresult == 1
32
+ return :complete if eventtype == 3 && eventresult == 2
33
+ return :error if eventtype == 3 && eventresult == 0
34
+ :unknown
35
+ end
36
+
37
+ def event_description
38
+ Proxy::Omaha::OmahaProtocol.event_description(eventtype)
39
+ end
40
+
41
+ def event_result
42
+ Proxy::Omaha::OmahaProtocol.event_result(eventresult)
43
+ end
44
+
45
+ def from_coreos?
46
+ appid == Proxy::Omaha::OmahaProtocol::COREOS_APPID
47
+ end
48
+
49
+ private
50
+
51
+ def parse_request
52
+ xml_request = Nokogiri::XML(body)
53
+ @appid = xml_request.xpath('/request/app/@appid').to_s.gsub(/^{?([a-f0-9\-]+)}?$/, '\1')
54
+ @version = xml_request.xpath('/request/app/@version').to_s
55
+ @osmajor = version.gsub(/^(\d+)\.\d\.\d$/, '\1')
56
+ @osminor = version.gsub(/^\d+\.(\d\.\d)$/, '\1')
57
+ @track = xml_request.xpath('/request/app/@track').to_s
58
+ @board = xml_request.xpath('/request/app/@board').to_s
59
+ @alephversion = xml_request.xpath('/request/app/@alephversion').to_s
60
+ @oemversion = xml_request.xpath('/request/app/@oemversion').to_s
61
+ @oem = xml_request.xpath('/request/app/@oem').to_s
62
+ @platform = xml_request.xpath('/request/os/@platform').to_s
63
+ @platform = 'CoreOS' if @platform.empty?
64
+ @updatecheck = !xml_request.xpath('/request/app/updatecheck').to_s.empty?
65
+ @eventtype = xml_request.xpath('/request/app/event/@eventtype').to_s.to_i
66
+ @eventresult = xml_request.xpath('/request/app/event/@eventresult').to_s.to_i
67
+ end
68
+
69
+ def parse_ipaddress
70
+ ipaddr = IPAddr.new(ip) rescue nil
71
+ return if ipaddr.nil?
72
+ @ipaddress = ipaddr.to_s if ipaddr.ipv4?
73
+ @ipaddress6 = ipaddr.to_s if ipaddr.ipv6?
74
+ @hostname = lookup_hostname(ipaddr.to_s)
75
+ end
76
+
77
+ def lookup_hostname(hostip)
78
+ Resolv.getname(hostip)
79
+ rescue Resolv::ResolvError
80
+ nil
81
+ end
82
+
83
+ def to_facts
84
+ {
85
+ :appid => appid,
86
+ :version => version,
87
+ :track => track,
88
+ :board => board,
89
+ :alephversion => alephversion,
90
+ :oemversion => oemversion,
91
+ :oem => oem,
92
+ :platform => platform,
93
+ :osmajor => osmajor,
94
+ :osminor => osminor,
95
+ :ipaddress => ipaddress,
96
+ :ipaddress6 => ipaddress6,
97
+ :hostname => hostname
98
+ }
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,33 @@
1
+ require 'uri'
2
+
3
+ module Proxy::Omaha::OmahaProtocol
4
+ class Response
5
+ include ::Proxy::Log
6
+
7
+ attr_reader :appid, :base_url, :host, :status
8
+
9
+ def initialize(options = {})
10
+ @appid = options.fetch(:appid)
11
+ @base_url = options.fetch(:base_url)
12
+ @host = URI.parse(base_url).host
13
+ @status = options.fetch(:status, 'ok')
14
+ end
15
+
16
+ def to_xml
17
+ xml.to_xml
18
+ end
19
+
20
+ protected
21
+
22
+ def xml
23
+ @xml ||= Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
24
+ xml.response(:protocol => '3.0', :server => host) do
25
+ xml.daystart(:elapsed_seconds => 0)
26
+ xml.app(:app_id => appid, :status => status) do
27
+ xml_response(xml)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ module Proxy::Omaha::OmahaProtocol
2
+ class Updateresponse < Response
3
+ attr_reader :metadata, :release, :architecture, :sha1_b64, :name, :size, :sha256_b64, :server, :track
4
+
5
+ def initialize(options = {})
6
+ @metadata = options.fetch(:metadata)
7
+ @architecture = options.fetch(:board)
8
+ @name = 'update.gz'
9
+ @size = metadata.size
10
+ @sha1_b64 = metadata.sha1_b64
11
+ @sha256_b64 = metadata.sha256_b64
12
+ @release = metadata.release
13
+ @track = metadata.track
14
+ super
15
+ end
16
+
17
+ protected
18
+
19
+ def xml_response(xml)
20
+ xml.updatecheck(:status => 'ok') do
21
+ xml.urls do
22
+ xml.url(:codebase => "#{base_url}/omahareleases/#{track}/#{release}/")
23
+ end
24
+ xml.manifest(:version => release) do
25
+ xml.packages do
26
+ xml.package(:hash => sha1_b64, :name => name, :size => size, :required => false)
27
+ end
28
+ xml.actions do
29
+ xml.action(:event => 'postinstall', :sha256 => sha256_b64, :needsadmin => false, :IsDelta => false, :DisablePayloadBackoff => true, :ChromeOSVersion => '')
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,121 @@
1
+ require 'fileutils'
2
+ require 'smart_proxy_omaha/http_download'
3
+ require 'smart_proxy_omaha/metadata_provider'
4
+
5
+ module Proxy::Omaha
6
+ class Release
7
+ include Proxy::Log
8
+
9
+ attr_accessor :track, :version
10
+
11
+ def initialize(options)
12
+ @track = options.fetch(:track).to_s
13
+ @version = Gem::Version.new(options.fetch(:version))
14
+ end
15
+
16
+ def path
17
+ @path ||= File.join(Proxy::Omaha::Plugin.settings.contentpath, track, version.to_s)
18
+ end
19
+
20
+ def metadata
21
+ metadata_provider.get(track, release)
22
+ end
23
+
24
+ def exists?
25
+ File.directory?(path)
26
+ end
27
+
28
+ def valid?
29
+ expected_files_exist?
30
+ end
31
+
32
+ def create
33
+ logger.debug "Creating #{track} #{version}"
34
+ return false unless create_path
35
+ return false unless download
36
+ return false unless create_metadata
37
+ true
38
+ end
39
+
40
+ def <=>(other)
41
+ return unless self.class === other
42
+ version.<=>(other.version)
43
+ end
44
+
45
+ def ==(other)
46
+ self.class === other && track == other.track && version == other.version
47
+ end
48
+
49
+ def to_s
50
+ version.to_s
51
+ end
52
+
53
+ def download
54
+ sources.map do |url|
55
+ file = URI.parse(url).path.split('/').last
56
+ dst = File.join(path, file)
57
+ logger.debug "Downloading file #{url} to #{dst}"
58
+ task = ::Proxy::Omaha::HttpDownload.new(url, dst)
59
+ task.start
60
+ task
61
+ end.each(&:join).map(&:result).all?
62
+ end
63
+
64
+ def create_metadata
65
+ metadata_provider.store(Metadata.new(
66
+ :track => track,
67
+ :release => version.to_s,
68
+ :size => File.size(updatefile),
69
+ :sha1_b64 => Digest::SHA1.file(updatefile).base64digest,
70
+ :sha256_b64 => Digest::SHA256.file(updatefile).base64digest,
71
+ ))
72
+ true
73
+ rescue
74
+ false
75
+ end
76
+
77
+ def create_path
78
+ FileUtils.mkdir_p(path)
79
+ true
80
+ rescue
81
+ false
82
+ end
83
+
84
+ def updatefile
85
+ File.join(path, 'update.gz')
86
+ end
87
+
88
+ def sources
89
+ upstream = "https://#{track}.release.core-os.net/amd64-usr/#{version}"
90
+ [
91
+ "#{upstream}/coreos_production_pxe.vmlinuz",
92
+ "#{upstream}/coreos_production_pxe_image.cpio.gz",
93
+ "https://update.release.core-os.net/amd64-usr/#{version}/update.gz"
94
+ ]
95
+ end
96
+
97
+ def expected_files
98
+ sources.map { |source| File.basename(source) }
99
+ end
100
+
101
+ def expected_files_exist?
102
+ expected_files.map {|file| File.file?(File.join(path, file)) }.all?
103
+ end
104
+
105
+ def purge
106
+ FileUtils.rm(Dir.glob(File.join(path, '*')), :force => true)
107
+ FileUtils.remove_dir(path)
108
+ true
109
+ rescue
110
+ false
111
+ end
112
+
113
+ private
114
+
115
+ def metadata_provider
116
+ MetadataProvider.new(
117
+ :contentpath => Proxy::Omaha::Plugin.settings.contentpath
118
+ )
119
+ end
120
+ end
121
+ end