@@ -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
61154func (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+ }
0 commit comments