Skip to content

Commit 30a80e5

Browse files
committed
address ai review
1 parent d4feaec commit 30a80e5

4 files changed

Lines changed: 171 additions & 18 deletions

File tree

cli/pkg/kubectl/bind-apiservice/plugin/bind.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (b *BindAPIServiceOptions) Complete(args []string) error {
117117
// Validate validates the BindAPIServiceOptions are complete and usable.
118118
func (b *BindAPIServiceOptions) Validate() error {
119119
if b.file == "" && b.Template == "" {
120-
return errors.New("file, template-name or arguments are required")
120+
return errors.New("file, template-name or --file are required")
121121
}
122122

123123
if allowed := sets.NewString(b.Print.AllowedFormats()...); *b.Print.OutputFormat != "" && !allowed.Has(*b.Print.OutputFormat) {
@@ -133,7 +133,8 @@ func (b *BindAPIServiceOptions) Validate() error {
133133
return errors.New("remote-kubeconfig or remote-kubeconfig-namespace and remote-kubeconfig-name are required")
134134
}
135135
}
136-
if b.Name == "" {
136+
// Name is required unless reading from file, where name will be read from the file.
137+
if b.Name == "" && b.file == "" {
137138
return errors.New("name is required")
138139
}
139140

@@ -149,11 +150,6 @@ func (b *BindAPIServiceOptions) Run(ctx context.Context) error {
149150
return err
150151
}
151152

152-
r, err := b.bindTemplate(ctx)
153-
if err != nil {
154-
return err
155-
}
156-
157153
// Use the shared binder to create bindings
158154
binderOpts := &BinderOptions{
159155
IOStreams: b.Options.IOStreams,
@@ -164,12 +160,25 @@ func (b *BindAPIServiceOptions) Run(ctx context.Context) error {
164160
RemoteKubeconfigNamespace: b.remoteKubeconfigNamespace,
165161
RemoteKubeconfigName: b.remoteKubeconfigName,
166162
RemoteNamespace: b.remoteNamespace,
163+
File: b.file,
167164
}
168-
169165
binder := NewBinder(config, binderOpts)
170-
bindings, err := binder.BindFromResponse(ctx, r.response)
171-
if err != nil {
172-
return fmt.Errorf("failed to create bindings: %w", err)
166+
167+
var bindings []*kubebindv1alpha2.APIServiceBinding
168+
if b.Template != "" {
169+
r, err := b.bindTemplate(ctx)
170+
if err != nil {
171+
return err
172+
}
173+
bindings, err = binder.BindFromResponse(ctx, r.response)
174+
if err != nil {
175+
return fmt.Errorf("failed to create bindings: %w", err)
176+
}
177+
} else if b.file != "" {
178+
bindings, err = binder.BindFromFile(ctx)
179+
if err != nil {
180+
return fmt.Errorf("failed to create bindings: %w", err)
181+
}
173182
}
174183

175184
fmt.Fprintln(b.Options.ErrOut)

cli/pkg/kubectl/bind-apiservice/plugin/binder.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"io"
24+
"os"
2325

2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527
"k8s.io/cli-runtime/pkg/genericclioptions"
2628
kubeclient "k8s.io/client-go/kubernetes"
2729
"k8s.io/client-go/rest"
30+
"sigs.k8s.io/yaml"
2831

2932
"github.com/kube-bind/kube-bind/cli/pkg/kubectl/base"
3033
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
@@ -40,6 +43,7 @@ type BinderOptions struct {
4043
RemoteKubeconfigNamespace string
4144
RemoteKubeconfigName string
4245
RemoteNamespace string
46+
File string
4347
DryRun bool
4448
}
4549

@@ -57,6 +61,95 @@ func NewBinder(config *rest.Config, opts *BinderOptions) *Binder {
5761
}
5862
}
5963

64+
// TODO: bindFromFile and bindFromResponse can likely share a lot of code. This slow is bit repetitive
65+
// but keeps the two paths separate for clarity. But it needs love.
66+
67+
func (b *Binder) BindFromFile(ctx context.Context) ([]*kubebindv1alpha2.APIServiceBinding, error) {
68+
// Ensure client side namespace exists
69+
err := b.ensureClientSideNamespaceExists(ctx)
70+
if err != nil {
71+
return nil, fmt.Errorf("failed to ensure kube-bind namespace exists: %w", err)
72+
}
73+
74+
remoteKubeconfig, _, _, err := b.getRemoteKubeconfig(ctx, "", "")
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
// Copy kubeconfig into local cluster
80+
remoteHost, remoteNamespace, err := base.ParseRemoteKubeconfig([]byte(remoteKubeconfig))
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
kubeClient, err := kubeclient.NewForConfig(b.config)
86+
if err != nil {
87+
return nil, fmt.Errorf("failed to create kube client: %w", err)
88+
}
89+
90+
secretName, err := base.FindRemoteKubeconfig(ctx, kubeClient, remoteNamespace, remoteHost)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
secret, created, err := base.EnsureKubeconfigSecret(ctx, string(remoteKubeconfig), secretName, kubeClient)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
if created {
101+
fmt.Fprintf(b.opts.IOStreams.ErrOut, "Created secret %s/%s for host %s, namespace %s\n", "kube-bind", secret.Name, remoteHost, remoteNamespace)
102+
} else {
103+
fmt.Fprintf(b.opts.IOStreams.ErrOut, "Updated secret %s/%s for host %s, namespace %s\n", "kube-bind", secret.Name, remoteHost, remoteNamespace)
104+
}
105+
106+
if b.opts.DryRun {
107+
return nil, nil
108+
}
109+
110+
// Get remote kubeconfig
111+
remoteKubeconfig, remoteNamespaceActual, remoteConfig, err := b.getRemoteKubeconfig(ctx, secret.Namespace, secret.Name)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to get remote kubeconfig: %w", err)
114+
}
115+
116+
data, err := b.getRequestManifest()
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to get request manifest: %w", err)
119+
}
120+
121+
request, err := b.unmarshalManifest(data)
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to unmarshal request manifest: %w", err)
124+
}
125+
126+
// Deploy konnector if needed
127+
if err := b.deployKonnector(ctx); err != nil {
128+
return nil, fmt.Errorf("failed to deploy konnector: %w", err)
129+
}
130+
131+
// Create bindings for all requests
132+
var bindings []*kubebindv1alpha2.APIServiceBinding
133+
result, err := b.createServiceExportRequest(ctx, remoteConfig, remoteNamespaceActual, request)
134+
if err != nil {
135+
return nil, fmt.Errorf("failed to create service export request: %w", err)
136+
}
137+
138+
secretName, err = b.createKubeconfigSecret(ctx, remoteConfig.Host, remoteNamespaceActual, remoteKubeconfig)
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to create kubeconfig secret: %w", err)
141+
}
142+
143+
results, err := b.createAPIServiceBindings(ctx, result, secretName)
144+
if err != nil {
145+
return nil, fmt.Errorf("failed to create API service bindings: %w", err)
146+
}
147+
bindings = append(bindings, results...)
148+
149+
return bindings, nil
150+
151+
}
152+
60153
// BindFromResponse processes a BindingResourceResponse and creates all necessary bindings
61154
func (b *Binder) BindFromResponse(ctx context.Context, response *kubebindv1alpha2.BindingResourceResponse) ([]*kubebindv1alpha2.APIServiceBinding, error) {
62155
if response.Authentication.OAuth2CodeGrant == nil {
@@ -96,6 +189,10 @@ func (b *Binder) BindFromResponse(ctx context.Context, response *kubebindv1alpha
96189
fmt.Fprintf(b.opts.IOStreams.ErrOut, "Updated secret %s/%s for host %s, namespace %s\n", "kube-bind", secret.Name, remoteHost, remoteNamespace)
97190
}
98191

192+
if b.opts.DryRun {
193+
return nil, nil
194+
}
195+
99196
// Get remote kubeconfig
100197
remoteKubeconfig, remoteNamespaceActual, remoteConfig, err := b.getRemoteKubeconfig(ctx, secret.Namespace, secret.Name)
101198
if err != nil {
@@ -172,6 +269,7 @@ func (b *Binder) deployKonnector(ctx context.Context) error {
172269
SkipKonnector: b.opts.SkipKonnector,
173270
KonnectorImageOverride: b.opts.KonnectorImageOverride,
174271
DowngradeKonnector: b.opts.DowngradeKonnector,
272+
DryRun: b.opts.DryRun,
175273
}
176274
return tempOpts.deployKonnector(ctx, b.config)
177275
}
@@ -198,3 +296,33 @@ func (b *Binder) createAPIServiceBindings(ctx context.Context, request *kubebind
198296
}
199297
return tempOpts.createAPIServiceBindings(ctx, b.config, request, secretName)
200298
}
299+
300+
func (b *Binder) getRequestManifest() ([]byte, error) {
301+
if b.opts.File == "-" {
302+
body, err := io.ReadAll(b.opts.IOStreams.In)
303+
if err != nil {
304+
return nil, fmt.Errorf("failed to read from stdin: %w", err)
305+
}
306+
return body, nil
307+
}
308+
309+
body, err := os.ReadFile(b.opts.File)
310+
if err != nil {
311+
return nil, fmt.Errorf("failed to read file %s: %w", b.opts.File, err)
312+
}
313+
return body, nil
314+
}
315+
316+
func (b *Binder) unmarshalManifest(bs []byte) (*kubebindv1alpha2.APIServiceExportRequest, error) {
317+
var request kubebindv1alpha2.APIServiceExportRequest
318+
if err := yaml.Unmarshal(bs, &request); err != nil {
319+
return nil, fmt.Errorf("failed to unmarshal manifest: %w", err)
320+
}
321+
if request.APIVersion != kubebindv1alpha2.SchemeGroupVersion.String() {
322+
return nil, fmt.Errorf("invalid apiVersion %q", request.APIVersion)
323+
}
324+
if request.Kind != "APIServiceExportRequest" {
325+
return nil, fmt.Errorf("invalid kind %q", request.Kind)
326+
}
327+
return &request, nil
328+
}

cli/pkg/kubectl/bind-login/plugin/login.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func (o *LoginOptions) Run(ctx context.Context, authURLCh chan<- string) error {
134134
tokenCh := make(chan *TokenResponse, 1)
135135
errCh := make(chan error, 1)
136136

137-
server, localCallbackURL, err := o.startCallbackServerWithRandomPort(tokenCh, errCh)
137+
server, localCallbackURL, err := o.startCallbackServerWithRandomPort(sessionID, tokenCh, errCh)
138138
if err != nil {
139139
return fmt.Errorf("failed to start callback server: %w", err)
140140
}
@@ -227,7 +227,11 @@ func (o *LoginOptions) Run(ctx context.Context, authURLCh chan<- string) error {
227227
}
228228

229229
if o.ShowToken {
230-
fmt.Fprintf(o.Streams.ErrOut, "\nStored token: %s\n", token.AccessToken[:20]+"...")
230+
displayToken := token.AccessToken
231+
if len(displayToken) > 20 {
232+
displayToken = displayToken[:20] + "..."
233+
}
234+
fmt.Fprintf(o.Streams.ErrOut, "\nStored token: %s\n", displayToken)
231235
}
232236

233237
configPath, _ := config.GetConfigPath()
@@ -304,21 +308,25 @@ func (o *LoginOptions) buildAuthURL(provider *kubebindv1alpha2.BindingProvider,
304308
return u.String(), nil
305309
}
306310

307-
func (o *LoginOptions) startCallbackServerWithRandomPort(tokenCh chan<- *TokenResponse, errCh chan<- error) (*http.Server, string, error) {
308-
// Try to find an available port
311+
func (o *LoginOptions) startCallbackServerWithRandomPort(sessionID string, tokenCh chan<- *TokenResponse, errCh chan<- error) (*http.Server, string, error) {
312+
expectedSessionID := sessionID
309313
listener, err := net.Listen("tcp", "127.0.0.1:0")
310314
if err != nil {
311315
return nil, "", fmt.Errorf("failed to find available port: %w", err)
312316
}
313317

314318
port := listener.Addr().(*net.TCPAddr).Port
315-
listener.Close()
316319

317320
callbackURL := fmt.Sprintf("http://127.0.0.1:%d/callback", port)
318321

319322
// Setup HTTP handler
320323
mux := http.NewServeMux()
321324
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
325+
receivedSessionID := r.URL.Query().Get("session_id")
326+
if receivedSessionID != expectedSessionID {
327+
http.Error(w, "Invalid session", http.StatusBadRequest)
328+
return
329+
}
322330
token := &TokenResponse{
323331
Error: r.URL.Query().Get("error"),
324332
ErrorMessage: r.URL.Query().Get("error_description"),

cli/pkg/kubectl/bind/plugin/bind.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,11 @@ func (b *BindOptions) runWithCallback(ctx context.Context, _ chan<- string) erro
158158
if err != nil {
159159
return fmt.Errorf("failed to start callback server: %w", err)
160160
}
161-
defer callbackServer.Close()
161+
defer func() {
162+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
163+
defer cancel()
164+
_ = callbackServer.Shutdown(shutdownCtx)
165+
}()
162166

163167
// Build the UI URL with callback parameters
164168
uiURL, err := b.buildUIURL(callbackPort, sessionID, b.Cluster)
@@ -222,7 +226,6 @@ func (b *BindOptions) runWithCallback(ctx context.Context, _ chan<- string) erro
222226
fmt.Fprintf(b.Options.IOStreams.Out, "%s", request.Raw)
223227
}
224228
}
225-
return nil
226229
}
227230

228231
// Create bindings using the shared binder
@@ -236,6 +239,11 @@ func (b *BindOptions) runWithCallback(ctx context.Context, _ chan<- string) erro
236239
return fmt.Errorf("failed to create APIServiceBindings: %w", err)
237240
}
238241

242+
if b.DryRun {
243+
fmt.Fprintf(b.Options.IOStreams.ErrOut, "\nDry-run mode: no APIServiceBindings were created.\n")
244+
return nil
245+
}
246+
239247
// Print the results
240248
if len(bindings) > 0 {
241249
fmt.Fprintf(b.Options.IOStreams.ErrOut, "Created %d APIServiceBinding(s):\n", len(bindings))

0 commit comments

Comments
 (0)