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