cloudstrap-azure 0.4.6.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +57 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.org +37 -0
- data/cloudstrap-azure.gemspec +40 -0
- data/command/cloudstrap-azure +13 -0
- data/command/internal/cloudstrap-azure.configure +201 -0
- data/command/internal/cloudstrap-azure.deploy +478 -0
- data/command/internal/cloudstrap-azure.environment +140 -0
- data/trust/certificates/colstrom.pem +25 -0
- metadata +390 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ca27473cedeb9289319e9ae294bd2f4a6e86637ad47a6c0ee969379db4458747
|
4
|
+
data.tar.gz: 9593998936bd33a2706017452203d52f0d06d09a46ed7d92f3b026f5f0692e07
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c205b22ad86de57064e64a21f5d46bc6cea9d2af73110dfeb9cc94c95d68621a850e34cc75fbe92ff8a1fd784b7107e25a3e67be2f4bf71e772d820c9028378c
|
7
|
+
data.tar.gz: 6acdf0b6a0870714d9f2bc5f95f9361b0c8c2ff431b3df75a448474c912a4c3f9a0a36978ff81432a285232ac07cfa1de97deaa34804522af57c3fce17b586ce
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
# Created by https://www.gitignore.io/api/ruby
|
3
|
+
|
4
|
+
### Ruby ###
|
5
|
+
*.gem
|
6
|
+
*.rbc
|
7
|
+
/.config
|
8
|
+
/coverage/
|
9
|
+
/InstalledFiles
|
10
|
+
/pkg/
|
11
|
+
/spec/reports/
|
12
|
+
/spec/examples.txt
|
13
|
+
/test/tmp/
|
14
|
+
/test/version_tmp/
|
15
|
+
/tmp/
|
16
|
+
|
17
|
+
# Used by dotenv library to load environment variables.
|
18
|
+
# .env
|
19
|
+
|
20
|
+
## Specific to RubyMotion:
|
21
|
+
.dat*
|
22
|
+
.repl_history
|
23
|
+
build/
|
24
|
+
*.bridgesupport
|
25
|
+
build-iPhoneOS/
|
26
|
+
build-iPhoneSimulator/
|
27
|
+
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
29
|
+
#
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
33
|
+
#
|
34
|
+
# vendor/Pods/
|
35
|
+
|
36
|
+
## Documentation cache and generated files:
|
37
|
+
/.yardoc/
|
38
|
+
/_yardoc/
|
39
|
+
/doc/
|
40
|
+
/rdoc/
|
41
|
+
|
42
|
+
## Environment normalization:
|
43
|
+
/.bundle/
|
44
|
+
/vendor/bundle
|
45
|
+
/lib/bundler/man/
|
46
|
+
|
47
|
+
# for a library or gem, you might want to ignore these files since the code is
|
48
|
+
# intended to run in multiple environments; otherwise, check them in:
|
49
|
+
# Gemfile.lock
|
50
|
+
# .ruby-version
|
51
|
+
# .ruby-gemset
|
52
|
+
|
53
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
54
|
+
.rvmrc
|
55
|
+
|
56
|
+
|
57
|
+
# End of https://www.gitignore.io/api/ruby
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
The MIT License (MIT)
|
3
|
+
Copyright © 2018 Chris Olstrom <chris@olstrom.com>
|
4
|
+
Copyright © 2018 SUSE LLC
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the “Software”), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.org
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#+TITLE: cloudstrap-azure
|
2
|
+
#+LATEX: \pagebreak
|
3
|
+
|
4
|
+
* Overview
|
5
|
+
|
6
|
+
~cloudstrap-azure~ deploys SCF to ACS.
|
7
|
+
|
8
|
+
* Prerequisites
|
9
|
+
|
10
|
+
- Logged in to Azure via ~az login~ at least once.
|
11
|
+
|
12
|
+
* Commands
|
13
|
+
|
14
|
+
** cloudstrap-azure environment
|
15
|
+
|
16
|
+
Deals with authentication for the Azure and Microsoft Graph services.
|
17
|
+
|
18
|
+
- Given no arguments, prints JSON containing access tokens to STDOUT.
|
19
|
+
- Given some arguments, sets up an environment containing access tokens, then execs into its arguments.
|
20
|
+
|
21
|
+
** cloudstrap-azure configure
|
22
|
+
|
23
|
+
An interactive configuration tool for ~cloudstrap-azure~. Requires access
|
24
|
+
tokens in the environment, as produced by the ~environment~ command.
|
25
|
+
|
26
|
+
** cloudstrap-azure deploy
|
27
|
+
|
28
|
+
A tool for deploying SCF on ACS. Requires access tokens and configuration.
|
29
|
+
|
30
|
+
* License
|
31
|
+
|
32
|
+
~cloudstrap-azure~ is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the
|
33
|
+
full text.
|
34
|
+
|
35
|
+
* Contributors
|
36
|
+
|
37
|
+
- [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
tag = `git describe --tags --abbrev=0`.chomp
|
3
|
+
|
4
|
+
gem.name = 'cloudstrap-azure'
|
5
|
+
gem.homepage = 'https://github.com/colstrom/cloudstrap-azure'
|
6
|
+
gem.summary = 'Cloudstrap for Azure'
|
7
|
+
|
8
|
+
gem.version = "#{tag}.pre"
|
9
|
+
gem.licenses = ['MIT']
|
10
|
+
gem.authors = ['Chris Olstrom']
|
11
|
+
gem.email = 'chris@olstrom.com'
|
12
|
+
|
13
|
+
gem.cert_chain = ['trust/certificates/colstrom.pem']
|
14
|
+
gem.signing_key = File.expand_path ENV.fetch 'GEM_SIGNING_KEY'
|
15
|
+
|
16
|
+
gem.files = `git ls-files -z`.split("\x0")
|
17
|
+
gem.test_files = `git ls-files -z -- {test,spec,features}/*`.split("\x0")
|
18
|
+
gem.executables = `git ls-files -z -- command/[^internal/]*`.split("\x0").map { |f| File.basename(f) }
|
19
|
+
|
20
|
+
gem.require_paths = ['lib']
|
21
|
+
gem.bindir = 'command'
|
22
|
+
|
23
|
+
gem.add_runtime_dependency 'azure_graph_rbac', '~> 0.16', '>= 0.16.0'
|
24
|
+
gem.add_runtime_dependency 'azure_mgmt_authorization' '~> 0.17', '>= 0.17.0'
|
25
|
+
gem.add_runtime_dependency 'azure_mgmt_compute' '~> 0.17', '>= 0.17.0'
|
26
|
+
gem.add_runtime_dependency 'azure_mgmt_container_service', '~> 0.16', '>= 0.16.0'
|
27
|
+
gem.add_runtime_dependency 'azure_mgmt_network', '~> 0.16', '>= 0.16.0'
|
28
|
+
gem.add_runtime_dependency 'azure_mgmt_resources' '~> 0.16', '>= 0.16.0'
|
29
|
+
gem.add_runtime_dependency 'azure_mgmt_subscriptions' '~> 0.16', '>= 0.16.0'
|
30
|
+
gem.add_runtime_dependency 'chamber', '~> 2.12', '>= 2.12.0'
|
31
|
+
gem.add_runtime_dependency 'chronic', '~> 0.10', '>= 0.10.0'
|
32
|
+
gem.add_runtime_dependency 'concurrent-ruby', '~> 1.0', '>= 1.0.5'
|
33
|
+
gem.add_runtime_dependency 'pastel', '~> 0.7', '>= 0.7.0'
|
34
|
+
gem.add_runtime_dependency 'sshkey' '~> 1.9', '>= 1.9.0'
|
35
|
+
gem.add_runtime_dependency 'tty-prompt', '~> 0.16', '>= 0.16.0'
|
36
|
+
gem.add_runtime_dependency 'tty-spinner', '~> 0.8', '>= 0.8.0'
|
37
|
+
gem.add_runtime_dependency 'tty-which', '~> 0.3', '>= 0.3.0'
|
38
|
+
gem.add_runtime_dependency 'uuid', '~> 2.3', '>= 2.3.8'
|
39
|
+
gem.add_runtime_dependency 'xxhash', '~> 0.4', '>= 0.4.0'
|
40
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
PATH = [
|
5
|
+
File.expand_path(File.join(__dir__, 'internal')),
|
6
|
+
ENV['PATH']
|
7
|
+
].join(':')
|
8
|
+
|
9
|
+
PROGRAM = File.basename($PROGRAM_NAME)
|
10
|
+
|
11
|
+
abort unless (COMMAND = ARGV.shift)
|
12
|
+
|
13
|
+
exec({ 'PATH' => PATH }, "#{PROGRAM}.#{COMMAND}", *ARGV)
|
@@ -0,0 +1,201 @@
|
|
1
|
+
#! /usr/bin/env ruby -W0
|
2
|
+
# coding: utf-8
|
3
|
+
# -*- ruby -*-
|
4
|
+
|
5
|
+
require 'securerandom' # Ruby Standard Library
|
6
|
+
require 'yaml' # Ruby Standard Library
|
7
|
+
|
8
|
+
require 'azure_mgmt_container_service' # MIT License
|
9
|
+
require 'azure_mgmt_subscriptions' # MIT License
|
10
|
+
require 'chamber' # MIT License
|
11
|
+
require 'chronic' # MIT License
|
12
|
+
require 'pastel' # MIT License
|
13
|
+
require 'sshkey' # MIT License
|
14
|
+
require 'tty-prompt' # MIT License
|
15
|
+
require 'uuid' # MIT License
|
16
|
+
require 'xxhash' # MIT License
|
17
|
+
|
18
|
+
####################
|
19
|
+
# Helper Functions #
|
20
|
+
####################
|
21
|
+
|
22
|
+
ConstantsToHash = (
|
23
|
+
->(object) {
|
24
|
+
object.constants.zip(
|
25
|
+
object.constants.map { |constant|
|
26
|
+
object.const_get(
|
27
|
+
constant) }).to_h })
|
28
|
+
|
29
|
+
SelectFromMenu = (
|
30
|
+
->(question, choices, default: nil) {
|
31
|
+
TTY::Prompt.new.select(question, filter: true) { |menu|
|
32
|
+
(choices.is_a?(Hash) ? choices : choices.zip(choices).to_h).each { |text, value| menu.choice(text, value) }
|
33
|
+
(choices.is_a?(Hash) ? choices.values : choices).index(default).tap { |index| menu.default(1+index) if index }}})
|
34
|
+
|
35
|
+
Ask = (
|
36
|
+
->(question, default: nil) {
|
37
|
+
TTY::Prompt.new.ask(question) { |ask| ask.default(default) if default }})
|
38
|
+
|
39
|
+
AskDate = (
|
40
|
+
->(question, default: nil) {
|
41
|
+
(Chronic.parse(Ask.(question, default: default)) || AskDate.(question, default: default)).utc })
|
42
|
+
|
43
|
+
AskSecret = (
|
44
|
+
->(question, default: nil) {
|
45
|
+
TTY::Prompt.new.mask(question) { |ask| ask.default(default) if default }})
|
46
|
+
|
47
|
+
Slider = (
|
48
|
+
->(question, **options) {
|
49
|
+
TTY::Prompt.new.slider(question, options) })
|
50
|
+
|
51
|
+
Confirm = (
|
52
|
+
->(question) {
|
53
|
+
TTY::Prompt.new.yes?(question)})
|
54
|
+
|
55
|
+
Warn = (
|
56
|
+
->(message) {
|
57
|
+
TTY::Prompt.new.warn(message)})
|
58
|
+
|
59
|
+
Error = (
|
60
|
+
->(message) {
|
61
|
+
TTY::Prompt.new.error(message)})
|
62
|
+
|
63
|
+
Bold = (
|
64
|
+
->(string) {
|
65
|
+
Pastel.new.bold(string)})
|
66
|
+
|
67
|
+
InitialSetup = -> {
|
68
|
+
File.write('settings.yml', '---')
|
69
|
+
%x(chamber init)
|
70
|
+
Chamber.load}
|
71
|
+
|
72
|
+
#################
|
73
|
+
# Sanity Checks #
|
74
|
+
#################
|
75
|
+
|
76
|
+
PROGRAM = "cloudstrap-azure"
|
77
|
+
|
78
|
+
ENV.fetch('MANAGEMENT_AZURE_COM_ACCESS_TOKEN') {
|
79
|
+
Error.("Access Token for #{Bold.('management.azure.com')} not found in Environment
|
80
|
+
|
81
|
+
If you have already logged in to Azure using the #{Bold.('az login')} command,
|
82
|
+
you can use #{Bold.(PROGRAM)}'s environment wrapper:
|
83
|
+
")
|
84
|
+
STDERR.puts Bold.("#{PROGRAM} environment -- #{PROGRAM} configure\n")
|
85
|
+
exit Errno::EINVAL::Errno}
|
86
|
+
|
87
|
+
begin
|
88
|
+
unless File.exist?('settings.yml')
|
89
|
+
Warn.("It looks like this is your first time running #{Bold.(PROGRAM)}.
|
90
|
+
|
91
|
+
Do you want to run the initial setup? This will create several files in the
|
92
|
+
current directory, including encryption keys and configuration files.
|
93
|
+
")
|
94
|
+
|
95
|
+
InitialSetup.call if Confirm.("Write files to #{Bold.(Dir.pwd)}?")
|
96
|
+
end
|
97
|
+
rescue Interrupt, TTY::Reader::InputInterrupt
|
98
|
+
exit Errno::EINTR::Errno
|
99
|
+
end
|
100
|
+
|
101
|
+
#############
|
102
|
+
# Constants #
|
103
|
+
#############
|
104
|
+
|
105
|
+
ORCHESTRATOR_TYPES = (
|
106
|
+
ConstantsToHash.(
|
107
|
+
Azure::ContainerService::Mgmt::V2017_01_31::Models::ContainerServiceOrchestratorTypes))
|
108
|
+
|
109
|
+
VM_SIZES = (
|
110
|
+
ConstantsToHash.(
|
111
|
+
Azure::ContainerService::Mgmt::V2017_01_31::Models::ContainerServiceVMSizeTypes))
|
112
|
+
|
113
|
+
AzureSubscriptionsAPI = (
|
114
|
+
::Azure::Subscriptions::Mgmt::V2016_06_01::SubscriptionClient.new(
|
115
|
+
::MsRest::TokenCredentials.new(
|
116
|
+
ENV.fetch('MANAGEMENT_AZURE_COM_ACCESS_TOKEN'))))
|
117
|
+
|
118
|
+
AvailableSubscriptions = (
|
119
|
+
->(client) {
|
120
|
+
client
|
121
|
+
.subscriptions
|
122
|
+
.list
|
123
|
+
.map { |subscription| [subscription.display_name, subscription.subscription_id] }
|
124
|
+
.to_h })
|
125
|
+
|
126
|
+
AvailableTenants = (
|
127
|
+
->(client) {
|
128
|
+
client
|
129
|
+
.tenants
|
130
|
+
.list
|
131
|
+
.map(&:tenant_id) })
|
132
|
+
|
133
|
+
AvailableLocations = (
|
134
|
+
->(client, subscription_id) {
|
135
|
+
client
|
136
|
+
.subscriptions
|
137
|
+
.list_locations(subscription_id)
|
138
|
+
.value
|
139
|
+
.map { |location| [location.display_name, location.name] }
|
140
|
+
.to_h })
|
141
|
+
|
142
|
+
DEFAULTS = {
|
143
|
+
'tenant_id' => ENV['AZURE_TENANT_ID'],
|
144
|
+
'subscription_id' => ENV['AZURE_SUBSCRIPTION_ID'],
|
145
|
+
'location' => 'westus',
|
146
|
+
'orchestrator_type' => 'Kubernetes',
|
147
|
+
'vm_size' => 'Standard_D2_v2',
|
148
|
+
'role_definition' => 'Contributor',
|
149
|
+
'admin_username' => 'scf-admin',
|
150
|
+
'agent_count' => 3,
|
151
|
+
'deployment_name' => 'cloudstrap',
|
152
|
+
'master_dns_suffix' => 'master',
|
153
|
+
'agent_dns_suffix' => 'agent',
|
154
|
+
'uuid' => UUID.generate,
|
155
|
+
'credential_end_date' => 'two weeks from now',
|
156
|
+
'_secure_ssh_private_key' => SSHKey.generate.private_key,
|
157
|
+
'_secure_password' => SecureRandom.uuid,
|
158
|
+
}
|
159
|
+
|
160
|
+
LOADED = DEFAULTS.merge(Chamber.env.to_hash)
|
161
|
+
|
162
|
+
SECRETS = Set.new(Chamber.env.securable.keys)
|
163
|
+
SECRETS.each { |key| LOADED["_secure_#{key}"] = LOADED.delete(key) }
|
164
|
+
|
165
|
+
################
|
166
|
+
# Main Program #
|
167
|
+
################
|
168
|
+
|
169
|
+
begin
|
170
|
+
subscription_id = SelectFromMenu.('Subscription ID:', AvailableSubscriptions.(AzureSubscriptionsAPI), default: LOADED['subscription_id'])
|
171
|
+
|
172
|
+
INTERACTIVE = {
|
173
|
+
'tenant_id' => SelectFromMenu.('Tenant ID:', AvailableTenants.(AzureSubscriptionsAPI), default: LOADED['tenant_id']),
|
174
|
+
'location' => SelectFromMenu.('Location:', AvailableLocations.(AzureSubscriptionsAPI, subscription_id), default: LOADED['location']),
|
175
|
+
'orchestrator_type' => SelectFromMenu.('Orchestrator Type:', ORCHESTRATOR_TYPES, default: LOADED['orchestrator_type']),
|
176
|
+
'vm_size' => SelectFromMenu.('VM Size:', VM_SIZES, default: LOADED['vm_size']),
|
177
|
+
'agent_count' => Slider.('Agent Count:', min: 1, max: 5, step: 2),
|
178
|
+
'uuid' => Ask.('UUID', default: LOADED['uuid']),
|
179
|
+
'deployment_name' => Ask.('Deployment Name:', default: LOADED['deployment_name']),
|
180
|
+
'role_definition' => Ask.('Role Definition:', default: LOADED['role_definition']),
|
181
|
+
'admin_username' => Ask.('Admin Username:', default: LOADED['admin_username']),
|
182
|
+
'master_dns_suffix' => Ask.('Master DNS Suffix:', default: LOADED['master_dns_suffix']),
|
183
|
+
'agent_dns_suffix' => Ask.('Agent DNS Suffix:', default: LOADED['agent_dns_suffix']),
|
184
|
+
'_secure_password' => AskSecret.('Password:', default: LOADED['_secure_password']),
|
185
|
+
'_secure_ssh_private_key' => AskSecret.('SSH Private Key:', default: LOADED['_secure_ssh_private_key']),
|
186
|
+
'credential_end_date' => AskDate.('Password Valid Until:', default: LOADED['credential_end_date']),
|
187
|
+
}.merge({'subscription_id' => subscription_id})
|
188
|
+
|
189
|
+
FINAL = LOADED.merge INTERACTIVE
|
190
|
+
FINAL['identifier'] = [FINAL['deployment_name'], FINAL['uuid']].join('.')
|
191
|
+
FINAL['dns_prefix'] = [FINAL['deployment_name'], XXhash.xxh32(FINAL['identifier'])].join('-')
|
192
|
+
FINAL['credential_end_date'] = Chronic.parse(FINAL['credential_end_date'].iso8601).utc.iso8601
|
193
|
+
|
194
|
+
yaml = YAML.dump Chamber.instance.encrypt(FINAL.reject { |_, v| v.nil? }.sort.to_h)
|
195
|
+
saving = File.expand_path(File.join(Dir.pwd, 'settings.yml'))
|
196
|
+
|
197
|
+
puts Pastel.new.bold "\n#{yaml}"
|
198
|
+
File.write(saving, yaml) if Confirm.("Save to #{saving}?")
|
199
|
+
rescue Interrupt, TTY::Reader::InputInterrupt
|
200
|
+
exit Errno::EINTR::Errno
|
201
|
+
end
|
@@ -0,0 +1,478 @@
|
|
1
|
+
#! /usr/bin/env ruby -W0
|
2
|
+
# coding: utf-8
|
3
|
+
# -*- ruby -*-
|
4
|
+
|
5
|
+
require 'time' # Ruby Standard Library
|
6
|
+
|
7
|
+
require 'azure_graph_rbac' # MIT License
|
8
|
+
require 'azure_mgmt_authorization' # MIT License
|
9
|
+
require 'azure_mgmt_compute' # MIT License
|
10
|
+
require 'azure_mgmt_container_service' # MIT License
|
11
|
+
require 'azure_mgmt_network' # MIT License
|
12
|
+
require 'azure_mgmt_resources' # MIT License
|
13
|
+
require 'chamber' # MIT License
|
14
|
+
require 'concurrent' # MIT License
|
15
|
+
require 'pastel' # MIT License
|
16
|
+
require 'sshkey' # MIT License
|
17
|
+
require 'tty-prompt' # MIT License
|
18
|
+
require 'tty-spinner' # MIT License
|
19
|
+
|
20
|
+
#############
|
21
|
+
# Constants #
|
22
|
+
#############
|
23
|
+
|
24
|
+
CREDENTIALS = {
|
25
|
+
'https://graph.windows.net' => (
|
26
|
+
MsRest::TokenCredentials.new(
|
27
|
+
ENV.fetch('GRAPH_WINDOWS_NET_ACCESS_TOKEN'))),
|
28
|
+
'https://management.azure.com' => (
|
29
|
+
MsRest::TokenCredentials.new(
|
30
|
+
ENV.fetch('MANAGEMENT_AZURE_COM_ACCESS_TOKEN'))),
|
31
|
+
}
|
32
|
+
|
33
|
+
SUBSCRIPTION_ID = Chamber.env.subscription_id
|
34
|
+
TENANT_ID = Chamber.env.tenant_id
|
35
|
+
LOCATION = Chamber.env.location
|
36
|
+
|
37
|
+
SPINNER_FORMAT = (Chamber.env[:spinner] || :arrow_pulse).to_sym
|
38
|
+
|
39
|
+
ENABLE_SWAP_ACCOUNTING = %q{sudo sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT=\"console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300\"/GRUB_CMDLINE_LINUX_DEFAULT=\"console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 swapaccount=1\"/g' /etc/default/grub.d/50-cloudimg-settings.cfg}
|
40
|
+
UPDATE_GRUB = %q{sudo update-grub}
|
41
|
+
|
42
|
+
####################
|
43
|
+
# Helper Functions #
|
44
|
+
####################
|
45
|
+
|
46
|
+
Nothing = ->(*) { nil }
|
47
|
+
StateEnabled = ->(object) { 'Enabled' == object.state }
|
48
|
+
FirstIfOnly = ->(list) { list.first if (1 == list.size) }
|
49
|
+
Properties = ->(*properties) { ->(object) { properties.map(&Property).map { |ƒ| ƒ.(object) } } }
|
50
|
+
SelectFromMenu = ->(title, choices) { TTY::Prompt.new.select(title, filter: true) { |menu| choices.each { |choice| menu.choice(*choice) }}}
|
51
|
+
Property = ->(property, object) { object.public_send(property) if object.respond_to?(property) }.curry
|
52
|
+
RespondsTo = ->(method, object) { object.respond_to?(method) }.curry
|
53
|
+
SendTo = ->(object, *args) { object.public_send(*args) }.curry(2)
|
54
|
+
Bind = ->(name, value, object) { object.tap { SendTo.(object, "#{name}=", value) } }.curry
|
55
|
+
Bindable = ->(name, object) { RespondsTo.("#{name}=", object) }.curry
|
56
|
+
BindConstant = ->(namespace, name, value) { namespace.const_set name, value }.curry
|
57
|
+
BindConstants = ->(namespace, interfaces) { interfaces.map { |constant, function| BindConstant.(namespace, constant, function) } }.curry
|
58
|
+
Itself = ->(object) { object.itself }
|
59
|
+
If = ->(predicate, consequent, alternative = Nothing) {
|
60
|
+
->(*arguments) {
|
61
|
+
predicate.(*arguments) ? consequent.(*arguments) : alternative.(*arguments) } }
|
62
|
+
ApplyIf = ->(predicate, consequent) { If.(predicate, consequent, Itself) }
|
63
|
+
|
64
|
+
WhileSpinning = lambda do |message, &block|
|
65
|
+
Concurrent::IVar.new.tap do |ivar|
|
66
|
+
TTY::Spinner.new("[:spinner] #{message}", format: SPINNER_FORMAT).tap do |spinner|
|
67
|
+
spinner.auto_spin
|
68
|
+
ivar.set block.call
|
69
|
+
end.success
|
70
|
+
end.value
|
71
|
+
end
|
72
|
+
|
73
|
+
BindLocation = Bind.(:location, LOCATION)
|
74
|
+
|
75
|
+
AsyncableMethods = ->(object) {
|
76
|
+
candidates = object.methods.map(&:to_s)
|
77
|
+
|
78
|
+
candidates
|
79
|
+
.select { |c| candidates.any? { |m| m == "#{c}_async" } }
|
80
|
+
.reject { |c| c.start_with? 'begin_' }
|
81
|
+
.reject { |c| c.end_with? '_next' }
|
82
|
+
.map { |c| object.method(c) }
|
83
|
+
}
|
84
|
+
|
85
|
+
MethodsReturningSiblings = ->(object) {
|
86
|
+
object
|
87
|
+
.methods
|
88
|
+
.map { |method| object.method(method) }
|
89
|
+
.select { |method| method.arity.zero? }
|
90
|
+
.select { |method| method.owner == object.class }
|
91
|
+
.select { |method| ParentClass.(method.call) == ParentClass.(object) }
|
92
|
+
}
|
93
|
+
|
94
|
+
Ancestors = ->(object) { ClassOf.(object).ancestors }
|
95
|
+
ClassName = ->(object) { ClassOf.(object).name }
|
96
|
+
ClassNameParts = ->(object) { ClassName.(object).split('::') }
|
97
|
+
ClassOf = ->(object) { object.is_a?(Class) ? object : ClassOf.(object.class) }
|
98
|
+
FormatMethodName = ->(method) { method.name.to_s.split('_').map(&:capitalize).join }
|
99
|
+
InstanceMethods = ->(object) { ClassOf.(object).instance_methods(false).map(&InstanceMethod.(object)) }
|
100
|
+
InstanceOf = ->(object) { object.instance_of?(ClassOf.(object)) ? object : ClassOf.(object).new }
|
101
|
+
OwnClass = ->(object) { ClassNameParts.(object).last }
|
102
|
+
ParentClass = ->(object) { ClassNameParts.(object).reverse.drop(1).reverse.join('::') }
|
103
|
+
RequiredArguments = ->(method) { method.parameters.count { |type, _| type == :req } }
|
104
|
+
|
105
|
+
HasAncestor = ->(ancestor, object) { Ancestors.(object).include?(ancestor) }.curry
|
106
|
+
HasInstanceMethod = ->(method, object) { InstanceMethods.(object).any? { |m| m.name == method.to_sym } }.curry
|
107
|
+
InstanceMethod = ->(object, method) { ClassOf.(object).instance_method(method) }.curry
|
108
|
+
IsDescendentOf = ->(ancestor, object) { HasAncestor.(ancestor, object) and ClassOf.(ancestor) != ClassOf.(object) }.curry
|
109
|
+
|
110
|
+
AvailableCredentials = ->(client) { true if CredentialsFor.(InstanceOf.(client).base_url) }
|
111
|
+
AzureServiceName = ->(object) { ClassNameParts.(object).drop(1).first }
|
112
|
+
BindCredentials = ->(client) { Bind.(:credentials, CredentialsFor.(client.base_url), client) }
|
113
|
+
CredentialsFor = ->(domain) { CREDENTIALS[domain] }
|
114
|
+
|
115
|
+
LatestServiceVersion = ->(_service, versions) { versions.sort_by(&:name).last }
|
116
|
+
|
117
|
+
Constants = ->(namespace) {
|
118
|
+
[
|
119
|
+
namespace,
|
120
|
+
namespace
|
121
|
+
.constants
|
122
|
+
.map { |c| namespace.const_get c }
|
123
|
+
.select { |c| c.respond_to? :constants }
|
124
|
+
.map(&Constants)
|
125
|
+
]
|
126
|
+
.flatten
|
127
|
+
.sort_by(&:to_s)
|
128
|
+
.uniq
|
129
|
+
}
|
130
|
+
|
131
|
+
Bold = ->(string) { Pastel.new.bold(string) }
|
132
|
+
Red = ->(string) { Pastel.new.red(string) }
|
133
|
+
Blue = ->(string) { Pastel.new.blue(string) }
|
134
|
+
|
135
|
+
UsageHelp = ->(method) {
|
136
|
+
[method.name,
|
137
|
+
method.parameters.map do |type, name|
|
138
|
+
case type
|
139
|
+
when :req then "<#{name}>"
|
140
|
+
when :opt then "[#{name}]"
|
141
|
+
when :keyreq then "<#{name}:>"
|
142
|
+
when :key then "[#{name}:]"
|
143
|
+
end
|
144
|
+
end]
|
145
|
+
.flatten
|
146
|
+
.join(' ')
|
147
|
+
}
|
148
|
+
|
149
|
+
UsageError = ->(method, exception) {
|
150
|
+
STDERR.puts(
|
151
|
+
Red.("#{Bold.(exception.class.name)}: #{exception.message}"))
|
152
|
+
STDERR.puts(
|
153
|
+
Blue.("#{Bold.('Usage')}: #{UsageHelp.(method)}"))
|
154
|
+
}
|
155
|
+
|
156
|
+
BindOperation = ->(namespace, operation) {
|
157
|
+
constant = FormatMethodName.(operation)
|
158
|
+
namespace.const_set(constant, operation)
|
159
|
+
namespace.define_singleton_method(operation.name) do |*args|
|
160
|
+
operation.call(*args)
|
161
|
+
rescue ArgumentError => error
|
162
|
+
UsageError.(operation, error)
|
163
|
+
end
|
164
|
+
}.curry
|
165
|
+
|
166
|
+
AddConstantCalls = ->(namespace, blacklist: []) {
|
167
|
+
namespace
|
168
|
+
.constants
|
169
|
+
.reject { |constant| namespace.singleton_methods.include?(constant) }
|
170
|
+
.reject { |constant| blacklist.include?(constant) }
|
171
|
+
.map { |constant| namespace.define_singleton_method(constant) do |*args|
|
172
|
+
namespace.const_get(constant).call(*args)
|
173
|
+
rescue ArgumentError => error
|
174
|
+
UsageError.(namespace.const_get(constant), error)
|
175
|
+
end}}
|
176
|
+
|
177
|
+
AddInteractiveCalls = ->(namespace, **options) {
|
178
|
+
AddConstantCalls.(namespace, **options)
|
179
|
+
return :call if namespace.singleton_methods.include?(:call)
|
180
|
+
|
181
|
+
namespace.define_singleton_method(:call) do |*args|
|
182
|
+
namespace.singleton_method(
|
183
|
+
SelectFromMenu.(namespace.name, namespace.singleton_methods.select { |method| method =~ /^[[:upper:]]/ })
|
184
|
+
).call(*args)
|
185
|
+
rescue TTY::Reader::InputInterrupt
|
186
|
+
puts
|
187
|
+
namespace
|
188
|
+
end
|
189
|
+
}
|
190
|
+
|
191
|
+
BindInterface = ->(namespace, interface) {
|
192
|
+
context = namespace.const_set(FormatMethodName.(interface), Module.new)
|
193
|
+
AsyncableMethods.(interface.call).map(&BindOperation.(context))
|
194
|
+
AddInteractiveCalls.(context)
|
195
|
+
}.curry
|
196
|
+
|
197
|
+
BindService = ->(namespace, service) {
|
198
|
+
context = namespace.const_set(AzureServiceName.(service), Module.new)
|
199
|
+
models = context.const_set('Models', ServiceModels.(service))
|
200
|
+
context.define_singleton_method(:Models) { |*args| models.const_get(SelectFromMenu.("#{context.name}::Models", models.constants)).new(*args) }
|
201
|
+
MethodsReturningSiblings.(service).map(&BindInterface.(context))
|
202
|
+
AddInteractiveCalls.(context, blacklist: [:Models])
|
203
|
+
}.curry
|
204
|
+
|
205
|
+
ServiceModels = ->(service) { Kernel.const_get(ParentClass.(service) + '::Models') }
|
206
|
+
|
207
|
+
BindSubscriptionID = Bind.(:subscription_id, SUBSCRIPTION_ID)
|
208
|
+
BindTenantID = Bind.(:tenant_id, TENANT_ID)
|
209
|
+
|
210
|
+
########################
|
211
|
+
# Deployment Functions #
|
212
|
+
########################
|
213
|
+
|
214
|
+
FindResourceGroup = ->(name) {
|
215
|
+
AzureAPI::Resources::ResourceGroups
|
216
|
+
.list
|
217
|
+
.find { |resource_group| resource_group.id == "/subscriptions/#{SUBSCRIPTION_ID}/resourceGroups/#{name}" }}
|
218
|
+
|
219
|
+
CreateResourceGroup = ->(name) {
|
220
|
+
AzureAPI::Resources::ResourceGroups.create_or_update(
|
221
|
+
name,
|
222
|
+
AzureAPI::Resources::Models::ResourceGroup.new.tap do |resource_group|
|
223
|
+
resource_group.location = LOCATION
|
224
|
+
end)}
|
225
|
+
|
226
|
+
FindApplication = ->(display_name) {
|
227
|
+
AzureAPI::GraphRbac::Applications
|
228
|
+
.list
|
229
|
+
.find { |application| application.display_name == display_name }}
|
230
|
+
|
231
|
+
CreateApplication = ->(display_name) {
|
232
|
+
AzureAPI::GraphRbac::Applications.create(
|
233
|
+
AzureAPI::GraphRbac::Models::ApplicationCreateParameters.new.tap do |application|
|
234
|
+
application.available_to_other_tenants = false
|
235
|
+
application.display_name = display_name
|
236
|
+
application.identifier_uris = ["http://#{display_name}"]
|
237
|
+
end)}
|
238
|
+
|
239
|
+
FindServicePrincipal = ->(application) {
|
240
|
+
AzureAPI::GraphRbac::ServicePrincipals
|
241
|
+
.list
|
242
|
+
.find { |service_principal| service_principal.app_id == application.app_id }}
|
243
|
+
|
244
|
+
CreateServicePrincipal = ->(application) {
|
245
|
+
AzureAPI::GraphRbac::ServicePrincipals.create(
|
246
|
+
AzureAPI::GraphRbac::Models::ServicePrincipalCreateParameters.new.tap do |service_principal|
|
247
|
+
service_principal.account_enabled = true
|
248
|
+
service_principal.app_id = application.app_id
|
249
|
+
end)}
|
250
|
+
|
251
|
+
FindRoleDefinition = ->(role_name) {
|
252
|
+
AzureAPI::Authorization::RoleDefinitions
|
253
|
+
.list("/subscriptions/#{SUBSCRIPTION_ID}")
|
254
|
+
.find { |role_definition| role_definition.role_name == role_name }}
|
255
|
+
|
256
|
+
FindRoleAssignment = ->(name) {
|
257
|
+
AzureAPI::Authorization::RoleAssignments
|
258
|
+
.list
|
259
|
+
.find { |role_assignment| role_assignment.name == name }}
|
260
|
+
|
261
|
+
CreateRoleAssignment = ->(role_definition, service_principal, resource_group) {
|
262
|
+
AzureAPI::Authorization::RoleAssignments.create(
|
263
|
+
resource_group.id,
|
264
|
+
Chamber.env.uuid,
|
265
|
+
AzureAPI::Authorization::Models::RoleAssignmentCreateParameters.new.tap do |role_assignment|
|
266
|
+
role_assignment.role_definition_id = role_definition.id
|
267
|
+
role_assignment.principal_id = service_principal.object_id
|
268
|
+
end)}
|
269
|
+
|
270
|
+
UpdatePassword = ->(service_principal, password) {
|
271
|
+
AzureAPI::GraphRbac::ServicePrincipals.update_password_credentials(
|
272
|
+
service_principal.object_id,
|
273
|
+
AzureAPI::GraphRbac::Models::PasswordCredentialsUpdateParameters.new.tap { |update|
|
274
|
+
update.value = [AzureAPI::GraphRbac::Models::PasswordCredential.new.tap { |credential|
|
275
|
+
credential.value = password
|
276
|
+
credential.end_date = Time.parse(
|
277
|
+
Chamber.env.credential_end_date).to_datetime}]})}
|
278
|
+
|
279
|
+
FindContainerService = ->(resource_group) {
|
280
|
+
AzureAPI::ContainerService::ContainerServices
|
281
|
+
.list_by_resource_group(resource_group.name)
|
282
|
+
.find { |container_service| container_service.name == Chamber.env.identifier }}
|
283
|
+
|
284
|
+
CreateContainerService = ->(service_principal, resource_group) {
|
285
|
+
AzureAPI::ContainerService::ContainerServices.create_or_update(
|
286
|
+
resource_group.name,
|
287
|
+
Chamber.env.identifier,
|
288
|
+
AzureAPI::ContainerService::Models::ContainerService.new.tap { |container_service|
|
289
|
+
container_service.agent_pool_profiles = [
|
290
|
+
AzureAPI::ContainerService::Models::ContainerServiceAgentPoolProfile.new.tap { |agent_pool_profile|
|
291
|
+
agent_pool_profile.count = (
|
292
|
+
Chamber.env.agent_count)
|
293
|
+
agent_pool_profile.dns_prefix = (
|
294
|
+
[Chamber.env.dns_prefix, Chamber.env.agent_dns_suffix]
|
295
|
+
.join('-'))
|
296
|
+
agent_pool_profile.name = (
|
297
|
+
Chamber.env.identifier)
|
298
|
+
agent_pool_profile.vm_size = (
|
299
|
+
Chamber.env.vm_size)}]
|
300
|
+
container_service.linux_profile = (
|
301
|
+
AzureAPI::ContainerService::Models::ContainerServiceLinuxProfile.new.tap { |linux_profile|
|
302
|
+
linux_profile.admin_username = (
|
303
|
+
Chamber.env.admin_username)
|
304
|
+
linux_profile.ssh = (
|
305
|
+
AzureAPI::ContainerService::Models::ContainerServiceSshConfiguration.new.tap { |ssh|
|
306
|
+
ssh.public_keys = [
|
307
|
+
AzureAPI::ContainerService::Models::ContainerServiceSshPublicKey.new.tap { |public_key|
|
308
|
+
public_key.key_data = (
|
309
|
+
SSHKey.new(Chamber.env.ssh_private_key).ssh_public_key)}]})})
|
310
|
+
container_service.location = Chamber.env.location
|
311
|
+
container_service.master_profile = (
|
312
|
+
AzureAPI::ContainerService::Models::ContainerServiceMasterProfile.new.tap { |master_profile|
|
313
|
+
master_profile.dns_prefix = [Chamber.env.dns_prefix, Chamber.env.master_dns_suffix].join('-')})
|
314
|
+
container_service.orchestrator_profile = (
|
315
|
+
AzureAPI::ContainerService::Models::ContainerServiceOrchestratorProfile.new.tap { |orchestrator_profile|
|
316
|
+
orchestrator_profile.orchestrator_type = Chamber.env.orchestrator_type})
|
317
|
+
container_service.service_principal_profile = (
|
318
|
+
AzureAPI::ContainerService::Models::ContainerServiceServicePrincipalProfile.new.tap { |service_principal_profile|
|
319
|
+
service_principal_profile.client_id = service_principal.app_id
|
320
|
+
service_principal_profile.secret = Chamber.env.password})})}
|
321
|
+
|
322
|
+
FindVirtualMachines = ->(resource_group) {
|
323
|
+
AzureAPI::Compute::VirtualMachines
|
324
|
+
.list(resource_group.name)}
|
325
|
+
|
326
|
+
FindVirtualMachine = ->(virtual_machine_name, resource_group) {
|
327
|
+
AzureAPI::Compute::VirtualMachines
|
328
|
+
.list(resource_group.name)
|
329
|
+
.find { |virtual_machine| virtual_machine.name == virtual_machine_name }}
|
330
|
+
|
331
|
+
KubernetesAgents = ->(resource_group) {
|
332
|
+
FindVirtualMachines
|
333
|
+
.(resource_group)
|
334
|
+
.select { |vm| vm.tags['orchestrator'] =~ /^Kubernetes:/ }
|
335
|
+
.select { |vm| vm.tags['poolName'] == 'agent' }}
|
336
|
+
|
337
|
+
# FIXME: Bad mojo of there's more than one cluster.
|
338
|
+
KubernetesMaster = ->(resource_group) {
|
339
|
+
FindVirtualMachines
|
340
|
+
.(resource_group)
|
341
|
+
.select { |vm| vm.tags['orchestrator'] =~ /^Kubernetes:/ }
|
342
|
+
.find { |vm| vm.tags['poolName'] == 'master' }}
|
343
|
+
|
344
|
+
RunShellScripts = ->(scripts, resource_group, virtual_machine) {
|
345
|
+
AzureAPI::Compute::VirtualMachines.run_command(
|
346
|
+
resource_group.name,
|
347
|
+
virtual_machine.name,
|
348
|
+
AzureAPI::Compute::Models::RunCommandInput.new.tap { |input|
|
349
|
+
input.command_id = 'RunShellScript'
|
350
|
+
input.script = scripts
|
351
|
+
})}.curry
|
352
|
+
|
353
|
+
RunShellScript = ->(script, *rest) { RunShellScripts.([script], *rest) }
|
354
|
+
RestartVirtualMachine = ->(resource_group, virtual_machine) { AzureAPI::Compute::VirtualMachines.restart(resource_group.name, virtual_machine.name) }.curry
|
355
|
+
|
356
|
+
FindPublicIPv4 = ->(resource_group) {
|
357
|
+
AzureAPI::Network::PublicIpaddresses
|
358
|
+
.list(resource_group.name)
|
359
|
+
.find { |public_ip_address| public_ip_address.name == Chamber.env.identifier }}
|
360
|
+
|
361
|
+
CreatePublicIPv4 = ->(resource_group) {
|
362
|
+
AzureAPI::Network::PublicIpaddresses.create_or_update(
|
363
|
+
resource_group.name,
|
364
|
+
Chamber.env.identifier,
|
365
|
+
AzureAPI::Network::Models::PublicIPAddress.new.tap { |public_ip_address|
|
366
|
+
public_ip_address.location = Chamber.env.location
|
367
|
+
public_ip_address.public_ipaddress_version = 'IPv4'
|
368
|
+
public_ip_address.public_ipallocation_method = 'Static'
|
369
|
+
})}
|
370
|
+
|
371
|
+
KubernetesMasterSecurityGroup = ->(resource_group) {
|
372
|
+
name = [*KubernetesMaster.(resource_group).name.split('-').first(3), 'nsg'].join('-')
|
373
|
+
AzureAPI::Network::NetworkSecurityGroups
|
374
|
+
.list(resource_group.name)
|
375
|
+
.find { |network_security_group| network_security_group.name == name }}
|
376
|
+
|
377
|
+
FindSecurityRule = ->(network_security_group) {
|
378
|
+
network_security_group
|
379
|
+
.security_rules
|
380
|
+
.find { |security_rule| security_rule.name == Chamber.env.identifier }
|
381
|
+
}
|
382
|
+
|
383
|
+
CreateSecurityRule = ->(network_security_group, resource_group) {
|
384
|
+
AzureAPI::Network::SecurityRules.create_or_update(
|
385
|
+
resource_group.name,
|
386
|
+
network_security_group.name,
|
387
|
+
Chamber.env.identifier,
|
388
|
+
AzureAPI::Network::Models::SecurityRule.new.tap { |security_rule|
|
389
|
+
security_rule.access = 'Allow'
|
390
|
+
security_rule.destination_address_prefix = '*'
|
391
|
+
security_rule.destination_port_ranges = [80,443,4443,2222,2793]
|
392
|
+
security_rule.direction = AzureAPI::Network::Models::SecurityRuleDirection::Inbound
|
393
|
+
security_rule.priority = network_security_group.security_rules.map(&:priority).max.next
|
394
|
+
security_rule.protocol = 'Tcp'
|
395
|
+
security_rule.source_address_prefix = '*'
|
396
|
+
security_rule.source_port_range = '*'})}
|
397
|
+
|
398
|
+
UpdateVirtualMachine = ->(virtual_machine, resource_group) {
|
399
|
+
AzureAPI::Compute::VirtualMachines.create_or_update(
|
400
|
+
resource_group.name,
|
401
|
+
virtual_machine.name,
|
402
|
+
virtual_machine)}
|
403
|
+
|
404
|
+
ApplyTag = ->(key, value, resource_group, virtual_machine) {
|
405
|
+
UpdateVirtualMachine.(virtual_machine.tap { virtual_machine.tags[key] = value }, resource_group)}
|
406
|
+
|
407
|
+
EnableSwapAccounting = ->(resource_group, virtual_machine) {
|
408
|
+
return if virtual_machine.tags['cloudstrap.swap_accounting'] == 'enabled'
|
409
|
+
RunShellScripts.([ENABLE_SWAP_ACCOUNTING, UPDATE_GRUB], resource_group, virtual_machine)
|
410
|
+
ApplyTag.('cloudstrap.swap_accounting', 'enabled', resource_group, virtual_machine)}.curry
|
411
|
+
|
412
|
+
RebootOnce = ->(resource_group, virtual_machine) {
|
413
|
+
return if virtual_machine.tags['cloudstrap.reboot'] == 'finished'
|
414
|
+
ApplyTag.('cloudstrap.reboot', 'started', resource_group, virtual_machine)
|
415
|
+
RestartVirtualMachine.(resource_group, virtual_machine)
|
416
|
+
ApplyTag.('cloudstrap.reboot', 'finished', resource_group, vritual_machine)}.curry
|
417
|
+
|
418
|
+
FindNetworkInterface = ->(resource_group) {
|
419
|
+
virtual_machine = KubernetesAgents.(resource_group).sort_by(&:name).first
|
420
|
+
AzureAPI::Network::NetworkInterfaces
|
421
|
+
.list(resource_group.name)
|
422
|
+
.find { |network_interface| network_interface.virtual_machine.id.end_with?(virtual_machine.name)}}
|
423
|
+
|
424
|
+
AssociatePublicIP = ->(resource_group, network_interface, public_ip_address) {
|
425
|
+
AzureAPI::Network::NetworkInterfaces.create_or_update(
|
426
|
+
resource_group.name,
|
427
|
+
network_interface.name,
|
428
|
+
network_interface.tap {
|
429
|
+
network_interface.ip_configurations[0].public_ipaddress = public_ip_address})}
|
430
|
+
|
431
|
+
################
|
432
|
+
# Main Program #
|
433
|
+
################
|
434
|
+
|
435
|
+
WhileSpinning.('Constructing Library') do
|
436
|
+
AzureAPI = Module.new
|
437
|
+
Constants
|
438
|
+
.(Azure)
|
439
|
+
.select(&IsDescendentOf.(MsRestAzure::AzureServiceClient))
|
440
|
+
.select(&AvailableCredentials)
|
441
|
+
.group_by(&AzureServiceName)
|
442
|
+
.map(&LatestServiceVersion)
|
443
|
+
.map(&InstanceOf)
|
444
|
+
.map(&ApplyIf.(Bindable.(:credentials), BindCredentials))
|
445
|
+
.map(&ApplyIf.(Bindable.(:tenant_id), BindTenantID))
|
446
|
+
.map(&ApplyIf.(Bindable.(:subscription_id), BindSubscriptionID))
|
447
|
+
.each(&BindService.(AzureAPI))
|
448
|
+
|
449
|
+
AddInteractiveCalls.(AzureAPI)
|
450
|
+
end
|
451
|
+
|
452
|
+
role_definition = WhileSpinning.('Find Role Definition') { FindRoleDefinition.(Chamber.env.role_definition) }
|
453
|
+
resource_group = WhileSpinning.('Find/Create Resource Group') { FindResourceGroup.(Chamber.env.identifier) || CreateResourceGroup.(Chamber.env.identifier) }
|
454
|
+
application = WhileSpinning.('Find/Create Application') { FindApplication.(Chamber.env.identifier) || CreateApplication.(Chamber.env.identifier) }
|
455
|
+
service_principal = WhileSpinning.('Find/Create Service Principal') { FindServicePrincipal.(application) || CreateServicePrincipal.(application) }
|
456
|
+
|
457
|
+
WhileSpinning.('Update Password for Service Principal') { UpdatePassword.(service_principal, Chamber.env.password) }
|
458
|
+
|
459
|
+
role_assignment = WhileSpinning.('Find/Create Role Assignment') { FindRoleAssignment.(Chamber.env.uuid) || CreateRoleAssignment.(role_definition, service_principal, resource_group) }
|
460
|
+
public_ip_address = WhileSpinning.('Find/Create Public IP Address') { FindPublicIPv4.(resource_group) || CreatePublicIPv4.(resource_group) }
|
461
|
+
container_service = WhileSpinning.('Find/Create Container Service') { FindContainerService.(resource_group) || CreateContainerService.(service_principal, resource_group) }
|
462
|
+
|
463
|
+
network_security_group = WhileSpinning.('Find Network Security Group') { KubernetesMasterSecurityGroup.(resource_group) }
|
464
|
+
security_rule = WhileSpinning.('Find/Create Security Rule') { FindSecurityRule.(network_security_group) || CreateSecurityRule.(network_security_group, resource_group) }
|
465
|
+
|
466
|
+
network_interface = WhileSpinning.('Find Network Interface') { FindNetworkInterface.(resource_group) }
|
467
|
+
AssociatePublicIP.(resource_group, network_interface, public_ip_address)
|
468
|
+
|
469
|
+
WhileSpinning.('Configuring Swap Accounting') do
|
470
|
+
KubernetesAgents
|
471
|
+
.(resource_group)
|
472
|
+
.each(&EnableSwapAccounting.(resource_group))
|
473
|
+
.each(&RebootOnce.(resource_group))
|
474
|
+
end
|
475
|
+
|
476
|
+
def api(*args)
|
477
|
+
AzureAPI.call(*args)
|
478
|
+
end
|