Skip to content

Commit 5052753

Browse files
Replace open structs with structs
1 parent 70328f5 commit 5052753

File tree

11 files changed

+182
-104
lines changed

11 files changed

+182
-104
lines changed

config/config.rb

Lines changed: 98 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,113 @@
44
require 'yaml'
55
require 'json'
66
require 'logger'
7-
require 'ostruct'
87
require_relative '../lib/docker'
98

109
# NOTE: dotenv/load must come after ../lib/docker
1110
require 'dotenv/load'
12-
# I think I need to only call this if we're NOT running in a container:
13-
# require 'dotenv/load' unless Docker.running_in_container?
1411

1512
# Config class to hold/manage our configuration
1613
class Config
17-
# Load the ENV vars
18-
Docker::Secret.setup_environment!
14+
# Define your Structs for Secrets:
15+
Ucpath = Struct.new(:root, :id, :key, keyword_init: true)
16+
Sis = Struct.new(:root, :id, :key, keyword_init: true)
17+
Ldap = Struct.new(:host, :pass, keyword_init: true)
18+
Secrets = Struct.new(:ucpath, :sis, :ldap, keyword_init: true)
1919

20-
# Secrets (passwords, api keys, etc...): Uses ERB for ENV variables
21-
@secrets = JSON.parse(YAML.safe_load(ERB.new(File.read('config/secrets.yml')).result).to_json,
22-
object_class: OpenStruct)
20+
class << self
21+
attr_reader :secrets, :settings, :help
22+
23+
def load!
24+
# Load the ENV vars
25+
Docker::Secret.setup_environment!
26+
27+
load_settings!('config/settings.yml')
28+
load_secrets!('config/secrets.yml')
29+
end
30+
31+
# Returns specified field value from settings.yml
32+
def setting(field)
33+
@settings[field.to_sym]
34+
end
35+
36+
def ucpath_employee_fields
37+
@ucpath_fields['employee']['fields']
38+
end
39+
40+
def sis_fields
41+
@sis_fields['SIS']['fields']
42+
end
43+
44+
def ucpath_job_fields
45+
@ucpath_fields['job']['fields']
46+
end
47+
48+
def student_affiated?(affiliation)
49+
@ldap_fields['student_affiliation'].include? affiliation
50+
end
51+
52+
def ldap_attributes
53+
@ldap_fields['attributes']
54+
end
55+
56+
def check_ucpath_code(type, value)
57+
@ucpath_codes[type].include? value
58+
end
59+
60+
private
61+
62+
def load_settings!(path)
63+
raw = yaml_with_erb(path)
64+
65+
# Load those config settings from the yaml hash:
66+
@settings = create_struct_from_hash(
67+
name: 'Settings',
68+
hash: raw.fetch('settings')
69+
)
70+
71+
# keep help separate...it's just a string
72+
@help = raw['help']
73+
end
74+
75+
def load_secrets!(path)
76+
raw = yaml_with_erb(path)
77+
78+
@secrets = Secrets.new(
79+
ucpath: Ucpath.new(**symbolize(raw.fetch('ucpath'))),
80+
sis: Sis.new(**symbolize(raw.fetch('sis'))),
81+
ldap: Ldap.new(**symbolize(raw.fetch('ldap')))
82+
)
83+
84+
# Over-ride the LDAP host if we're in CI land... JUST to make
85+
# sure we don't hit the actual host when we're running rspec!
86+
@secrets.ldap.host = 'ldap.fake.edu' if ENV['CI']
87+
end
88+
89+
def symbolize(hash)
90+
hash.transform_keys(&:to_sym)
91+
end
92+
93+
def yaml_with_erb(path)
94+
YAML.safe_load(ERB.new(File.read(path)).result)
95+
end
96+
97+
# Since settings isn't nested hash of hashes easy enough to create this struct dynamically:
98+
def create_struct_from_hash(name:, hash:)
99+
# Since Structs need symbols....
100+
sym_hash = hash.transform_keys(&:to_sym)
101+
102+
struct_class = if const_defined?(name, false)
103+
const_get(name)
104+
else
105+
const_set(name, Struct.new(*sym_hash.keys, keyword_init: true))
106+
end
107+
108+
struct_class.new(**sym_hash)
109+
end
110+
end
23111

