@@ -67,6 +67,9 @@ type ToolboxEnrollOptions struct {
67
67
SSHUser string
68
68
SSHPort int
69
69
70
+ // BuildHost is a flag to only build the host resource, don't apply it or enroll the node
71
+ BuildHost bool
72
+
70
73
// PodCIDRs is the list of IP Address ranges to use for pods that run on this node
71
74
PodCIDRs []string
72
75
}
@@ -101,14 +104,6 @@ func RunToolboxEnroll(ctx context.Context, f commandutils.Factory, out io.Writer
101
104
if err != nil {
102
105
return err
103
106
}
104
- fullInstanceGroup , err := configBuilder .GetFullInstanceGroup (ctx )
105
- if err != nil {
106
- return err
107
- }
108
- bootstrapData , err := configBuilder .GetBootstrapData (ctx )
109
- if err != nil {
110
- return err
111
- }
112
107
113
108
// Enroll the node over SSH.
114
109
if options .Host != "" {
@@ -117,104 +112,126 @@ func RunToolboxEnroll(ctx context.Context, f commandutils.Factory, out io.Writer
117
112
return err
118
113
}
119
114
120
- if err := enrollHost (ctx , fullInstanceGroup , options , bootstrapData , restConfig ); err != nil {
115
+ sudo := true
116
+ if options .SSHUser == "root" {
117
+ sudo = false
118
+ }
119
+
120
+ target , err := NewSSHHost (ctx , options .Host , options .SSHPort , options .SSHUser , sudo )
121
+ if err != nil {
121
122
return err
122
123
}
123
- }
124
+ defer target . Close ()
124
125
125
- return nil
126
- }
126
+ hostData , err := buildHostData (ctx , target , options )
127
+ if err != nil {
128
+ return err
129
+ }
127
130
128
- func enrollHost (ctx context.Context , ig * kops.InstanceGroup , options * ToolboxEnrollOptions , bootstrapData * BootstrapData , restConfig * rest.Config ) error {
129
- scheme := runtime .NewScheme ()
130
- if err := v1alpha2 .AddToScheme (scheme ); err != nil {
131
- return fmt .Errorf ("building kubernetes scheme: %w" , err )
132
- }
133
- kubeClient , err := client .New (restConfig , client.Options {
134
- Scheme : scheme ,
135
- })
136
- if err != nil {
137
- return fmt .Errorf ("building kubernetes client: %w" , err )
138
- }
131
+ if options .BuildHost {
132
+ klog .Infof ("building host data for %+v" , hostData )
133
+ b , err := yaml .Marshal (hostData )
134
+ if err != nil {
135
+ return fmt .Errorf ("error marshalling host data: %w" , err )
136
+ }
137
+ fmt .Fprintf (out , "%s\n " , string (b ))
138
+ } else {
139
+ fullInstanceGroup , err := configBuilder .GetFullInstanceGroup (ctx )
140
+ if err != nil {
141
+ return err
142
+ }
143
+ bootstrapData , err := configBuilder .GetBootstrapData (ctx )
144
+ if err != nil {
145
+ return err
146
+ }
139
147
140
- sudo := true
141
- if options .SSHUser == "root" {
142
- sudo = false
148
+ if err := enrollHost (ctx , fullInstanceGroup , options , bootstrapData , restConfig , hostData , target ); err != nil {
149
+ return err
150
+ }
151
+ }
143
152
}
144
153
145
- host , err := NewSSHHost (ctx , options .Host , options .SSHPort , options .SSHUser , sudo )
146
- if err != nil {
147
- return err
148
- }
149
- defer host .Close ()
154
+ return nil
155
+ }
150
156
157
+ // buildHostData builds an instance of the Host CRD, based on information in the options and by SSHing to the target host.
158
+ func buildHostData (ctx context.Context , target * SSHHost , options * ToolboxEnrollOptions ) (* v1alpha2.Host , error ) {
151
159
publicKeyPath := "/etc/kubernetes/kops/pki/machine/public.pem"
152
160
153
- publicKeyBytes , err := host .readFile (ctx , publicKeyPath )
161
+ publicKeyBytes , err := target .readFile (ctx , publicKeyPath )
154
162
if err != nil {
155
163
if errors .Is (err , fs .ErrNotExist ) {
156
164
publicKeyBytes = nil
157
165
} else {
158
- return fmt .Errorf ("error reading public key %q: %w" , publicKeyPath , err )
166
+ return nil , fmt .Errorf ("error reading public key %q: %w" , publicKeyPath , err )
159
167
}
160
168
}
161
169
170
+ // Create the key if it doesn't exist
162
171
publicKeyBytes = bytes .TrimSpace (publicKeyBytes )
163
172
if len (publicKeyBytes ) == 0 {
164
- if _ , err := host .runScript (ctx , scriptCreateKey , ExecOptions {Sudo : sudo , Echo : true }); err != nil {
165
- return err
173
+ if _ , err := target .runScript (ctx , scriptCreateKey , ExecOptions {Echo : true }); err != nil {
174
+ return nil , err
166
175
}
167
176
168
- b , err := host .readFile (ctx , publicKeyPath )
177
+ b , err := target .readFile (ctx , publicKeyPath )
169
178
if err != nil {
170
- return fmt .Errorf ("error reading public key %q (after creation): %w" , publicKeyPath , err )
179
+ return nil , fmt .Errorf ("error reading public key %q (after creation): %w" , publicKeyPath , err )
171
180
}
172
181
publicKeyBytes = b
173
182
}
174
183
klog .Infof ("public key is %s" , string (publicKeyBytes ))
175
184
176
- hostname , err := host .getHostname (ctx )
185
+ hostname , err := target .getHostname (ctx )
177
186
if err != nil {
178
- return err
187
+ return nil , err
188
+ }
189
+
190
+ host := & v1alpha2.Host {}
191
+ host .SetGroupVersionKind (v1alpha2 .SchemeGroupVersion .WithKind ("Host" ))
192
+ host .Namespace = "kops-system"
193
+ host .Name = hostname
194
+ host .Spec .InstanceGroup = options .InstanceGroup
195
+ host .Spec .PublicKey = string (publicKeyBytes )
196
+ host .Spec .PodCIDRs = options .PodCIDRs
197
+
198
+ return host , nil
199
+ }
200
+
201
+ func enrollHost (ctx context.Context , ig * kops.InstanceGroup , options * ToolboxEnrollOptions , bootstrapData * BootstrapData , restConfig * rest.Config , hostData * v1alpha2.Host , target * SSHHost ) error {
202
+ scheme := runtime .NewScheme ()
203
+ if err := v1alpha2 .AddToScheme (scheme ); err != nil {
204
+ return fmt .Errorf ("building kubernetes scheme: %w" , err )
205
+ }
206
+ kubeClient , err := client .New (restConfig , client.Options {
207
+ Scheme : scheme ,
208
+ })
209
+ if err != nil {
210
+ return fmt .Errorf ("building kubernetes client: %w" , err )
179
211
}
180
212
181
213
// We can't create the host resource in the API server for control-plane nodes,
182
214
// because the API server (likely) isn't running yet.
183
215
if ! ig .IsControlPlane () {
184
- if err := createHostResourceInAPIServer (ctx , options , hostname , publicKeyBytes , kubeClient ); err != nil {
185
- return err
216
+ if err := kubeClient . Create (ctx , hostData ); err != nil {
217
+ return fmt . Errorf ( "failed to create host %s/%s: %w" , hostData . Namespace , hostData . Name , err )
186
218
}
187
219
}
188
220
189
221
for k , v := range bootstrapData .NodeupScriptAdditionalFiles {
190
- if err := host .writeFile (ctx , k , bytes .NewReader (v )); err != nil {
222
+ if err := target .writeFile (ctx , k , bytes .NewReader (v )); err != nil {
191
223
return fmt .Errorf ("writing file %q over SSH: %w" , k , err )
192
224
}
193
225
}
194
226
195
227
if len (bootstrapData .NodeupScript ) != 0 {
196
- if _ , err := host .runScript (ctx , string (bootstrapData .NodeupScript ), ExecOptions {Sudo : sudo , Echo : true }); err != nil {
228
+ if _ , err := target .runScript (ctx , string (bootstrapData .NodeupScript ), ExecOptions {Echo : true }); err != nil {
197
229
return err
198
230
}
199
231
}
200
232
return nil
201
233
}
202
234
203
- func createHostResourceInAPIServer (ctx context.Context , options * ToolboxEnrollOptions , nodeName string , publicKey []byte , client client.Client ) error {
204
- host := & v1alpha2.Host {}
205
- host .Namespace = "kops-system"
206
- host .Name = nodeName
207
- host .Spec .InstanceGroup = options .InstanceGroup
208
- host .Spec .PublicKey = string (publicKey )
209
- host .Spec .PodCIDRs = options .PodCIDRs
210
-
211
- if err := client .Create (ctx , host ); err != nil {
212
- return fmt .Errorf ("failed to create host %s/%s: %w" , host .Namespace , host .Name , err )
213
- }
214
-
215
- return nil
216
- }
217
-
218
235
const scriptCreateKey = `
219
236
#!/bin/bash
220
237
set -o errexit
@@ -315,7 +332,7 @@ func (s *SSHHost) runScript(ctx context.Context, script string, options ExecOpti
315
332
p := vfs .NewSSHPath (s .sshClient , s .hostname , scriptPath , s .sudo )
316
333
317
334
defer func () {
318
- if _ , err := s .runCommand (ctx , "rm -rf " + tempDir , ExecOptions {Sudo : s . sudo , Echo : false }); err != nil {
335
+ if _ , err := s .runCommand (ctx , "rm -rf " + tempDir , ExecOptions {Echo : false }); err != nil {
319
336
klog .Warningf ("error cleaning up temp directory %q: %v" , tempDir , err )
320
337
}
321
338
}()
@@ -336,7 +353,6 @@ type CommandOutput struct {
336
353
337
354
// ExecOptions holds options for running a command remotely.
338
355
type ExecOptions struct {
339
- Sudo bool
340
356
Echo bool
341
357
}
342
358
@@ -353,10 +369,11 @@ func (s *SSHHost) runCommand(ctx context.Context, command string, options ExecOp
353
369
session .Stderr = & output .Stderr
354
370
355
371
if options .Echo {
356
- session .Stdout = io .MultiWriter (os .Stdout , session .Stdout )
372
+ // We send both to stderr, so we don't "corrupt" stdout
373
+ session .Stdout = io .MultiWriter (os .Stderr , session .Stdout )
357
374
session .Stderr = io .MultiWriter (os .Stderr , session .Stderr )
358
375
}
359
- if options . Sudo {
376
+ if s . sudo {
360
377
command = "sudo " + command
361
378
}
362
379
if err := session .Run (command ); err != nil {
@@ -368,7 +385,7 @@ func (s *SSHHost) runCommand(ctx context.Context, command string, options ExecOp
368
385
// getHostname gets the hostname of the SSH target.
369
386
// This is used as the node name when registering the node.
370
387
func (s * SSHHost ) getHostname (ctx context.Context ) (string , error ) {
371
- output , err := s .runCommand (ctx , "hostname" , ExecOptions {Sudo : false , Echo : true })
388
+ output , err := s .runCommand (ctx , "hostname" , ExecOptions {Echo : true })
372
389
if err != nil {
373
390
return "" , fmt .Errorf ("failed to get hostname: %w" , err )
374
391
}
0 commit comments