terraorg 0.2.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47445e122d82b740189fbba1cc8179c45c14708e9d9287fa4d8c61b4b722df27
4
- data.tar.gz: b05234e33dcae32e79e1f6031a59134820e9d2689336f3dccab5ee057dde2dd1
3
+ metadata.gz: 4b911041c898541c3fbfd5b2241f6b94410e779259021c602f8273346be0cc52
4
+ data.tar.gz: 52fe26871ada42a5a1673830963f44eac7f892b9b757afcbd357108dc89495e2
5
5
  SHA512:
6
- metadata.gz: 5312e3f28bfb1b40fb62e6df66dc03034bef9431a7b64f3c944308a3bd203b8ef3ce66baa97214c5a4935d35ed2788672bf6176f1a577e98607d6514de58bc8a
7
- data.tar.gz: f2d97474041d4aafc6aba7a4255cfd52129526a042c3dff0dc0d6e2f5eeeec0449e432e7e918ad8d2a7b5930c7976476bef435acd3986ef2ea396992e08b818c
6
+ metadata.gz: 6ab26716f6708453cd347283174b8026db71996536f6f0c9b2d2633eb40b4442ddd4b459465def0fb0667294b18095d22df2ca5cdbdc8855fb220fdebdc820a4
7
+ data.tar.gz: 232ca6a681bd5429fe483105d9e4a25586eace20e672e2e7998d9c36d9e9bb14a9b47949567f53a59ca765ca12e0c4c6b41764b55ab9117febbb017ef8352ba3
data/README.md CHANGED
@@ -34,7 +34,9 @@ Based on the org that this tool was originally designed for, orgs are expected
34
34
  to have three levels:
35
35
 
36
36
  * *squads*: the base unit of team-dom, containing people, who may be in
37
- different geographical regions.
37
+ different geographical regions. Teams contain _members_ (full time heads)
38
+ and _associates_ (typically part time floaters.) Any associate of a squad
39
+ must also have a home squad for which they are a full time member.
38
40
  * *platoons*: a unit which contains squads and exceptional people who are
39
41
  members of the platoon, but not part of any squad
40
42
  * *org*: The whole organization, including its manager, any exceptional squads
@@ -45,6 +47,10 @@ The tool generates groups for each granular unit of organization in Okta and G
45
47
  Suite in Terraform. With patching, it could be possible for more organizational
46
48
  systems to be supported.
47
49
 
50
+ ## Diagram
51
+
52
+ ![Diagram of org structure](img/diagram.png)
53
+
48
54
  ## How it works
49
55
 
50
56
  Firstly, take your entire existing organization and define it using the
@@ -120,6 +126,10 @@ information on how to configure the providers.
120
126
  [articulate/terraform-provider-okta]: https://github.com/articulate/terraform-provider-okta
121
127
  [DeviaVir/terraform-provider-gsuite]: https://github.com/DeviaVir/terraform-provider-gsuite
122
128
 
129
+ ## Running tests
130
+ There are a limited number of tests that can be invoked with
131
+ `ruby -I lib test/terraorg/model/org_test.rb `
132
+
123
133
  ## Suggested process
124
134
 
125
135
  At [LiveRamp], a pull request based workflow leveraging [Atlantis] is used to
@@ -31,14 +31,16 @@ ACTIONS = [
31
31
  'validate'
32
32
  ].freeze
33
33
 
34
+ STRICT_VALIDATION = ENV.fetch('TERRAORG_STRICT_VALIDATION', 'true')
35
+ ALLOW_ORPHANED_ASSOCIATES = ENV.fetch('ALLOW_ORPHANED_ASSOCIATES', 'false')
34
36
  SQUADS_FILE = ENV.fetch('TERRAORG_SQUADS', 'squads.json')
35
37
  PLATOONS_FILE = ENV.fetch('TERRAORG_PLATOONS', 'platoons.json')
36
38
  ORG_FILE = ENV.fetch('TERRAORG_ROOT', 'org.json')
