Skip to content

Commit

Permalink
feat(APIv2): expose the TOML format of the tailoring_file
Browse files Browse the repository at this point in the history
  • Loading branch information
skateman committed Oct 24, 2024
1 parent e329757 commit 84ee11d
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 21 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ gem 'faraday-retry'
gem 'activerecord-import'
gem 'friendly_id', '~> 5.2.4'
gem 'oj'
gem 'toml'
gem 'scoped_search'
gem 'uuid'
gem 'will_paginate'
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ GEM
parser (3.3.2.0)
ast (~> 2.4.1)
racc
parslet (2.0.0)
pg (1.5.6)
prometheus-client-mmap (0.28.1)
rb_sys (~> 0.9)
Expand Down Expand Up @@ -426,6 +427,8 @@ GEM
time (0.3.0)
date
timeout (0.4.1)
toml (0.3.0)
parslet (>= 1.8.0, < 3.0.0)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
Expand Down Expand Up @@ -541,6 +544,7 @@ DEPENDENCIES
spring-watcher-listen
stronger_parameters
time (>= 0.2.2)
toml
tzinfo-data
uri (>= 0.11.1)
uuid
Expand Down
24 changes: 9 additions & 15 deletions app/controllers/v2/tailorings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ def update
permitted_params_for_action :update, { id: ID_TYPE, **UPDATE_ATTRIBUTES }

def tailoring_file
return unless tailoring.tailored?
builder = file_builder(params[:format].to_sym)

format = params[:format].to_sym
send_data(build_file(format), filename: filename(format), type: Mime[format])
return if builder.empty? # no-content for empty XMLs

send_data(builder.output, filename: builder.filename, type: builder.mime)
end
permission_for_action :tailoring_file, Rbac::POLICY_READ
permitted_params_for_action :tailoring_file, id: ID_TYPE.required
Expand All @@ -71,21 +72,14 @@ def policy
V2::Policy.find(permitted_params[:policy_id])
end

def build_file(format)
builder = format == :json ? JsonTailoringFile : XccdfTailoringFile

builder.new(
def file_builder(format)
V2::TailoringFile.new(
profile: tailoring,
rules: tailoring.rules_added + tailoring.rules_removed,
rule_group_ref_ids: tailoring.rule_group_ref_ids,
set_values: tailoring.value_overrides_by_ref_id
).output
end

def filename(format)
extension = format == :json ? 'json' : 'xml'

"#{tailoring.security_guide.ref_id}__#{tailoring.profile.ref_id}__tailoring.#{extension}"
set_values: tailoring.value_overrides_by_ref_id,
format: format
)
end

def resource
Expand Down
8 changes: 8 additions & 0 deletions app/models/v2/fix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ class Fix < ApplicationRecord
# FIXME: clean up after the remodel
self.table_name = :fixes

ANACONDA = 'urn:redhat:anaconda:pre'
BLUEPRINT = 'urn:redhat:osbuild:blueprint'
ANSIBLE = 'urn:xccdf:fix:script:ansible'
IGNITION = 'urn:xccdf:fix:script:ignition'
KUBERNETES = 'urn:xccdf:fix:script:kubernetes'
PUPPET = 'urn:xccdf:fix:script:puppet'
SHELL = 'urn:xccdf:fix:script:sh'

belongs_to :rule
has_one :security_guide, through: :rule

Expand Down
16 changes: 14 additions & 2 deletions app/services/v2/json_tailoring_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@

module V2
# A class representing a JSON Tailoring File
class JsonTailoringFile
def initialize(profile:, rules: {}, set_values: {}, rule_group_ref_ids: [])
class JsonTailoringFile < TailoringFile
def initialize(profile:, rules: {}, set_values: {}, rule_group_ref_ids: [], **_hsh)
@tailoring = profile
@rules = rules
@rule_group_ref_ids = rule_group_ref_ids
@set_values = set_values
end

def mime
Mime[:json]
end

def extension
'json'
end

def output
Oj.dump('profiles' => [build_profile])
end

def empty?
!@tailoring.tailored?
end

private

