foreman_omaha 0.0.3 → 1.0.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.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +5 -2
  3. data/app/assets/javascripts/foreman_omaha/application.js +1 -0
  4. data/app/assets/stylesheets/foreman_omaha/version_breakdown.scss +13 -0
  5. data/app/controllers/api/v2/omaha_groups_controller.rb +29 -0
  6. data/app/controllers/api/v2/omaha_reports_controller.rb +9 -7
  7. data/app/controllers/omaha_groups_controller.rb +28 -0
  8. data/app/controllers/omaha_hosts_controller.rb +42 -0
  9. data/app/controllers/omaha_reports_controller.rb +0 -11
  10. data/app/helpers/concerns/foreman_omaha/hosts_helper_extensions.rb +15 -6
  11. data/app/helpers/foreman_omaha/application_helper.rb +7 -0
  12. data/app/helpers/foreman_omaha/omaha_groups_helper.rb +52 -0
  13. data/app/helpers/omaha_hosts_helper.rb +43 -0
  14. data/app/models/concerns/foreman_omaha/host_extensions.rb +16 -2
  15. data/app/models/concerns/foreman_omaha/omaha_facet_host_extensions.rb +23 -0
  16. data/app/models/foreman_omaha/omaha_facet.rb +40 -0
  17. data/app/models/foreman_omaha/omaha_group.rb +34 -0
  18. data/app/models/foreman_omaha/omaha_report.rb +13 -21
  19. data/app/models/host_status/omaha_status.rb +6 -16
  20. data/app/services/foreman_omaha/charts/oem_distribution.rb +24 -0
  21. data/app/services/foreman_omaha/charts/status_distribution.rb +57 -0
  22. data/app/services/foreman_omaha/charts/version_distribution.rb +24 -0
  23. data/app/services/foreman_omaha/container_linux_config_transpiler.rb +33 -0
  24. data/app/services/foreman_omaha/fact_parser.rb +19 -3
  25. data/app/services/foreman_omaha/group_version_breakdown.rb +30 -0
  26. data/app/services/foreman_omaha/omaha_report_importer.rb +52 -3
  27. data/app/services/foreman_omaha/renderer_methods.rb +7 -0
  28. data/app/services/foreman_omaha/status_mapper.rb +53 -0
  29. data/app/views/api/v2/omaha_groups/base.json.rabl +3 -0
  30. data/app/views/api/v2/omaha_groups/index.json.rabl +3 -0
  31. data/app/views/api/v2/omaha_groups/main.json.rabl +5 -0
  32. data/app/views/api/v2/omaha_groups/show.json.rabl +3 -0
  33. data/app/views/application/foreman_omaha/_toolbar.html.erb +11 -0
  34. data/app/views/foreman_omaha/api/v2/omaha_facets/base.json.rabl +5 -0
  35. data/app/views/foreman_omaha/api/v2/omaha_facets/base_with_root.json.rabl +3 -0
  36. data/app/views/foreman_omaha/api/v2/omaha_facets/show.json.rabl +3 -0
  37. data/app/views/hosts/_omaha_tab.html.erb +29 -0
  38. data/app/views/omaha_groups/index.html.erb +34 -0
  39. data/app/views/omaha_groups/show.html.erb +196 -0
  40. data/app/views/omaha_hosts/index.html.erb +51 -0
  41. data/app/views/omaha_hosts/welcome.html.erb +12 -0
  42. data/app/views/omaha_reports/_details.html.erb +5 -3
  43. data/app/views/omaha_reports/_list.html.erb +2 -2
  44. data/app/views/omaha_reports/show.html.erb +1 -1
  45. data/app/views/omaha_reports/welcome.html.erb +1 -1
  46. data/config/routes.rb +14 -1
  47. data/db/migrate/20160812083100_add_omaha_fields_to_reports.foreman_omaha.rb +1 -1
  48. data/db/migrate/20171101204100_create_omaha_facets.foreman_omaha.rb +22 -0
  49. data/db/seeds.d/200_omaha_groups.rb +6 -0
  50. data/lib/foreman_omaha/engine.rb +74 -21
  51. data/lib/foreman_omaha/version.rb +1 -1
  52. data/lib/tasks/foreman_omaha_tasks.rake +2 -4
  53. data/test/factories/feature.rb +1 -1
  54. data/test/factories/foreman_omaha_factories.rb +16 -1
  55. data/test/factories/host.rb +21 -0
  56. data/test/factories/smart_proxy.rb +1 -1
  57. data/test/functional/api/v2/omaha_groups_controller_test.rb +32 -0
  58. data/test/functional/api/v2/omaha_reports_controller_test.rb +24 -24
  59. data/test/functional/omaha_groups_controller_test.rb +18 -0
  60. data/test/functional/omaha_hosts_controller_test.rb +11 -0
  61. data/test/functional/omaha_reports_controller_test.rb +13 -13
  62. data/test/test_plugin_helper.rb +3 -3
  63. data/test/unit/charts/oem_distribution_test.rb +26 -0
  64. data/test/unit/charts/status_distribution_test.rb +28 -0
  65. data/test/unit/charts/version_distribution_test.rb +28 -0
  66. data/test/unit/group_version_breakdown_test.rb +65 -0
  67. data/test/unit/host_status/omaha_status_test.rb +19 -0
  68. data/test/unit/host_test.rb +55 -0
  69. data/test/unit/omaha_fact_parser_test.rb +77 -0
  70. data/test/unit/omaha_report_test.rb +63 -17
  71. data/test/unit/renderer_test.rb +21 -0
  72. metadata +80 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d3ec4d257a6b23da8a778b2c6c7646bda239ed8a