37
39
  CACHE_FILE = ENV.fetch('TERRAORG_OKTA_CACHE', 'okta_cache.json')
38
- GSUITE_DOMAIN = ENV.fetch('GSUITE_DOMAIN')
39
- SLACK_DOMAIN = ENV.fetch('SLACK_DOMAIN', 'yourcompany.slack.com')
40
- OKTA_ORG_NAME = ENV.fetch('OKTA_ORG_NAME')
41
- OKTA_API_TOKEN = ENV.fetch('OKTA_API_TOKEN')
40
+ GSUITE_DOMAIN = ENV.fetch('GSUITE_DOMAIN', 'fillmein_gsuite.com')
41
+ SLACK_DOMAIN = ENV.fetch('SLACK_DOMAIN', 'fillmein.slack.com')
42
+ OKTA_ORG_NAME = ENV.fetch('OKTA_ORG_NAME', 'fillmein_okta')
43
+ OKTA_API_TOKEN = ENV.fetch('OKTA_API_TOKEN', 'fillmein_okta_api_token')
42
44
  OKTA_BASE_URL = ENV.fetch('OKTA_BASE_URL', 'okta.com')
43
45
 
44
46
  action = ARGV[0]
@@ -95,7 +97,9 @@ platoons = Platoons.new(JSON.parse(platoons_data), squads, people, GSUITE_DOMAIN
95
97
  org_data = File.read(ORG_FILE)
96
98
  org = Org.new(JSON.parse(org_data), platoons, squads, people, GSUITE_DOMAIN)
97
99
 
98
- org.validate!
100
+ strict = (STRICT_VALIDATION == 'true')
101
+ allow_orphaned_associates = (ALLOW_ORPHANED_ASSOCIATES == 'true')
102
+ org.validate!(strict: strict, allow_orphaned_associates: allow_orphaned_associates)
99
103
 
100
104
  case action
101
105
  when 'generate-squads-md'
@@ -1,4 +1,5 @@
1
1
  # Copyright 2019-2020 LiveRamp Holdings, Inc.
2
+ # Copyright 2020- Joshua Kwan
2
3
  #
3
4
  # Licensed under the Apache License, Version 2.0 (the "License");
4
5
  # you may not use this file except in compliance with the License.
@@ -49,23 +50,33 @@ class Org
49
50
  @squads = squads
50
51
  end
51
52
 
52
- def validate!
53
+ def validate!(strict: true, allow_orphaned_associates: false)
54
+ failure = false
55
+
53
56
  # Do not allow the JSON files to contain any people who have left.
54
- raise "Users have left the company: #{@people.inactive.map(&:id).join(', ')}" unless @people.inactive.empty?
57
+ unless @people.inactive.empty?
58
+ $stderr.puts "ERROR: Users have left the company, or are Suspended in Okta: #{@people.inactive.map(&:id).join(', ')}"
59
+ failure = true
60
+ end
55
61
 
56
62
  # Do not allow the org to be totally empty.
57
- raise 'Org has no platoons or exception squads' if @member_platoons.size + @member_exception_squads.size == 0
63
+ if @member_platoons.size + @member_exception_squads.size == 0
64
+ $stderr.puts 'ERROR: Org has no platoons or exception squads'
65
+ failure = true
66
+ end
58
67
 
59
68
  # Require all platoons to be part of the org.
60
69
  platoon_diff = Set.new(@platoons.all_names) - Set.new(@member_platoon_names)
61
70
  unless platoon_diff.empty?
62
- raise "Platoons are not used in the org: #{platoon_diff.to_a.sort}"
71
+ $stderr.puts "ERROR: Platoons are not used in the org: #{platoon_diff.to_a.sort}"
72
+ failure = true
63
73
  end
64
74
 
65
75
  # Require all squads to be used in the org.
66
76
  squad_diff = Set.new(@squads.all_names) - Set.new(@platoons.all_squad_names) - Set.new(@member_exception_squad_names)
67
77
  unless squad_diff.empty?
68
- raise "Squad(s) are not used in the org: #{squad_diff.to_a.sort}"
78
+ $stderr.puts "ERROR: Squad(s) are not used in the org: #{squad_diff.to_a.sort}"
79
+ failure = true
69
80
  end
70
81
 
71
82
  all_squads = (@member_platoons.map(&:member_squads) + @member_exception_squads).flatten
@@ -79,34 +90,37 @@ class Org
79
90
  count > 1
80
91
  end
81
92
  if !more_than_one_platoon.empty?
82
- raise "Squads are part of more than one platoon: #{more_than_one_platoon}"
93
+ $stderr.puts "ERROR: Squads are part of more than one platoon: #{more_than_one_platoon}"
94
+ failure = true
83
95
  end
84
96
 
85
97
  # Validate that a squad member belongs to some maximum number of squads
86
98
  # across the entire org. A person can be an associate of other squads
87
99
  # at a different count. See top of file for defined limits.
88
100
  squad_count = {}
89
- all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:members).flatten.each do |member|
101
+ all_members = all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:members).flatten
102
+ all_members.each do |member|
90
103
  squad_count[member.id] = squad_count.fetch(member.id, 0) + 1
