Skip to content

Commit 869cd93

Browse files
author
Jeremy Nicklas
committed
first attempt
1 parent c3bce05 commit 869cd93

19 files changed

+991
-4
lines changed

.rspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
--format documentation
21
--color
2+
--require spec_helper

Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
22

33
# Specify your gem's dependencies in ood_core.gemspec
44
gemspec
5+
6+
# FIXME
7+
gem "ood_job", git: "[email protected]:OSC/ood_job.git"

lib/ood_core.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
require "ood_core/version"
2+
require "ood_core/errors"
3+
require "ood_core/job_factory"
4+
require "ood_core/acl_factory"
5+
require "ood_core/cluster"
6+
require "ood_core/clusters"
27

38
# The main namespace for ood_core
49
module OodCore
5-
# Your code goes here...
10+
# A namespace for job adapters
11+
module JobAdapters
12+
end
13+
14+
# A namespace for acl adapters
15+
module AclAdapters
16+
end
617
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require "ood_support"
2+
3+
module OodCore
4+
module AclAdapters
5+
# A class that handles whether a resource is allowed to be used through an
6+
# ACL
7+
# @abstract
8+
class AbstractAdapter
9+
# Whether this ACL allows access for the principle
10+
# @abstract Subclass is expected to implement {#allow?}
11+
# @raise [NotImplementedError] if subclass did not define {#allow?}
12+
# @return [Boolean] whether principle is allowed
13+
def allow?
14+
raise NotImplementedError, "subclass did not define #allow?"
15+
end
16+
end
17+
end
18+
end
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require "ood_core/refinements/hash_extensions"
2+
require "ood_core/acl_adapters/abstract_adapter"
3+
4+
module OodCore
5+
class AclFactory
6+
using Refinements::HashExtensions
7+
8+
# Build the group acl adapter from a configuration
9+
# @param config [#to_h] the configuration for an acl adapter
10+
# @option config [Array<#to_s>] :groups The list of groups
11+
# @option config [#to_s] :type ('whitelist') The type of ACL ('whitelist' or 'blacklist')
12+
def self.build_group(config)
13+
c = config.to_h.symbolize_keys
14+
15+
groups = c.fetch(:groups) { raise ArgumentError, "No groups specified. Missing argument: groups" }.map(&:to_s)
16+
acl = OodSupport::ACL.new(entries: groups.map { |g| OodSupport::ACLEntry.new principle: g })
17+
18+
type = c.fetch(:type, "whitelist").to_s
19+
if type == "whitelist"
20+
allow = true
21+
elsif type == "blacklist"
22+
allow = false
23+
else
24+
raise ArgumentError, "Invalid type specified. Valid types: whitelist, blacklist"
25+
end
26+
27+
OodCore::AclAdapters::GroupAdapter.new(acl: acl, allow: allow)
28+
end
29+
end
30+
31+
module AclAdapters
32+
# An adapter object that describes a group permission ACL
33+
class GroupAdapter < AbstractAdapter
34+
using Refinements::HashExtensions
35+
36+
# @param opts [#to_h] the options defining this adapter
37+
# @option opts [OodSupport::ACL] :acl The ACL permission
38+
# @option opts [Boolean] :allow (true) Whether this ACL allows access
39+
def initialize(opts)
40+
o = opts.to_h.symbolize_keys
41+
@acl = o.fetch(:acl) { raise ArgumentError, "No acl specified. Missing argument: acl" }
42+
@allow = o.fetch(:allow, true)
43+
end
44+
45+
# Whether this ACL allows the active user access based on their groups
46+
# @return [Boolean] whether principle is allowed
47+
def allow?
48+
!( OodSupport::User.new.groups.map(&:to_s).any? { |g| @acl.allow?(principle: g) } ^ @allow )
49+
end
50+
end
51+
end
52+
end

lib/ood_core/acl_factory.rb

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require "ood_core/refinements/hash_extensions"
2+
3+
module OodCore
4+
# A factory that builds acl adapter objects from a configuration.
5+
class AclFactory
6+
using Refinements::HashExtensions
7+
8+
class << self
9+
# Build an acl adapter from a configuration
10+
# @param config [#to_h] configuration describing acl adapter
11+
# @option config [#to_s] :adapter The acl adapter to use
12+
# @raise [AdapterNotSpecified] if no adapter is specified
13+
# @raise [AdapterNotFound] if the specified adapter does not exist
14+
# @return [AclAdapters::AbstractAdapter] the acl adapter object
15+
def build(config)
16+
c = config.to_h.symbolize_keys
17+
18+
adapter = c.fetch(:adapter) { raise AdapterNotSpecified, "acl configuration does not specify adapter" }.to_s
19+
20+
path_to_adapter = "ood_core/acl_adapters/#{adapter}_adapter"
21+
begin
22+
require path_to_adapter
23+
rescue Gem::LoadError => e
24+
raise Gem::LoadError, "Specified '#{adapter}' for acl adapter, but the gem is not loaded."
25+
rescue LoadError => e
26+
raise LoadError, "Could not load '#{adapter}'. Make sure that the acl adapter in the configuration file is valid."
27+
end
28+
29+
adapter_method = "build_#{adapter}"
30+
31+
unless respond_to?(adapter_method)
32+
raise AdapterNotFound, "acl configuration specifies nonexistent #{adapter} adapter"
33+
end
34+
35+
send(adapter_method, c)
36+
end
37+
end
38+
end
39+
end