4
- data.tar.gz: 72996f1f0545e2c51e9bda9d0d1ec3c1765ff483
2
+ SHA256:
3
+ metadata.gz: 6ad84de9035d283fda62edcb31c461f88ef3811ce4caa64c2f7b299ec9c20474
4
+ data.tar.gz: 3884e00feb8dff9ca2409006709f344e7085085b11e923058188101b8a196022
5
5
  SHA512:
6
- metadata.gz: 49351ba242b536b61204bd5dde6f559a843e979c7de3e3fddb1a3e7c0e104bd5303fdcce6d7fcb4e3bb3a2d9639a6af872e14167289c5d02164b02a2b0acd314
7
- data.tar.gz: afcc44fb7da39ece8712fd38d18d9dd1e7cff99e938284b90b5d2a4d0909924753f410a189b25f8b67ce8827ab64532917afa3677d73fb0084353f6bd24b3557
6
+ metadata.gz: c0a3e1ffe8c2d3fee38ccc48edfed5ed12e9416fd123378c08f92772386efec252bcf5f2615539d1667292c439e95535f06133700df04f94529a482a6068cdf6
7
+ data.tar.gz: efd82da861cc6e7a6515136df34c4cfc46cd6eb7b6d3a5f480468778bdce57fcab7142c0d0cb9f50587cfda96f26f7bfbddc301681101e22e2424cc35eb667ce
data/README.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # ForemanOmaha
2
2
 
