Skip to content

Commit

Permalink
Delete services not defined anymore
Browse files Browse the repository at this point in the history
  • Loading branch information
feloy committed Jun 8, 2021
1 parent b48f180 commit f8761c0
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 24 deletions.
18 changes: 12 additions & 6 deletions pkg/devfile/adapters/kubernetes/component/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,22 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
return errors.Wrap(err, "error while trying to fetch service(s) from devfile")
}
labels := componentlabels.GetLabels(a.ComponentName, a.AppName, true)
// create the Kubernetes objects from the manifest
services, err := service.CreateServiceFromKubernetesInlineComponents(a.Client.GetKubeClient(), k8sComponents, labels)
// create the Kubernetes objects from the manifest and delete the ones not in the devfile
createdServices, deletedServices, err := service.PushServiceFromKubernetesInlineComponents(a.Client.GetKubeClient(), k8sComponents, labels)
if err != nil {
return errors.Wrap(err, "failed to create service(s) associated with the component")
}

if len(services) == 1 {
log.Infof("Created service %q on the cluster; refer %q to know how to link it to the component", services[0], "odo link -h")
} else if len(services) > 1 {
log.Infof("Created services %q on the cluster; refer %q to know how to link them to the component", strings.Join(services, ", "), "odo link -h")
if len(createdServices) == 1 {
log.Infof("Created service %q on the cluster; refer %q to know how to link it to the component", createdServices[0], "odo link -h")
} else if len(createdServices) > 1 {
log.Infof("Created services %q on the cluster; refer %q to know how to link them to the component", strings.Join(createdServices, ", "), "odo link -h")
}

if len(deletedServices) == 1 {
log.Infof("Deleted service %q from the cluster", deletedServices[0])
} else if len(deletedServices) > 1 {
log.Infof("Deleted services %q from the cluster", strings.Join(deletedServices, ", "))
}

deployment, err := a.Client.GetKubeClient().WaitForDeploymentRollout(a.ComponentName)
Expand Down
5 changes: 4 additions & 1 deletion pkg/odo/cli/service/list_operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ metadata:
t.Run(tt.name, func(t *testing.T) {
us := make([]unstructured.Unstructured, len(tt.clusterListInlined))
for i, clusterInlined := range tt.clusterListInlined {
yaml.Unmarshal([]byte(clusterInlined), &us[i])
err := yaml.Unmarshal([]byte(clusterInlined), &us[i])
if err != nil {
t.Errorf("Fail to unmarshal spec manifest")
}
}
result := mixServices(us, tt.devfileList)
if !reflect.DeepEqual(result, tt.want) {
Expand Down
76 changes: 60 additions & 16 deletions pkg/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ func ListOperatorServices(client *kclient.Client) ([]unstructured.Unstructured,

// First let's get the list of all the operators in the namespace
csvs, err := client.ListClusterServiceVersions()
if err == kclient.ErrNoSuchOperator {
return nil, nil, err
}

if err != nil {
return nil, nil, errors.Wrap(err, "Unable to list operator backed services")
}
Expand Down Expand Up @@ -707,7 +711,7 @@ func IsDefined(name string, devfileObj parser.DevfileObj) (bool, error) {
return false, nil
}

// IsDefined checks if a service with the given name is defined in a DevFile
// ListDevfileServices returns the names of the services defined in a Devfile
func ListDevfileServices(devfileObj parser.DevfileObj) ([]string, error) {
if devfileObj.Data == nil {
return nil, nil
Expand All @@ -723,8 +727,7 @@ func ListDevfileServices(devfileObj parser.DevfileObj) ([]string, error) {
var u unstructured.Unstructured
err = yaml.Unmarshal([]byte(c.Kubernetes.Inlined), &u)
if err != nil {
// TODO log
continue
return nil, err
}
services = append(services, strings.Join([]string{u.GetKind(), c.Name}, "/"))
}
Expand Down Expand Up @@ -853,15 +856,29 @@ func (d *DynamicCRD) AddComponentLabelsToCRD(labels map[string]string) {
metaMap["labels"] = labels
}

// CreateServiceFromKubernetesInlineComponents creates service(s) from Kubernetes Inlined component in a devfile
func CreateServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string) ([]string, error) {
if len(k8sComponents) == 0 {
// if there's no Kubernetes Inlined component, there's nothing to do.
return []string{}, nil
// PushServiceFromKubernetesInlineComponents updates service(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones
func PushServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string) ([]string, []string, error) {

created := []string{}
deleted := []string{}

deployed := map[string]struct{}{}

deployedServices, _, err := ListOperatorServices(client)
if err != nil && err != kclient.ErrNoSuchOperator {
// We ignore ErrNoSuchOperator error as we can deduce Operator Services are not installed
return nil, nil, err
}
for _, svc := range deployedServices {
name := svc.GetName()
kind := svc.GetKind()
deployedLabels := svc.GetLabels()
if deployedLabels[applabels.OdoManagedBy] == "odo" && deployedLabels[componentlabels.ComponentLabel] == labels[componentlabels.ComponentLabel] {
deployed[kind+"/"+name] = struct{}{}
}
}

// create an object on the kubernetes cluster for all the Kubernetes Inlined components
var services []string
for _, c := range k8sComponents {
// get the string representation of the YAML definition of a CRD
strCRD := c.Kubernetes.Inlined
Expand All @@ -870,20 +887,20 @@ func CreateServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComp
d := NewDynamicCRD()
err := yaml.Unmarshal([]byte(strCRD), &d.OriginalCRD)
if err != nil {
return []string{}, err
return nil, nil, err
}

cr, csv, err := GetCSV(client, d.OriginalCRD)
if err != nil {
return []string{}, err
return nil, nil, err
}

var group, version, kind, resource string
for _, crd := range csv.Spec.CustomResourceDefinitions.Owned {
if crd.Kind == cr {
group, version, kind, resource, err = getGVKRFromCR(crd)
if err != nil {
return []string{}, err
return nil, nil, err
}
break
}
Expand All @@ -892,22 +909,49 @@ func CreateServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComp
// add labels to the CRD before creation
d.AddComponentLabelsToCRD(labels)

crdName, ok := getCRDName(d.OriginalCRD)
if !ok {
continue
}

delete(deployed, cr+"/"+crdName)

// create the service on cluster
err = client.CreateDynamicResource(d.OriginalCRD, group, version, resource)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
// this could be the case when "odo push" was executed after making change to code but there was no change to the service itself
// TODO: better way to handle this might be introduced by https://github.com/openshift/odo/issues/4553
err = nil
break // this ensures that services slice is not updated
continue // this ensures that services slice is not updated
} else {
return []string{}, err
return nil, nil, err
}
}

name, _ := d.GetServiceNameFromCRD() // ignoring error because invalid yaml won't be inserted into devfile through odo
services = append(services, strings.Join([]string{kind, name}, "/"))
created = append(created, strings.Join([]string{kind, name}, "/"))
}

return services, nil
for key := range deployed {
err = DeleteOperatorService(client, key)
if err != nil {
return nil, nil, err

}
deleted = append(deleted, key)
}
return created, deleted, nil
}

func getCRDName(crd map[string]interface{}) (string, bool) {
metadata, ok := crd["metadata"].(map[string]interface{})
if !ok {
return "", false
}
name, ok := metadata["name"].(string)
if !ok {
return "", false
}
return name, true
}
3 changes: 2 additions & 1 deletion tests/integration/operatorhub/cmd_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ spec:
cleanPreSetup()
})

It("should list the services if they exist", func() { // TODO
It("should list the services if they exist", func() {
helper.CmdShouldPass("odo", "create", "nodejs")

operators := helper.CmdShouldPass("odo", "catalog", "list", "services")
Expand All @@ -385,6 +385,7 @@ spec:

helper.CmdShouldPass("odo", "service", "delete", "EtcdCluster/example", "-f")

helper.CmdShouldPass("odo", "push")
// Now let's check the output again to ensure expected behaviour
stdOut = helper.CmdShouldFail("odo", "service", "list")
jsonOut = helper.CmdShouldFail("odo", "service", "list", "-o", "json")
Expand Down

0 comments on commit f8761c0

Please sign in to comment.