class Org
Constants
- MAX_ASSOCIATE_SQUADS_PER_PERSON
- MAX_MEMBER_SQUADS_PER_PERSON
- SCHEMA_VERSION
Public Class Methods
new(parsed_data, platoons, squads, people, gsuite_domain)
click to toggle source
# File lib/terraorg/model/org.rb, line 23 def initialize(parsed_data, platoons, squads, people, gsuite_domain) @people = people @id = parsed_data.fetch('id') @name = parsed_data.fetch('name') @metadata = parsed_data.fetch('metadata', {}) @manager = @people.get_or_create!(parsed_data.fetch('manager')) @manager_location = parsed_data.fetch('manager_location') # @member_exception_people is a Hash of Squad::Teams, like the teams attribute in a Squad. @member_exception_people = Hash[parsed_data.fetch('exception_people').map { |p| [p.fetch('location'), Squad::Team.new(p, @people)] }] @member_exception_squad_names = [] @member_exception_squads = parsed_data.fetch('exception_squads').map do |s| @member_exception_squad_names.push(s) squads.lookup!(s) end @member_platoon_names = [] @member_platoons = parsed_data.fetch('platoons').map do |p| @member_platoon_names.push(p) platoons.lookup!(p) end # used to generate a group @gsuite_domain = gsuite_domain # used for validate! @platoons = platoons @squads = squads end
Public Instance Methods
generate_tf()
click to toggle source
# File lib/terraorg/model/org.rb, line 280 def generate_tf tf = generate_tf_platoons File.write('auto.platoons.tf', tf) tf = generate_tf_squads File.write('auto.exception_squads.tf', tf) tf = generate_tf_org File.write('auto.org.tf', tf) end
generate_tf_org()
click to toggle source
# File lib/terraorg/model/org.rb, line 216 def generate_tf_org tf = '' # Roll all platoons and exception squads into the org. roll_up_to_org = \ @member_exception_squads.map { |s| s.unique_name(@id, nil) } + \ @member_platoons.map { |p| p.unique_name(@id) } # Generate the org, which contains: # - exception people (added manually to group) # - the org manager (added manually to group) # - all the platoons (via rule) # - all exception squads (via rule) org_condition = roll_up_to_org.map { |n| "\\\"${okta_group.#{n}.id}\\\"" }.join(',') description = "#{@name} organization worldwide members (terraorg)" tf = <<-EOF # Okta for Organization: #{@name} resource "okta_group" "#{unique_name}" { name = "#{unique_name}" description = "#{description}" users = #{Util.persons_tf(members)} } #{Util.gsuite_group_tf(unique_name, @gsuite_domain, members, description)} EOF # Generate a special group for all org members grouped by country all_squads = (@member_platoons.map(&:member_squads) + @member_exception_squads).flatten all_locations = {} (all_squads.map(&:teams).flatten + [@member_exception_people]).each do |team| team.each do |location, subteam| all_locations[location] = all_locations.fetch(location, Set.new).merge(subteam.members) end end # Manually add the manager to a specific location all_locations[@manager_location] = all_locations.fetch(@manager_location, Set.new).add(@manager) all_locations.each do |l, m| description = "#{@name} organization members based in #{l} (terraorg)" name = "#{unique_name}-#{l.downcase}" tf += <<-EOF resource "okta_group" "#{name}" { name = "#{name}" description = "#{description}" users = #{Util.persons_tf(m)} } #{Util.gsuite_group_tf(name, @gsuite_domain, m, description)} EOF end # Generate a special GSuite group for all managers (org, platoon, squad # level.) We don't generate such an okta group (For now) # As Squad#manager may return nil, select the non-nils all_managers = Set.new([@manager] + @platoons.all.map(&:manager) + @squads.all.map(&:manager).select { |m| m }) manager_dl = "#{@id}-managers" tf += Util.gsuite_group_tf(manager_dl, @gsuite_domain, all_managers, "All managers of the #{@name} organization (terraorg)") tf end
generate_tf_platoons()
click to toggle source
# File lib/terraorg/model/org.rb, line 208 def generate_tf_platoons @member_platoons.map { |p| p.generate_tf(@id) }.join("\n") end
generate_tf_squads()
click to toggle source
# File lib/terraorg/model/org.rb, line 212 def generate_tf_squads @member_exception_squads.map { |s| s.generate_tf(@id) }.join("\n") end
get_acl_groups(attr: :id)
click to toggle source
# File lib/terraorg/model/org.rb, line 152 def get_acl_groups(attr: :id) # Return a LIST_NAME => [MEMBER1, MEMBER2...] hash of ACL groups { unique_name => members.map(&attr).sort }. merge(get_platoon_acl_groups(attr: attr)). merge(get_exception_squad_acl_groups(attr: attr)) end
get_exception_squad_acl_groups(attr: :id)
click to toggle source
# File lib/terraorg/model/org.rb, line 163 def get_exception_squad_acl_groups(attr: :id) @member_exception_squads.map { |p| p.get_acl_groups(@id) }.map(&attr).reduce({}, :merge) end
get_platoon_acl_groups(attr: :id)
click to toggle source
# File lib/terraorg/model/org.rb, line 159 def get_platoon_acl_groups(attr: :id) @member_platoons.map { |p| p.get_acl_groups(@id, attr: attr) }.reduce({}, :merge) end
get_platoons_md()
click to toggle source
# File lib/terraorg/model/org.rb, line 171 def get_platoons_md # 90 degree rotated version of the Platoons page of the legacy Engineering Squads sheet # Format: # Platoon,Total,Members # [ORG_HUMAN_NAME],[COUNT] # [PLAT1_HUMAN_NAME],[PLAT1_COUNT],[PLAT1_MEMBER1],[PLAT1_MEMBER2],... # [PLAT2_HUMAN_NAME],[PLAT2_COUNT],[PLAT2_MEMBER1],[PLAT2_MEMBER2],... md_lines = [ '# Engineering Platoons List', '', '|Platoon|Manager|Total|Members|', '|---|---|---|---|', "|_#{@name} Total_|#{@manager.name}|#{members.size}|", ] md_lines += @member_platoons.map(&:get_platoons_psv_row) raise "Users have left the company: #{@people.inactive.map(&:id).join(', ')}" unless @people.inactive.empty? md_lines.join("\n") end
get_squads_md()
click to toggle source
# File lib/terraorg/model/org.rb, line 191 def get_squads_md # Format: # platoon name, squad name, PM, email list, SME, slack, # people, squad manager, eng product owner, members md_lines = [ '# Engineering Squads List', '', '|Platoon|Squad|PM|Mailing list|TS SME|Slack|# Engineers|Squad Manager|Members|', '|---|---|---|---|---|---|---|---|---|', ] md_lines += @member_platoons.map { |s| s.get_squads_psv_rows(@id) } md_lines += @member_exception_squads.map { |s| s.to_md('_No Platoon_', @id) } md_lines.push "|_No Platoon_|_No Squad_|||||#{@member_exception_people.size}|||#{@member_exception_people.values.map(&:to_md).join(' / ')}|" raise "Users have left the company: #{@people.inactive.map(&:id).join(', ')}" unless @people.inactive.empty? md_lines.join("\n") end
members()
click to toggle source
# File lib/terraorg/model/org.rb, line 148 def members Set.new([@manager] + (@member_platoons + @member_exception_squads).map(&:members).flatten + @member_exception_people.map { |_, t| t.members }.flatten).to_a end
to_h()
click to toggle source
Output a canonical (sorted, formatted) version of this organization.
-
Sort the platoon names lexically
-
Sort the exception squad ids lexically
-
Sort the exception people ids lexically
# File lib/terraorg/model/org.rb, line 295 def to_h obj = { 'version' => SCHEMA_VERSION, 'id' => @id, 'name' => @name, 'manager_location' => @manager_location, 'manager' => @manager.id } obj['platoons'] = @platoons.all.map(&:id).sort if @metadata obj['metadata'] = @metadata end if @member_exception_people obj['exception_people'] = @member_exception_people.values.sort_by(&:location).map(&:to_h) end if @member_exception_squads obj['exception_squads'] = @member_exception_squads.map(&:id).sort end obj end
unique_name()
click to toggle source
# File lib/terraorg/model/org.rb, line 167 def unique_name "#{@id}-all" end
validate!(strict: true, allow_orphaned_associates: false)
click to toggle source
# File lib/terraorg/model/org.rb, line 53 def validate!(strict: true, allow_orphaned_associates: false) failure = false # Do not allow the JSON files to contain any people who have left. unless @people.inactive.empty? $stderr.puts "ERROR: Users have left the company, or are Suspended in Okta: #{@people.inactive.map(&:id).join(', ')}" failure = true end # Do not allow the org to be totally empty. if @member_platoons.size + @member_exception_squads.size == 0 $stderr.puts 'ERROR: Org has no platoons or exception squads' failure = true end # Require all platoons to be part of the org. platoon_diff = Set.new(@platoons.all_names) - Set.new(@member_platoon_names) unless platoon_diff.empty? $stderr.puts "ERROR: Platoons are not used in the org: #{platoon_diff.to_a.sort}" failure = true end # Require all squads to be used in the org. squad_diff = Set.new(@squads.all_names) - Set.new(@platoons.all_squad_names) - Set.new(@member_exception_squad_names) unless squad_diff.empty? $stderr.puts "ERROR: Squad(s) are not used in the org: #{squad_diff.to_a.sort}" failure = true end all_squads = (@member_platoons.map(&:member_squads) + @member_exception_squads).flatten seen_squads = {} # Validate that a squad is not part of more than one platoon all_squads.map(&:id).each do |id| seen_squads[id] = seen_squads.fetch(id, 0) + 1 end more_than_one_platoon = seen_squads.select do |squad, count| count > 1 end if !more_than_one_platoon.empty? $stderr.puts "ERROR: Squads are part of more than one platoon: #{more_than_one_platoon}" failure = true end # Validate that a squad member belongs to some maximum number of squads # across the entire org. A person can be an associate of other squads # at a different count. See top of file for defined limits. squad_count = {} all_members = all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:members).flatten all_members.each do |member| squad_count[member.id] = squad_count.fetch(member.id, 0) + 1 end more_than_max_squads = squad_count.select do |member, count| count > MAX_MEMBER_SQUADS_PER_PERSON end if !more_than_max_squads.empty? $stderr.puts "ERROR: People are members of more than #{MAX_MEMBER_SQUADS_PER_PERSON} squads: #{more_than_max_squads}" failure = true end associate_count = {} all_associates = all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:associates).flatten all_associates.each do |assoc| associate_count[assoc.id] = associate_count.fetch(assoc.id, 0) + 1 end more_than_max_squads = associate_count.select do |_, count| count > MAX_ASSOCIATE_SQUADS_PER_PERSON end if !more_than_max_squads.empty? $stderr.puts "ERROR: People are associates of more than #{MAX_ASSOCIATE_SQUADS_PER_PERSON} squads: #{more_than_max_squads}" failure = true end # Validate that a squad member is not also an org exception exceptions = Set.new(@member_exception_people.map { |_, t| t.members }.flatten.map(&:id)) exception_and_squad_member = squad_count.keys.select do |member| exceptions.member? member end if !exception_and_squad_member.empty? $stderr.puts "ERROR: Exception members are also squad members: #{exception_and_squad_member}" failure = true end # Validate that any associate is a member of some squad if !allow_orphaned_associates associates_but_not_members = Set.new(all_associates.map(&:id)) - Set.new(all_members.map(&:id)) - exceptions if !associates_but_not_members.empty? $stderr.puts "ERROR: #{associates_but_not_members.to_a} are associates of squads but not members of any squad" failure = true end end raise "CRITICAL: Validation failed due to at least one error above" if failure && strict end