diff --git a/pkg/binding/binding.go b/pkg/binding/binding.go index 0b285f3fd40..953d894c2fa 100644 --- a/pkg/binding/binding.go +++ b/pkg/binding/binding.go @@ -11,9 +11,11 @@ import ( "github.com/devfile/library/pkg/devfile/parser" devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" "gopkg.in/yaml.v2" + appsv1 "k8s.io/api/apps/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" devfilev1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" @@ -284,3 +286,56 @@ func (o *BindingClient) getStatusFromSpec(name string) (*api.ServiceBindingStatu BindingEnvVars: bindingEnvVars, }, nil } + +func (o *BindingClient) CheckServiceBindingsInjectionDone(componentName string, appName string) (bool, error) { + + deployment, err := o.kubernetesClient.GetOneDeployment(componentName, appName) + if err != nil { + // If not deployment yet => all bindings are done + if _, ok := err.(*kclient.DeploymentNotFoundError); ok { + return true, nil + } + return false, err + } + deploymentName := deployment.GetName() + + specList, bindingList, err := o.kubernetesClient.ListServiceBindingsFromAllGroups() + if err != nil { + // If ServiceBinding kind is not registered => all bindings are done + if runtime.IsNotRegisteredError(err) { + return true, nil + } + return false, err + } + + for _, binding := range bindingList { + app := binding.Spec.Application + if app.Group != appsv1.SchemeGroupVersion.Group || + app.Version != appsv1.SchemeGroupVersion.Version || + (app.Kind != "Deployment" && app.Resource != "deployments") { + continue + } + if app.Name != deploymentName { + continue + } + if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected { + return false, nil + } + } + + for _, binding := range specList { + app := binding.Spec.Workload + if app.APIVersion != appsv1.SchemeGroupVersion.String() || + app.Kind != "Deployment" { + continue + } + if app.Name != deploymentName { + continue + } + if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected { + return false, nil + } + } + + return true, nil +} diff --git a/pkg/binding/interface.go b/pkg/binding/interface.go index 6fdb5eed93c..e63377e5d3c 100644 --- a/pkg/binding/interface.go +++ b/pkg/binding/interface.go @@ -67,4 +67,7 @@ type Client interface { ValidateRemoveBinding(flags map[string]string) error // RemoveBinding removes the binding from devfile RemoveBinding(bindingName string, obj parser.DevfileObj) (parser.DevfileObj, error) + + // CheckServiceBindingsInjectionDone checks that all service bindings poiting to component have InjectionReady condition + CheckServiceBindingsInjectionDone(componentName string, appName string) (bool, error) } diff --git a/pkg/binding/mock.go b/pkg/binding/mock.go index c87e03959ff..2af57be5c6e 100644 --- a/pkg/binding/mock.go +++ b/pkg/binding/mock.go @@ -115,6 +115,21 @@ func (mr *MockClientMockRecorder) AskNamingStrategy(flags interface{}) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskNamingStrategy", reflect.TypeOf((*MockClient)(nil).AskNamingStrategy), flags) } +// CheckServiceBindingsInjectionDone mocks base method. +func (m *MockClient) CheckServiceBindingsInjectionDone(componentName, appName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckServiceBindingsInjectionDone", componentName, appName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckServiceBindingsInjectionDone indicates an expected call of CheckServiceBindingsInjectionDone. +func (mr *MockClientMockRecorder) CheckServiceBindingsInjectionDone(componentName, appName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckServiceBindingsInjectionDone", reflect.TypeOf((*MockClient)(nil).CheckServiceBindingsInjectionDone), componentName, appName) +} + // GetBindingFromCluster mocks base method. func (m *MockClient) GetBindingFromCluster(name string) (api.ServiceBinding, error) { m.ctrl.T.Helper() diff --git a/pkg/dev/dev.go b/pkg/dev/dev.go index 805f9a64192..6a1652d69dd 100644 --- a/pkg/dev/dev.go +++ b/pkg/dev/dev.go @@ -4,6 +4,7 @@ import ( "context" "io" + "github.com/redhat-developer/odo/pkg/binding" "github.com/redhat-developer/odo/pkg/envinfo" "github.com/redhat-developer/odo/pkg/kclient" "github.com/redhat-developer/odo/pkg/portForward" @@ -22,6 +23,7 @@ type DevClient struct { prefClient preference.Client portForwardClient portForward.Client watchClient watch.Client + bindingClient binding.Client } var _ Client = (*DevClient)(nil) @@ -31,12 +33,14 @@ func NewDevClient( prefClient preference.Client, portForwardClient portForward.Client, watchClient watch.Client, + bindingClient binding.Client, ) *DevClient { return &DevClient{ kubernetesClient: kubernetesClient, prefClient: prefClient, portForwardClient: portForwardClient, watchClient: watchClient, + bindingClient: bindingClient, } } @@ -53,7 +57,7 @@ func (o *DevClient) Start( ) (watch.ComponentStatus, error) { klog.V(4).Infoln("Creating new adapter") adapter := component.NewKubernetesAdapter( - o.kubernetesClient, o.prefClient, o.portForwardClient, + o.kubernetesClient, o.prefClient, o.portForwardClient, o.bindingClient, component.AdapterContext{ ComponentName: devfileObj.GetMetadataName(), Context: path, diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 7397d4299a7..f33299029ea 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -1,6 +1,7 @@ package component import ( + "errors" "fmt" "io" "path/filepath" @@ -9,6 +10,7 @@ import ( "k8s.io/utils/pointer" + "github.com/redhat-developer/odo/pkg/binding" "github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/storage" @@ -44,6 +46,7 @@ type Adapter struct { kubeClient kclient.ClientInterface prefClient preference.Client portForwardClient portForward.Client + bindingClient binding.Client AdapterContext logger machineoutput.MachineEventLoggingClient @@ -65,6 +68,7 @@ func NewKubernetesAdapter( kubernetesClient kclient.ClientInterface, prefClient preference.Client, portForwardClient portForward.Client, + bindingClient binding.Client, context AdapterContext, namespace string, ) Adapter { @@ -77,6 +81,7 @@ func NewKubernetesAdapter( kubeClient: kubernetesClient, prefClient: prefClient, portForwardClient: portForwardClient, + bindingClient: bindingClient, AdapterContext: context, logger: machineoutput.NewMachineEventLoggingClient(), } @@ -158,6 +163,16 @@ func (a Adapter) Push(parameters adapters.PushParameters, componentStatus *watch return nil } + injected, err := a.bindingClient.CheckServiceBindingsInjectionDone(a.ComponentName, a.AppName) + if err != nil { + return err + } + + if !injected { + klog.V(4).Infof("Waiting for all service bindings to be injected...\n") + return errors.New("some servicebindings are not injected") + } + // Check if endpoints changed in Devfile portsToForward, err := a.portForwardClient.GetPortsToForward(a.Devfile) if err != nil { diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/devfile/adapters/kubernetes/component/adapter_test.go index f7b9e26e74f..8e4bfe91dff 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter_test.go @@ -136,7 +136,7 @@ func TestCreateOrUpdateComponent(t *testing.T) { ctrl := gomock.NewController(t) fakePrefClient := preference.NewMockClient(ctrl) fakePrefClient.EXPECT().GetEphemeralSourceVolume() - componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, adapterCtx, "") + componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, adapterCtx, "") _, _, err := componentAdapter.createOrUpdateComponent(tt.running, tt.envInfo, libdevfile.DevfileCommands{}, 0, nil) // Checks for unexpected error cases @@ -352,7 +352,7 @@ func TestDoesComponentExist(t *testing.T) { ctrl := gomock.NewController(t) fakePrefClient := preference.NewMockClient(ctrl) fakePrefClient.EXPECT().GetEphemeralSourceVolume() - componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, adapterCtx, "") + componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, adapterCtx, "") _, _, err := componentAdapter.createOrUpdateComponent(false, tt.envInfo, libdevfile.DevfileCommands{}, 0, nil) // Checks for unexpected error cases diff --git a/pkg/odo/cli/dev/dev.go b/pkg/odo/cli/dev/dev.go index 6d84fbff13c..7697f81cccd 100644 --- a/pkg/odo/cli/dev/dev.go +++ b/pkg/odo/cli/dev/dev.go @@ -299,6 +299,7 @@ func (o *Handler) regenerateComponentAdapterFromWatchParams(parameters watch.Wat o.clientset.KubernetesClient, o.clientset.PreferenceClient, o.clientset.PortForwardClient, + o.clientset.BindingClient, kcomponent.AdapterContext{ ComponentName: parameters.ComponentName, Context: parameters.Path, @@ -341,6 +342,7 @@ It forwards endpoints with exposure values 'public' or 'internal' to a port on l devCmd.Flags().StringVar(&o.runCommandFlag, "run-command", "", "Alternative run command to execute. The default one will be used if this flag is not set.") clientset.Add(devCmd, + clientset.BINDING, clientset.DEV, clientset.FILESYSTEM, clientset.INIT, diff --git a/pkg/odo/genericclioptions/clientset/clientset.go b/pkg/odo/genericclioptions/clientset/clientset.go index 943ee09c0f4..95b0c3336ba 100644 --- a/pkg/odo/genericclioptions/clientset/clientset.go +++ b/pkg/odo/genericclioptions/clientset/clientset.go @@ -74,7 +74,7 @@ var subdeps map[string][]string = map[string][]string{ ALIZER: {REGISTRY}, DELETE_COMPONENT: {KUBERNETES}, DEPLOY: {KUBERNETES}, - DEV: {KUBERNETES, PORT_FORWARD, PREFERENCE, WATCH}, + DEV: {BINDING, KUBERNETES, PORT_FORWARD, PREFERENCE, WATCH}, INIT: {ALIZER, FILESYSTEM, PREFERENCE, REGISTRY}, LOGS: {KUBERNETES}, PORT_FORWARD: {KUBERNETES, STATE}, @@ -180,7 +180,7 @@ func Fetch(command *cobra.Command) (*Clientset, error) { dep.PortForwardClient = portForward.NewPFClient(dep.KubernetesClient, dep.StateClient) } if isDefined(command, DEV) { - dep.DevClient = dev.NewDevClient(dep.KubernetesClient, dep.PreferenceClient, dep.PortForwardClient, dep.WatchClient) + dep.DevClient = dev.NewDevClient(dep.KubernetesClient, dep.PreferenceClient, dep.PortForwardClient, dep.WatchClient, dep.BindingClient) } /* Instantiate new clients here. Take care to instantiate after all sub-dependencies */