Skip to content

Commit 8d70fbb

Browse files
wtrockiEnda Phelan
andcommitted
fix: add support for creating operator based resource (#599)
* fix: add support for creating operator based resource * chore: refactor logic to use additional flags * fix: allow to override bindAsFiles in SDK * fix: reduce size of random characters in binding name * fix: problem when having no kafkas available * style: ignore lint warning Co-authored-by: Enda Phelan <ephelan@redhat.com>
1 parent 1e978eb commit 8d70fbb

File tree

6 files changed

+130
-42
lines changed

6 files changed

+130
-42
lines changed

cmd/rhoas/pkged.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/commands/rhoas_cluster_bind.adoc

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

locales/cluster/bind/active.en.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[cluster.serviceBinding.namespaceInfo]
2-
one = '"Namespace not provided. Using {{.Namespace}} namespace'
2+
one = 'Namespace not provided. Using {{.Namespace}} namespace'
33

44
[cluster.serviceBinding.confirm.message]
55
one = 'Do you want to continue?'
@@ -19,4 +19,12 @@ one = 'Binding %v with %v app succeeded'
1919
[cluster.serviceBinding.status.message]
2020
one = 'Binding "%v" with "%v" app'
2121

22+
[cluster.serviceBinding.operatorMissing]
23+
one = '''
24+
Operator is not available on the current cluster.
25+
Please remove --force-operator=true if you wish to create resource without operator
26+
Error:
27+
'''
2228

29+
[cluster.serviceBinding.usingOperator]
30+
one = 'Using ServiceBinding Operator to perform binding'

locales/cmd/cluster/bind/active.en.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ services with.
1111
Bind command detects Kubernetes deployments and inject managed services credentials to them.
1212
For information about what credentials are injected please refer to individual services
1313
Command will inject credentials as files into `/bindings` folder inside your application.
14+
15+
Bind command will create volume inside your deployment or
16+
ServiceBindingOperator resource if you have it installed on your cluster
1417
'''
1518

1619
[cluster.bind.cmd.example]
@@ -26,5 +29,13 @@ $ rhoas cluster bind --namespace=ns --app-name=myapp
2629
[cluster.bind.flag.appName]
2730
one = '''Name of the kubernetes deployment to bind'''
2831

32+
[cluster.bind.flag.forceOperator.description]
33+
one = '''Use ServiceBindingOperator only and fail if Operator is not installed'''
34+
35+
[cluster.bind.flag.forceSDK.description]
36+
one = '''Use Service Binding SDK and skip ServiceBindingOperator even if installed on the cluster'''
37+
38+
39+
2940
[cluster.bind.error.emptyResponse]
3041
one = '''Server returned empty response for service'''

pkg/cluster/serviceBinding.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@ package cluster
22

33
import (
44
"context"
5+
"crypto/rand"
56
"errors"
67
"fmt"
78
"github.com/AlecAivazis/survey/v2"
9+
"github.com/redhat-developer/app-services-cli/internal/localizer"
810
"github.com/redhat-developer/app-services-cli/pkg/color"
911
"github.com/redhat-developer/app-services-cli/pkg/logging"
10-
sboContext "github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context"
11-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12-
"k8s.io/client-go/rest"
13-
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
14-
"os"
15-
"path/filepath"
16-
"time"
17-
18-
"github.com/redhat-developer/app-services-cli/internal/localizer"
1912
"github.com/redhat-developer/service-binding-operator/api/v1alpha1"
2013
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/builder"
14+
sboContext "github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context"
2115
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
17+
"k8s.io/apimachinery/pkg/runtime"
2218
"k8s.io/apimachinery/pkg/runtime/schema"
2319
"k8s.io/client-go/dynamic"
20+
"k8s.io/client-go/rest"
2421
"k8s.io/client-go/tools/clientcmd"
22+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
23+
"os"
24+
"path/filepath"
2525
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2626
)
2727

@@ -33,42 +33,53 @@ type KubernetesClients struct {
3333

3434
var deploymentResource = schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
3535

36-
func ExecuteServiceBinding(logger logging.Logger, serviceName string, ns string, appName string, forceCreationWithoutAsk bool) error {
36+
type ServiceBindingOptions struct {
37+
ServiceName string
38+
Namespace string
39+
AppName string
40+
ForceCreationWithoutAsk bool
41+
ForceUseOperator bool
42+
ForceUseSDK bool
43+
BindingName string
44+
BindAsFiles bool
45+
}
46+
47+
func ExecuteServiceBinding(logger logging.Logger, options *ServiceBindingOptions) error {
3748
clients, err := client()
3849
if err != nil {
3950
return err
4051
}
41-
52+
ns := options.Namespace
4253
if ns == "" {
4354
ns, _, err = (*clients.clientConfig).Namespace()
4455
if err != nil {
4556
return err
4657
}
47-
logger.Info(&localizer.Config{
58+
logger.Info(localizer.MustLocalize(&localizer.Config{
4859
MessageID: "cluster.serviceBinding.namespaceInfo",
4960
TemplateData: map[string]interface{}{
5061
"Namespace": color.Info(ns),
5162
},
52-
})
63+
}))
5364
}
5465

5566
// Get proper deployment
56-
if appName == "" {
57-
appName, err = fetchAppNameFromCluster(clients, ns)
67+
if options.AppName == "" {
68+
options.AppName, err = fetchAppNameFromCluster(clients, ns)
5869
if err != nil {
5970
return err
6071
}
6172
} else {
62-
_, err = clients.dynamicClient.Resource(deploymentResource).Namespace(ns).Get(context.TODO(), appName, metav1.GetOptions{})
73+
_, err = clients.dynamicClient.Resource(deploymentResource).Namespace(ns).Get(context.TODO(), options.AppName, metav1.GetOptions{})
6374
if err != nil {
6475
return err
6576
}
6677
}
6778

6879
// Print desired action
69-
logger.Info(fmt.Sprintf(localizer.MustLocalizeFromID("cluster.serviceBinding.status.message"), serviceName, appName))
80+
logger.Info(fmt.Sprintf(localizer.MustLocalizeFromID("cluster.serviceBinding.status.message"), options.ServiceName, options.AppName))
7081

71-
if !forceCreationWithoutAsk {
82+
if !options.ForceCreationWithoutAsk {
7283
var shouldContinue bool
7384
confirm := &survey.Confirm{
7485
Message: localizer.MustLocalizeFromID("cluster.serviceBinding.confirm.message"),
@@ -84,29 +95,29 @@ func ExecuteServiceBinding(logger logging.Logger, serviceName string, ns string,
8495
}
8596

8697
// Check KafkaConnection
87-
_, err = clients.dynamicClient.Resource(AKCResource).Namespace(ns).Get(context.TODO(), serviceName, metav1.GetOptions{})
98+
_, err = clients.dynamicClient.Resource(AKCResource).Namespace(ns).Get(context.TODO(), options.ServiceName, metav1.GetOptions{})
8899
if err != nil {
89100
return errors.New(localizer.MustLocalizeFromID("cluster.serviceBinding.serviceMissing.message"))
90101
}
91102

92103
// Execute binding
93-
err = performBinding(serviceName, appName, ns, clients)
104+
err = performBinding(options, ns, clients, logger)
94105
if err != nil {
95106
return err
96107
}
97108

98-
logger.Info(fmt.Sprintf(localizer.MustLocalizeFromID("cluster.serviceBinding.bindingSuccess"), serviceName, appName))
109+
logger.Info(fmt.Sprintf(localizer.MustLocalizeFromID("cluster.serviceBinding.bindingSuccess"), options.ServiceName, options.AppName))
99110
return nil
100111
}
101112

102-
func performBinding(serviceName string, appName string, ns string, clients *KubernetesClients) error {
113+
func performBinding(options *ServiceBindingOptions, ns string, clients *KubernetesClients, logger logging.Logger) error {
103114
serviceRef := v1alpha1.Service{
104115
NamespacedRef: v1alpha1.NamespacedRef{
105116
Ref: v1alpha1.Ref{
106117
Group: AKCResource.Group,
107118
Version: AKCResource.Version,
108119
Resource: AKCResource.Resource,
109-
Name: serviceName,
120+
Name: options.ServiceName,
110121
},
111122
},
112123
}
@@ -116,14 +127,22 @@ func performBinding(serviceName string, appName string, ns string, clients *Kube
116127
Group: deploymentResource.Group,
117128
Version: deploymentResource.Version,
118129
Resource: deploymentResource.Resource,
119-
Name: appName,
130+
Name: options.AppName,
120131
},
121132
}
122133

123-
now := time.Now()
134+
if options.BindingName == "" {
135+
randomValue := make([]byte, 2)
136+
_, err := rand.Read(randomValue)
137+
if err != nil {
138+
return err
139+
}
140+
options.BindingName = fmt.Sprintf("%v-%x", options.ServiceName, randomValue)
141+
}
142+
124143
sb := &v1alpha1.ServiceBinding{
125144
ObjectMeta: metav1.ObjectMeta{
126-
Name: fmt.Sprintf("%v-%v", serviceName, now.Unix()),
145+
Name: options.BindingName,
127146
Namespace: ns,
128147
},
129148
Spec: v1alpha1.ServiceBindingSpec{
@@ -134,6 +153,41 @@ func performBinding(serviceName string, appName string, ns string, clients *Kube
134153
}
135154
sb.SetGroupVersionKind(v1alpha1.GroupVersionKind)
136155

156+
if options.ForceUseSDK {
157+
return useSDKForBinding(clients, sb)
158+
}
159+
160+
// Check of operator is installed
161+
_, err := clients.dynamicClient.Resource(v1alpha1.GroupVersionResource).Namespace(ns).
162+
List(context.TODO(), metav1.ListOptions{Limit: 1})
163+
164+
if err != nil {
165+
if options.ForceUseOperator {
166+
return errors.New(localizer.MustLocalizeFromID("cluster.serviceBinding.operatorMissing") + err.Error())
167+
}
168+
logger.Debug("Service binding Operator not available. Will use SDK option for binding")
169+
return useSDKForBinding(clients, sb)
170+
}
171+
172+
return useOperatorForBinding(logger, sb, clients, ns)
173+
174+
}
175+
176+
func useOperatorForBinding(logger logging.Logger, sb *v1alpha1.ServiceBinding, clients *KubernetesClients, ns string) error {
177+
logger.Info(localizer.MustLocalizeFromID("cluster.serviceBinding.usingOperator"))
178+
sbData, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sb)
179+
if err != nil {
180+
return err
181+
}
182+
183+
unstructuredSB := unstructured.Unstructured{Object: sbData}
184+
_, err = clients.dynamicClient.Resource(v1alpha1.GroupVersionResource).Namespace(ns).
185+
Create(context.TODO(), &unstructuredSB, metav1.CreateOptions{})
186+
187+
return err
188+
}
189+
190+
func useSDKForBinding(clients *KubernetesClients, sb *v1alpha1.ServiceBinding) error {
137191
restMapper, err := apiutil.NewDynamicRESTMapper(clients.restConfig)
138192
if err != nil {
139193
return err

pkg/cmd/cluster/bind/bind.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type Options struct {
2828
ignoreContext bool
2929
appName string
3030
selectedKafka string
31+
32+
forceOperator bool
33+
forceSDK bool
34+
bindingName string
3135
}
3236

3337
func NewBindCommand(f *factory.Factory) *cobra.Command {
@@ -69,12 +73,13 @@ func NewBindCommand(f *factory.Factory) *cobra.Command {
6973
cmd.Flags().BoolVarP(&opts.forceCreationWithoutAsk, "yes", "y", false, localizer.MustLocalizeFromID("cluster.common.flag.yes.description"))
7074
cmd.Flags().StringVarP(&opts.namespace, "namespace", "n", "", localizer.MustLocalizeFromID("cluster.common.flag.namespace.description"))
7175
cmd.Flags().BoolVarP(&opts.ignoreContext, "ignore-context", "", false, localizer.MustLocalizeFromID("cluster.common.flag.ignoreContext.description"))
72-
76+
cmd.Flags().BoolVarP(&opts.forceOperator, "force-operator", "", false, localizer.MustLocalizeFromID("cluster.bind.flag.forceOperator.description"))
77+
cmd.Flags().BoolVarP(&opts.forceSDK, "force-sdk", "", false, localizer.MustLocalizeFromID("cluster.bind.flag.forceSDK.description"))
7378
return cmd
7479
}
7580

7681
func runBind(opts *Options) error {
77-
connection, err := opts.Connection(connection.DefaultConfigSkipMasAuth)
82+
apiConnection, err := opts.Connection(connection.DefaultConfigSkipMasAuth)
7883
if err != nil {
7984
return err
8085
}
@@ -91,32 +96,37 @@ func runBind(opts *Options) error {
9196

9297
// In future config will include Id's of other services
9398
if cfg.Services.Kafka == nil || opts.ignoreContext {
94-
// nolint
95-
selectedKafka, err := kafka.InteractiveSelect(connection, logger)
99+
// nolint:govet
100+
selectedKafka, err := kafka.InteractiveSelect(apiConnection, logger)
96101
if err != nil {
97-
98102
return err
99103
}
100-
opts.selectedKafka = *selectedKafka.Id
104+
opts.selectedKafka = selectedKafka.GetId()
101105
} else {
102106
opts.selectedKafka = cfg.Services.Kafka.ClusterID
103107
}
104108

105-
api := connection.API()
106-
kafkaInstance, _, error := api.Kafka().GetKafkaById(context.Background(), opts.selectedKafka).Execute()
109+
api := apiConnection.API()
110+
kafkaInstance, _, err := api.Kafka().GetKafkaById(context.Background(), opts.selectedKafka).Execute()
107111

108-
if error != nil {
109-
return error
112+
if err != nil {
113+
return err
110114
}
111115

112116
if kafkaInstance.Name == nil {
113117
return errors.New(localizer.MustLocalizeFromID("cluster.bind.error.emptyResponse"))
114118
}
115119

116-
err = cluster.ExecuteServiceBinding(logger, *kafkaInstance.Name,
117-
opts.namespace,
118-
opts.appName,
119-
opts.forceCreationWithoutAsk)
120+
err = cluster.ExecuteServiceBinding(logger, &cluster.ServiceBindingOptions{
121+
ServiceName: kafkaInstance.GetName(),
122+
Namespace: opts.namespace,
123+
AppName: opts.appName,
124+
ForceCreationWithoutAsk: opts.forceCreationWithoutAsk,
125+
ForceUseOperator: opts.forceOperator,
126+
ForceUseSDK: opts.forceSDK,
127+
BindingName: opts.bindingName,
128+
BindAsFiles: true,
129+
})
120130

121131
return err
122132
}

0 commit comments

Comments
 (0)