3
3
  This plug-in enables CoreOS updates using [The Foreman](https://theforeman.org/) without using the public update infrastructure.
4
- To follow along with common Foreman architecture, you need this Foreman plug-in installed and a [Smart-Proxy plugin](https://github.com/timogoebel/smart_proxy_omaha). The smart-proxy plug-in does all the heavy lifting. The Foreman plug-in shows facts and reports for your hosts.
4
+ To follow along with common Foreman architecture, you need this Foreman plug-in installed and a [Smart-Proxy plugin](https://github.com/theforeman/smart_proxy_omaha). The smart-proxy plug-in does all the heavy lifting. The Foreman plug-in shows facts and reports for your hosts.
5
5
  Foreman core already supports deploying CoreOS hosts and is great for on-premise setups. This plug-in enables you to better manage your servers.
6
6
 
7
7
  ## Compatibility
8
8
 
9
9
  | Foreman Version | Plugin Version |
10
10
  | --------------- | -------------- |
11
- | >= 1.12 | any |
11
+ | >= 1.12 | ~> 0.0 |
12
+ | >= 1.17 | ~> 1.0 |
12
13
 
13
14
  ## Installation
14
15
 
@@ -22,6 +23,8 @@ To prepare the Foreman database, issue the following command:
22
23
  foreman-rake db:migrate SCOPE=foreman_omaha
23
24
  ```
24
25
 
26
+ This plugins adds a global method `transpile_container_linux_config` to the template renderer that transpiles yaml to ignition. Make sure the binary [ct](https://github.com/coreos/container-linux-config-transpiler) is in your `PATH` when you want to use this feature.
27
+
25
28
  ## Uninstalling
26
29
 
27
30
  You need to revert all database changes made when installing this plug-in:
@@ -0,0 +1 @@
1
+ //= require jquery.matchHeight
@@ -0,0 +1,13 @@
1
+ .version-breakdown {
2
+ .progress {
3
+ overflow: hidden;
4
+ text-overflow: ellipsis;
5
+ }
6
+
7
+ .progress-bar {
8
+ a {
9
+ color: white;
10
+ text-decoration: none;
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,29 @@
1
+ module Api
2
+ module V2
3
+ class OmahaGroupsController < V2::BaseController
4
+ include Api::Version2
5
+
6
+ before_action :find_resource, :only => [:show]
7
+ before_action :setup_search_options, :only => [:index]
8
+
9
+ api :GET, '/omaha_groups/', N_('List all omaha groups')
10
+ param_group :search_and_pagination, ::Api::V2::BaseController
11
+
12
+ def index
13
+ @omaha_groups = resource_scope_for_index
14
+ @total = resource_scope_for_index.count
15
+ end
16
+
17
+ api :GET, '/omaha_groups/:id/', N_('Show a Omaha Group')
18
+ param :id, :identifier, :required => true, :desc => N_('Id of the Omaha Group')
19
+
20
+ def show; end
21
+
22
+ private
23
+
24
+ def resource_class
25
+ ::ForemanOmaha::OmahaGroup
26
+ end
27
+ end
28
+ end
29
+ end
@@ -13,10 +13,13 @@ module Api
13
13
  def_param_group :omaha_report do
14
14
  param :report, Hash, :required => true, :action_aware => true do
15
15
  param :host, String, :required => true, :desc => N_('Hostname or certname')
16
- param :status, String,
16
+ param :status, ::ForemanOmaha::OmahaReport.statuses.keys,
17
17
  :required => true,
18
- :desc => N_('Omaha status, can be one of %s') % ::ForemanOmaha::OmahaReport.statuses.keys.to_sentence
19
- param :omaha_version, String, :required => true, :desc => N_('Omaha OS version')
18
+ :desc => N_('Omaha update status')
19
+ param :omaha_version, String, :required => true, :desc => N_('Omaha OS version using semantic versioning, e.g. 1590.0.0')
20
+ param :machineid, String, :required => true, :desc => N_('Unique machine id of the host')
21
+ param :omaha_group, String, :required => true, :desc => N_('The uuid if the channel that the host is attached to. Use alpha, beta or stable for built-in channels.')
22
+ param :oem, String, :desc => N_('OEM identifier')
20
23
  end
21
24
  end
22
25
 
@@ -37,7 +40,7 @@ module Api
37
40
  param_group :omaha_report, :as => :create
38
41
 
39
42
  def create
40
- @omaha_report = resource_class.import(params[:omaha_report], detected_proxy.try(:id))
43
+ @omaha_report = resource_class.import(params.to_unsafe_h[:omaha_report], detected_proxy.try(:id))
41
44
  process_response @omaha_report.errors.empty?
42
45
  rescue ::Foreman::Exception => e
43
46
  render_message(e.to_s, :status => :unprocessable_entity)
@@ -51,12 +54,11 @@ module Api
51
54
  end
52
55
 
53
56
  api :GET, '/hosts/:host_id/omaha_reports/last', N_('Show the last report for a host')
57
+ param :host_id, String, :required => true, :desc => N_('ID of host')
54
58
  param :id, :identifier, :required => true
55
59
 
56
60
  def last
57
- if params[:host_id].present?
58
- conditions = { :host_id => resource_finder(Host.authorized(:view_hosts), params[:host_id]).try(:id) }
59
- end
61
+ conditions = params[:host_id].present? ? { :host_id => resource_finder(Host.authorized(:view_hosts), params[:host_id]).try(:id) } : nil
60
62
  max_id = resource_scope.where(conditions).maximum(:id)
61
63
  @omaha_report = resource_scope.find(max_id)
62
64
  render :show
@@ -0,0 +1,28 @@
1
+ class OmahaGroupsController < ApplicationController
2
+ include Foreman::Controller::AutoCompleteSearch
3
+
4
+ before_action :setup_search_options, :only => :index
5
+ before_action :find_resource, :only => :show
6
+
7
+ def index
8
+ @omaha_groups = resource_base_with_search
9
+ end
10
+
11
+ def show
12
+ @out_of_sync = ForemanOmaha::OmahaFacet.where(:omaha_group => @omaha_group).out_of_sync.includes(:host)
13
+ @latest_operatingsystem = @omaha_group.latest_operatingsystem
14
+ @status_distribution = ForemanOmaha::Charts::StatusDistribution.new(@omaha_group.hosts)
15
+ @version_distribution = ForemanOmaha::Charts::VersionDistribution.new(@omaha_group.hosts)
16
+ @oem_distribution = ForemanOmaha::Charts::OemDistribution.new(@omaha_group.hosts)
17
+ end
18
+
19
+ private
20
+
21
+ def model_of_controller
22
+ ::ForemanOmaha::OmahaGroup
23
+ end
24
+
25
+ def resource_class
26
+ ::ForemanOmaha::OmahaGroup
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ class OmahaHostsController < ApplicationController
2
+ include Foreman::Controller::AutoCompleteSearch
3
+ include ScopesPerAction
4
+
5
+ before_action :setup_search_options, :only => :index
6
+
7
+ add_scope_for(:index) do |base_scope|
8
+ base_scope.includes(:operatingsystem).includes(:omaha_facet).includes(:omaha_facet => :omaha_group)
9
+ end
10
+
11
+ def index
12
+ @hosts = action_scope_for(:index, resource_base_with_search)
13
+ @last_report_ids = ForemanOmaha::OmahaReport.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
14
+ end
15
+
16
+ def welcome
17
+ if begin
18
+ resource_base.first.nil?
19
+ rescue StandardError
20
+ false
21
+ end
22
+ @welcome = true
23
+ render :welcome
24
+ end
25
+ rescue StandardError
26
+ not_found
27
+ end
28
+
29
+ protected
30
+
31
+ def model_of_controller
32
+ ::Host::Managed
33
+ end
34
+
35
+ def resource_base
36
+ super.joins(:omaha_facet)
37
+ end
38
+
39
+ def controller_permission
40
+ 'hosts'
41
+ end
42
+ end
@@ -42,17 +42,6 @@ class OmahaReportsController < ApplicationController
42
42
  end
43
43
  end
44
44
 
45
- # TODO: Remove for Foreman 1.14+
46
- def resource_base_with_search
47
- resource_base.search_for(params[:search], :order => params[:order])
48
- end
49
-
50
- # TODO: Remove for Foreman 1.14+
51
- def resource_base_search_and_page(tables = [])
52
- base = tables.empty? ? resource_base_with_search : resource_base_with_search.eager_load(*tables)
53
- base.paginate(:page => params[:page], :per_page => params[:per_page])
54
- end
55
-
56
45
  private
57
46
 
58
47
  def resource_base
@@ -2,14 +2,23 @@ module ForemanOmaha
2
2
  module HostsHelperExtensions
3
3
  extend ActiveSupport::Concern
4
4
 
5
- included do
6
- alias_method_chain :show_appropriate_host_buttons, :foreman_omaha
5
+ module Overrides
6
+ def show_appropriate_host_buttons(host)
7
+ buttons = super
8
+ if host.omaha_reports.any?
9
+ buttons << link_to_if_authorized(
10
+ _('Omaha'),
11
+ hash_for_host_omaha_reports_path(:host_id => host),
12
+ :title => _('Browse host omaha reports'),
13
+ :class => 'btn btn-default'
14
+ )
15
+ end
16
+ buttons.compact
17
+ end
7
18
  end
8
19
 
9
- def show_appropriate_host_buttons_with_foreman_omaha(host)
10
- buttons = show_appropriate_host_buttons_without_foreman_omaha(host)
11
- buttons << (link_to_if_authorized(_('Omaha'), hash_for_host_omaha_reports_path(:host_id => host), :title => _('Browse host omaha reports'), :class => 'btn btn-default') if host.omaha_reports.any?)
12
- buttons.compact
20
+ included do
21
+ prepend Overrides
13
22
  end
14
23
  end
15
24
  end
@@ -0,0 +1,7 @@
1
+ module ForemanOmaha
2
+ module ApplicationHelper
3
+ def facets_count(association, resource_name = controller.resource_name)
4
+ @facets_count ||= Host::Managed.reorder('').authorized.joins(association).group("#{resource_name}_id").count
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ module ForemanOmaha
2
+ module OmahaGroupsHelper
3
+ def omaha_version_breakdown_bar(omaha_group)
4
+ stylesheet 'foreman_omaha/version_breakdown'
5
+ colors = {
6
+ :success => ['#3f9c35', '#6ec664', '#2d7623'],
7
+ :warning => ['#ec7a08', '#f7bd7f', '#f39d3c'],
8
+ :danger => ['#cc0000', '#a30000', '#8b0000', '#470000']
9
+ }
10
+ versions = ForemanOmaha::GroupVersionBreakdown.new(:omaha_group => omaha_group).version_breakdown
11
+ version_list = omaha_group.operatingsystems.pluck(:major, :minor).map { |v| Gem::Version.new(v.join('.')) }.sort.reverse
12
+ current = version_list.max
13
+ content_tag :div, :class => 'progress version-breakdown' do
14
+ safe_join(versions.map do |version|
15
+ semver = Gem::Version.new(version[:version])
16
+ position = version_list.index(semver)
17
+ css = {
18
+ :width => "#{version[:percentage]}%"
19
+ }
20
+ label = if semver > current
21
+ :info
22
+ elsif semver == current
23
+ :success
24
+ elsif position == 1
25
+ :warning
26
+ else
27
+ :danger
28
+ end
29
+ if colors.key?(label)
30
+ color = colors[label].first
31
+ colors[label].rotate!
32
+ css[:'background-color'] = color
33
+ end
34
+ content_tag :div,
35
+ :class => "progress-bar progress-bar-#{label}",
36
+ :role => 'progressbar',
37
+ :title => version[:version],
38
+ :style => css_style(css) do
39
+ link_to(
40
+ version[:version],
41
+ hosts_path(:search => "omaha_group = #{omaha_group} and omaha_version = #{version[:version]}")
42
+ )
43
+ end
44
+ end)
45
+ end
46
+ end
47
+
48
+ def css_style(hash)
49
+ hash.map { |k, v| "#{k}: #{v};" }.join(' ')
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ module OmahaHostsHelper
2
+ def last_omaha_report_column(record)
3
+ time = record.omaha_facet.last_report? ? _('%s ago') % time_ago_in_words(record.omaha_facet.last_report) : ''
4
+ link_to_if_authorized(time,
5
+ hash_for_host_omaha_report_path(:host_id => record.to_param, :id => 'last'),
6
+ last_omaha_report_tooltip(record))
7
+ end
8
+
9
+ def last_omaha_report_tooltip(record)
10
+ opts = { :rel => 'twipsy' }
11
+ if @last_report_ids[record.id]
12
+ opts['data-original-title'] = _('View last report details')
13
+ else
14
+ opts.merge!(:disabled => true, :class => 'disabled', :onclick => 'return false')
15
+ opts['data-original-title'] = _('Report Already Deleted') unless record.omaha_facet.last_report.nil?
16
+ end
17
+ opts
18
+ end
19
+
20
+ def list_view_class_for_omaha_status(status)
21
+ klasses = [
22
+ iconclass_for_omaha_status(status),
23
+ 'list-view-pf-icon-md'
24
+ ]
25
+ klasses << case status.to_sym
26
+ when :complete
27
+ 'list-view-pf-icon-success'
28
+ when :downloading
29
+ 'list-view-pf-icon-info'
30
+ when :downloaded
31
+ 'list-view-pf-icon-success'
32
+ when :installed
33
+ 'fa fa-sign-in text-success'
34
+ when :instance_hold
35
+ 'list-view-pf-icon-warning'
36
+ when :error
37
+ 'list-view-pf-icon-danger'
38
+ else
39
+ 'list-view-pf-icon-info'
40
+ end
41
+ klasses.join(' ')
42
+ end
43
+ end
@@ -1,11 +1,25 @@
1
1
  module ForemanOmaha
2
2
  module HostExtensions
3
3
  extend ActiveSupport::Concern
4
+
4
5
  included do
5
6
  has_many :omaha_reports, :class_name => '::ForemanOmaha::OmahaReport',
6
- :foreign_key => :host_id, :dependent => :destroy
7
+ :foreign_key => :host_id, :dependent => :destroy,
8
+ :inverse_of => :host
7
9
  has_one :last_omaha_report_object, -> { order("#{Report.table_name}.id DESC") },
8
- :foreign_key => :host_id, :class_name => '::ForemanOmaha::OmahaReport'
10
+ :foreign_key => :host_id, :class_name => '::ForemanOmaha::OmahaReport',
11
+ :inverse_of => :host
12
+
13
+ before_save :clear_omaha_facet_on_build
14
+ end
15
+
16
+ def clear_omaha_facet
17
+ omaha_facet.destroy if omaha_facet
18
+ end
19
+
20
+ def clear_omaha_facet_on_build
21
+ return unless respond_to?(:old) && old && build? && !old.build?
22
+ clear_omaha_facet
9
23
  end
10
24
  end
11
25
  end
@@ -0,0 +1,23 @@
1
+ module ForemanOmaha
2
+ module OmahaFacetHostExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :omaha_facet, :class_name => '::ForemanOmaha::OmahaFacet', :foreign_key => :host_id, :inverse_of => :host, :dependent => :destroy
7
+ has_one :omaha_group, :through => :omaha_facet, :inverse_of => :hosts
8
+
9
+ accepts_nested_attributes_for :omaha_facet, :update_only => true, :reject_if => ->(attrs) { attrs.values.compact.empty? }
10
+
11
+ scoped_search :on => :last_report, :relation => :omaha_facet, :rename => :last_omaha_report, :complete_value => true, :only_explicit => true
12
+ scoped_search :on => :machineid, :relation => :omaha_facet, :rename => :omaha_machineid, :complete_value => true, :only_explicit => true
13
+ scoped_search :on => :version, :relation => :omaha_facet, :rename => :omaha_version, :complete_value => true, :only_explicit => true
14
+ scoped_search :on => :oem, :relation => :omaha_facet, :rename => :omaha_oem, :complete_value => true, :only_explicit => true
15
+ scoped_search :on => :status, :relation => :omaha_facet, :rename => :omaha_status, :only_explicit => true, :complete_value => {
16
+ :unknown => 0, :complete => 1, :downloading => 2, :downloaded => 3,
17
+ :installed => 4, :instance_hold => 5, :error => 6
18
+ }
19
+
20
+ scoped_search :on => :name, :relation => :omaha_group, :complete_value => true, :rename => :omaha_group
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module ForemanOmaha
2
+ class OmahaFacet < ApplicationRecord
3
+ include Facets::Base
4
+
5
+ VALID_OMAHA_STATUSES = [:unknown, :complete, :downloading, :downloaded,
6
+ :installed, :instance_hold, :error].freeze
7
+
8
+ enum :status => VALID_OMAHA_STATUSES
9
+
10
+ belongs_to :omaha_group, :inverse_of => :omaha_facets, :class_name => 'ForemanOmaha::OmahaGroup'
11
+
12
+ scope :out_of_sync, ->(*args) { where(['last_report < ?', (args.first || (30 + Setting[:outofsync_interval]).minutes.ago)]) }
13
+
14
+ validates_lengths_from_database
15
+
16
+ validates :omaha_group, :presence => true, :allow_blank => false
17
+ validates :host, :presence => true, :allow_blank => false
18
+ validates :version, format: { with: /\A[0-9]+\.[0-9]+\.[0-9]+\z/, message: _('must use semantic versioning') }
19
+
20
+ def to_status_label
21
+ status_mapper.to_label
22
+ end
23
+
24
+ def major
25
+ return unless version
26
+ version.split('.').first
27
+ end
28
+
29
+ def minor
30
+ return unless version
31
+ version.split('.').last(2).join('.')
32
+ end
33
+
34
+ private
35
+
36
+ def status_mapper
37
+ StatusMapper.new(status)
38
+ end
39
+ end
40
+ end