def build_profile
Expand Down
23 changes: 23 additions & 0 deletions app/services/v2/tailoring_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module V2
# Factory for selecting the right tailoring file format
class TailoringFile
def self.new(format: :xml, **hsh)
return super if self != V2::TailoringFile

case format
when :json
JsonTailoringFile.new(**hsh)
when :toml
TomlTailoringFile.new(**hsh)
when :xml
XccdfTailoringFile.new(**hsh)
end
end

def filename
"#{@tailoring.security_guide.ref_id}__#{@tailoring.profile.ref_id}__tailoring.#{extension}"
end
end
end
44 changes: 44 additions & 0 deletions app/services/v2/toml_tailoring_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'deep_merge'

module V2
# A class representing a TOML Tailoring File
class TomlTailoringFile < TailoringFile
def initialize(profile:, **_hsh)
@tailoring = profile
end

def mime
'application/toml'
end

def extension
'toml'
end

def output
TOML::Generator.new(build_profile).body
end

def empty?
false
end

private

def build_profile
{
'name' => @tailoring.profile.title,
'description' => @tailoring.profile.description,
'version' => @tailoring.profile.security_guide.version
}.merge(build_fixes)
end

def build_fixes
V2::Fix.where(rule: @tailoring.rules, system: V2::Fix::BLUEPRINT).reduce({}) do |obj, fix|
obj.deep_merge(TOML.load(fix.text))

Check warning on line 40 in app/services/v2/toml_tailoring_file.rb

View check run for this annotation

Codecov / codecov/patch

app/services/v2/toml_tailoring_file.rb#L40

Added line #L40 was not covered by tests
end
end
end
end
16 changes: 14 additions & 2 deletions app/services/v2/xccdf_tailoring_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@

module V2
# A class representing an XCCDF Tailoring File
class XccdfTailoringFile
class XccdfTailoringFile < TailoringFile
extend Forwardable

XCCDF = 'xccdf'

def initialize(profile:, rules: {}, set_values: {}, rule_group_ref_ids: [])
def initialize(profile:, rules: {}, set_values: {}, rule_group_ref_ids: [], **_hsh)
@tailoring = profile
@rules = rules
@rule_group_ref_ids = rule_group_ref_ids
@set_values = set_values
end

def mime
Mime[:xml]
end

def extension
'xml'
end

def output
builder.to_xml
end

def empty?
!@tailoring.tailored?
end

private

def builder
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def draw_routes(prefix)

resources :policies, except: %i[new edit] do
resources :tailorings, only: %i[index show create update], parents: %i[policy] do
get :tailoring_file, on: :member, defaults: { format: 'xml' }, constraints: { format: /json|xml/ }
get :tailoring_file, on: :member, defaults: { format: 'xml' }, constraints: { format: /json|xml|toml/ }
get :rule_tree, on: :member, parents: %i[policy]

resources :rules, only: %i[index create update destroy], parents: %i[policies tailorings]
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20241024044139_reset_datastreams_fixes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ResetDatastreamsFixes < ActiveRecord::Migration[7.1]
def up
Revision.find_by(name: 'datastreams')&.delete
end
end
2 changes: 1 addition & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_10_14_100839) do
ActiveRecord::Schema[7.1].define(version: 2024_10_24_044139) do
create_schema "inventory"

# These are extensions that must be enabled in order to support this database
Expand Down
32 changes: 32 additions & 0 deletions spec/controllers/v2/tailorings_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -399,5 +399,37 @@

include_examples 'tailoring_file'
end

context 'TOML' do
let(:format) { :toml }
let(:tailoring_file) { TOML.load(response.body) }
let(:extra_params) { { policy_id: parent.id, id: item.id } }

let(:parent) do
FactoryBot.create(
:v2_policy,
:for_tailoring,
account: current_user.account,
supports_minors: [8]
)
end

let(:item) do
FactoryBot.create(
:v2_tailoring,
:without_rules,
value_overrides: {}, # no tailored values
policy: parent,
os_minor_version: 8
)
end

it 'returns tailoring_file' do
get :tailoring_file, params: extra_params.merge(parents: [:policy], format: format)

expect(response).to have_http_status :ok
expect(tailoring_file).not_to be_empty
end
end
end
end

0 comments on commit 84ee11d

Please sign in to comment.