lib/ood_core/cluster.rb

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
require "ostruct"
2+
3+
require "ood_core/refinements/hash_extensions"
4+
5+
module OodCore
6+
# An object that describes a cluster and its given features that third-party
7+
# code can take advantage of.
8+
class Cluster
9+
using Refinements::HashExtensions
10+
11+
# The unique identifier for a given cluster
12+
# @return [Symbol] the cluster id
13+
attr_reader :id
14+
15+
# Metadata configuration providing descriptive information about cluster
16+
# @return [Hash] the metadata configuration
17+
attr_reader :metadata_config
18+
19+
# The login configuration used for this cluster
20+
# @return [Hash] the login configuration
21+
attr_reader :login_config
22+
23+
# The job adapter configuration used for this cluster
24+
# @return [Hash] the job configuration
25+
attr_reader :job_config
26+
27+
# The acls configuration describing the permissions for this cluster
28+
# @return [Hash] the acls configuration
29+
attr_reader :acls_config
30+
31+
# @param cluster [#to_h] the cluster object
32+
# @option cluster [#to_sym] :id The cluster id
33+
# @option cluster [#to_h] :metadata ({}) The cluster's metadata
34+
# @option cluster [#to_h] :login ({}) The cluster's SSH host
35+
# @option cluster [#to_h] :job ({}) The job adapter for this cluster
36+
# @option cluster [#to_h] :custom ({}) Any custom resources for this cluster
37+
# @option cluster [Array<#to_h>] :acls ([]) List of ACLs to validate against
38+
def initialize(cluster)
39+
c = cluster.to_h.symbolize_keys
40+
41+
# Required options
42+
@id = c.fetch(:id) { raise ArgumentError, "No id specified. Missing argument: id" }.to_sym
43+
44+
# General options
45+
@metadata_config = c.fetch(:metadata, {}).to_h.symbolize_keys
46+
@login_config = c.fetch(:login, {}) .to_h.symbolize_keys
47+
@job_config = c.fetch(:job, {}) .to_h.symbolize_keys
48+
@custom_config = c.fetch(:custom, {}) .to_h.symbolize_keys
49+
@acls_config = c.fetch(:acls, []) .map(&:to_h)
50+
end
51+
52+
# Metadata that provides extra information about this cluster
53+
# @return [OpenStruct] the metadata
54+
def metadata
55+
OpenStruct.new metadata_config
56+
end
57+
58+
# The login used for this cluster
59+
# @return [OpenStruct] the login
60+
def login
61+
OpenStruct.new(login_config)
62+
end
63+
64+
# Whether the login feature is allowed
65+
# @return [Boolean] is login allowed
66+
def login_allow?
67+
allow? && !login.empty?
68+
end
69+
70+
# Build a job adapter from the job configuration
71+
# @return [JobAdapters::AbstractAdapter] the job adapter
72+
def job_adapter
73+
JobFactory.build(job_config)
74+
end
75+
76+
# Whether the job feature is allowed based on the ACLs
77+
# @return [Boolean] is the job feature allowed
78+
def job_allow?
79+
allow? &&
80+
!job_config.empty? &&
81+
build_acls(job_config.fetch(:acls, []).map(&:to_h)).all?(&:allow?)
82+
end
83+
84+
# The configuration for any custom features or resources for this cluster
85+
# @param feature [#to_sym, nil] the feature or resource
86+
# @return [Hash] configuration for custom feature or resource
87+
def custom_config(feature = nil)
88+
feature ? @custom_config.fetch(feature.to_sym, {}).to_h.symbolize_keys : @custom_config
89+
end
90+
91+
# Whether the custom feature is allowed based on the ACLs
92+
# @return [Boolean] is this custom feature allowed
93+
def custom_allow?(feature)
94+
allow? &&
95+
!custom_config(feature).empty? &&
96+
build_acls(custom_config(feature).fetch(:acls, []).map(&:to_h)).all?(&:allow?)
97+
end
98+
99+
# Build the ACL adapters from the ACL list configuration
100+
# @return [Array<AclAdapters::AbstractAdapter>] the acl adapter list
101+
def acls
102+
build_acls acls_config
103+
end
104+
105+
# Whether this cluster is allowed to be used
106+
# @return [Boolean] whether cluster is allowed
107+
def allow?
108+
acls.all?(&:allow?)
109+
end
110+
111+
# The comparison operator
112+
# @param other [#to_sym] object to compare against
113+
# @return [Boolean] whether objects are equivalent
114+
def ==(other)
115+
id == other.to_sym
116+
end
117+
118+
# Convert object to symbol
119+
# @return [Symbol] the symbol describing this object
120+
def to_sym
121+
id
122+
end
123+
124+
# Convert object to hash
125+
# @return [Hash] the hash describing this object
126+
def to_h
127+
{
128+
id: id,
129+
metadata: metadata_config,
130+
login: login_config,
131+
job: job_config,
132+
custom: custom_config,
133+
acls: acls_config
134+
}
135+
end
136+
137+
private
138+
# Build acl adapter objects from array
139+
def build_acls(ary)
140+
ary.map { |a| AclFactory.build a }
141+
end
142+
end
143+
end

0 commit comments

Comments
 (0)