24-
# General Settings: Uses ERB for ENV variables
25-
@settings = JSON.parse(YAML.safe_load(ERB.new(File.read('config/settings.yml')).result).to_json,
26-
object_class: OpenStruct)
112+
# Let's do this!!!!
113+
load!
27114

28115
ucpath_contents = File.read('config/ucpath_fields.yml')
29116
@ucpath_fields = YAML.safe_load(ERB.new(ucpath_contents).result)
@@ -36,42 +123,4 @@ class Config
36123

37124
sis_contents = File.read('config/sis_fields.yml')
38125
@sis_fields = YAML.safe_load(ERB.new(sis_contents).result)
39-
40-
# Over-ride the LDAP host if we're in CI land... JUST to make
41-
# sure we don't hit the actual host when we're running rspec!
42-
@secrets.ldap.host = 'ldap.fake.edu' if ENV['CI']
43-
44-
# Returns ostruct of the secrets yaml file
45-
class << self
46-
attr_reader :secrets
47-
end
48-
49-
def self.ucpath_employee_fields
50-
@ucpath_fields['employee']['fields']
51-
end
52-
53-
def self.sis_fields
54-
@sis_fields['SIS']['fields']
55-
end
56-
57-
def self.ucpath_job_fields
58-
@ucpath_fields['job']['fields']
59-
end
60-
61-
def self.student_affiated?(affiliation)
62-
@ldap_fields['student_affiliation'].include? affiliation
63-
end
64-
65-
def self.check_ucpath_code(type, value)
66-
@ucpath_codes[type].include? value
67-
end
68-
69-
# Returns specified field value from settings.yml
70-
def self.setting(field)
71-
@settings.settings[field] || nil
72-
end
73-
74-
def self.help
75-
@settings.help
76-
end
77126
end

config/ldap_fields.yml

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
attributes:
2-
- berkeleyEduExpDate
3-
- berkeleyEduHCMID
4-
- berkeleyEduAffID
5-
- berkeleyEduAffiliations
6-
- berkeleyEduBirthDay
7-
- berkeleyEduBirthMonth
8-
- berkeleyEduBirthYear
9-
- berkeleyEduAlternateID
10-
- berkeleyEduCSID
11-
- berkeleyEduEmpTitleCode
12-
- berkeleyEduEmpDeptUnitTitleCode
13-
- berkeleyEduKerberosPrincipalString
14-
- berkeleyEduStuID
15-
- employeeNumber
16-
- employeeType
2+
- berkeleyeduexpdate
3+
- berkeleyeduhcmid
4+
- berkeleyeduaffid
5+
- berkeleyeduaffiliations
6+
- berkeleyedubirthday
7+
- berkeleyedubirthmonth
8+
- berkeleyedubirthyear
9+
- berkeleyedualternateid
10+
- berkeleyeducsid
11+
- berkeleyeduemptitlecode
12+
- berkeleyeduempdeptunittitlecode
13+
- berkeleyedukerberosprincipalstring
14+
- berkeleyedustuid
15+
- employeenumber
16+
- employeetype
1717
- uid
18-
- berkeleyEduUnitHRDeptName
19-
- givenName
18+
- berkeleyeduunithrdeptname
19+
- givenname
2020
- sn
21-
- berkeleyEduOfficialEmail
22-
- berkeleyEduPrimaryDeptUnit
23-
- postalAddress
24-
- postalCode
25-
- telephoneNumber
21+
- berkeleyeduofficialemail
22+
- berkeleyeduprimarydeptunit
23+
- postaladdress
24+
- postalcode
25+
- telephonenumber
2626
- mail
2727

2828
student_affiliation:

config/settings.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ settings:
66
upload_host: "upload.lib.berkeley.edu"
77
upload_user: "ssullivan"
88
last_alma_purge: "2023-06-30"
9-
application_version: "1.6.4"
9+
application_version: "1.6.5"
1010

1111
# TODO - flesh this out
1212
# http://docopt.org/

config/ucpath_fields.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,6 @@ job:
9191
dbdef: "VARCHAR(16)"
9292
status: OPTIONAL
9393

94-
- name: job_code
95-
jpath: "$.position.jobCode.code.code"
96-
dbdef: "VARCHAR(16)"
97-
status: OPTIONAL
98-
99-
- name: dept_code
100-
jpath: "$.department.code"
101-
dbdef: "VARCHAR(16)"
102-
status: OPTIONAL
103-
10494
- name: dept_desc
10595
jpath: "$.department.description"
10696
dbdef: "VARCHAR(64)"