91
104
  end
92
105
  more_than_max_squads = squad_count.select do |member, count|
93
106
  count > MAX_MEMBER_SQUADS_PER_PERSON
94
107
  end
95
108
  if !more_than_max_squads.empty?
96
- # TODO(joshk): Enforce after April 17th
97
- $stderr.puts "WARNING: Members are part of more than #{MAX_MEMBER_SQUADS_PER_PERSON} squads: #{more_than_max_squads}"
109
+ $stderr.puts "ERROR: People are members of more than #{MAX_MEMBER_SQUADS_PER_PERSON} squads: #{more_than_max_squads}"
110
+ failure = true
98
111
  end
99
112
 
100
113
  associate_count = {}
101
- all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:associates).flatten.each do |assoc|
114
+ all_associates = all_squads.map(&:teams).flatten.map(&:values).flatten.map(&:associates).flatten
115
+ all_associates.each do |assoc|
102
116
  associate_count[assoc.id] = associate_count.fetch(assoc.id, 0) + 1
103
117
  end
104
118
  more_than_max_squads = associate_count.select do |_, count|
105
119
  count > MAX_ASSOCIATE_SQUADS_PER_PERSON
106
120
  end
107
121
  if !more_than_max_squads.empty?
108
- # TODO(joshk): Enforce after April 17th
109
- $stderr.puts "WARNING: People associated with more than #{MAX_ASSOCIATE_SQUADS_PER_PERSON} squads: #{more_than_max_squads}"
122
+ $stderr.puts "ERROR: People are associates of more than #{MAX_ASSOCIATE_SQUADS_PER_PERSON} squads: #{more_than_max_squads}"
123
+ failure = true
110
124
  end
111
125
 
112
126
  # Validate that a squad member is not also an org exception
@@ -115,8 +129,20 @@ class Org
115
129
  exceptions.member? member
116
130
  end
117
131
  if !exception_and_squad_member.empty?
118
- raise "Exception members are also squad members: #{exception_and_squad_member}"
132
+ $stderr.puts "ERROR: Exception members are also squad members: #{exception_and_squad_member}"
133
+ failure = true
119
134
  end
135
+
136
+ # Validate that any associate is a member of some squad
137
+ if !allow_orphaned_associates
138
+ associates_but_not_members = Set.new(all_associates.map(&:id)) - Set.new(all_members.map(&:id)) - exceptions
139
+ if !associates_but_not_members.empty?
140
+ $stderr.puts "ERROR: #{associates_but_not_members.to_a} are associates of squads but not members of any squad"
141
+ failure = true
142
+ end
143
+ end
144
+
145
+ raise "CRITICAL: Validation failed due to at least one error above" if failure && strict
120
146
  end
121
147
 
122
148
  def members
@@ -169,7 +195,7 @@ class Org
169
195
  '# Engineering Squads List',
170
196
  '',
