terraorg 0.2.3 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/bin/terraorg +5 -1
- data/lib/terraorg/model/org.rb +62 -19
- data/lib/terraorg/model/person.rb +1 -1
- data/lib/terraorg/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '081a5e9b4201f219e7b153a554a57e32060a8c043b2769d8bd9199d25f871850'
|
4
|
+
data.tar.gz: f6be12775e8435085b35da3bdc0b66af701705ffc9c6d0de45a6cc47cc237737
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ca99240c4e75a63c6af0e7ff83efe3994dc3da8ac3072a524d3c845a5c99f68211bc189aafe53ed03593a9c748d7179d5f92bc67b4c15cbdc33c1ca3c632c71
|
7
|
+
data.tar.gz: dcb7597efd163ca22e17968c81cd80dd599ddaa98acf304972e25ed5fafe6c9745f15ec143878c6f3898ed415393caaf109b25d5312953fc9fe6393936b0a0db
|
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
|
data/bin/terraorg
CHANGED
@@ -31,6 +31,8 @@ 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')
|
@@ -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
|
-
|
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'
|
data/lib/terraorg/model/org.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
97
|
-
|
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
|
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
|
-
|
109
|
-
|
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
|
-
|
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
|
@@ -179,13 +205,16 @@ class Org
|
|
179
205
|
md_lines.join("\n")
|
180
206
|
end
|
181
207
|
|
182
|
-
def
|
183
|
-
|
184
|
-
|
208
|
+
def generate_tf_platoons
|
209
|
+
@member_platoons.map { |p| p.generate_tf(@id) }.join("\n")
|
210
|
+
end
|
185
211
|
|
186
|
-
|
187
|
-
|
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 = "#{
|
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
|
|
data/lib/terraorg/version.rb
CHANGED
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.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Kwan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-11 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.
|
121
|
+
rubygems_version: 3.0.8
|
108
122
|
signing_key:
|
109
123
|
specification_version: 4
|
110
124
|
summary: terraorg
|