lib/ldap/api.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# frozen_string_literal: true
22

3-
require 'ostruct'
43
require 'net/ldap'
54

65
module LDAP
7-
# Fetches an LDAP record by UID returns an ostruct
6+
# Fetches an LDAP record by UID returns an struct
87
module API
98
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
109
def fetch_ldap_rec(id)
11-
ldap_rec = OpenStruct.new
10+
ldap_rec = ldap_struct_class.new
11+
1212
filter = Net::LDAP::Filter.eq('uid', id)
1313

1414
# Track number of attempts incase of a timeout or other temporary glitch
@@ -23,7 +23,12 @@ def fetch_ldap_rec(id)
2323
ldap.bind
2424
ldap.search(base: base, filter: filter) do |entry|
2525
entry.each do |attribute, values|
26-
ldap_rec[attribute] = values
26+
27+
# Only grab the LDAP fields we care about....
28+
attr_sym = attribute.to_sym
29+
next unless ldap_rec.members.include?(attr_sym)
30+
31+
ldap_rec[attr_sym] = values
2732
end
2833
end
2934
rescue StandardError => e
@@ -63,5 +68,13 @@ def base
6368
def pass
6469
Config.secrets.ldap.pass
6570
end
71+
72+
# Define the LDAP Struct from the attributes we have in config > ldap_fields.yml[attributes]
73+
def ldap_struct_class
74+
return self.class::Ldap if self.class.const_defined?(:Ldap, false)
75+
76+
attribute_symbols = Config.ldap_attributes.map(&:to_sym)
77+
self.class.const_set(:Ldap, Struct.new(*attribute_symbols, keyword_init: true))
78+
end
6679
end
6780
end

lib/sis/student.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# frozen_string_literal: true
22

33
require 'json'
4-
require 'ostruct'
54
require 'date'
65

76
# rubocop:disable Metrics/ClassLength
@@ -18,9 +17,15 @@ class Student
1817
Email = Struct.new(:preferred, :email_address, :email_types)
1918
Phone = Struct.new(:preferred, :preferred_sms, :phone_number, :phone_types)
2019

20+
Rec = Struct.new(:primary_id, :job_description, :expiry_date, :purge_date, :contact_info,
21+
:identifiers, :user_group, :full_name, :first_name, :middle_name, :last_name,
22+
:preferred_name, :pref_name_givenname, :pref_name_middlename, :pref_name_familyname,
23+
:campus_code, :account_type, :status,
24+
keyword_init: true)
25+
2126
def initialize(user)
2227
@user = user
23-
@rec = OpenStruct.new
28+
@rec = Rec.new
2429
create_user_record
2530
end
2631

lib/ucpath/jobs.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
require 'date'
22
require 'json'
3-
require 'ostruct'
43
require 'jsonpath'
54
require 'nokogiri'
65
require_relative '../alma'
@@ -101,12 +100,26 @@ def find_priority_jobs(job_list)
101100
end
102101
end
103102

103+
# Return the Job Struct class, creating it once using the
104+
# field names defined in ucpath_fields.yml if it does not already exist.
105+
def job_struct_class
106+
return self.class::Job if self.class.const_defined?(:Job, false)
107+
108+
keys = Config.ucpath_job_fields.map { |f| f['name'].to_sym }
109+
self.class.const_set(:Job, Struct.new(*keys, keyword_init: true))
110+
end
111+
112+
# Extract the configured fields from the raw job JSON and
113+
# instantiate a Job struct with the resulting values.
104114
def map_job_to_struct(job_hash)
105-
OpenStruct.new(
106-
Config.ucpath_job_fields.to_h do |field|
107-
[field['name'], JsonPath.on(job_hash, field['jpath']).first || '']
108-
end
109-
)
115+
attrs = Config.ucpath_job_fields.to_h do |field|
116+
[
117+
field['name'].to_sym,
118+
JsonPath.on(job_hash, field['jpath']).first || ''
119+
]
120+
end
121+
122+
job_struct_class.new(**attrs)
110123
end
111124

112125
def fetch_jobs(id)

0 commit comments

Comments
 (0)