171
197
  '|Platoon|Squad|PM|Mailing list|TS SME|Slack|# Engineers|Squad Manager|Members|',
172
- '|---|---|---|---|---|---|---|---|---|---|',
198
+ '|---|---|---|---|---|---|---|---|---|',
173
199
  ]
174
200
  md_lines += @member_platoons.map { |s| s.get_squads_psv_rows(@id) }
175
201
  md_lines += @member_exception_squads.map { |s| s.to_md('_No Platoon_', @id) }
@@ -179,13 +205,16 @@ class Org
179
205
  md_lines.join("\n")
180
206
  end
181
207
 
182
- def generate_tf
183
- tf = @member_platoons.map { |p| p.generate_tf(@id) }.join("\n")
184
- File.write('auto.platoons.tf', tf)
208
+ def generate_tf_platoons
209
+ @member_platoons.map { |p| p.generate_tf(@id) }.join("\n")
210
+ end
185
211
 
186
- tf = @member_exception_squads.map { |s| s.generate_tf(@id) }.join("\n")
187
- File.write('auto.exception_squads.tf', tf)
212
+ def generate_tf_squads
213
+ @member_exception_squads.map { |s| s.generate_tf(@id) }.join("\n")
214
+ end
188
215
 
216
+ def generate_tf_org
217
+ tf = ''
189
218
  # Roll all platoons and exception squads into the org.
190
219
  roll_up_to_org = \
191
220
  @member_exception_squads.map { |s| s.unique_name(@id, nil) } + \
@@ -225,14 +254,18 @@ EOF
225
254
  all_locations[@manager_location] = all_locations.fetch(@manager_location, Set.new).add(@manager)
226
255
 
227
256
  all_locations.each do |l, m|
257
+ description = "#{@name} organization members based in #{l} (terraorg)"
228
258
  name = "#{unique_name}-#{l.downcase}"
229
259
  tf += <<-EOF
230
260
  resource "okta_group" "#{name}" {
231
261
  name = "#{name}"
232
- description = "#{@name} organization members based in #{l} (terraorg)"
262
+ description = "#{description}"
233
263
  users = #{Util.persons_tf(m)}
234
264
  }
265
+
266
+ #{Util.gsuite_group_tf(name, @gsuite_domain, m, description)}
235
267
  EOF
268
+
236
269
  end
237
270
 
238
271
  # Generate a special GSuite group for all managers (org, platoon, squad
@@ -241,7 +274,17 @@ EOF
241
274
  all_managers = Set.new([@manager] + @platoons.all.map(&:manager) + @squads.all.map(&:manager).select { |m| m })
242
275
  manager_dl = "#{@id}-managers"
243
276
  tf += Util.gsuite_group_tf(manager_dl, @gsuite_domain, all_managers, "All managers of the #{@name} organization (terraorg)")
277
+ tf
278
+ end
279
+
280
+ def generate_tf
281
+ tf = generate_tf_platoons
282
+ File.write('auto.platoons.tf', tf)
283
+
284
+ tf = generate_tf_squads
285
+ File.write('auto.exception_squads.tf', tf)
244
286
 
287
+ tf = generate_tf_org
245
288
  File.write('auto.org.tf', tf)
246
289
  end
247
290
 
@@ -13,5 +13,5 @@
13
13
  # limitations under the License.
14
14
 
15
15
  module Terraorg
16
- VERSION = '0.2.1'
16
+ VERSION = '0.5.2'
17
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraorg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Kwan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-09 00:00:00.000000000 Z
11
+ date: 2020-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: countries
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.14'
69
83
  description: Manage an organizational structure with Okta and G-Suite using Terraform
70
84
  email: joshk@triplehelix.org
71
85
  executables:
@@ -104,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
118
  - !ruby/object:Gem::Version
105
119
  version: '0'
106
120
  requirements: []
107
- rubygems_version: 3.0.3
121
+ rubygems_version: 3.0.8
108
122
  signing_key:
109
123
  specification_version: 4
110
124
  summary: terraorg