diff --git a/pkg/application/application.go b/pkg/application/application.go index ccc6b997862..bac8d2b30fd 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -1,6 +1,7 @@ package application import ( + "github.com/openshift/odo/pkg/kclient" "github.com/pkg/errors" "k8s.io/klog" @@ -18,42 +19,24 @@ const ( appList = "List" ) -// List all applications names in current project by looking at `app` labels in deploymentconfigs, deployments and services instances -func List(client *occlient.Client) ([]string, error) { - var appNames []string - +// List all applications names in current project by looking at `app` labels in deployments +func List(client *kclient.Client) ([]string, error) { if client == nil { - return appNames, nil - } - - deploymentSupported, err := client.IsDeploymentConfigSupported() - if err != nil { - return nil, err + return nil, nil } - // Get all DeploymentConfigs with the "app" label - deploymentAppNames, err := client.GetKubeClient().GetDeploymentLabelValues(applabels.ApplicationLabel, applabels.ApplicationLabel) + // Get all Deployments with the "app" label + deploymentAppNames, err := client.GetDeploymentLabelValues(applabels.ApplicationLabel, applabels.ApplicationLabel) if err != nil { return nil, errors.Wrap(err, "unable to list applications from deployments") } - appNames = append(appNames, deploymentAppNames...) - - if deploymentSupported { - // Get all DeploymentConfigs with the "app" label - deploymentConfigAppNames, err := client.GetDeploymentConfigLabelValues(applabels.ApplicationLabel, applabels.ApplicationLabel) - if err != nil { - return nil, errors.Wrap(err, "unable to list applications from deployment config") - } - appNames = append(appNames, deploymentConfigAppNames...) - } - // Filter out any names, as there could be multiple components but within the same application - return util.RemoveDuplicates(appNames), nil + return util.RemoveDuplicates(deploymentAppNames), nil } // Exists checks whether the given app exist or not in the list of applications -func Exists(app string, client *occlient.Client) (bool, error) { +func Exists(app string, client *kclient.Client) (bool, error) { appList, err := List(client) @@ -68,26 +51,14 @@ func Exists(app string, client *occlient.Client) (bool, error) { return false, nil } -// Delete the given application by deleting deploymentconfigs, deployments and services instances belonging to this application -func Delete(client *occlient.Client, name string) error { +// Delete the given application by deleting deployments and services instances belonging to this application +func Delete(client *kclient.Client, name string) error { klog.V(4).Infof("Deleting application %s", name) labels := applabels.GetLabels(name, false) - supported, err := client.IsDeploymentConfigSupported() - if err != nil { - return err - } - if supported { - // delete application from cluster - err = client.Delete(labels, false) - if err != nil { - return errors.Wrapf(err, "unable to delete application %s", name) - } - } - // delete application from cluster - err = client.GetKubeClient().Delete(labels, false) + err := client.Delete(labels, false) if err != nil { return errors.Wrapf(err, "unable to delete application %s", name) } diff --git a/pkg/application/application_test.go b/pkg/application/application_test.go index d206b04e3a1..9df69b6df9c 100644 --- a/pkg/application/application_test.go +++ b/pkg/application/application_test.go @@ -4,23 +4,21 @@ import ( "reflect" "testing" - "github.com/openshift/odo/pkg/testingutil" - "github.com/openshift/odo/pkg/version" - - appsv1 "github.com/openshift/api/apps/v1" applabels "github.com/openshift/odo/pkg/application/labels" - "github.com/openshift/odo/pkg/component" componentlabels "github.com/openshift/odo/pkg/component/labels" "github.com/openshift/odo/pkg/occlient" + "github.com/openshift/odo/pkg/testingutil" + "github.com/openshift/odo/pkg/version" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ktesting "k8s.io/client-go/testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestGetMachineReadableFormat(t *testing.T) { type args struct { - // client *occlient.Client appName string projectName string active bool @@ -53,9 +51,8 @@ func TestGetMachineReadableFormat(t *testing.T) { }, }, } - - dcList := appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ + deploymentList := appsv1.DeploymentList{ + Items: []appsv1.Deployment{ { ObjectMeta: metav1.ObjectMeta{ Name: "frontend-myapp", @@ -67,12 +64,9 @@ func TestGetMachineReadableFormat(t *testing.T) { applabels.ManagedBy: "odo", applabels.ManagerVersion: version.VERSION, }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { @@ -94,12 +88,9 @@ func TestGetMachineReadableFormat(t *testing.T) { applabels.ManagedBy: "odo", applabels.ManagerVersion: version.VERSION, }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { @@ -119,18 +110,18 @@ func TestGetMachineReadableFormat(t *testing.T) { client, fakeClientSet := occlient.FakeNew() // fake the project - fakeClientSet.ProjClientset.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { + fakeClientSet.Kubernetes.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil }) - //fake the dcs - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &dcList, nil + //fake the deployments + fakeClientSet.Kubernetes.PrependReactor("list", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) { + return true, &deploymentList, nil }) - for i := range dcList.Items { - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &dcList.Items[i], nil + for i := range deploymentList.Items { + fakeClientSet.Kubernetes.PrependReactor("get", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) { + return true, &deploymentList.Items[i], nil }) } if got := GetMachineReadableFormat(client, tt.args.appName, tt.args.projectName); !reflect.DeepEqual(got, tt.want) { diff --git a/pkg/catalog/catalog.go b/pkg/catalog/catalog.go index c5d67ed00a7..07d3f40205e 100644 --- a/pkg/catalog/catalog.go +++ b/pkg/catalog/catalog.go @@ -10,7 +10,6 @@ import ( "github.com/openshift/odo/pkg/preference" "github.com/zalando/go-keyring" - imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" @@ -22,30 +21,8 @@ import ( "github.com/openshift/odo/pkg/util" olm "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" ) -const ( - apiVersion = "odo.dev/v1alpha1" -) - -var supportedImages = map[string]bool{ - "redhat-openjdk-18/openjdk18-openshift:latest": true, - "openjdk/openjdk-11-rhel8:latest": true, - "openjdk/openjdk-11-rhel7:latest": true, - "ubi8/openjdk-11:latest": true, - "centos/nodejs-10-centos7:latest": true, - "centos/nodejs-12-centos7:latest": true, - "centos7/nodejs-10-centos7:latest": true, - "centos7/nodejs-12-centos7:latest": true, - "rhscl/nodejs-10-rhel7:latest": true, - "rhscl/nodejs-12-rhel7:latest": true, - "rhoar-nodejs/nodejs-10:latest": true, - "ubi8/nodejs-12:latest": true, - "ubi8/nodejs-14:latest": true, -} - // GetDevfileRegistries gets devfile registries from preference file, // if registry name is specified return the specific registry, otherwise return all registries func GetDevfileRegistries(registryName string) ([]Registry, error) { @@ -248,58 +225,25 @@ func ListDevfileComponents(registryName string) (DevfileComponentTypeList, error return *catalogDevfileList, nil } -// ListComponents lists all the available component types -func ListComponents(client *occlient.Client) (ComponentTypeList, error) { - - catalogList, err := getDefaultBuilderImages(client) - if err != nil { - return ComponentTypeList{}, errors.Wrap(err, "unable to get image streams") - } - - if len(catalogList) == 0 { - return ComponentTypeList{}, errors.New("unable to retrieve any catalog images from the OpenShift cluster") - } - - return ComponentTypeList{ - TypeMeta: metav1.TypeMeta{ - Kind: "List", - APIVersion: apiVersion, - }, - Items: catalogList, - }, nil - -} - // SearchComponent searches for the component +//TODO: Fix this to return devfile components func SearchComponent(client *occlient.Client, name string) ([]string, error) { - var result []string - componentList, err := ListComponents(client) - if err != nil { - return nil, errors.Wrap(err, "unable to list components") - } - - // do a partial search in all the components - for _, component := range componentList.Items { - // we only show components that contain the search term and that have at least non-hidden tag - // since a component with all hidden tags is not shown in the odo catalog list components either - if strings.Contains(component.ObjectMeta.Name, name) && len(component.Spec.NonHiddenTags) > 0 { - result = append(result, component.ObjectMeta.Name) - } - } - - return result, nil -} - -// ComponentExists returns true if the given component type and the version are valid, false if not -func ComponentExists(client *occlient.Client, componentType string, componentVersion string) (bool, error) { - imageStream, err := client.GetImageStream("", componentType, componentVersion) - if err != nil { - return false, errors.Wrapf(err, "unable to get from catalog") - } - if imageStream == nil { - return false, nil - } - return true, nil + //var result []string + //componentList, err := ListDevfileComponents(client) + //if err != nil { + // return nil, errors.Wrap(err, "unable to list components") + //} + // + //// do a partial search in all the components + //for _, component := range componentList.Items { + // // we only show components that contain the search term and that have at least non-hidden tag + // // since a component with all hidden tags is not shown in the odo catalog list components either + // if strings.Contains(component.ObjectMeta.Name, name) && len(component.Spec.NonHiddenTags) > 0 { + // result = append(result, component.ObjectMeta.Name) + // } + //} + + return []string{}, nil } // ListOperatorServices fetches a list of Operators from the cluster and @@ -329,235 +273,3 @@ func ListOperatorServices(client kclient.ClientInterface) (*olm.ClusterServiceVe return &csvList, nil } - -// getDefaultBuilderImages returns the default builder images available in the -// openshift and the current namespaces -func getDefaultBuilderImages(client *occlient.Client) ([]ComponentType, error) { - - var imageStreams []imagev1.ImageStream - currentNamespace := client.GetCurrentProjectName() - - // Fetch imagestreams from default openshift namespace - openshiftNSImageStreams, openshiftNSISFetchError := client.ListImageStreams(occlient.OpenShiftNameSpace) - if openshiftNSISFetchError != nil { - // Tolerate the error as it might only be a partial failure - // We may get the imagestreams from other Namespaces - //err = errors.Wrapf(openshiftNSISFetchError, "unable to get Image Streams from namespace %s", occlient.OpenShiftNameSpace) - // log it for debugging purposes - klog.V(4).Infof("Unable to get Image Streams from namespace %s. Error %s", occlient.OpenShiftNameSpace, openshiftNSISFetchError.Error()) - } - - // Fetch imagestreams from current namespace - currentNSImageStreams, currentNSISFetchError := client.ListImageStreams(currentNamespace) - // If failure to fetch imagestreams from current namespace, log the failure for debugging purposes - if currentNSISFetchError != nil { - // Tolerate the error as it is totally a valid scenario to not have any imagestreams in current namespace - // log it for debugging purposes - klog.V(4).Infof("Unable to get Image Streams from namespace %s. Error %s", currentNamespace, currentNSISFetchError.Error()) - } - - // If failure fetching imagestreams from both namespaces, error out - if openshiftNSISFetchError != nil && currentNSISFetchError != nil { - return nil, errors.Wrapf( - fmt.Errorf("%s.\n%s", openshiftNSISFetchError, currentNSISFetchError), - "Failed to fetch imagestreams from both openshift and %s namespaces.\nPlease ensure that a builder imagestream of required version for the component exists in either openshift or %s namespaces", - currentNamespace, - currentNamespace, - ) - } - - // Resultant imagestreams is list of imagestreams from current and openshift namespaces - imageStreams = append(imageStreams, openshiftNSImageStreams...) - imageStreams = append(imageStreams, currentNSImageStreams...) - - // create a map from name (builder image name + tag) to the ImageStreamTag - // we need this in order to filter out hidden tags - imageStreamTagMap := make(map[string]imagev1.ImageStreamTag) - - currentNSImageStreamTags, currentNSImageStreamTagsErr := client.GetImageStreamTags(currentNamespace) - openshiftNSImageStreamTags, openshiftNSImageStreamTagsErr := client.GetImageStreamTags(occlient.OpenShiftNameSpace) - - // If failure fetching imagestreamtags from both namespaces, error out - if currentNSImageStreamTagsErr != nil && openshiftNSImageStreamTagsErr != nil { - return nil, errors.Wrapf( - fmt.Errorf("%s.\n%s", currentNSImageStreamTagsErr, openshiftNSImageStreamTagsErr), - "Failed to fetch imagestreamtags from both openshift and %s namespaces.\nPlease ensure that a builder imagestream of required version for the component exists in either openshift or %s namespaces", - currentNamespace, - currentNamespace, - ) - } - - // create a map from name to ImageStreamTag out of all the ImageStreamTag objects we collect - var imageStreamTags []imagev1.ImageStreamTag - imageStreamTags = append(imageStreamTags, currentNSImageStreamTags...) - imageStreamTags = append(imageStreamTags, openshiftNSImageStreamTags...) - for _, imageStreamTag := range imageStreamTags { - imageStreamTagMap[imageStreamTag.Name] = imageStreamTag - } - - builderImages := getBuildersFromImageStreams(imageStreams, imageStreamTagMap) - - return builderImages, nil -} - -// SliceSupportedTags splits the tags in to fully supported and unsupported tags -func SliceSupportedTags(component ComponentType) ([]string, []string) { - - // this makes sure that json marshal shows these lists as [] instead of null - supTag, unSupTag := []string{}, []string{} - tagMap := createImageTagMap(component.Spec.ImageStreamTags) - - for _, tag := range component.Spec.NonHiddenTags { - imageName := tagMap[tag] - if isSupportedImage(imageName) { - supTag = append(supTag, tag) - } else { - unSupTag = append(unSupTag, tag) - } - } - return supTag, unSupTag -} - -// IsComponentTypeSupported takes the componentType e.g. java:8 and return true if -// it is fully supported i.e. debug mode and more. -func IsComponentTypeSupported(client *occlient.Client, componentType string) (bool, error) { - _, componentType, _, componentVersion := util.ParseComponentImageName(componentType) - - imageStream, err := client.GetImageStream("", componentType, componentVersion) - if err != nil { - return false, err - } - tagMap := createImageTagMap(imageStream.Spec.Tags) - - return isSupportedImage(tagMap[componentVersion]), nil -} - -// createImageTagMap takes a list of image TagReferences and creates a map of type tag name => image name e.g. 1.11 => openshift/nodejs-11 -func createImageTagMap(tagRefs []imagev1.TagReference) map[string]string { - tagMap := make(map[string]string) - for _, tagRef := range tagRefs { - imageName := tagRef.From.Name - if tagRef.From.Kind == "DockerImage" { - // we get the image name from the repo url e.g. registry.redhat.com/openshift/nodejs:10 will give openshift/nodejs:10 - imageNameParts := strings.SplitN(imageName, "/", 2) - - var urlImageName string - // this means the docker image url might just be something like nodejs:10, no namespace or registry info - if len(imageNameParts) == 1 { - urlImageName = imageNameParts[0] - // else block executes when there is a registry information attached in the docker image url - } else { - // we dont want the registry url portion - urlImageName = imageNameParts[1] - } - // here we remove the tag and digest - ns, img, tag, _, _ := occlient.ParseImageName(urlImageName) - imageName = ns + "/" + img + ":" + tag - tagMap[tagRef.Name] = imageName - } - } - - for _, tagRef := range tagRefs { - if tagRef.From.Kind == "ImageStreamTag" { - imageName := tagRef.From.Name - tagList := strings.Split(imageName, ":") - tag := tagList[len(tagList)-1] - // if the kind is a image stream tag that means its pointing to an existing dockerImage or image stream image - // we just look it up from the tapMap we already have - imageName = tagMap[tag] - tagMap[tagRef.Name] = imageName - } - - } - return tagMap -} - -// isSupportedImages returns if the image is supported or not. the supported images have been provided here -// https://github.com/openshift/odo-init-image/blob/master/language-scripts/image-mappings.json -func isSupportedImage(imgName string) bool { - return supportedImages[imgName] -} - -// getBuildersFromImageStreams returns all the builder Images from the image streams provided and also hides the builder images -// which have hidden annotation attached to it -func getBuildersFromImageStreams(imageStreams []imagev1.ImageStream, imageStreamTagMap map[string]imagev1.ImageStreamTag) []ComponentType { - var builderImages []ComponentType - // Get builder images from the available imagestreams - for _, imageStream := range imageStreams { - var allTags []string - var hiddenTags []string - buildImage := false - - for _, tagReference := range imageStream.Spec.Tags { - allTags = append(allTags, tagReference.Name) - // Check to see if it is a "builder" image - if _, ok := tagReference.Annotations["tags"]; ok { - for _, t := range strings.Split(tagReference.Annotations["tags"], ",") { - // If the tagReference has "builder" then we will add the image to the list - if t == "builder" { - buildImage = true - } - } - } - - } - - // Append to the list of images if a "builder" tag was found - if buildImage { - // We need to gauge the ImageStreamTag of each potential builder image, because it might contain - // the 'hidden' tag. If so, this builder image is deprecated and should not be offered to the user - // as candidate - for _, tag := range allTags { - imageStreamTag := imageStreamTagMap[imageStream.Name+":"+tag] - if _, ok := imageStreamTag.Annotations["tags"]; ok { - for _, t := range strings.Split(imageStreamTag.Annotations["tags"], ",") { - // If the tagReference has "builder" then we will add the image to the list - if t == "hidden" { - klog.V(5).Infof("Tag: %v of builder: %v is marked as hidden and therefore will be excluded", tag, imageStream.Name) - hiddenTags = append(hiddenTags, tag) - } - } - } - - } - - catalogImage := ComponentType{ - TypeMeta: metav1.TypeMeta{ - Kind: "ComponentType", - APIVersion: apiVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: imageStream.Name, - Namespace: imageStream.Namespace, - }, - Spec: ComponentSpec{ - AllTags: allTags, - NonHiddenTags: getAllNonHiddenTags(allTags, hiddenTags), - ImageStreamTags: imageStream.Spec.Tags, - }, - } - builderImages = append(builderImages, catalogImage) - klog.V(5).Infof("Found builder image: %#v", catalogImage) - } - - } - return builderImages -} - -func getAllNonHiddenTags(allTags []string, hiddenTags []string) []string { - result := make([]string, 0, len(allTags)) - for _, t1 := range allTags { - found := false - for _, t2 := range hiddenTags { - if t1 == t2 { - found = true - break - } - } - - if !found { - result = append(result, t1) - } - } - return result -} diff --git a/pkg/catalog/catalog_test.go b/pkg/catalog/catalog_test.go index bab29cb65fc..d8b31f42f1d 100644 --- a/pkg/catalog/catalog_test.go +++ b/pkg/catalog/catalog_test.go @@ -9,171 +9,13 @@ import ( "testing" "github.com/golang/mock/gomock" - imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/odo/pkg/kclient" - "github.com/openshift/odo/pkg/occlient" "github.com/openshift/odo/pkg/preference" - "github.com/openshift/odo/pkg/testingutil" olm "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ktesting "k8s.io/client-go/testing" ) -func TestListComponents(t *testing.T) { - type args struct { - name string - namespace string - tags []string - hiddenTags []string - } - tests := []struct { - name string - args args - wantErr bool - wantAllTags []string - wantNonHiddenTags []string - }{ - { - name: "Case 1: Valid image output with one tag which is not hidden", - args: args{ - name: "foobar", - namespace: "openshift", - tags: []string{"latest"}, - hiddenTags: []string{}, - }, - wantErr: false, - wantAllTags: []string{"latest"}, - wantNonHiddenTags: []string{"latest"}, - }, - { - name: "Case 2: Valid image output with one tag which is hidden", - args: args{ - name: "foobar", - namespace: "openshift", - tags: []string{"latest"}, - hiddenTags: []string{"latest"}, - }, - wantErr: false, - wantAllTags: []string{"latest"}, - wantNonHiddenTags: []string{}, - }, - { - name: "Case 3: Valid image output with multiple tags none of which are hidden", - args: args{ - name: "foobar", - namespace: "openshift", - tags: []string{"1.0.0", "1.0.1", "0.0.1", "latest"}, - hiddenTags: []string{}, - }, - wantErr: false, - wantAllTags: []string{"1.0.0", "1.0.1", "0.0.1", "latest"}, - wantNonHiddenTags: []string{"1.0.0", "1.0.1", "0.0.1", "latest"}, - }, - { - name: "Case 4: Valid image output with multiple tags some of which are hidden", - args: args{ - name: "foobar", - namespace: "openshift", - tags: []string{"1.0.0", "1.0.1", "0.0.1", "latest"}, - hiddenTags: []string{"0.0.1", "1.0.0"}, - }, - wantErr: false, - wantAllTags: []string{"1.0.0", "1.0.1", "0.0.1", "latest"}, - wantNonHiddenTags: []string{"1.0.1", "latest"}, - }, - { - name: "Case 3: Invalid image output with no tags", - args: args{ - name: "foobar", - namespace: "foo", - tags: []string{}, - }, - wantErr: true, - wantAllTags: []string{}, - wantNonHiddenTags: []string{}, - }, - { - name: "Case 4: Valid image with output tags from a different namespace none of which are hidden", - args: args{ - name: "foobar", - namespace: "foo", - tags: []string{"1", "2", "4", "latest", "10"}, - hiddenTags: []string{"1", "2"}, - }, - wantErr: false, - wantAllTags: []string{"1", "2", "4", "latest", "10"}, - wantNonHiddenTags: []string{"4", "latest", "10"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - // Fake the client with the appropriate arguments - client, fakeClientSet := occlient.FakeNew() - fakeClientSet.ImageClientset.PrependReactor("list", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, testingutil.FakeImageStreams(tt.args.name, tt.args.namespace, tt.args.tags), nil - }) - fakeClientSet.ImageClientset.PrependReactor("list", "imagestreamtags", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, testingutil.FakeImageStreamTags(tt.args.name, tt.args.namespace, tt.args.tags, tt.args.hiddenTags), nil - }) - - // The function we are testing - output, err := ListComponents(client) - - //Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf("component ListComponents() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // 1 call for current project + 1 call from openshift project for each of the ImageStream and ImageStreamTag - if len(fakeClientSet.ImageClientset.Actions()) != 4 { - t.Errorf("expected 2 ImageClientset.Actions() in ListComponents, got: %v", fakeClientSet.ImageClientset.Actions()) - } - - // Check if the output is the same as what's expected (for all tags) - // and only if output is more than 0 (something is actually returned) - if len(output.Items) > 0 && !(reflect.DeepEqual(output.Items[0].Spec.AllTags, tt.wantAllTags)) { - t.Errorf("expected all tags: %s, got: %s", tt.wantAllTags, output.Items[0].Spec.AllTags) - } - - // Check if the output is the same as what's expected (for hidden tags) - // and only if output is more than 0 (something is actually returned) - if len(output.Items) > 0 && !(reflect.DeepEqual(output.Items[0].Spec.NonHiddenTags, tt.wantNonHiddenTags)) { - t.Errorf("expected non hidden tags: %s, got: %s", tt.wantNonHiddenTags, output.Items[0].Spec.NonHiddenTags) - } - - }) - } -} - -func TestSliceSupportedTags(t *testing.T) { - - imageStream := MockImageStream() - - img := ComponentType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - Namespace: "openshift", - }, - Spec: ComponentSpec{ - NonHiddenTags: []string{ - "12", "10", "8", "6", "latest", - }, - ImageStreamTags: (*imageStream).Spec.Tags, - }, - } - - supTags, unSupTags := SliceSupportedTags(img) - if !reflect.DeepEqual(supTags, []string{"12", "10", "latest"}) || - !reflect.DeepEqual(unSupTags, []string{"8", "6"}) { - t.Fatal("supported or unsupported tags are not as expected") - } -} - func TestGetDevfileRegistries(t *testing.T) { tempConfigFile, err := ioutil.TempFile("", "odoconfig") if err != nil { @@ -317,51 +159,6 @@ func TestGetRegistryDevfiles(t *testing.T) { } } -func MockImageStream() *imagev1.ImageStream { - - tags := map[string]string{ - "12": "docker.io/rhscl/nodejs-12-rhel7:latest", - "10": "docker.io/rhscl/nodejs-10-rhel7:latest", - - // unsupported ones - "8": "docker.io/rhoar-nodejs/nodejs-8:latest", - "6": "docker.io/rhoar-nodejs/nodejs-6:latest", - } - - imageStream := &imagev1.ImageStream{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - Namespace: "openshift", - }, - } - - // this append is intentionally added before adding other tags - // to confirm that tag references work even when they are out of order - imageStream.Spec.Tags = append(imageStream.Spec.Tags, - imagev1.TagReference{ - Name: "latest", - Annotations: map[string]string{"tags": "builder"}, - From: &corev1.ObjectReference{ - Kind: "ImageStreamTag", - Name: "12", - }, - }) - - for tagName, imageName := range tags { - imageTag := imagev1.TagReference{ - Name: tagName, - Annotations: map[string]string{"tags": "builder"}, - From: &corev1.ObjectReference{ - Kind: "DockerImage", - Name: imageName, - }, - } - imageStream.Spec.Tags = append(imageStream.Spec.Tags, imageTag) - } - - return imageStream -} - func TestConvertURL(t *testing.T) { tests := []struct { name string diff --git a/pkg/catalog/types.go b/pkg/catalog/types.go index 1a92cd4ba22..83e5b48e62d 100644 --- a/pkg/catalog/types.go +++ b/pkg/catalog/types.go @@ -1,17 +1,9 @@ package catalog import ( - imagev1 "github.com/openshift/api/image/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// ComponentType is the main struct for catalog components -type ComponentType struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec ComponentSpec `json:"spec,omitempty"` -} - // Registry is the main struct of devfile registry type Registry struct { Name string @@ -30,21 +22,6 @@ type DevfileComponentType struct { Tags []string } -// ComponentSpec is the spec for ComponentType -type ComponentSpec struct { - AllTags []string `json:"allTags"` - NonHiddenTags []string `json:"nonHiddenTags"` - SupportedTags []string `json:"supportedTags"` - ImageStreamTags []imagev1.TagReference `json:"imageStreamTags"` -} - -// ComponentTypeList lists all the ComponentType's -type ComponentTypeList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []ComponentType `json:"items"` -} - // DevfileComponentTypeList lists all the DevfileComponentType's type DevfileComponentTypeList struct { DevfileRegistries []Registry diff --git a/pkg/component/component.go b/pkg/component/component.go index 96ba29f0e5a..bc6ce45ceba 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -1,17 +1,13 @@ package component import ( - "bufio" - "bytes" "encoding/json" "fmt" - "io" "os" "path/filepath" "reflect" "sort" "strings" - "time" "github.com/devfile/api/v2/pkg/devfile" @@ -19,34 +15,22 @@ import ( "github.com/devfile/library/pkg/devfile/parser" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" - "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/localConfigProvider" "github.com/openshift/odo/pkg/service" - "github.com/pkg/errors" - "k8s.io/klog" - applabels "github.com/openshift/odo/pkg/application/labels" - "github.com/openshift/odo/pkg/catalog" componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" - "github.com/openshift/odo/pkg/odo/util/validation" "github.com/openshift/odo/pkg/preference" - "github.com/openshift/odo/pkg/sync" urlpkg "github.com/openshift/odo/pkg/url" "github.com/openshift/odo/pkg/util" + "github.com/pkg/errors" servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// componentSourceURLAnnotation is an source url from which component was build -const componentSourceURLAnnotation = "app.openshift.io/vcs-uri" -const ComponentSourceTypeAnnotation = "app.kubernetes.io/component-source-type" const componentRandomNamePartsMaxLen = 12 const componentNameMaxRetries = 3 const componentNameMaxLen = -1 @@ -54,42 +38,15 @@ const NotAvailable = "Not available" const apiVersion = "odo.dev/v1alpha1" -var validSourceTypes = map[string]bool{ - "git": true, - "local": true, - "binary": true, -} - -type componentAdapter struct { - client occlient.Client -} - -func (a componentAdapter) ExecCMDInContainer(componentInfo common.ComponentInfo, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error { - return a.client.GetKubeClient().ExecCMDInContainer(componentInfo.ContainerName, componentInfo.PodName, cmd, stdout, stderr, stdin, tty) -} - -// ExtractProjectToComponent extracts the project archive(tar) to the target path from the reader stdin -func (a componentAdapter) ExtractProjectToComponent(componentInfo common.ComponentInfo, targetPath string, stdin io.Reader) error { - return a.client.GetKubeClient().ExtractProjectToComponent(componentInfo.ContainerName, componentInfo.PodName, targetPath, stdin) -} - // GetComponentDir returns source repo name // Parameters: -// path: git url or source path or binary path -// paramType: One of CreateType as in GIT/LOCAL/BINARY +// path: source path // Returns: directory name -func GetComponentDir(path string, paramType config.SrcType) (string, error) { +func GetComponentDir(path string) (string, error) { retVal := "" - switch paramType { - case config.GIT: - retVal = strings.TrimSuffix(path[strings.LastIndex(path, "/")+1:], ".git") - case config.LOCAL: + if path != "" { retVal = filepath.Base(path) - case config.BINARY: - filename := filepath.Base(path) - var extension = filepath.Ext(filename) - retVal = filename[0 : len(filename)-len(extension)] - default: + } else { currDir, err := os.Getwd() if err != nil { return "", errors.Wrapf(err, "unable to generate a random name as getting current directory failed") @@ -103,7 +60,7 @@ func GetComponentDir(path string, paramType config.SrcType) (string, error) { // GetDefaultComponentName generates a unique component name // Parameters: desired default component name(w/o prefix) and slice of existing component names // Returns: Unique component name and error if any -func GetDefaultComponentName(componentPath string, componentPathType config.SrcType, componentType string, existingComponentList ComponentList) (string, error) { +func GetDefaultComponentName(componentPath string, componentType string, existingComponentList ComponentList) (string, error) { var prefix string // Get component names from component list @@ -120,7 +77,7 @@ func GetDefaultComponentName(componentPath string, componentPathType config.SrcT // If there's no prefix in config file, or its value is empty string use safe default - the current directory along with component type if cfg.OdoSettings.NamePrefix == nil || *cfg.OdoSettings.NamePrefix == "" { - prefix, err = GetComponentDir(componentPath, componentPathType) + prefix, err = GetComponentDir(componentPath) if err != nil { return "", errors.Wrap(err, "unable to generate random component name") } @@ -144,455 +101,14 @@ func GetDefaultComponentName(componentPath string, componentPathType config.SrcT return util.GetDNS1123Name(componentName), nil } -// validateSourceType check if given sourceType is supported -func validateSourceType(sourceType string) bool { - return validSourceTypes[sourceType] -} - -// CreateFromGit inputPorts is the array containing the string port values -// inputPorts is the array containing the string port values -// envVars is the array containing the environment variables -func CreateFromGit(client *occlient.Client, params occlient.CreateArgs) error { - - // Create the labels - labels := componentlabels.GetLabels(params.Name, params.ApplicationName, true) - - // Parse componentImageType before adding to labels - _, imageName, imageTag, _, err := occlient.ParseImageName(params.ImageName) - if err != nil { - return errors.Wrap(err, "unable to parse image name") - } - - // save component type as label - labels[componentlabels.ComponentTypeLabel] = imageName - labels[componentlabels.ComponentTypeVersion] = imageTag - - // save source path as annotation - annotations := map[string]string{componentSourceURLAnnotation: params.SourcePath} - annotations[ComponentSourceTypeAnnotation] = "git" - - // Namespace the component - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(params.Name, params.ApplicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - // Create CommonObjectMeta to be passed in - commonObjectMeta := metav1.ObjectMeta{ - Name: namespacedOpenShiftObject, - Labels: labels, - Annotations: annotations, - } - - err = client.NewAppS2I(params, commonObjectMeta) - if err != nil { - return errors.Wrapf(err, "unable to create git component %s", namespacedOpenShiftObject) - } - - return nil -} - -// GetComponentPorts provides slice of ports used by the component in the form port_no/protocol -func GetComponentPorts(client *occlient.Client, componentName string, applicationName string) (ports []string, err error) { - componentLabels := componentlabels.GetLabels(componentName, applicationName, false) - componentSelector := util.ConvertLabelsToSelector(componentLabels) - - dc, err := client.GetDeploymentConfigFromSelector(componentSelector) - if err != nil { - return nil, errors.Wrapf(err, "unable to fetch deployment configs for the selector %v", componentSelector) - } - - for _, container := range dc.Spec.Template.Spec.Containers { - for _, port := range container.Ports { - ports = append(ports, fmt.Sprintf("%v/%v", port.ContainerPort, port.Protocol)) - } - } - - return ports, nil -} - -// GetComponentLinkedSecretNames provides a slice containing the names of the secrets that are present in envFrom -func GetComponentLinkedSecretNames(client *occlient.Client, componentName string, applicationName string) (secretNames []string, err error) { - componentLabels := componentlabels.GetLabels(componentName, applicationName, false) - componentSelector := util.ConvertLabelsToSelector(componentLabels) - - dc, err := client.GetDeploymentConfigFromSelector(componentSelector) - if err != nil { - return nil, errors.Wrapf(err, "unable to fetch deployment configs for the selector %v", componentSelector) - } - - for _, env := range dc.Spec.Template.Spec.Containers[0].EnvFrom { - if env.SecretRef != nil { - secretNames = append(secretNames, env.SecretRef.Name) - } - } - - return secretNames, nil -} - -// CreateFromPath create new component with source or binary from the given local path -// sourceType indicates the source type of the component and can be either local or binary -// envVars is the array containing the environment variables -func CreateFromPath(client *occlient.Client, params occlient.CreateArgs) error { - - // Create the labels to be used - labels := componentlabels.GetLabels(params.Name, params.ApplicationName, true) - - // Parse componentImageType before adding to labels - _, imageName, imageTag, _, err := occlient.ParseImageName(params.ImageName) - if err != nil { - return errors.Wrap(err, "unable to parse image name") - } - - // save component type as label - labels[componentlabels.ComponentTypeLabel] = imageName - labels[componentlabels.ComponentTypeVersion] = imageTag - - // save source path as annotation - annotations := map[string]string{} - annotations[ComponentSourceTypeAnnotation] = string(params.SourceType) - - // Namespace the component - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(params.Name, params.ApplicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - // Create CommonObjectMeta to be passed in - commonObjectMeta := metav1.ObjectMeta{ - Name: namespacedOpenShiftObject, - Labels: labels, - Annotations: annotations, - } - - // Bootstrap the deployment with SupervisorD - err = client.BootstrapSupervisoredS2I(params, commonObjectMeta) - if err != nil { - return err - } - - if params.Wait { - // if wait flag is present then extract the podselector - // use the podselector for calling WaitAndGetPod - selectorLabels, err := util.NamespaceOpenShiftObject(labels[componentlabels.ComponentLabel], labels["app"]) - if err != nil { - return err - } - - podSelector := fmt.Sprintf("deploymentconfig=%s", selectorLabels) - _, err = client.GetKubeClient().WaitAndGetPodWithEvents(podSelector, corev1.PodRunning, "Waiting for component to start") - if err != nil { - return err - } - return nil - } - - return nil -} - -// Delete whole component -func Delete(client *occlient.Client, wait bool, componentName, applicationName string) error { - - // Loading spinner - s := log.Spinnerf("Deleting component %s", componentName) - defer s.End(false) - - labels := componentlabels.GetLabels(componentName, applicationName, false) - err := client.Delete(labels, wait) - if err != nil { - return errors.Wrapf(err, "error deleting component %s", componentName) - } - - s.End(true) - return nil -} - -// getEnvFromPodEnvs loops through the passed slice of pod#EnvVars and gets the value corresponding to the key passed, returns empty stirng if not available -func getEnvFromPodEnvs(envName string, podEnvs []corev1.EnvVar) string { - for _, podEnv := range podEnvs { - if podEnv.Name == envName { - return podEnv.Value - } - } - return "" -} - -// getS2IPaths returns slice of s2i paths of odo interest -// Parameters: -// podEnvs: Slice of env vars extracted from pod template -// Returns: -// Slice of s2i paths extracted from passed parameters -func getS2IPaths(podEnvs []corev1.EnvVar) []string { - retVal := []string{} - // List of s2i Paths exported for use in container pod for working with source/binary - s2iPathEnvs := []string{ - occlient.EnvS2IDeploymentDir, - occlient.EnvS2ISrcOrBinPath, - occlient.EnvS2IWorkingDir, - occlient.EnvS2ISrcBackupDir, - } - // For each of the required env var - for _, s2iPathEnv := range s2iPathEnvs { - // try to fetch the value of required env from the ones set already in the component container like for the case of watch or multiple pushes - envVal := getEnvFromPodEnvs(s2iPathEnv, podEnvs) - isEnvValPresent := false - if envVal != "" { - for _, e := range retVal { - if envVal == e { - isEnvValPresent = true - break - } - } - if !isEnvValPresent { - // If `src` not in path, append it - if filepath.Base(envVal) != "src" { - envVal = filepath.Join(envVal, "src") - } - retVal = append(retVal, envVal) - } - } - } - return retVal -} - -// CreateComponent creates component as per the passed component settings -// Parameters: -// client: occlient instance -// componentConfig: the component configuration that holds all details of component -// context: the component context indicating the location of component config and hence its source as well -// stdout: io.Writer instance to write output to -// Returns: -// err: errors if any -func CreateComponent(client *occlient.Client, componentConfig config.LocalConfigInfo, context string, stdout io.Writer) (err error) { - - cmpName := componentConfig.GetName() - cmpType := componentConfig.GetType() - cmpSrcType := componentConfig.GetSourceType() - cmpPorts, err := componentConfig.GetComponentPorts() - if err != nil { - return err - } - cmpSrcRef := componentConfig.GetRef() - appName := componentConfig.GetApplication() - envVarsList := componentConfig.GetEnvVars() - addDebugPortToEnv(&envVarsList, componentConfig) - - log.Successf("Initializing component") - createArgs := occlient.CreateArgs{ - Name: cmpName, - ImageName: cmpType, - ApplicationName: appName, - EnvVars: envVarsList.ToStringSlice(), - } - createArgs.SourceType = cmpSrcType - createArgs.SourcePath = componentConfig.GetSourceLocation() - - if len(cmpPorts) > 0 { - createArgs.Ports = cmpPorts - } - - createArgs.Resources, err = occlient.GetResourceRequirementsFromCmpSettings(componentConfig) - if err != nil { - return errors.Wrap(err, "failed to create component") - } - - s := log.Spinner("Creating component") - defer s.End(false) - - switch cmpSrcType { - case config.GIT: - // Use Git - if cmpSrcRef != "" { - createArgs.SourceRef = cmpSrcRef - } - - createArgs.Wait = true - createArgs.StdOut = stdout - - if err = CreateFromGit( - client, - createArgs, - ); err != nil { - return errors.Wrapf(err, "failed to create component with args %+v", createArgs) - } - - s.End(true) - - // Trigger build - if err = Build(client, createArgs.Name, createArgs.ApplicationName, createArgs.Wait, createArgs.StdOut, false); err != nil { - return errors.Wrapf(err, "failed to build component with args %+v", createArgs) - } - - // deploy the component and wait for it to complete - // desiredRevision is 1 as this is the first push - if err = Deploy(client, createArgs, 1); err != nil { - return errors.Wrapf(err, "failed to deploy component with args %+v", createArgs) - } - case config.LOCAL: - fileInfo, err := os.Stat(createArgs.SourcePath) - if err != nil { - return errors.Wrapf(err, "failed to get info of path %+v of component %+v", createArgs.SourcePath, createArgs.Name) - } - if !fileInfo.IsDir() { - return fmt.Errorf("component creation with args %+v as path needs to be a directory", createArgs) - } - // Create - if err = CreateFromPath(client, createArgs); err != nil { - return errors.Wrapf(err, "failed to create component with args %+v", createArgs) - } - case config.BINARY: - if err = CreateFromPath(client, createArgs); err != nil { - return errors.Wrapf(err, "failed to create component with args %+v", createArgs) - } - default: - // If the user does not provide anything (local, git or binary), use the current absolute path and deploy it - createArgs.SourceType = config.LOCAL - dir, err := os.Getwd() - if err != nil { - return errors.Wrap(err, "failed to create component with current directory as source for the component") - } - createArgs.SourcePath = dir - if err = CreateFromPath(client, createArgs); err != nil { - return errors.Wrapf(err, "") - } - } - s.End(true) - return -} - -// CheckComponentMandatoryParams checks mandatory parammeters for component -func CheckComponentMandatoryParams(componentSettings config.ComponentSettings) error { - var req_fields string - - if componentSettings.Name == nil { - req_fields = fmt.Sprintf("%s name", req_fields) - } - - if componentSettings.Application == nil { - req_fields = fmt.Sprintf("%s application", req_fields) - } - - if componentSettings.Project == nil { - req_fields = fmt.Sprintf("%s project name", req_fields) - } - - if componentSettings.SourceType == nil { - req_fields = fmt.Sprintf("%s source type", req_fields) - } - - if componentSettings.SourceLocation == nil { - req_fields = fmt.Sprintf("%s source location", req_fields) - } - - if componentSettings.Type == nil { - req_fields = fmt.Sprintf("%s type", req_fields) - } - - if len(req_fields) > 0 { - return fmt.Errorf("missing mandatory parameters:%s", req_fields) - } - return nil -} - -// ValidateComponentCreateRequest validates request for component creation and returns errors if any -// Returns: -// errors if any -func ValidateComponentCreateRequest(client *occlient.Client, componentSettings config.ComponentSettings, contextDir string) (err error) { - // Check the mandatory parameters first - err = CheckComponentMandatoryParams(componentSettings) - if err != nil { - return err - } - - // Parse the image name - _, componentType, _, componentVersion := util.ParseComponentImageName(*componentSettings.Type) - - // Check to see if the catalog type actually exists - exists, err := catalog.ComponentExists(client, componentType, componentVersion) - if err != nil { - return errors.Wrapf(err, "failed to check component of type %s", componentType) - } - if !exists { - return fmt.Errorf("failed to find component of type %s and version %s", componentType, componentVersion) - } - - // Validate component name - err = validation.ValidateName(*componentSettings.Name) - if err != nil { - return errors.Wrapf(err, "failed to check component of name %s", *componentSettings.Name) - } - - // If component is of type local, check if the source path is valid - if *componentSettings.SourceType == config.LOCAL { - klog.V(4).Infof("Checking source location: %s", *(componentSettings.SourceLocation)) - srcLocInfo, err := os.Stat(*(componentSettings.SourceLocation)) - if err != nil { - return errors.Wrap(err, "failed to create component. Please view the settings used using the command `odo config view`") - } - if !srcLocInfo.IsDir() { - return fmt.Errorf("source path for component created for local source needs to be a directory") - } - } - - if *componentSettings.SourceType == config.BINARY { - // if relative path starts with ../ (or windows equivalent), it means that binary file is not inside the context - if strings.HasPrefix(*(componentSettings.SourceLocation), fmt.Sprintf("..%c", filepath.Separator)) { - return fmt.Errorf("%s binary needs to be inside of the context directory (%s)", *(componentSettings.SourceLocation), contextDir) - } - } - - if err := ensureAndLogProperResourceUsage(componentSettings.MinMemory, componentSettings.MaxMemory, "memory"); err != nil { - return err - } - - if err := ensureAndLogProperResourceUsage(componentSettings.MinCPU, componentSettings.MaxCPU, "cpu"); err != nil { - return err - } - - return -} - -func ensureAndLogProperResourceUsage(resourceMin, resourceMax *string, resourceName string) error { - klog.V(4).Infof("Validating configured %v values", resourceName) - - err := fmt.Errorf("`min%s` should accompany `max%s` or use `odo config set %s` to use same value for both min and max", resourceName, resourceName, resourceName) - - if (resourceMin == nil) != (resourceMax == nil) { - return err - } - if (resourceMin != nil && *resourceMin == "") != (resourceMax != nil && *resourceMax == "") { - return err - } - return nil -} - -// ApplyConfig applies the component config onto component dc +// ApplyConfig applies the component config onto component deployment // Parameters: // client: occlient instance // componentConfig: Component configuration // envSpecificInfo: Component environment specific information, available if uses devfile -// cmpExist: true if components exists in the cluster -// isS2I: Legacy option. Set as true if you want to use the old S2I method as it differentiates slightly. // Returns: // err: Errors if any else nil -func ApplyConfig(client *occlient.Client, componentConfig config.LocalConfigInfo, envSpecificInfo envinfo.EnvSpecificInfo, stdout io.Writer, cmpExist bool, isS2I bool) (err error) { - - var configProvider localConfigProvider.LocalConfigProvider - if isS2I { - // if component exist then only call the update function - if cmpExist { - if err = Update(client, componentConfig, componentConfig.GetSourceLocation(), stdout); err != nil { - return err - } - } - } - - if isS2I { - configProvider = &componentConfig - } else { - configProvider = &envSpecificInfo - } - +func ApplyConfig(client *occlient.Client, envSpecificInfo envinfo.EnvSpecificInfo) (err error) { isRouteSupported := false isRouteSupported, err = client.IsRouteSupported() if err != nil { @@ -602,230 +118,16 @@ func ApplyConfig(client *occlient.Client, componentConfig config.LocalConfigInfo urlClient := urlpkg.NewClient(urlpkg.ClientOptions{ OCClient: *client, IsRouteSupported: isRouteSupported, - LocalConfigProvider: configProvider, + LocalConfigProvider: &envSpecificInfo, }) return urlpkg.Push(urlpkg.PushParameters{ - LocalConfig: configProvider, - URLClient: urlClient, - IsRouteSupported: isRouteSupported, + LocalConfigProvider: &envSpecificInfo, + URLClient: urlClient, + IsRouteSupported: isRouteSupported, }) } -// PushLocal push local code to the cluster and trigger build there. -// During copying binary components, path represent base directory path to binary and files contains path of binary -// During copying local source components, path represent base directory path whereas files is empty -// During `odo watch`, path represent base directory path whereas files contains list of changed Files -// Parameters: -// componentName is name of the component to update sources to -// applicationName is the name of the application of which the component is a part -// path is base path of the component source/binary -// files is list of changed files captured during `odo watch` as well as binary file path -// delFiles is the list of files identified as deleted -// isForcePush indicates if the sources to be updated are due to a push in which case its a full source directory push or only push of identified sources -// globExps are the glob expressions which are to be ignored during the push -// show determines whether or not to show the log (passed in by po.show argument within /cmd) -// Returns -// Error if any -func PushLocal(client *occlient.Client, componentName string, applicationName string, path string, out io.Writer, files []string, delFiles []string, isForcePush bool, globExps []string, show bool) error { - klog.V(4).Infof("PushLocal: componentName: %s, applicationName: %s, path: %s, files: %s, delFiles: %s, isForcePush: %+v", componentName, applicationName, path, files, delFiles, isForcePush) - - // Edge case: check to see that the path is NOT empty. - emptyDir, err := util.IsEmpty(path) - if err != nil { - return errors.Wrapf(err, "Unable to check directory: %s", path) - } else if emptyDir { - return errors.New(fmt.Sprintf("Directory / file %s is empty", path)) - } - - // Find DeploymentConfig for component - componentLabels := componentlabels.GetLabels(componentName, applicationName, false) - componentSelector := util.ConvertLabelsToSelector(componentLabels) - dc, err := client.GetDeploymentConfigFromSelector(componentSelector) - if err != nil { - return errors.Wrap(err, "unable to get deployment for component") - } - // Find Pod for component - podSelector := fmt.Sprintf("deploymentconfig=%s", dc.Name) - - // Wait for Pod to be in running state otherwise we can't sync data to it. - pod, err := client.GetKubeClient().WaitAndGetPodWithEvents(podSelector, corev1.PodRunning, "Waiting for component to start") - if err != nil { - return errors.Wrapf(err, "error while waiting for pod %s", podSelector) - } - - // Get S2I Source/Binary Path from Pod Env variables created at the time of component create - s2iSrcPath := getEnvFromPodEnvs(occlient.EnvS2ISrcOrBinPath, pod.Spec.Containers[0].Env) - if s2iSrcPath == "" { - s2iSrcPath = occlient.DefaultS2ISrcOrBinPath - } - targetPath := fmt.Sprintf("%s/src", s2iSrcPath) - - // Sync the files to the pod - s := log.Spinner("Syncing files to the component") - defer s.End(false) - - // If there are files identified as deleted, propagate them to the component pod - if len(delFiles) > 0 { - klog.V(4).Infof("propagating deletion of files %s to pod", strings.Join(delFiles, " ")) - /* - Delete files observed by watch to have been deleted from each of s2i directories like: - deployment dir: In interpreted runtimes like python, source is copied over to deployment dir so delete needs to happen here as well - destination dir: This is the directory where s2i expects source to be copied for it be built and deployed - working dir: Directory where, sources are copied over from deployment dir from where the s2i builds and deploys source. - Deletes need to happen here as well otherwise, even if the latest source is copied over, the stale source files remain - source backup dir: Directory used for backing up source across multiple iterations of push and watch in component container - In case of python, s2i image moves sources from destination dir to workingdir which means sources are deleted from destination dir - So, during the subsequent watch pushing new diff to component pod, the source as a whole doesn't exist at destination dir and hence needs - to be backed up. - */ - err := client.PropagateDeletes(pod.Name, delFiles, getS2IPaths(pod.Spec.Containers[0].Env)) - if err != nil { - return errors.Wrapf(err, "unable to propagate file deletions %+v", delFiles) - } - } - - if !isForcePush { - if len(files) == 0 && len(delFiles) == 0 { - // nothing to push - s.End(true) - return nil - } - } - - adapter := componentAdapter{ - client: *client, - } - - if isForcePush || len(files) > 0 { - klog.V(4).Infof("Copying files %s to pod", strings.Join(files, " ")) - compInfo := common.ComponentInfo{ - PodName: pod.Name, - } - err = sync.CopyFile(adapter, path, compInfo, targetPath, files, globExps, util.IndexerRet{}) - if err != nil { - s.End(false) - return errors.Wrap(err, "unable push files to pod") - } - } - s.End(true) - - if show { - s = log.SpinnerNoSpin("Building component") - } else { - s = log.Spinner("Building component") - } - - // We will use the assemble-and-restart script located within the supervisord container we've created - cmdArr := []string{"/opt/odo/bin/assemble-and-restart"} - - compInfo := common.ComponentInfo{ - PodName: pod.Name, - } - - err = common.ExecuteCommand(adapter, compInfo, cmdArr, show, nil, nil) - - if err != nil { - s.End(false) - return errors.Wrap(err, "unable to execute assemble script") - } - - s.End(true) - - return nil -} - -// Build component from BuildConfig. -// If 'wait' is true than it waits for build to successfully complete. -// If 'wait' is false than this function won't return error even if build failed. -// 'show' will determine whether or not the log will be shown to the user (while building) -func Build(client *occlient.Client, componentName string, applicationName string, wait bool, stdout io.Writer, show bool) error { - - // Try to grab the preference in order to set a timeout.. but if not, we’ll use the default. - buildTimeout := preference.DefaultBuildTimeout * time.Second - cfg, configReadErr := preference.New() - if configReadErr != nil { - klog.V(4).Info(errors.Wrap(configReadErr, "unable to read config file")) - } else { - buildTimeout = time.Duration(cfg.GetBuildTimeout()) * time.Second - } - - // Loading spinner - // No loading spinner if we're showing the logging output - s := log.Spinnerf("Triggering build from git") - defer s.End(false) - - // Namespace the component - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - buildName, err := client.StartBuild(namespacedOpenShiftObject) - if err != nil { - return errors.Wrapf(err, "unable to rebuild %s", componentName) - } - s.End(true) - - // Retrieve the Build Log and write to buffer if debug is disabled, else we we output to stdout / debug. - - var b bytes.Buffer - if !log.IsDebug() && !show { - stdout = bufio.NewWriter(&b) - } - - if wait { - - if show { - s = log.SpinnerNoSpin("Waiting for build to finish") - } else { - s = log.Spinner("Waiting for build to finish") - } - - defer s.End(false) - - if err := client.WaitForBuildToFinish(buildName, stdout, buildTimeout); err != nil { - return errors.Wrapf(err, "unable to build %s, error: %s", buildName, b.String()) - } - s.End(true) - } - - return nil -} - -// Deploy deploys the component -// it starts a new deployment and wait for the new dc to be available -// desiredRevision is the desired version of the deployment config to wait for -func Deploy(client *occlient.Client, params occlient.CreateArgs, desiredRevision int64) error { - - // Loading spinner - s := log.Spinnerf("Deploying component %s", params.Name) - defer s.End(false) - - // Namespace the component - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(params.Name, params.ApplicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - // start the deployment - // the build must be finished before this call and the new image must be successfully updated - _, err = client.StartDeployment(namespacedOpenShiftObject) - if err != nil { - return errors.Wrapf(err, "unable to create DeploymentConfig for %s", namespacedOpenShiftObject) - } - - // Watch / wait for deployment config to update annotations - _, err = client.WaitAndGetDC(namespacedOpenShiftObject, desiredRevision, occlient.OcUpdateTimeout, occlient.IsDCRolledOut) - if err != nil { - return errors.Wrapf(err, "unable to wait for DeploymentConfig %s to update", namespacedOpenShiftObject) - } - - s.End(true) - - return nil -} - // GetComponentNames retrieves the names of the components in the specified application func GetComponentNames(client *occlient.Client, applicationName string) ([]string, error) { components, err := GetPushedComponents(client, applicationName) @@ -862,70 +164,7 @@ func ListDevfileComponents(client *occlient.Client, selector string) (ComponentL } if !reflect.ValueOf(component).IsZero() { - component.Spec.SourceType = string(config.LOCAL) - components = append(components, component) - } - - } - - compoList := newComponentList(components) - return compoList, nil -} - -// ListS2IComponents lists s2i components in active application -func ListS2IComponents(client *occlient.Client, applicationSelector string, localConfigInfo *config.LocalConfigInfo) (ComponentList, error) { - deploymentConfigSupported := false - var err error - - var components []Component - componentNamesMap := make(map[string]bool) - - if client != nil { - deploymentConfigSupported, err = client.IsDeploymentConfigSupported() - if err != nil { - return ComponentList{}, err - } - } - - if deploymentConfigSupported && client != nil { - // retrieve all the deployment configs that are associated with this application - dcList, err := client.ListDeploymentConfigs(applicationSelector) - if err != nil { - return ComponentList{}, errors.Wrapf(err, "unable to list components") - } - - // create a list of object metadata based on the component and application name (extracted from DeploymentConfig labels) - for _, elem := range dcList { - component, err := GetComponent(client, elem.Labels[componentlabels.ComponentLabel], elem.Labels[applabels.ApplicationLabel], client.Namespace) - if err != nil { - return ComponentList{}, errors.Wrap(err, "Unable to get component") - } - value := reflect.ValueOf(component) - if !value.IsZero() { - components = append(components, component) - componentNamesMap[component.Name] = true - } - } - } - - // this adds the local s2i component if there is one - if localConfigInfo != nil { - component, err := GetComponentFromConfig(localConfigInfo) - - if err != nil { - return newComponentList(components), err - } - - if client != nil { - _, ok := componentNamesMap[component.Name] - if component.Name != "" && !ok && component.Spec.App == localConfigInfo.GetApplication() && component.Namespace == client.Namespace { - component.Status.State = GetComponentState(client, component.Name, component.Spec.App) - components = append(components, component) - } - } else { - component.Status.State = StateTypeUnknown components = append(components, component) - } } @@ -934,54 +173,13 @@ func ListS2IComponents(client *occlient.Client, applicationSelector string, loca return compoList, nil } -// List lists all s2i and devfile components in active application -func List(client *occlient.Client, applicationSelector string, localConfigInfo *config.LocalConfigInfo) (ComponentList, error) { - var components []Component +// List lists all the devfile components in active application +func List(client *occlient.Client, applicationSelector string) (ComponentList, error) { devfileList, err := ListDevfileComponents(client, applicationSelector) if err != nil { return ComponentList{}, nil } - components = append(components, devfileList.Items...) - - s2iList, err := ListS2IComponents(client, applicationSelector, localConfigInfo) - if err != nil { - return ComponentList{}, err - } - components = append(components, s2iList.Items...) - - return newComponentList(components), nil -} - -// GetComponentFromConfig returns the component on the config if it exists -func GetComponentFromConfig(localConfig *config.LocalConfigInfo) (Component, error) { - component, err := getComponentFrom(localConfig, localConfig.GetType()) - if err != nil { - return Component{}, err - } - if len(component.Name) > 0 { - location := localConfig.GetSourceLocation() - sourceType := localConfig.GetSourceType() - component.Spec.Ports, err = localConfig.GetComponentPorts() - if err != nil { - return Component{}, err - } - component.Spec.SourceType = string(sourceType) - component.Spec.Source = location - - for _, localEnv := range localConfig.GetEnvVars() { - component.Spec.Env = append(component.Spec.Env, corev1.EnvVar{Name: localEnv.Name, Value: localEnv.Value}) - } - - configStorage, err := localConfig.ListStorage() - if err != nil { - return Component{}, err - } - for _, localStorage := range configStorage { - component.Spec.Storage = append(component.Spec.Storage, localStorage.Name) - } - return component, nil - } - return Component{}, nil + return newComponentList(devfileList.Items), nil } // GetComponentFromDevfile extracts component's metadata from the specified env info if it exists @@ -1053,52 +251,6 @@ func getComponentFrom(info localConfigProvider.LocalConfigProvider, componentTyp return Component{}, nil } -// ListIfPathGiven lists all available component in given path directory -func ListIfPathGiven(client *occlient.Client, paths []string) ([]Component, error) { - var components []Component - var err error - for _, path := range paths { - err = filepath.Walk(path, func(path string, f os.FileInfo, err error) error { - if f != nil && strings.Contains(f.Name(), ".odo") { - data, err := config.NewLocalConfigInfo(filepath.Dir(path)) - if err != nil { - return err - } - - // if the .odo folder doesn't contain a proper config file - if data.GetName() == "" || data.GetApplication() == "" || data.GetProject() == "" { - return nil - } - - // since the config file maybe belong to a component of a different project - if client != nil { - client.Namespace = data.GetProject() - } - - con, _ := filepath.Abs(filepath.Dir(path)) - a := newComponentWithType(data.GetName(), data.GetType()) - a.Namespace = data.GetProject() - a.Spec.App = data.GetApplication() - a.Spec.Ports, err = data.GetComponentPorts() - if err != nil { - return err - } - a.Spec.SourceType = string(data.GetSourceType()) - a.Status.Context = con - if client != nil { - a.Status.State = GetComponentState(client, data.GetName(), data.GetApplication()) - } else { - a.Status.State = StateTypeUnknown - } - components = append(components, a) - } - return nil - }) - - } - return components, err -} - func ListDevfileComponentsInPath(client kclient.ClientInterface, paths []string) ([]Component, error) { var components []Component var err error @@ -1155,295 +307,16 @@ func ListDevfileComponentsInPath(client kclient.ClientInterface, paths []string) return components, err } -// GetComponentSource what source type given component uses -// The first returned string is component source type ("git" or "local" or "binary") -// The second returned string is a source (url to git repository or local path or path to binary) -// we retrieve the source type by looking up the DeploymentConfig that's deployed -func GetComponentSource(client *occlient.Client, componentName string, applicationName string) (string, string, error) { - component, err := GetPushedComponent(client, componentName, applicationName) - if err != nil { - return "", "", errors.Wrapf(err, "unable to get type of %s component", componentName) - } else { - return component.GetSource() - } -} - -// Update updates the requested component -// Parameters: -// client: occlient instance -// componentConfig: Component configuration -// newSource: Location of component source resolved to absolute path -// stdout: io pipe to write logs to -// Returns: -// errors if any -func Update(client *occlient.Client, componentConfig config.LocalConfigInfo, newSource string, stdout io.Writer) error { - - retrievingSpinner := log.Spinner("Retrieving component data") - defer retrievingSpinner.End(false) - - // STEP 1. Create the common Object Meta for updating. - - componentName := componentConfig.GetName() - applicationName := componentConfig.GetApplication() - newSourceType := componentConfig.GetSourceType() - newSourceRef := componentConfig.GetRef() - componentImageType := componentConfig.GetType() - cmpPorts, err := componentConfig.GetComponentPorts() - if err != nil { - return err - } - envVarsList := componentConfig.GetEnvVars() - addDebugPortToEnv(&envVarsList, componentConfig) - - // Retrieve the old source type - oldSourceType, _, err := GetComponentSource(client, componentName, applicationName) - if err != nil { - return errors.Wrapf(err, "unable to get source of %s component", componentName) - } - - // Namespace the application - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - // Create annotations - annotations := make(map[string]string) - if newSourceType == config.GIT { - annotations[componentSourceURLAnnotation] = newSource - } - annotations[ComponentSourceTypeAnnotation] = string(newSourceType) - - // Parse componentImageType before adding to labels - imageNS, imageName, imageTag, _, err := occlient.ParseImageName(componentImageType) - if err != nil { - return errors.Wrap(err, "unable to parse image name") - } - - // Create labels for the component - // Save component type as label - labels := componentlabels.GetLabels(componentName, applicationName, true) - labels[componentlabels.ComponentTypeLabel] = imageName - labels[componentlabels.ComponentTypeVersion] = imageTag - - // ObjectMetadata are the same for all generated objects - // Create common metadata that will be updated throughout all objects. - commonObjectMeta := metav1.ObjectMeta{ - Name: namespacedOpenShiftObject, - Labels: labels, - Annotations: annotations, - } - - // Retrieve the current DC in order to obtain what the current inputPorts are.. - currentDC, err := client.GetDeploymentConfigFromName(commonObjectMeta.Name) - if err != nil { - return errors.Wrapf(err, "unable to get DeploymentConfig %s", commonObjectMeta.Name) - } - - foundCurrentDCContainer, err := occlient.FindContainer(currentDC.Spec.Template.Spec.Containers, commonObjectMeta.Name) - if err != nil { - return errors.Wrapf(err, "Unable to find container %s", commonObjectMeta.Name) - } - - ports := foundCurrentDCContainer.Ports - if len(cmpPorts) > 0 { - ports, err = util.GetContainerPortsFromStrings(cmpPorts) - if err != nil { - return errors.Wrapf(err, "failed to apply component config %+v to component %s", componentConfig, commonObjectMeta.Name) - } - } - - commonImageMeta := occlient.CommonImageMeta{ - Namespace: imageNS, - Name: imageName, - Tag: imageTag, - Ports: ports, - } - - // Generate the new DeploymentConfig - resourceLimits := occlient.FetchContainerResourceLimits(foundCurrentDCContainer) - resLts, err := occlient.GetResourceRequirementsFromCmpSettings(componentConfig) - if err != nil { - return errors.Wrap(err, "failed to update component") - } - if resLts != nil { - resourceLimits = *resLts - } - - // we choose the env variables in the config over the one present in the DC - // so the local config is reflected on the cluster - evl, err := kclient.GetInputEnvVarsFromStrings(envVarsList.ToStringSlice()) - if err != nil { - return err - } - updateComponentParams := occlient.UpdateComponentParams{ - CommonObjectMeta: commonObjectMeta, - ImageMeta: commonImageMeta, - ResourceLimits: resourceLimits, - DcRollOutWaitCond: occlient.IsDCRolledOut, - ExistingDC: currentDC, - EnvVars: evl, - } - - // STEP 2. Determine what the new source is going to be - - klog.V(4).Infof("Updating component %s, from %s to %s (%s).", componentName, oldSourceType, newSource, newSourceType) - - if (oldSourceType == "local" || oldSourceType == "binary") && newSourceType == "git" { - // Steps to update component from local or binary to git - // 1. Create a BuildConfig - // 2. Update DeploymentConfig with the new image - // 3. Clean up - // 4. Build the application - - // CreateBuildConfig here! - klog.V(4).Infof("Creating BuildConfig %s using imageName: %s for updating", namespacedOpenShiftObject, imageName) - bc, err := client.CreateBuildConfig(commonObjectMeta, componentImageType, newSource, newSourceRef, evl) - if err != nil { - return errors.Wrapf(err, "unable to update BuildConfig for %s component", componentName) - } - - retrievingSpinner.End(true) - - // we need to retrieve and build the git repository before deployment for the git components - // so we build before updating the deployment - err = Build(client, componentName, applicationName, true, stdout, false) - if err != nil { - return errors.Wrapf(err, "unable to build the component %s", componentName) - } - - s := log.Spinner("Applying configuration") - defer s.End(false) - - // Update / replace the current DeploymentConfig with a Git one (not SupervisorD!) - klog.V(4).Infof("Updating the DeploymentConfig %s image to %s", namespacedOpenShiftObject, bc.Spec.Output.To.Name) - - // Update the image for git deployment to the BC built component image - updateComponentParams.ImageMeta.Name = bc.Spec.Output.To.Name - isDeleteSupervisordVolumes := (oldSourceType != string(config.GIT)) - - err = client.UpdateDCToGit( - updateComponentParams, - isDeleteSupervisordVolumes, - ) - if err != nil { - return errors.Wrapf(err, "unable to update DeploymentConfig image for %s component", componentName) - } - - s.End(true) - - } else if oldSourceType == "git" && (newSourceType == "binary" || newSourceType == "local") { - - // Steps to update component from git to local or binary - updateComponentParams.CommonObjectMeta.Annotations = annotations - - retrievingSpinner.End(true) - - s := log.Spinner("Applying configuration") - defer s.End(false) - - // Need to delete the old BuildConfig - err = client.DeleteBuildConfig(commonObjectMeta) - - if err != nil { - return errors.Wrapf(err, "unable to delete BuildConfig for %s component", componentName) - } - - // Update the DeploymentConfig - err = client.UpdateDCToSupervisor( - updateComponentParams, - newSourceType == config.LOCAL, - true, - ) - if err != nil { - return errors.Wrapf(err, "unable to update DeploymentConfig for %s component", componentName) - } - - s.End(true) - } else { - // save source path as annotation - // this part is for updates where the source does not change or change from local to binary and vice versa - - if newSourceType == "git" { - - // Update the BuildConfig - err = client.UpdateBuildConfig(namespacedOpenShiftObject, newSource, annotations) - if err != nil { - return errors.Wrapf(err, "unable to update the build config %v", componentName) - } - - bc, err := client.GetBuildConfigFromName(namespacedOpenShiftObject) - if err != nil { - return errors.Wrap(err, "unable to get the BuildConfig file") - } - - retrievingSpinner.End(true) - - // we need to retrieve and build the git repository before deployment for git components - // so we build it before running the deployment - err = Build(client, componentName, applicationName, true, stdout, false) - if err != nil { - return errors.Wrapf(err, "unable to build the component: %v", componentName) - } - - // Update the current DeploymentConfig with all config applied - klog.V(4).Infof("Updating the DeploymentConfig %s image to %s", namespacedOpenShiftObject, bc.Spec.Output.To.Name) - - s := log.Spinner("Applying configuration") - defer s.End(false) - - // Update the image for git deployment to the BC built component image - updateComponentParams.ImageMeta.Name = bc.Spec.Output.To.Name - isDeleteSupervisordVolumes := (oldSourceType != string(config.GIT)) - - err = client.UpdateDCToGit( - updateComponentParams, - isDeleteSupervisordVolumes, - ) - if err != nil { - return errors.Wrapf(err, "unable to update DeploymentConfig image for %s component", componentName) - } - s.End(true) - - } else if newSourceType == "local" || newSourceType == "binary" { - - updateComponentParams.CommonObjectMeta.Annotations = annotations - - retrievingSpinner.End(true) - - s := log.Spinner("Applying configuration") - defer s.End(false) - - // Update the DeploymentConfig - err = client.UpdateDCToSupervisor( - updateComponentParams, - newSourceType == config.LOCAL, - false, - ) - if err != nil { - return errors.Wrapf(err, "unable to update DeploymentConfig for %s component", componentName) - } - s.End(true) - - } - - if err != nil { - return errors.Wrap(err, "unable to update the component") - } - } - return nil -} - // Exists checks whether a component with the given name exists in the current application or not // componentName is the component name to perform check for // The first returned parameter is a bool indicating if a component with the given name already exists or not // The second returned parameter is the error that might occurs while execution -func Exists(client *occlient.Client, componentName, applicationName string) (bool, error) { +func Exists(client kclient.ClientInterface, componentName, applicationName string) (bool, error) { deploymentName, err := util.NamespaceOpenShiftObject(componentName, applicationName) if err != nil { return false, errors.Wrapf(err, "unable to create namespaced name") } - deployment, _ := client.GetDeploymentConfigFromName(deploymentName) + deployment, _ := client.GetDeploymentByName(deploymentName) if deployment != nil { return true, nil } @@ -1483,18 +356,6 @@ func getRemoteComponentMetadata(client *occlient.Client, componentName string, a // init component component = newComponentWithType(componentName, componentType) - // Source - sourceType, path, sourceError := fromCluster.GetSource() - if sourceError != nil { - _, sourceAbsent := sourceError.(noSourceError) - if !sourceAbsent { - return Component{}, errors.Wrap(err, "unable to get source path") - } - } else { - component.Spec.Source = path - component.Spec.SourceType = sourceType - } - // URL if getUrls { urls, err := fromCluster.GetURLs() @@ -1643,29 +504,3 @@ func setLinksServiceNames(client *occlient.Client, linkedSecrets []SecretMount, } return nil } - -// GetLogs follow the DeploymentConfig logs if follow is set to true -func GetLogs(client *occlient.Client, componentName string, applicationName string, follow bool, stdout io.Writer) error { - - // Namespace the component - namespacedOpenShiftObject, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return errors.Wrapf(err, "unable to create namespaced name") - } - - // Retrieve the logs - err = client.DisplayDeploymentConfigLog(namespacedOpenShiftObject, follow) - if err != nil { - return err - } - - return nil -} - -func addDebugPortToEnv(envVarList *config.EnvVarList, componentConfig config.LocalConfigInfo) { - // adding the debug port as an env variable - *envVarList = append(*envVarList, config.EnvVar{ - Name: "DEBUG_PORT", - Value: fmt.Sprint(componentConfig.GetDebugPort()), - }) -} diff --git a/pkg/component/component_full_description.go b/pkg/component/component_full_description.go index 5263ef692ff..d985bf5e20f 100644 --- a/pkg/component/component_full_description.go +++ b/pkg/component/component_full_description.go @@ -9,7 +9,6 @@ import ( "github.com/openshift/odo/pkg/service" devfileParser "github.com/devfile/library/pkg/devfile/parser" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" @@ -21,14 +20,13 @@ import ( // ComponentFullDescriptionSpec represents the complete description of the component type ComponentFullDescriptionSpec struct { - App string `json:"app,omitempty"` - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` - SourceType string `json:"sourceType,omitempty"` - URL urlpkg.URLList `json:"urls,omitempty"` - Storage storage.StorageList `json:"storages,omitempty"` - Env []corev1.EnvVar `json:"env,omitempty"` - Ports []string `json:"ports,omitempty"` + App string `json:"app,omitempty"` + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` + URL urlpkg.URLList `json:"urls,omitempty"` + Storage storage.StorageList `json:"storages,omitempty"` + Env []corev1.EnvVar `json:"env,omitempty"` + Ports []string `json:"ports,omitempty"` } // ComponentFullDescription describes a component fully @@ -108,8 +106,8 @@ func (cfd *ComponentFullDescription) fillEmptyFields(componentDesc Component, co cfd.Spec.Ports = componentDesc.Spec.Ports } -// NewComponentFullDescriptionFromClientAndLocalConfig gets the complete description of the component from both localconfig and cluster -func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client, localConfigInfo *config.LocalConfigInfo, envInfo *envinfo.EnvSpecificInfo, componentName string, applicationName string, projectName string, context string) (*ComponentFullDescription, error) { +// NewComponentFullDescriptionFromClientAndLocalConfigProvider gets the complete description of the component from cluster +func NewComponentFullDescriptionFromClientAndLocalConfigProvider(client *occlient.Client, envInfo *envinfo.EnvSpecificInfo, componentName string, applicationName string, projectName string, context string) (*ComponentFullDescription, error) { cfd := &ComponentFullDescription{} var state State if client == nil { @@ -121,15 +119,12 @@ func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client var devfile devfileParser.DevfileObj var err error var configLinks []string - if envInfo != nil { - componentDesc, devfile, err = GetComponentFromDevfile(envInfo) - if err != nil { - return cfd, err - } - configLinks, err = service.ListDevfileLinks(devfile, context) - } else { - componentDesc, err = GetComponentFromConfig(localConfigInfo) + componentDesc, devfile, err = GetComponentFromDevfile(envInfo) + if err != nil { + return cfd, err } + configLinks, err = service.ListDevfileLinks(devfile, context) + if err != nil { return cfd, err } @@ -145,7 +140,6 @@ func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client } cfd.Spec.Env = componentDescFromCluster.Spec.Env cfd.Spec.Type = componentDescFromCluster.Spec.Type - cfd.Spec.SourceType = componentDescFromCluster.Spec.SourceType cfd.Status.LinkedServices = componentDescFromCluster.Status.LinkedServices } @@ -181,12 +175,8 @@ func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client } var configProvider localConfigProvider.LocalConfigProvider - if envInfo != nil { - envInfo.SetDevfileObj(devfile) - configProvider = envInfo - } else { - configProvider = localConfigInfo - } + envInfo.SetDevfileObj(devfile) + configProvider = envInfo var derefClient occlient.Client @@ -265,7 +255,7 @@ func (cfd *ComponentFullDescription) Print(client *occlient.Client) error { // if the component is not pushed for _, componentURL := range cfd.Spec.URL.Items { if componentURL.Status.State == urlpkg.StateTypePushed { - output += fmt.Sprintf(" · %v exposed via %v\n", urlpkg.GetURLString(componentURL.Spec.Protocol, componentURL.Spec.Host, "", true), componentURL.Spec.Port) + output += fmt.Sprintf(" · %v exposed via %v\n", urlpkg.GetURLString(componentURL.Spec.Protocol, componentURL.Spec.Host, ""), componentURL.Spec.Port) } else { output += fmt.Sprintf(" · URL named %s will be exposed via %v\n", componentURL.Name, componentURL.Spec.Port) } @@ -337,7 +327,6 @@ func (cfd *ComponentFullDescription) GetComponent() Component { cmp.Spec.App = cfd.Spec.App cmp.Spec.Ports = cfd.Spec.Ports cmp.Spec.Type = cfd.Spec.Type - cmp.Spec.SourceType = string(config.LOCAL) cmp.Spec.StorageSpec = cfd.Spec.Storage.Items cmp.Spec.URLSpec = cfd.Spec.URL.Items for _, url := range cfd.Spec.URL.Items { diff --git a/pkg/component/component_test.go b/pkg/component/component_test.go index 60e8bd351dd..02b03e688cd 100644 --- a/pkg/component/component_test.go +++ b/pkg/component/component_test.go @@ -2,12 +2,9 @@ package component import ( "fmt" - "io/ioutil" "os" - "path/filepath" "reflect" "regexp" - "sort" "testing" devfilepkg "github.com/devfile/api/v2/pkg/devfile" @@ -18,12 +15,10 @@ import ( "github.com/golang/mock/gomock" applabels "github.com/openshift/odo/pkg/application/labels" componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/localConfigProvider" "github.com/openshift/odo/pkg/occlient" "github.com/openshift/odo/pkg/testingutil" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ktesting "k8s.io/client-go/testing" @@ -145,237 +140,7 @@ func TestGetComponentFrom(t *testing.T) { } } -func TestGetS2IPaths(t *testing.T) { - - tests := []struct { - name string - podEnvs []corev1.EnvVar - want []string - }{ - { - name: "Case 1: odo expected s2i envs available", - podEnvs: []corev1.EnvVar{ - { - Name: occlient.EnvS2IDeploymentDir, - Value: "abc", - }, - { - Name: occlient.EnvS2ISrcOrBinPath, - Value: "def", - }, - { - Name: occlient.EnvS2IWorkingDir, - Value: "ghi", - }, - { - Name: occlient.EnvS2ISrcBackupDir, - Value: "ijk", - }, - }, - want: []string{ - filepath.FromSlash("abc/src"), - filepath.FromSlash("def/src"), - filepath.FromSlash("ghi/src"), - filepath.FromSlash("ijk/src"), - }, - }, - { - name: "Case 2: some of the odo expected s2i envs not available", - podEnvs: []corev1.EnvVar{ - { - Name: occlient.EnvS2IDeploymentDir, - Value: "abc", - }, - { - Name: occlient.EnvS2ISrcOrBinPath, - Value: "def", - }, - { - Name: occlient.EnvS2ISrcBackupDir, - Value: "ijk", - }, - }, - want: []string{ - filepath.FromSlash("abc/src"), - filepath.FromSlash("def/src"), - filepath.FromSlash("ijk/src"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := getS2IPaths(tt.podEnvs) - sort.Strings(got) - sort.Strings(tt.want) - if !reflect.DeepEqual(tt.want, got) { - t.Errorf("got: %+v, want: %+v", got, tt.want) - } - }) - } -} -func TestGetComponentPorts(t *testing.T) { - type args struct { - componentName string - applicationName string - } - - tests := []struct { - name string - args args - wantErr bool - output []string - }{ - { - name: "Case 1: Invalid/Unexisting component name", - args: args{ - componentName: "r", - applicationName: "app", - }, - wantErr: true, - output: []string{}, - }, - { - name: "Case 2: Valid params with multiple containers each with multiple ports", - args: args{ - componentName: "python", - applicationName: "app", - }, - output: []string{"10080/TCP", "8080/TCP", "9090/UDP", "10090/UDP"}, - wantErr: false, - }, - { - name: "Case 3: Valid params with single container and single port", - args: args{ - componentName: "nodejs", - applicationName: "app", - }, - output: []string{"8080/TCP"}, - wantErr: false, - }, - { - name: "Case 4: Valid params with single container and multiple port", - args: args{ - componentName: "wildfly", - applicationName: "app", - }, - output: []string{"8090/TCP", "8080/TCP"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - // Fake the client with the appropriate arguments - client, fakeClientSet := occlient.FakeNew() - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, testingutil.FakeDeploymentConfigs(), nil - }) - - // The function we are testing - output, err := GetComponentPorts(client, tt.args.componentName, tt.args.applicationName) - - // Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf("component List() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // Sort the output and expected o/p in-order to avoid issues due to order as its not important - sort.Strings(output) - sort.Strings(tt.output) - - // Check if the output is the same as what's expected (tags) - // and only if output is more than 0 (something is actually returned) - if len(output) > 0 && !(reflect.DeepEqual(output, tt.output)) { - t.Errorf("expected tags: %s, got: %s", tt.output, output) - } - }) - } -} - -func TestGetComponentLinkedSecretNames(t *testing.T) { - type args struct { - componentName string - applicationName string - } - - tests := []struct { - name string - args args - wantErr bool - output []string - }{ - { - name: "Case 1: Invalid/Unexisting component name", - args: args{ - componentName: "r", - applicationName: "app", - }, - wantErr: true, - output: []string{}, - }, - { - name: "Case 2: Valid params nil env source", - args: args{ - componentName: "python", - applicationName: "app", - }, - output: []string{}, - wantErr: false, - }, - { - name: "Case 3: Valid params multiple secrets", - args: args{ - componentName: "nodejs", - applicationName: "app", - }, - output: []string{"s1", "s2"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - // Fake the client with the appropriate arguments - client, fakeClientSet := occlient.FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, testingutil.FakeDeploymentConfigs(), nil - }) - - // The function we are testing - output, err := GetComponentLinkedSecretNames(client, tt.args.componentName, tt.args.applicationName) - - // Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf("component List() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // Sort the output and expected o/p in-order to avoid issues due to order as its not important - sort.Strings(output) - sort.Strings(tt.output) - - // Check if the output is the same as what's expected (tags) - // and only if output is more than 0 (something is actually returned) - if len(output) > 0 && !(reflect.DeepEqual(output, tt.output)) { - t.Errorf("expected tags: %s, got: %s", tt.output, output) - } - }) - } -} - func TestList(t *testing.T) { - mockConfig := config.GetOneExistingConfigInfo("comp", "app", "test") - componentConfig, err := GetComponentFromConfig(&mockConfig) - if err != nil { - t.Errorf("error occured while calling GetComponentFromConfig, error: %v", err) - } - componentConfig.Status.State = StateTypeNotPushed - componentConfig2, err := GetComponentFromConfig(&mockConfig) - if err != nil { - t.Errorf("error occured while calling GetComponentFromConfig, error: %v", err) - } - componentConfig2.Status.State = StateTypeUnknown - deploymentList := v1.DeploymentList{Items: []v1.Deployment{ *testingutil.CreateFakeDeployment("comp0"), *testingutil.CreateFakeDeployment("comp1"), @@ -383,23 +148,18 @@ func TestList(t *testing.T) { deploymentList.Items[0].Labels[componentlabels.ComponentTypeLabel] = "nodejs" deploymentList.Items[0].Annotations = map[string]string{ - ComponentSourceTypeAnnotation: "local", componentlabels.ComponentTypeAnnotation: "nodejs", } deploymentList.Items[1].Labels[componentlabels.ComponentTypeLabel] = "wildfly" deploymentList.Items[1].Annotations = map[string]string{ - ComponentSourceTypeAnnotation: "local", componentlabels.ComponentTypeAnnotation: "wildfly", } - tests := []struct { - name string - deploymentConfigSupported bool - deploymentList v1.DeploymentList - projectExists bool - wantErr bool - existingLocalConfigInfo *config.LocalConfigInfo - output ComponentList + name string + deploymentList v1.DeploymentList + projectExists bool + wantErr bool + output ComponentList }{ { name: "Case 1: no component and no config exists", @@ -408,11 +168,10 @@ func TestList(t *testing.T) { output: newComponentList([]Component{}), }, { - name: "Case 2: Components are returned from deployments on a kubernetes cluster", - deploymentList: deploymentList, - wantErr: false, - projectExists: true, - deploymentConfigSupported: false, + name: "Case 2: Components are returned from deployments on a kubernetes cluster", + deploymentList: deploymentList, + wantErr: false, + projectExists: true, output: ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", @@ -429,11 +188,6 @@ func TestList(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if !tt.deploymentConfigSupported { - os.Setenv("KUBERNETES", "true") - defer os.Unsetenv("KUBERNETES") - } - client, fakeClientSet := occlient.FakeNew() client.Namespace = "test" @@ -462,7 +216,7 @@ func TestList(t *testing.T) { } }) - results, err := List(client, applabels.GetSelector("app"), tt.existingLocalConfigInfo) + results, err := List(client, applabels.GetSelector("app")) if (err != nil) != tt.wantErr { t.Errorf("expected err: %v, but err is %v", tt.wantErr, err) @@ -480,42 +234,20 @@ func TestGetDefaultComponentName(t *testing.T) { testName string componentType string componentPath string - componentPathType config.SrcType existingComponents ComponentList wantErr bool wantRE string needPrefix bool }{ - { - testName: "Case: App prefix not configured", - componentType: "nodejs", - componentPathType: config.GIT, - componentPath: "https://github.com/openshift/nodejs.git", - existingComponents: ComponentList{}, - wantErr: false, - wantRE: "nodejs-*", - needPrefix: false, - }, { testName: "Case: App prefix configured", componentType: "nodejs", - componentPathType: config.LOCAL, componentPath: "./testing", existingComponents: ComponentList{}, wantErr: false, wantRE: "nodejs-testing-*", needPrefix: true, }, - { - testName: "Case: App prefix configured", - componentType: "wildfly", - componentPathType: config.BINARY, - componentPath: "./testing.war", - existingComponents: ComponentList{}, - wantErr: false, - wantRE: "wildfly-testing-*", - needPrefix: true, - }, } for _, tt := range tests { @@ -537,7 +269,7 @@ func TestGetDefaultComponentName(t *testing.T) { t.Errorf("failed to setup test env. Error %v", err) } - name, err := GetDefaultComponentName(tt.componentPath, tt.componentPathType, tt.componentType, tt.existingComponents) + name, err := GetDefaultComponentName(tt.componentPath, tt.componentType, tt.existingComponents) if err != nil { t.Errorf("failed to setup mock environment. Error: %v", err) } @@ -557,8 +289,7 @@ func TestGetDefaultComponentName(t *testing.T) { func TestGetComponentDir(t *testing.T) { type args struct { - path string - paramType config.SrcType + path string } tests := []struct { testName string @@ -566,29 +297,10 @@ func TestGetComponentDir(t *testing.T) { want string wantErr bool }{ - { - testName: "Case: Git URL", - args: args{ - paramType: config.GIT, - path: "https://github.com/openshift/nodejs-ex.git", - }, - want: "nodejs-ex", - wantErr: false, - }, { testName: "Case: Source Path", args: args{ - paramType: config.LOCAL, - path: "./testing", - }, - wantErr: false, - want: "testing", - }, - { - testName: "Case: Binary path", - args: args{ - paramType: config.BINARY, - path: "./testing.war", + path: "./testing", }, wantErr: false, want: "testing", @@ -596,8 +308,7 @@ func TestGetComponentDir(t *testing.T) { { testName: "Case: No clue of any component", args: args{ - paramType: config.NONE, - path: "", + path: "", }, wantErr: false, want: "component", @@ -607,7 +318,7 @@ func TestGetComponentDir(t *testing.T) { for _, tt := range tests { t.Log("Running test: ", tt.testName) t.Run(tt.testName, func(t *testing.T) { - name, err := GetComponentDir(tt.args.path, tt.args.paramType) + name, err := GetComponentDir(tt.args.path) if (err != nil) != tt.wantErr { t.Errorf("expected err: %v, but err is %v", tt.wantErr, err) } @@ -744,135 +455,6 @@ func Test_getMachineReadableFormatForList(t *testing.T) { } } -func TestGetComponentFromConfig(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv("LOCALODOCONFIG", tempConfigFile.Name()) - - localExistingConfigInfoValue := config.GetOneExistingConfigInfo("comp", "app", "project") - localExistingConfigInfoUrls, err := localExistingConfigInfoValue.LocalConfig.ListURLs() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - localExistingConfigInfoStorage, err := localExistingConfigInfoValue.ListStorage() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - localExistingConfigInfoPorts, err := localExistingConfigInfoValue.GetComponentPorts() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - localNonExistingConfigInfoValue := config.GetOneNonExistingConfigInfo() - gitExistingConfigInfoValue := config.GetOneGitExistingConfigInfo("comp", "app", "project") - gitExistingConfigInfoUrls, err := gitExistingConfigInfoValue.LocalConfig.ListURLs() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - gitExistingConfigInfoStorage, err := gitExistingConfigInfoValue.ListStorage() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - gitExistingConfigInfoPorts, err := gitExistingConfigInfoValue.GetComponentPorts() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - tests := []struct { - name string - isConfigExists bool - existingConfig config.LocalConfigInfo - wantSpec Component - }{ - { - name: "case 1: config file exists", - isConfigExists: true, - existingConfig: localExistingConfigInfoValue, - wantSpec: Component{ - Spec: ComponentSpec{ - App: localExistingConfigInfoValue.GetApplication(), - Type: localExistingConfigInfoValue.GetType(), - Source: localExistingConfigInfoValue.GetSourceLocation(), - URL: []string{ - localExistingConfigInfoUrls[0].Name, localExistingConfigInfoUrls[1].Name, - }, - Storage: []string{ - localExistingConfigInfoStorage[0].Name, localExistingConfigInfoStorage[1].Name, - }, - Env: []corev1.EnvVar{ - { - Name: localExistingConfigInfoValue.LocalConfig.GetEnvs()[0].Name, - Value: localExistingConfigInfoValue.LocalConfig.GetEnvs()[0].Value, - }, - { - Name: localExistingConfigInfoValue.LocalConfig.GetEnvs()[1].Name, - Value: localExistingConfigInfoValue.LocalConfig.GetEnvs()[1].Value, - }, - }, - SourceType: "local", - Ports: localExistingConfigInfoPorts, - }, - }, - }, - { - name: "case 2: config file doesn't exists", - isConfigExists: false, - existingConfig: localNonExistingConfigInfoValue, - wantSpec: Component{}, - }, - { - name: "case 3: config file exists", - isConfigExists: true, - existingConfig: gitExistingConfigInfoValue, - wantSpec: Component{ - Spec: ComponentSpec{ - App: gitExistingConfigInfoValue.GetApplication(), - Type: gitExistingConfigInfoValue.GetType(), - Source: gitExistingConfigInfoValue.GetSourceLocation(), - URL: []string{ - gitExistingConfigInfoUrls[0].Name, gitExistingConfigInfoUrls[1].Name, - }, - Storage: []string{ - gitExistingConfigInfoStorage[0].Name, gitExistingConfigInfoStorage[1].Name, - }, - Env: []corev1.EnvVar{ - { - Name: gitExistingConfigInfoValue.LocalConfig.GetEnvs()[0].Name, - Value: gitExistingConfigInfoValue.LocalConfig.GetEnvs()[0].Value, - }, - { - Name: gitExistingConfigInfoValue.LocalConfig.GetEnvs()[1].Name, - Value: gitExistingConfigInfoValue.LocalConfig.GetEnvs()[1].Value, - }, - }, - SourceType: "git", - Ports: gitExistingConfigInfoPorts, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := config.NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg := &tt.existingConfig - - got, _ := GetComponentFromConfig(cfg) - - if !reflect.DeepEqual(got.Spec, tt.wantSpec.Spec) { - t.Errorf("the component spec is different, want: %v,\n got: %v", tt.wantSpec.Spec, got.Spec) - } - - }) - } - -} - func TestGetComponentTypeFromDevfileMetadata(t *testing.T) { tests := []devfilepkg.DevfileMetadata{ { @@ -908,7 +490,6 @@ func TestGetComponentTypeFromDevfileMetadata(t *testing.T) { } func getFakeComponent(compName, namespace, appName, compType string, state State) Component { - sourceType := "local" return Component{ TypeMeta: metav1.TypeMeta{ Kind: "Component", @@ -926,13 +507,11 @@ func getFakeComponent(compName, namespace, appName, compType string, state State }, Annotations: map[string]string{ componentlabels.ComponentTypeAnnotation: compType, - ComponentSourceTypeAnnotation: sourceType, }, }, Spec: ComponentSpec{ - Type: compType, - App: appName, - SourceType: sourceType, + Type: compType, + App: appName, }, Status: ComponentStatus{ State: state, diff --git a/pkg/component/pushed_component.go b/pkg/component/pushed_component.go index 32deb373d5f..427a54d7568 100644 --- a/pkg/component/pushed_component.go +++ b/pkg/component/pushed_component.go @@ -3,15 +3,12 @@ package component import ( "fmt" - appsv1 "github.com/openshift/api/apps/v1" applabels "github.com/openshift/odo/pkg/application/labels" componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/occlient" "github.com/openshift/odo/pkg/storage" "github.com/openshift/odo/pkg/url" - "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" v1 "k8s.io/api/apps/v1" v12 "k8s.io/api/core/v1" @@ -33,7 +30,6 @@ type PushedComponent interface { GetURLs() ([]url.URL, error) GetApplication() string GetType() (string, error) - GetSource() (string, string, error) GetStorage() ([]storage.Storage, error) } @@ -63,10 +59,6 @@ func (d defaultPushedComponent) GetType() (string, error) { return getType(d.provider) } -func (d defaultPushedComponent) GetSource() (string, string, error) { - return getSource(d.provider) -} - func (d defaultPushedComponent) GetEnvVars() []v12.EnvVar { return d.provider.GetEnvVars() } @@ -104,46 +96,6 @@ func (d defaultPushedComponent) GetApplication() string { return d.application } -type s2iComponent struct { - dc appsv1.DeploymentConfig -} - -func (s s2iComponent) GetLinkedSecrets() (secretMounts []SecretMount) { - for _, env := range s.dc.Spec.Template.Spec.Containers[0].EnvFrom { - if env.SecretRef != nil { - secretMounts = append(secretMounts, SecretMount{ - SecretName: env.SecretRef.Name, - MountVolume: false, - }) - } - } - return secretMounts -} - -func (s s2iComponent) GetEnvVars() []v12.EnvVar { - return s.dc.Spec.Template.Spec.Containers[0].Env -} - -func (s s2iComponent) GetLabels() map[string]string { - return s.dc.Labels -} - -func (s s2iComponent) GetAnnotations() map[string]string { - return s.dc.Annotations -} - -func (s s2iComponent) GetName() string { - return s.dc.Labels[componentlabels.ComponentLabel] -} - -func (s s2iComponent) GetType() (string, error) { - return getType(s) -} - -func (s s2iComponent) GetSource() (string, string, error) { - return getSource(s) -} - type devfileComponent struct { d v1.Deployment } @@ -206,35 +158,6 @@ func (d devfileComponent) GetType() (string, error) { return getType(d) } -func (d devfileComponent) GetSource() (string, string, error) { - return getSource(d) -} - -type noSourceError struct { - msg string -} - -func (n noSourceError) Error() string { - return n.msg -} - -func getSource(component provider) (string, string, error) { - annotations := component.GetAnnotations() - if sourceType, ok := annotations[ComponentSourceTypeAnnotation]; ok { - if !validateSourceType(sourceType) { - return "", "", fmt.Errorf("unsupported component source type %s", sourceType) - } - var sourcePath string - if sourceType == string(config.GIT) { - sourcePath = annotations[componentSourceURLAnnotation] - } - - klog.V(4).Infof("Source for component %s is %s (%s)", component.GetName(), sourcePath, sourceType) - return sourceType, sourcePath, nil - } - return "", "", noSourceError{msg: fmt.Sprintf("%s component doesn't provide a source type annotation", component.GetName())} -} - func getType(component provider) (string, error) { if componentType, ok := component.GetAnnotations()[componentlabels.ComponentTypeAnnotation]; ok { return componentType, nil @@ -249,23 +172,11 @@ func getType(component provider) (string, error) { func GetPushedComponents(c *occlient.Client, applicationName string) (map[string]PushedComponent, error) { applicationSelector := fmt.Sprintf("%s=%s", applabels.ApplicationLabel, applicationName) - dcList, err := c.ListDeploymentConfigs(applicationSelector) - if err != nil { - if !isIgnorableError(err) { - return nil, err - } - } - res := make(map[string]PushedComponent, len(dcList)) - for _, dc := range dcList { - comp := newPushedComponent(applicationName, &s2iComponent{dc: dc}, c, nil, nil) - res[comp.GetName()] = comp - } - deploymentList, err := c.GetKubeClient().ListDeployments(applicationSelector) if err != nil { return nil, errors.Wrapf(err, "unable to list components") } - + res := make(map[string]PushedComponent, len(deploymentList.Items)) for _, d := range deploymentList.Items { deployment := d storageClient := storage.NewClient(storage.ClientOptions{ @@ -299,26 +210,7 @@ func GetPushedComponent(c *occlient.Client, componentName, applicationName strin d, err := c.GetKubeClient().GetOneDeployment(componentName, applicationName) if err != nil { if isIgnorableError(err) { - // if it's not found, check if there's a deploymentconfig - deploymentName, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return nil, err - } - dc, err := c.GetDeploymentConfigFromName(deploymentName) - if err != nil { - if kerrors.IsNotFound(err) { - // in case where odo's standard naming practices are not followed, it makes sense to do a double check with component name - // this is useful when dealing with components that are not managed/created by odo - dc, err = c.GetDeploymentConfigFromName(componentName) - if err != nil { - return nil, nil - } else { - return newPushedComponent(applicationName, &s2iComponent{dc: *dc}, c, nil, nil), nil - } - } - } else { - return newPushedComponent(applicationName, &s2iComponent{dc: *dc}, c, nil, nil), nil - } + return nil, nil } return nil, err } diff --git a/pkg/component/types.go b/pkg/component/types.go index 29e51020c83..951a77aba74 100644 --- a/pkg/component/types.go +++ b/pkg/component/types.go @@ -23,7 +23,6 @@ type ComponentSpec struct { App string `json:"app,omitempty"` Type string `json:"type,omitempty"` Source string `json:"source,omitempty"` - SourceType string `json:"sourceType,omitempty"` URL []string `json:"url,omitempty"` URLSpec []url.URL `json:"-"` Storage []string `json:"storage,omitempty"` @@ -58,7 +57,6 @@ type ComponentStatus struct { type CombinedComponentList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - S2IComponents []Component `json:"s2iComponents"` DevfileComponents []Component `json:"devfileComponents"` OtherComponents []Component `json:"otherComponents"` } @@ -112,10 +110,8 @@ func newComponentList(comps []Component) ComponentList { } // NewCombinedComponentList returns list of devfile, s2i components and other components(not managed by odo) in machine readable format -func NewCombinedComponentList(s2iComps []Component, devfileComps []Component, otherComps []Component) CombinedComponentList { - if len(s2iComps) == 0 { - s2iComps = []Component{} - } +func NewCombinedComponentList(devfileComps []Component, otherComps []Component) CombinedComponentList { + if len(devfileComps) == 0 { devfileComps = []Component{} } @@ -129,7 +125,6 @@ func NewCombinedComponentList(s2iComps []Component, devfileComps []Component, ot APIVersion: machineoutput.APIVersion, }, ListMeta: metav1.ListMeta{}, - S2IComponents: s2iComps, DevfileComponents: devfileComps, OtherComponents: otherComps, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 9d14b8da9c0..6486d4f7deb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,586 +1,27 @@ package config import ( - "fmt" - "io" - "net/url" - "os" - "path/filepath" - "strconv" "strings" "github.com/devfile/library/pkg/devfile/parser" - "github.com/openshift/odo/pkg/localConfigProvider" - "github.com/openshift/odo/pkg/testingutil/filesystem" - - "github.com/pkg/errors" - "k8s.io/klog" "github.com/openshift/odo/pkg/util" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/pkg/errors" ) const ( - localConfigEnvName = "LOCALODOCONFIG" - configFileName = "config.yaml" - localConfigKind = "LocalConfig" - localConfigAPIVersion = "odo.dev/v1alpha1" - // DefaultDebugPort is the default port used for debugging on remote pod - DefaultDebugPort = 5858 -) - -// ComponentSettings holds all component related information -type ComponentSettings struct { - // The builder image to use - Type *string `yaml:"Type,omitempty"` - - // SourceLocation is path to binary in current/context dir, it can be the - // git url in case of source type being git - SourceLocation *string `yaml:"SourceLocation,omitempty"` - - // Ref is component source git ref but can be levaraged for more in future - Ref *string `yaml:"Ref,omitempty"` - - // Type is type of component source: git/local/binary - SourceType *SrcType `yaml:"SourceType,omitempty"` - - // Ports is a slice of ports to be exposed when a component is created - // the format of the port is "PORT/PROTOCOL" e.g. "8080/TCP" - Ports *[]string `yaml:"Ports,omitempty"` - - Application *string `yaml:"Application,omitempty"` - - Project *string `yaml:"Project,omitempty"` - - Name *string `yaml:"Name,omitempty"` - - MinMemory *string `yaml:"MinMemory,omitempty"` - - MaxMemory *string `yaml:"MaxMemory,omitempty"` - - // DebugPort controls the port used by the pod to run the debugging agent on - DebugPort *int `yaml:"DebugPort,omitempty"` - - Storage *[]localConfigProvider.LocalStorage `yaml:"Storage,omitempty"` - - // Ignore if set to true then odoignore file should be considered - Ignore *bool `yaml:"Ignore,omitempty"` - - MinCPU *string `yaml:"MinCPU,omitempty"` - - MaxCPU *string `yaml:"MaxCPU,omitempty"` - - Envs EnvVarList `yaml:"Envs,omitempty"` - - URL *[]localConfigProvider.LocalURL `yaml:"Url,omitempty"` -} - -// LocalConfig holds all the config relavent to a specific Component. -type LocalConfig struct { - typeMeta metav1.TypeMeta `yaml:",inline"` - componentSettings ComponentSettings `yaml:"ComponentSettings,omitempty"` -} - -// proxyLocalConfig holds all the parameter that local config does but exposes all -// of it, used for serialization. -type proxyLocalConfig struct { - metav1.TypeMeta `yaml:",inline"` - ComponentSettings ComponentSettings `yaml:"ComponentSettings,omitempty"` -} - -// LocalConfigInfo wraps the local config and provides helpers to -// serialize it. -type LocalConfigInfo struct { - Filename string `yaml:"FileName,omitempty"` - fs filesystem.Filesystem - LocalConfig `yaml:",omitempty"` - configFileExists bool -} - -func getLocalConfigFile(cfgDir string) (string, error) { - if env, ok := os.LookupEnv(localConfigEnvName); ok { - return env, nil - } - - if cfgDir == "" { - var err error - cfgDir, err = os.Getwd() - if err != nil { - return "", err - } - } - - return filepath.Join(cfgDir, ".odo", configFileName), nil -} - -// New returns the localConfigInfo -func New() (*LocalConfigInfo, error) { - return NewLocalConfigInfo("") -} - -// NewLocalConfigInfo gets the LocalConfigInfo from local config file and creates the local config file in case it's -// not present then it -func NewLocalConfigInfo(cfgDir string) (*LocalConfigInfo, error) { - return newLocalConfigInfo(cfgDir, filesystem.DefaultFs{}) -} - -func newLocalConfigInfo(cfgDir string, fs filesystem.Filesystem) (*LocalConfigInfo, error) { - configFile, err := getLocalConfigFile(cfgDir) - if err != nil { - return nil, errors.Wrap(err, "unable to get odo config file") - } - c := LocalConfigInfo{ - LocalConfig: NewLocalConfig(), - Filename: configFile, - configFileExists: true, - fs: fs, - } - - // if the config file doesn't exist then we dont worry about it and return - if _, err = c.fs.Stat(configFile); os.IsNotExist(err) { - c.configFileExists = false - return &c, nil - } - - err = getFromFile(&c.LocalConfig, c.Filename) - if err != nil { - return nil, err - } - return &c, nil -} - -func getFromFile(lc *LocalConfig, filename string) error { - plc := newProxyLocalConfig() - - err := util.GetFromFile(&plc, filename) - if err != nil { - return err - } - lc.typeMeta = plc.TypeMeta - lc.componentSettings = plc.ComponentSettings - return nil -} - -// NewLocalConfig creates an empty LocalConfig struct with typeMeta populated -func NewLocalConfig() LocalConfig { - return LocalConfig{ - typeMeta: metav1.TypeMeta{ - Kind: localConfigKind, - APIVersion: localConfigAPIVersion, - }, - } -} - -// newProxyLocalConfig creates an empty proxyLocalConfig struct with typeMeta populated -func newProxyLocalConfig() proxyLocalConfig { - lc := NewLocalConfig() - return proxyLocalConfig{ - TypeMeta: lc.typeMeta, - } -} - -// SetConfiguration sets the common config settings like component type, min memory -// max memory etc. -// TODO: Use reflect to set parameters -func (lci *LocalConfigInfo) SetConfiguration(parameter string, value interface{}) (err error) { - // getting the second arg makes sure that this never panics - strValue, _ := value.(string) - if parameter, ok := AsLocallySupportedParameter(parameter); ok { - switch parameter { - case "type": - lci.componentSettings.Type = &strValue - case "application": - lci.componentSettings.Application = &strValue - case "project": - lci.componentSettings.Project = &strValue - case "sourcetype": - cmpSourceType, err := GetSrcType(strValue) - if err != nil { - return errors.Wrapf(err, "unable to set %s to %s", parameter, strValue) - } - lci.componentSettings.SourceType = &cmpSourceType - case "ref": - lci.componentSettings.Ref = &strValue - case "sourcelocation": - lci.componentSettings.SourceLocation = &strValue - case "ports": - arrValue := strings.Split(strValue, ",") - lci.componentSettings.Ports = &arrValue - case "name": - lci.componentSettings.Name = &strValue - case "minmemory": - lci.componentSettings.MinMemory = &strValue - case "maxmemory": - lci.componentSettings.MaxMemory = &strValue - case "memory": - lci.componentSettings.MaxMemory = &strValue - lci.componentSettings.MinMemory = &strValue - case "debugport": - dbgPort, err := strconv.Atoi(strValue) - if err != nil { - return err - } - lci.componentSettings.DebugPort = &dbgPort - case "ignore": - val, err := strconv.ParseBool(strings.ToLower(strValue)) - if err != nil { - return errors.Wrapf(err, "unable to set %s to %s", parameter, strValue) - } - lci.componentSettings.Ignore = &val - case "mincpu": - lci.componentSettings.MinCPU = &strValue - case "maxcpu": - lci.componentSettings.MaxCPU = &strValue - case "storage": - storageSetting, _ := value.(localConfigProvider.LocalStorage) - if lci.componentSettings.Storage != nil { - *lci.componentSettings.Storage = append(*lci.componentSettings.Storage, storageSetting) - } else { - lci.componentSettings.Storage = &[]localConfigProvider.LocalStorage{storageSetting} - } - case "cpu": - lci.componentSettings.MinCPU = &strValue - lci.componentSettings.MaxCPU = &strValue - case "url": - urlValue := value.(localConfigProvider.LocalURL) - if lci.componentSettings.URL != nil { - *lci.componentSettings.URL = append(*lci.componentSettings.URL, urlValue) - } else { - lci.componentSettings.URL = &[]localConfigProvider.LocalURL{urlValue} - } - } - - return lci.writeToFile() - } - return errors.Errorf("unknown parameter :'%s' is not a parameter in local odo config", parameter) - -} - -// DeleteConfigDirIfEmpty Deletes the config directory if its empty -func (lci *LocalConfigInfo) DeleteConfigDirIfEmpty() error { - configDir := filepath.Dir(lci.Filename) - _, err := lci.fs.Stat(configDir) - if os.IsNotExist(err) { - // If the config dir doesn't exist then we dont mind - return nil - } else if err != nil { - // Possible to not have permission to the dir - return err - } - f, err := lci.fs.Open(configDir) - if err != nil { - return err - } - defer f.Close() - _, err = f.Readdir(1) - - // If directory is empty we can remove it - if err == io.EOF { - klog.V(4).Info("Deleting the config directory as well because its empty") - - return lci.fs.Remove(configDir) - } - return err -} - -// DeleteConfigFile deletes the odo-config.yaml file if it exists -func (lci *LocalConfigInfo) DeleteConfigFile() error { - return util.DeletePath(lci.Filename) -} - -// IsSet uses reflection to get the parameter from the localconfig struct, currently -// it only searches the componentSettings -func (lci *LocalConfigInfo) IsSet(parameter string) bool { - - switch strings.ToLower(parameter) { - case "cpu": - return (lci.componentSettings.MinCPU != nil && lci.componentSettings.MaxCPU != nil) && - (*lci.componentSettings.MinCPU == *lci.componentSettings.MaxCPU) - case "memory": - return (lci.componentSettings.MinMemory != nil && lci.componentSettings.MaxMemory != nil) && - (*lci.componentSettings.MinMemory == *lci.componentSettings.MaxMemory) - } - - return util.IsSet(lci.componentSettings, parameter) -} - -// Exists returns whether the config file exists or not -func (lci *LocalConfigInfo) Exists() bool { - return lci.configFileExists -} - -// DeleteConfiguration is used to delete config from local odo config -func (lci *LocalConfigInfo) DeleteConfiguration(parameter string) error { - if parameter, ok := AsLocallySupportedParameter(parameter); ok { - - switch parameter { - case "cpu": - lci.componentSettings.MinCPU = nil - lci.componentSettings.MaxCPU = nil - case "memory": - lci.componentSettings.MinMemory = nil - lci.componentSettings.MaxMemory = nil - default: - if err := util.DeleteConfiguration(&lci.componentSettings, parameter); err != nil { - return err - } - } - return lci.writeToFile() - } - return errors.Errorf("unknown parameter :'%s' is not a parameter in local odo config", parameter) - -} - -// DeleteFromConfigurationList is used to delete a value from a list from the local odo config -// parameter is the name of the config parameter -// value is the value to be deleted -func (lci *LocalConfigInfo) DeleteFromConfigurationList(parameter string, value string) error { - configStorage, err := lci.ListStorage() - if err != nil { - return err - } - if parameter, ok := AsLocallySupportedParameter(parameter); ok { - switch parameter { - case "storage": - for i, storage := range configStorage { - if storage.Name == value { - *lci.componentSettings.Storage = append((*lci.componentSettings.Storage)[:i], (*lci.componentSettings.Storage)[i+1:]...) - } - } - return lci.writeToFile() - } - } - return errors.Errorf("unknown parameter :'%s' is not a parameter in local odo config", parameter) -} - -// GetComponentSettings returns the componentSettings from local config -func (lci *LocalConfigInfo) GetComponentSettings() ComponentSettings { - return lci.componentSettings -} - -// SetComponentSettings sets the componentSettings from to the local config and writes to the file -func (lci *LocalConfigInfo) SetComponentSettings(cs ComponentSettings) error { - lci.componentSettings = cs - return lci.writeToFile() -} - -// SetEnvVars sets the env variables on the component settings -func (lci *LocalConfigInfo) SetEnvVars(envVars EnvVarList) error { - lci.componentSettings.Envs = envVars - return lci.writeToFile() -} - -// GetEnvVars gets the env variables from the component settings -func (lci *LocalConfigInfo) GetEnvVars() EnvVarList { - return lci.GetEnvs() -} - -func (lci *LocalConfigInfo) writeToFile() error { - plc := newProxyLocalConfig() - plc.TypeMeta = lci.typeMeta - plc.ComponentSettings = lci.componentSettings - - return util.WriteToFile(&plc, lci.Filename) -} - -// GetType returns type of component (builder image name) in the config -func (lc *LocalConfig) GetType() string { - return util.GetStringOrEmpty(lc.componentSettings.Type) -} - -// GetSourceLocation returns the sourcelocation, returns default if nil -func (lc *LocalConfig) GetSourceLocation() string { - return util.GetStringOrEmpty(lc.componentSettings.SourceLocation) -} - -// GetRef returns the ref, returns default if nil -func (lc *LocalConfig) GetRef() string { - return util.GetStringOrEmpty(lc.componentSettings.Ref) -} - -// GetSourceType returns the source type, returns default if nil -func (lc *LocalConfig) GetSourceType() SrcType { - if lc.componentSettings.SourceType == nil { - return "" - } - return *lc.componentSettings.SourceType -} - -// SetComponentSettingsWithoutFileWrite sets the componentSetting but doesn't write to file -func (lci *LocalConfigInfo) SetComponentSettingsWithoutFileWrite(cs ComponentSettings) { - lci.componentSettings = cs -} - -// GetApplication returns the app, returns default if nil -func (lc *LocalConfig) GetApplication() string { - return util.GetStringOrEmpty(lc.componentSettings.Application) -} - -// GetProject returns the project, returns default if nil -func (lc *LocalConfig) GetProject() string { - return util.GetStringOrEmpty(lc.componentSettings.Project) -} - -// GetProject returns the project, returns default if nil -func (lc *LocalConfig) GetNamespace() string { - return lc.GetProject() -} - -// GetName returns the Name, returns default if nil -func (lc *LocalConfig) GetName() string { - return util.GetStringOrEmpty(lc.componentSettings.Name) -} - -// GetMinMemory returns the MinMemory, returns default if nil -func (lc *LocalConfig) GetMinMemory() string { - return util.GetStringOrEmpty(lc.componentSettings.MinMemory) -} - -// GetMaxMemory returns the MaxMemory, returns default if nil -func (lc *LocalConfig) GetMaxMemory() string { - return util.GetStringOrEmpty(lc.componentSettings.MaxMemory) -} - -// GetDebugPort returns the DebugPort, returns default if nil -func (lc *LocalConfig) GetDebugPort() int { - return util.GetIntOrDefault(lc.componentSettings.DebugPort, DefaultDebugPort) -} - -// GetContainers returns the Container components from the config -// returns empty list if nil -func (lc *LocalConfig) GetContainers() ([]localConfigProvider.LocalContainer, error) { - if lc.GetName() == "" { - return []localConfigProvider.LocalContainer{}, nil - } - return []localConfigProvider.LocalContainer{ - { - Name: lc.GetName(), - }, - }, nil -} - -// GetIgnore returns the Ignore, returns default if nil -func (lc *LocalConfig) GetIgnore() bool { - return util.GetBoolOrDefault(lc.componentSettings.Ignore, false) -} - -// GetMinCPU returns the MinCPU, returns default if nil -func (lc *LocalConfig) GetMinCPU() string { - return util.GetStringOrEmpty(lc.componentSettings.MinCPU) -} - -// GetMaxCPU returns the MaxCPU, returns default if nil -func (lc *LocalConfig) GetMaxCPU() string { - return util.GetStringOrEmpty(lc.componentSettings.MaxCPU) -} - -// GetEnvs returns the Envs, returns empty if nil -func (lc *LocalConfig) GetEnvs() EnvVarList { - if lc.componentSettings.Envs == nil { - return EnvVarList{} - } - return lc.componentSettings.Envs -} - -const ( - // Type is the name of the setting controlling the component type i.e. builder image - Type = "Type" - // TypeDescription is human-readable description of the Type setting - TypeDescription = "The type of component" // Name is the name of the setting controlling the component name Name = "Name" // NameDescription is human-readable description of the Name setting NameDescription = "The name of the component" - // MinMemory is the name of the setting controlling the min memory a component consumes - MinMemory = "MinMemory" - // MinMemoryDescription is the description of the setting controlling the minimum memory - MinMemoryDescription = "The minimum memory a component is provided" - // MaxMemory is the name of the setting controlling the min memory a component consumes - MaxMemory = "MaxMemory" - // MaxMemoryDescription is the description of the setting controlling the maximum memory - MaxMemoryDescription = "The maximum memory a component can consume" // Memory is the name of the setting controlling the memory a component consumes Memory = "Memory" // MemoryDescription is the description of the setting controlling the min and max memory to same value MemoryDescription = "The minimum and maximum memory a component can consume" - // DebugPort is the port where the application is set to listen for debugger - DebugPort = "DebugPort" - // DebugPortDescription is the description for debug port - DebugPortDescription = "The port on which the debugger will listen on the component" - // Ignore is the name of the setting controlling the min memory a component consumes - Ignore = "Ignore" - // IgnoreDescription is the description of the setting controlling the use of .odoignore file - IgnoreDescription = "Consider the .odoignore file for push and watch" - // MinCPU is the name of the setting controlling minimum cpu - MinCPU = "MinCPU" - // MinCPUDescription is the description of the setting controlling the min CPU value - MinCPUDescription = "The minimum CPU a component can consume" - // MaxCPU is the name of the setting controlling the use of .odoignore file - MaxCPU = "MaxCPU" - //MaxCPUDescription is the description of the setting controlling the max CPU value - MaxCPUDescription = "The maximum CPU a component can consume" - // CPU is the name of the setting controlling the cpu a component consumes - CPU = "CPU" - // CPUDescription is the description of the setting controlling the min and max CPU to same value - CPUDescription = "The minimum and maximum CPU a component can consume" - // SourceLocation indicates path of the source e.g. location of the git repo - SourceLocation = "SourceLocation" - // SourceType indicates type of component source -- git/binary/local - SourceType = "SourceType" - // Ref indicates git ref for the component source - Ref = "Ref" // Ports is the space separated list of user specified ports to be opened in the component Ports = "Ports" - // Application indicates application of which component is part of - Application = "Application" - // Project indicates project the component is part of - Project = "Project" - // ProjectDescription is the description of project component setting - ProjectDescription = "Project is the name of the project the component is part of" - // ApplicationDescription is the description of app component setting - ApplicationDescription = "Application is the name of application the component needs to be part of" // PortsDescription is the description of the ports component setting PortsDescription = "Ports to be opened in the component" - // RefDescription is the description of ref setting - RefDescription = "Git ref to use for creating component from git source" - // SourceTypeDescription is the description of type setting - SourceTypeDescription = "Type of component source - git/binary/local" - // Storage is the name of the setting controlling storage - Storage = "Storage" - // StorageDescription is the description of the storage - StorageDescription = "Storage of the component" - // SourceLocationDescription is the human-readable description of path setting - SourceLocationDescription = "The path indicates the location of binary file or git source" - // URL - URL = "URL" - // URLDescription is the description of URL - URLDescription = "URL to access the component" -) - -var ( - supportedLocalParameterDescriptions = map[string]string{ - Type: TypeDescription, - Name: NameDescription, - Application: ApplicationDescription, - Project: ProjectDescription, - SourceLocation: SourceLocationDescription, - SourceType: SourceTypeDescription, - Ref: RefDescription, - Ports: PortsDescription, - MinMemory: MinMemoryDescription, - MaxMemory: MaxMemoryDescription, - Memory: MemoryDescription, - DebugPort: DebugPortDescription, - Ignore: IgnoreDescription, - MinCPU: MinCPUDescription, - MaxCPU: MaxCPUDescription, - Storage: StorageDescription, - CPU: CPUDescription, - URL: URLDescription, - } - - lowerCaseLocalParameters = util.GetLowerCaseParameters(GetLocallySupportedParameters()) ) var ( @@ -592,14 +33,6 @@ var ( lowerCaseDevfileParameters = util.GetLowerCaseParameters(GetDevfileSupportedParameters()) ) -// FormatLocallySupportedParameters outputs supported parameters and their description -func FormatLocallySupportedParameters() (result string) { - for _, v := range GetLocallySupportedParameters() { - result = result + " " + v + " - " + supportedLocalParameterDescriptions[v] + "\n" - } - return "\nAvailable Parameters for s2i Components:\n" + result -} - // FormatDevfileSupportedParameters outputs supported parameters and their description func FormatDevfileSupportedParameters() (result string) { for _, v := range GetDevfileSupportedParameters() { @@ -612,17 +45,6 @@ func GetDevfileSupportedParameters() []string { return util.GetSortedKeys(supportedDevfileParameterDescriptions) } -// AsLocallySupportedParameter returns the parameter in lower case and a boolean indicating if it is a supported parameter -func AsLocallySupportedParameter(param string) (string, bool) { - lower := strings.ToLower(param) - return lower, lowerCaseLocalParameters[lower] -} - -// GetLocallySupportedParameters returns the name of the supported global parameters -func GetLocallySupportedParameters() []string { - return util.GetSortedKeys(supportedLocalParameterDescriptions) -} - // AsDevfileSupportedParameter returns the parameter in lower case and a boolean indicating if it is a supported parameter func AsDevfileSupportedParameter(param string) (string, bool) { lower := strings.ToLower(param) @@ -683,71 +105,3 @@ func IsSetInDevfile(d parser.DevfileObj, parameter string) bool { return false } - -// SrcType is an enum to indicate the type of source of component -- local source/binary or git for the generation of app/component names -type SrcType string - -const ( - // GIT as source of component - GIT SrcType = "git" - // LOCAL Local source path as source of component - LOCAL SrcType = "local" - // BINARY Local Binary as source of component - BINARY SrcType = "binary" - // NONE indicates there's no information about the type of source of the component - NONE SrcType = "" -) - -// GetSrcType returns enum equivalent of passed component source type or error if unsupported type passed -func GetSrcType(ctStr string) (SrcType, error) { - switch strings.ToLower(ctStr) { - case string(GIT): - return GIT, nil - case string(LOCAL): - return LOCAL, nil - case string(BINARY): - return BINARY, nil - default: - return NONE, fmt.Errorf("Unsupported component source type: %s", ctStr) - } -} - -// GetOSSourcePath corrects the current sourcePath depending on local or binary configuration, -// if Git has been passed, we simply return the source location from LocalConfig -// this will get the correct source path whether on Windows, macOS or Linux. -// -// This function also takes in the current working directory + context directory in order -// to correctly retrieve WHERE the source is located.. -func (lci *LocalConfigInfo) GetOSSourcePath() (path string, err error) { - - sourceType := lci.GetSourceType() - sourceLocation := lci.GetSourceLocation() - - // Get the component context folder - // ".odo" is removed as lci.Filename will always return the '.odo' folder.. we don't need that! - componentContext := strings.TrimSuffix(filepath.Dir(lci.Filename), ".odo") - - if sourceLocation == "" { - return "", fmt.Errorf("Blank source location, does the .odo directory exist?") - } - - if sourceType == GIT { - klog.V(4).Info("Git source type detected, not correcting SourcePath location") - return sourceLocation, nil - } - - // Validation check if the user passes in a URL despite us being LOCAL or BINARY - u, err := url.Parse(sourceLocation) - if err != nil || (u.Scheme == "https" || u.Scheme == "http") { - return "", fmt.Errorf("URL %s passed even though source type is: %s", sourceLocation, sourceType) - } - - // Always piped to "fromslash" so it's correct for the OS.. - // after retrieving the sourceLocation we will covert it to the - // correct source path depending on the OS. - absPath, err := util.GetAbsPath(filepath.Join(componentContext, lci.GetSourceLocation())) - - sourceOSPath := filepath.FromSlash(absPath) - - return sourceOSPath, err -} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b1432cd4d56..125707a3837 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,10 +1,6 @@ package config import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" "reflect" "testing" @@ -16,496 +12,8 @@ import ( devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/kylelemons/godebug/pretty" odoTestingUtil "github.com/openshift/odo/pkg/testingutil" - "github.com/openshift/odo/pkg/testingutil/filesystem" ) -func TestSetLocalConfiguration(t *testing.T) { - - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - minCPUValue := "0.5" - maxCPUValue := "2" - minMemValue := "500M" - maxMemValue := "1000M" - testValue := "test" - portsValue := "8080/TCP,45/UDP" - typeValue := "nodejs" - applicationValue := "odotestapp" - projectValue := "odotestproject" - sourceTypeValue := "git" - sourceLocationValue := "https://github.com/sclorg/nodejs-ex" - refValue := "develop" - - tests := []struct { - name string - parameter string - value string - existingConfig LocalConfig - }{ - // update notification - { - name: fmt.Sprintf("Case 1: %s set nil to true", Ignore), - parameter: Ignore, - value: "true", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 2: %s set true to false", Ignore), - parameter: Ignore, - value: "false", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 3: %s to test", Name), - parameter: Name, - value: testValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 5: %s set to %s from 0", MaxCPU, maxCPUValue), - parameter: MaxCPU, - value: maxCPUValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 6: %s set to %s", MinCPU, minCPUValue), - parameter: MinCPU, - value: minCPUValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 6: %s set to %s", MinMemory, minMemValue), - parameter: MinMemory, - value: minMemValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 7: %s set to %s", MaxMemory, maxCPUValue), - parameter: MaxMemory, - value: maxMemValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 8: %s set to %s", Ports, portsValue), - parameter: Ports, - value: portsValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 9: %s set to %s", Type, typeValue), - parameter: Type, - value: typeValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 10: %s set to %s", Application, applicationValue), - parameter: Application, - value: applicationValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 11: %s set to %s", Project, projectValue), - parameter: Project, - value: projectValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 12: %s set to %s", SourceType, sourceTypeValue), - parameter: SourceType, - value: sourceTypeValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 12: %s set to %s", SourceLocation, sourceLocationValue), - parameter: SourceLocation, - value: sourceLocationValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: fmt.Sprintf("Case 13: %s set to %s", Ref, refValue), - parameter: Ref, - value: refValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.SetConfiguration(tt.parameter, tt.value) - if err != nil { - t.Error(err) - } - - isSet := cfg.IsSet(tt.parameter) - - if !isSet { - t.Errorf("the '%v' is not set", tt.parameter) - } - - }) - } -} - -func TestLocalUnsetConfiguration(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - trueValue := true - minCPUValue := "0.5" - maxCPUValue := "2" - minMemValue := "500M" - testValue := "test" - - tests := []struct { - name string - parameter string - value string - existingConfig LocalConfig - }{ - // update notification - { - name: fmt.Sprintf("Case 1: unset %s", Ignore), - parameter: Ignore, - value: "true", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Ignore: &trueValue, - }, - }, - }, - { - name: fmt.Sprintf("Case 3: unset %s", Name), - parameter: Name, - value: testValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Name: &testValue, - }, - }, - }, - { - name: fmt.Sprintf("Case 5: unset %s", MaxCPU), - parameter: MaxCPU, - value: maxCPUValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - MaxCPU: &maxCPUValue, - }, - }, - }, - { - name: fmt.Sprintf("Case 6: unset %s", MinCPU), - parameter: MinCPU, - value: minCPUValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - MinCPU: &minCPUValue, - }, - }, - }, - { - name: fmt.Sprintf("Case 6: unset %s", MinMemory), - parameter: MinMemory, - value: minMemValue, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - MinMemory: &minMemValue, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.SetConfiguration(tt.parameter, tt.value) - if err != nil { - t.Error(err) - } - isSet := cfg.IsSet(tt.parameter) - if !isSet { - t.Errorf("the '%v' was not set", tt.parameter) - } - - err = cfg.DeleteConfiguration(tt.parameter) - - if err != nil { - t.Error(err) - } - isSet = cfg.IsSet(tt.parameter) - if isSet { - t.Errorf("the '%v' is not set to nil", tt.parameter) - } - - }) - } -} - -func TestLocalConfigInitDoesntCreateLocalOdoFolder(t *testing.T) { - // cleaning up old odo files if any - filename, err := getLocalConfigFile("") - if err != nil { - t.Error(err) - } - os.RemoveAll(filename) - - conf, err := NewLocalConfigInfo("") - if err != nil { - t.Errorf("error while creating local config %v", err) - } - if _, err = os.Stat(conf.Filename); !os.IsNotExist(err) { - t.Errorf("local config.yaml shouldn't exist yet") - } -} - -func TestMetaTypePopulatedInLocalConfig(t *testing.T) { - ci, err := NewLocalConfigInfo("") - - if err != nil { - t.Error(err) - } - if ci.typeMeta.APIVersion != localConfigAPIVersion || ci.typeMeta.Kind != localConfigKind { - t.Error("the api version and kind in local config are incorrect") - } -} - -// TODO: Write Windows tests for below -func TestGetOSSourcePath(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - binarySourceType := BINARY - localSourceType := LOCAL - gitSourceType := GIT - - tests := []struct { - name string - parameter string - value string - wantErr bool - existingConfig LocalConfig - }{ - { - name: "Case 1: Valid location (even though it shows c:/)", - parameter: SourceLocation, - value: "file://c:/foo/bar", - wantErr: false, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &binarySourceType, - }, - }, - }, - { - name: "Case 2: Error if passing in blank", - parameter: SourceLocation, - value: "", - wantErr: true, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &localSourceType, - }, - }, - }, - { - name: "Case 3: Error if we're passing in git source type...", - parameter: SourceLocation, - value: "", - wantErr: true, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &gitSourceType, - }, - }, - }, - { - name: "Case 4: Error if passing in just a url but using local", - parameter: SourceLocation, - value: "https://redhat.com", - wantErr: true, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &localSourceType, - }, - }, - }, - { - name: "Case 5: Valid path", - parameter: SourceLocation, - value: "/var/foo/bar", - wantErr: false, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &localSourceType, - }, - }, - }, - { - name: "Case 6: Error if URL escapes were passed in..", - parameter: SourceLocation, - value: "%a", - wantErr: true, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &localSourceType, - }, - }, - }, - { - name: "Case 7: Valid binary path", - parameter: SourceLocation, - value: "/var/foo/bar", - wantErr: false, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - SourceType: &binarySourceType, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.SetConfiguration(tt.parameter, tt.value) - if err != nil { - t.Error(err) - } - isSet := cfg.IsSet(tt.parameter) - if !isSet { - t.Errorf("the '%v' was not set", tt.parameter) - } - - _, err = cfg.GetOSSourcePath() - if tt.wantErr && err == nil { - t.Errorf("expected error for %s source path", tt.value) - } else if !tt.wantErr && err != nil { - t.Error(err) - } - - }) - } -} - -func TestDeleteConfigDirIfEmpty(t *testing.T) { - // create a fake fs in memory - fs := filesystem.NewFakeFs() - // create a odo config directory on fake fs - configDir, err := fs.TempDir(os.TempDir(), "odo") - if err != nil { - t.Error(err) - } - // create a mock local configuration from above fake fs & dir - lci, err := mockLocalConfigInfo(configDir, fs) - if err != nil { - t.Error(err) - } - - odoDir := filepath.Join(configDir, ".odo") - if _, err = fs.Stat(odoDir); os.IsNotExist(err) { - t.Error("config directory doesn't exist") - } - - tests := []struct { - name string - // create indicates if a file is supposed to be created in the odo config dir - create bool - setupEnv func(create bool, fs filesystem.Filesystem, odoDir string) error - wantOdoDir bool - wantErr bool - }{ - { - name: "Case 1: Empty config dir", - create: false, - setupEnv: createDirectoryAndFile, - wantOdoDir: false, - }, - { - name: "Case 2: Config dir with test file", - create: true, - setupEnv: createDirectoryAndFile, - wantOdoDir: true, - }, - } - - for _, tt := range tests { - - err := tt.setupEnv(tt.create, fs, odoDir) - if err != nil { - t.Error(err) - } - - err = lci.DeleteConfigDirIfEmpty() - if err != nil { - t.Error(err) - } - - file, err := fs.Stat(odoDir) - if !tt.wantOdoDir && !os.IsNotExist(err) { - // we don't want odo dir but odo dir exists - fmt.Println(file.Size()) - t.Error("odo config directory exists even after deleting it") - t.Errorf("Error in test %q", tt.name) - } else if tt.wantOdoDir && os.IsNotExist(err) { - // we want odo dir to exist after odo delete --all but it does not exist - t.Error("wanted odo directory to exist after odo delete --all") - t.Errorf("Error in test %q", tt.name) - } - } -} - func TestSetDevfileConfiguration(t *testing.T) { // Use fakeFs @@ -699,25 +207,3 @@ func TestSetDevfileConfiguration(t *testing.T) { } } - -func createDirectoryAndFile(create bool, fs filesystem.Filesystem, odoDir string) error { - if !create { - return nil - } - - file, err := fs.Create(filepath.Join(odoDir, "testfile")) - if err != nil { - return err - } - - _, err = file.Write([]byte("hello world")) - if err != nil { - return err - } - - file.Close() - if err != nil { - return err - } - return nil -} diff --git a/pkg/config/fakeConfig.go b/pkg/config/fakeConfig.go deleted file mode 100644 index 78941c95755..00000000000 --- a/pkg/config/fakeConfig.go +++ /dev/null @@ -1,122 +0,0 @@ -package config - -import ( - "os" - "path/filepath" - - "github.com/openshift/odo/pkg/localConfigProvider" - "github.com/openshift/odo/pkg/testingutil/filesystem" -) - -func GetOneExistingConfigInfo(componentName, applicationName, projectName string) LocalConfigInfo { - componentType := "nodejs" - sourceLocation := "./" - - storageValue := []localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - }, - { - Name: "example-storage-1", - }, - } - - portsValue := []string{"8080/TCP", "45/UDP"} - - urlValue := []localConfigProvider.LocalURL{ - { - Name: "example-url-0", - Port: 8080, - }, - { - Name: "example-url-1", - Port: 45, - }, - } - - envVars := EnvVarList{ - EnvVar{Name: "env-0", Value: "value-0"}, - EnvVar{Name: "env-1", Value: "value-1"}, - } - - localVar := LOCAL - - return LocalConfigInfo{ - configFileExists: true, - LocalConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Name: &componentName, - Application: &applicationName, - Type: &componentType, - SourceLocation: &sourceLocation, - Storage: &storageValue, - Envs: envVars, - Ports: &portsValue, - URL: &urlValue, - Project: &projectName, - SourceType: &localVar, - }, - }, - } -} - -func GetOneExistingConfigInfoStorage(componentName, applicationName, projectName, storeName, storeSize, storePath string) LocalConfigInfo { - componentType := "nodejs" - sourceLocation := "./" - - storageValue := []localConfigProvider.LocalStorage{ - { - Name: storeName, - Size: storeSize, - Path: storePath, - }, - } - - localVar := LOCAL - - return LocalConfigInfo{ - configFileExists: true, - LocalConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Name: &componentName, - Application: &applicationName, - Type: &componentType, - SourceLocation: &sourceLocation, - Storage: &storageValue, - Project: &projectName, - SourceType: &localVar, - }, - }, - } -} - -func GetOneGitExistingConfigInfo(componentName, applicationName, projectName string) LocalConfigInfo { - localConfigInfo := GetOneExistingConfigInfo(componentName, applicationName, projectName) - git := GIT - location := "https://example.com" - localConfigInfo.LocalConfig.componentSettings.SourceType = &git - localConfigInfo.LocalConfig.componentSettings.SourceLocation = &location - return localConfigInfo -} - -func GetOneNonExistingConfigInfo() LocalConfigInfo { - return LocalConfigInfo{ - configFileExists: false, - LocalConfig: LocalConfig{}, - } -} - -func mockLocalConfigInfo(configDir string, fs filesystem.Filesystem) (*LocalConfigInfo, error) { - - lci := &LocalConfigInfo{ - Filename: filepath.Join(configDir, ".odo", "config.yaml"), - fs: fs, - } - err := fs.MkdirAll(filepath.Join(configDir, ".odo"), os.ModePerm) - if err != nil { - return nil, err - } - - return lci, nil - -} diff --git a/pkg/config/storage.go b/pkg/config/storage.go deleted file mode 100644 index 165f2b3a483..00000000000 --- a/pkg/config/storage.go +++ /dev/null @@ -1,100 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/openshift/odo/pkg/localConfigProvider" - "github.com/pkg/errors" -) - -// GetStorage gets the storage with the given name -func (lci *LocalConfigInfo) GetStorage(storageName string) (*localConfigProvider.LocalStorage, error) { - configStorage, err := lci.ListStorage() - if err != nil { - return nil, err - } - for _, storage := range configStorage { - if storageName == storage.Name { - return &storage, nil - } - } - return nil, nil -} - -// CreateStorage sets the storage related information in the local configuration -func (lci *LocalConfigInfo) CreateStorage(storage localConfigProvider.LocalStorage) error { - err := lci.SetConfiguration("storage", storage) - if err != nil { - return err - } - return err -} - -// ListStorage gets all the storage from the config -func (lci *LocalConfigInfo) ListStorage() ([]localConfigProvider.LocalStorage, error) { - if lci.componentSettings.Storage == nil { - return []localConfigProvider.LocalStorage{}, nil - } - - var storageList []localConfigProvider.LocalStorage - for _, storage := range *lci.componentSettings.Storage { - storageList = append(storageList, localConfigProvider.LocalStorage{ - Name: storage.Name, - Path: storage.Path, - Size: storage.Size, - }) - } - return storageList, nil -} - -// DeleteStorage deletes the storage with the given name -func (lci *LocalConfigInfo) DeleteStorage(name string) error { - storage, err := lci.GetStorage(name) - if err != nil { - return err - } - if storage == nil { - return errors.Errorf("storage named %s doesn't exists", name) - } - return lci.DeleteFromConfigurationList("storage", name) -} - -// CompleteStorage completes the given storage -func (lci *LocalConfigInfo) CompleteStorage(storage *localConfigProvider.LocalStorage) {} - -// ValidateStorage validates the given storage -func (lci *LocalConfigInfo) ValidateStorage(storage localConfigProvider.LocalStorage) error { - if storage.Size == "" || storage.Path == "" { - return fmt.Errorf("\"size\" and \"path\" flags are required for s2i components") - } - - configStorage, err := lci.ListStorage() - if err != nil { - return err - } - for _, store := range configStorage { - if store.Name == storage.Name { - return errors.Errorf("there already is a storage with the name %s", storage.Name) - } - if store.Path == storage.Path { - return errors.Errorf("there already is a storage mounted at %s", storage.Path) - } - } - return nil -} - -// GetStorageMountPath gets the mount path of the storage with the given storage name -func (lci *LocalConfigInfo) GetStorageMountPath(storageName string) (string, error) { - configStorage, err := lci.ListStorage() - if err != nil { - return "", err - } - - var mPath string - for _, storage := range configStorage { - if storage.Name == storageName { - mPath = storage.Path - } - } - return mPath, nil -} diff --git a/pkg/config/storage_test.go b/pkg/config/storage_test.go deleted file mode 100644 index b679bd843db..00000000000 --- a/pkg/config/storage_test.go +++ /dev/null @@ -1,585 +0,0 @@ -package config - -import ( - "io/ioutil" - "os" - "reflect" - "testing" - - "github.com/openshift/odo/pkg/localConfigProvider" -) - -func TestLocalConfigInfo_StorageCreate(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - tests := []struct { - name string - storageName string - storageSize string - storagePath string - existingConfig LocalConfig - }{ - { - name: "case 1: no other storage present", - storageName: "example-storage-0", - storageSize: "100M", - storagePath: "/data", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{}, - }, - }, - { - name: "case 2: one other storage present", - storageName: "example-storage-1", - storageSize: "100M", - storagePath: "/data-1", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data", - Size: "100M", - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.CreateStorage(localConfigProvider.LocalStorage{ - Name: tt.storageName, - Size: tt.storageSize, - Path: tt.storagePath, - }) - - if err != nil { - t.Error(err) - } - - found := false - for _, storage := range *cfg.componentSettings.Storage { - if storage.Name == tt.storageName && storage.Size == tt.storageSize && storage.Path == tt.storagePath { - found = true - } - } - if !found { - t.Errorf("the storage '%v' is not set properly in the config", tt) - } - }) - } -} - -func TestLocalConfigInfo_StorageExists(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - tests := []struct { - name string - storageName string - existingConfig LocalConfig - want localConfigProvider.LocalStorage - wantErr bool - }{ - { - name: "case 1: storage present", - storageName: "example-storage-1", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-1", - Size: "5Gi", - Path: "/data", - }, - }, - }, - }, - want: localConfigProvider.LocalStorage{ - Name: "example-storage-1", - }, - }, - { - name: "case 2: storage present", - storageName: "example-storage-1", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - }, - }, - }, - }, - want: localConfigProvider.LocalStorage{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - gotStorage, err := cfg.GetStorage(tt.storageName) - if (err != nil) != tt.wantErr { - t.Errorf("GetStorage() error = %v, wantErr %v", err, tt.wantErr) - } - - if reflect.DeepEqual(gotStorage, tt.want) { - t.Errorf("wrong value of exists, expected: %v, unexpected: %v", tt.want, gotStorage) - } - }) - } -} - -func TestLocalConfigInfo_StorageList(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - tests := []struct { - name string - existingConfig LocalConfig - wantErr bool - }{ - { - name: "case 1: one storage exists", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data-0", - Size: "100M", - }, - }, - }, - }, - }, - { - name: "case 2: more than one storage exists", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data-0", - Size: "100M", - }, - { - Name: "example-storage-1", - Path: "/data-1", - Size: "100M", - }, - }, - }, - }, - }, - { - name: "case 3: no storage exists", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - storageList, err := cfg.ListStorage() - if (err != nil) != tt.wantErr { - t.Errorf("ListStorage() error = %v, wantErr %v", err, tt.wantErr) - } - - if len(*tt.existingConfig.componentSettings.Storage) != len(storageList) { - t.Errorf("length mismatch, expected: %v, unexpected: %v", len(*tt.existingConfig.componentSettings.Storage), len(storageList)) - } - - for _, storageConfig := range *tt.existingConfig.componentSettings.Storage { - found := false - - for _, storageResult := range storageList { - if reflect.DeepEqual(storageResult, storageConfig) { - found = true - } - } - - if !found { - t.Errorf("storage %v not found while listing", storageConfig) - } - } - }) - } -} - -func TestLocalConfigInfo_ValidateStorage(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - type args struct { - storage localConfigProvider.LocalStorage - } - - tests := []struct { - name string - args args - existingConfig LocalConfig - wantError bool - }{ - { - name: "case 1: no storage present in config", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Size: "1Gi", - Path: "/data", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - wantError: false, - }, - { - name: "case 2: storage present in config with no conflict", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Path: "/data", - Size: "5Gi", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-1", - Path: "/data-1", - Size: "100M", - }, - }, - }, - }, - wantError: false, - }, - { - name: "case 3: storage present in config and with path conflict", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Path: "/data", - Size: "1Gi", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-1", - Path: "/data", - Size: "100M", - }, - }, - }, - }, - wantError: true, - }, - { - name: "case 4: storage present in config and with name conflict", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Path: "/data", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data-1", - Size: "100M", - }, - }, - }, - }, - wantError: true, - }, - { - name: "case 5: storage present in config and with name and path conflicts", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Path: "/data", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data", - Size: "100M", - }, - }, - }, - }, - wantError: true, - }, - { - name: "case 6: No size provided in the storage", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Path: "/data", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - wantError: true, - }, - { - name: "case 7: No path provided in the storage", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - Size: "1Gi", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - wantError: true, - }, - { - name: "case 8: No path and size provided in the storage", - args: args{ - storage: localConfigProvider.LocalStorage{ - Name: "example-storage-0", - }, - }, - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.ValidateStorage(tt.args.storage) - - if !tt.wantError && err != nil { - t.Errorf("no error expected,but got error: %v", err) - } - - if tt.wantError && err == nil { - t.Errorf("error expected,but got no error") - } - }) - } -} - -func TestLocalConfigInfo_StorageDelete(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - tests := []struct { - name string - storageName string - existingConfig LocalConfig - wantError bool - }{ - { - name: "case 1: storage does exist", - storageName: "example-storage-0", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - }, - }, - }, - }, - wantError: false, - }, - { - name: "case 2: storage doesn't exist", - storageName: "example-storage-0", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-1", - }, - }, - }, - }, - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - err = cfg.DeleteStorage(tt.storageName) - - if !tt.wantError && err != nil { - t.Errorf("no error expected,but got error: %v", err) - } - - if tt.wantError && err == nil { - t.Errorf("error expected,but got no error") - } - - found := false - for _, storage := range *cfg.componentSettings.Storage { - if storage.Name == tt.storageName { - found = true - } - } - if found { - t.Errorf("the storage '%v' is not deleted properly from the config", tt.storageName) - } - }) - } -} - -func TestLocalConfigInfo_GetStorageMountPath(t *testing.T) { - tempConfigFile, err := ioutil.TempFile("", "odoconfig") - if err != nil { - t.Fatal(err) - } - defer tempConfigFile.Close() - os.Setenv(localConfigEnvName, tempConfigFile.Name()) - - tests := []struct { - name string - storageName string - existingConfig LocalConfig - wantPath string - }{ - { - name: "case 1: no storage exists", - storageName: "example-storage-0", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{}, - }, - }, - wantPath: "", - }, - { - name: "case 2: storage exists and one storage exists in config", - storageName: "example-storage-0", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data", - }, - }, - }, - }, - wantPath: "/data", - }, - { - name: "case 3: storage exists and two storage exists in config", - storageName: "example-storage-1", - existingConfig: LocalConfig{ - componentSettings: ComponentSettings{ - Storage: &[]localConfigProvider.LocalStorage{ - { - Name: "example-storage-0", - Path: "/data", - }, - { - Name: "example-storage-1", - Path: "/data-1", - }, - }, - }, - }, - wantPath: "/data-1", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg, err := NewLocalConfigInfo("") - if err != nil { - t.Error(err) - } - cfg.LocalConfig = tt.existingConfig - - path, err := cfg.GetStorageMountPath(tt.storageName) - if err != nil { - t.Error(err) - } - - if path != tt.wantPath { - t.Errorf("the value of returned path is different, expected: %v, got: %v", tt.wantPath, path) - } - }) - } -} diff --git a/pkg/config/url.go b/pkg/config/url.go deleted file mode 100644 index 308f2cfef22..00000000000 --- a/pkg/config/url.go +++ /dev/null @@ -1,116 +0,0 @@ -package config - -import ( - "fmt" - "strings" - - "github.com/openshift/odo/pkg/localConfigProvider" - "github.com/openshift/odo/pkg/util" -) - -// GetPorts returns the ports stored in the config for the component -// returns default i.e nil if nil -func (lc *LocalConfig) GetComponentPorts() ([]string, error) { - if lc.componentSettings.Ports == nil { - return nil, nil - } - return *lc.componentSettings.Ports, nil -} - -func (lc *LocalConfig) GetContainerPorts(container string) ([]string, error) { - return nil, fmt.Errorf("getting specific container ports not implemented for local config") -} - -// CompleteURL completes the given URL with default values -func (lc *LocalConfig) CompleteURL(url *localConfigProvider.LocalURL) error { - var err error - - url.Kind = localConfigProvider.ROUTE - - ports, err := lc.GetComponentPorts() - if err != nil { - return err - } - url.Port, err = util.GetValidPortNumber(lc.GetName(), url.Port, ports) - if err != nil { - return err - } - - // get the name - if len(url.Name) == 0 { - url.Name = util.GetURLName(lc.GetName(), url.Port) - } - - return nil -} - -// ValidateURL validates the given URL -func (lc *LocalConfig) ValidateURL(url localConfigProvider.LocalURL) error { - errorList := make([]string, 0) - - urls, err := lc.ListURLs() - if err != nil { - return err - } - for _, localURL := range urls { - if url.Name == localURL.Name { - errorList = append(errorList, fmt.Sprintf("URL %s already exists in application: %s", url.Name, lc.GetApplication())) - } - } - - if len(errorList) > 0 { - return fmt.Errorf(strings.Join(errorList, "\n")) - } - - return nil -} - -// GetURL gets the given url localConfig -func (lc *LocalConfig) GetURL(name string) (*localConfigProvider.LocalURL, error) { - urls, err := lc.ListURLs() - if err != nil { - return nil, err - } - for _, url := range urls { - if name == url.Name { - return &url, nil - } - } - return nil, nil -} - -// CreateURL writes the given url to the localConfig -func (lci *LocalConfigInfo) CreateURL(url localConfigProvider.LocalURL) error { - return lci.SetConfiguration("url", localConfigProvider.LocalURL{Name: url.Name, Port: url.Port, Secure: url.Secure}) -} - -// ListURLs returns the ConfigURL, returns default if nil -func (lc *LocalConfig) ListURLs() ([]localConfigProvider.LocalURL, error) { - if lc.componentSettings.URL == nil { - return []localConfigProvider.LocalURL{}, nil - } - var resultURLs []localConfigProvider.LocalURL - for _, url := range *lc.componentSettings.URL { - resultURLs = append(resultURLs, localConfigProvider.LocalURL{ - Name: url.Name, - Port: url.Port, - Secure: url.Secure, - Host: url.Host, - Path: "/", - Kind: localConfigProvider.ROUTE, - }) - } - return resultURLs, nil -} - -// DeleteURL is used to delete config from local odo config -func (lci *LocalConfigInfo) DeleteURL(parameter string) error { - for i, url := range *lci.componentSettings.URL { - if url.Name == parameter { - s := *lci.componentSettings.URL - s = append(s[:i], s[i+1:]...) - lci.componentSettings.URL = &s - } - } - return lci.writeToFile() -} diff --git a/pkg/debug/portforward.go b/pkg/debug/portforward.go index 82978965ebe..a9a317f0e24 100644 --- a/pkg/debug/portforward.go +++ b/pkg/debug/portforward.go @@ -61,11 +61,6 @@ func (f *DefaultPortForwarder) ForwardPorts(portPair string, stopChan, readyChan if err != nil { return err } - - pod, err = f.client.GetPodUsingDeploymentConfig(f.componentName, f.appName) - if err != nil { - return err - } } if pod.Status.Phase != corev1.PodRunning { diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 3d64e114152..60941820af0 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -18,14 +18,12 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/fatih/color" "github.com/pkg/errors" "k8s.io/klog" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/devfile/adapters/kubernetes/storage" "github.com/openshift/odo/pkg/devfile/adapters/kubernetes/utils" @@ -262,7 +260,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { } parameters.EnvSpecificInfo.SetDevfileObj(a.Devfile) - err = component.ApplyConfig(&a.Client, config.LocalConfigInfo{}, parameters.EnvSpecificInfo, color.Output, componentExists, false) + err = component.ApplyConfig(&a.Client, parameters.EnvSpecificInfo) if err != nil { return errors.Wrapf(err, "Failed to update config to component deployed.") } diff --git a/pkg/devfile/convert/convert.go b/pkg/devfile/convert/convert.go deleted file mode 100644 index 8f949123588..00000000000 --- a/pkg/devfile/convert/convert.go +++ /dev/null @@ -1,477 +0,0 @@ -package convert - -import ( - "fmt" - "path/filepath" - "strconv" - "strings" - - "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/devfile/parser/data" - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/devfile/adapters/common" - "github.com/openshift/odo/pkg/envinfo" - "github.com/openshift/odo/pkg/occlient" - "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" - "k8s.io/klog" - - imagev1 "github.com/openshift/api/image/v1" - - devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - devfilepkg "github.com/devfile/api/v2/pkg/devfile" - devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" -) - -const ( - buildCommandID = "s2i-assemble" - // build command to be used in s2i devfile - buildCommandS2i = "/opt/odo/bin/s2i-setup && /opt/odo/bin/assemble-and-restart" - // run command id to be used in devfile - runCommandID = "s2i-run" - // run command to be used in s2i devfile - runCommandS2i = "/opt/odo/bin/run" - // debug command id to be used in devfile - debugCommandID = "s2i-debug" - // ContainerName is component name to be used in devfile - ContainerName = "s2i-builder" - // DefaultSourceMappingS2i is the directory to sync s2i source code - DefaultSourceMappingS2i = "/tmp/projects" - // devfile version - devfileVersion = "2.0.0" - // environment variable set for s2i assemble and restart scripts - // some change in script if scripts is executed for a s2i component converted to devfile - envS2iConvertedDevfile = "ODO_S2I_CONVERTED_DEVFILE" -) - -// GenerateDevfileYaml generates a devfile.yaml from s2i data. -func GenerateDevfileYaml(client *occlient.Client, co *config.LocalConfigInfo, context string) error { - klog.V(2).Info("Generating devfile.yaml") - - // builder image to use - componentType := co.GetType() - // git, local, binary, none - sourceType := co.GetSourceType() - - debugPort := co.GetDebugPort() - - imageStream, imageforDevfile, err := getImageforDevfile(client, componentType) - if err != nil { - return errors.Wrap(err, "Failed to get image details") - } - - envVarList := co.GetEnvVars() - - if debugPort != 0 || debugPort == config.DefaultDebugPort { - env := config.EnvVar{ - Name: common.EnvDebugPort, - Value: fmt.Sprint(debugPort), - } - envVarList = append(envVarList, env) - } - - s2iEnv, err := occlient.GetS2IEnvForDevfile(string(sourceType), envVarList, *imageStream) - if err != nil { - return err - } - - s2iDevfile, err := data.NewDevfileData(devfileVersion) - if err != nil { - return err - } - - // set schema version - s2iDevfile.SetSchemaVersion(devfileVersion) - - // set metadata - s2iDevfile.SetMetadata(devfilepkg.DevfileMetadata{ - Name: co.GetName(), - Version: "1.0.0", - Language: componentType, - ProjectType: componentType, - }) - // set components - err = setDevfileComponentsForS2I(s2iDevfile, imageforDevfile, co, s2iEnv) - if err != nil { - return err - } - // set commands - setDevfileCommandsForS2I(s2iDevfile) - - // set an init container to copy files from /opt/app-root - err = setInitContainer(s2iDevfile, imageforDevfile) - if err != nil { - return err - } - - ctx := devfileCtx.NewDevfileCtx(filepath.Join(context, "devfile.yaml")) - err = ctx.SetAbsPath() - if err != nil { - return err - } - - devObj := parser.DevfileObj{ - Ctx: ctx, - Data: s2iDevfile, - } - - err = devObj.WriteYamlDevfile() - klog.V(2).Info("Generated devfile.yaml successfully") - - if err != nil { - return err - } - - return nil -} - -// GenerateEnvYaml generates .odo/env.yaml from s2i data. -func GenerateEnvYaml(client *occlient.Client, co *config.LocalConfigInfo, context string) (*envinfo.EnvSpecificInfo, error) { - klog.V(2).Info("Generating env.yaml") - - debugPort := co.GetDebugPort() - - application := co.GetApplication() - - // Generate env.yaml - envSpecificInfo, err := envinfo.NewEnvSpecificInfo(context) - if err != nil { - return nil, err - } - - componentSettings := envinfo.ComponentSettings{ - Name: co.GetName(), - Project: co.GetProject(), - AppName: application, - } - - if debugPort != 0 || debugPort == config.DefaultDebugPort { - componentSettings.DebugPort = &debugPort - } - - err = envSpecificInfo.SetComponentSettings(componentSettings) - if err != nil { - return nil, err - } - klog.V(2).Info("Generated env.yaml successfully") - - return envSpecificInfo, nil -} - -// getImageforDevfile gets image details from s2i component type. -func getImageforDevfile(client *occlient.Client, componentType string) (*imagev1.ImageStreamImage, string, error) { - klog.V(2).Info("Getting container image details") - - imageNS, imageName, imageTag, _, err := occlient.ParseImageName(componentType) - if err != nil { - return nil, "", err - } - imageStream, err := client.GetImageStream(imageNS, imageName, imageTag) - if err != nil { - return nil, "", err - } - - imageStreamImage, err := client.GetImageStreamImage(imageStream, imageTag) - if err != nil { - return nil, "", err - } - - imageforDevfile := imageStream.Spec.Tags[0].From.Name - - return imageStreamImage, imageforDevfile, nil -} - -// setDevfileCommandsForS2I sets command in devfile.yaml from s2i data. -func setDevfileCommandsForS2I(d data.DevfileData) { - klog.V(2).Info("Set devfile commands from s2i data") - - buildCommand := devfilev1.Command{ - Id: buildCommandID, - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - Component: ContainerName, - CommandLine: buildCommandS2i, - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{ - Kind: devfilev1.BuildCommandGroupKind, - IsDefault: util.GetBoolPtr(true), - }, - }, - }, - }, - }, - } - - runCommand := devfilev1.Command{ - Id: runCommandID, - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - Component: ContainerName, - CommandLine: runCommandS2i, - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{ - Kind: devfilev1.RunCommandGroupKind, - IsDefault: util.GetBoolPtr(true), - }, - }, - }, - }, - }, - } - - debugCommand := devfilev1.Command{ - Id: debugCommandID, - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - Component: ContainerName, - CommandLine: runCommandS2i, - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{ - Kind: devfilev1.DebugCommandGroupKind, - IsDefault: util.GetBoolPtr(true), - }, - }, - }, - }, - }, - } - // Ignoring error as we are writing new file - _ = d.AddCommands([]devfilev1.Command{buildCommand, runCommand, debugCommand}) - -} - -func setInitContainer(d data.DevfileData, s2iImage string) error { - err := d.AddEvents(devfilev1.Events{ - DevWorkspaceEvents: devfilev1.DevWorkspaceEvents{ - PreStart: []string{"copy-app-root"}, - }, - }) - if err != nil { - return err - } - err = d.AddCommands([]devfilev1.Command{ - { - Id: "copy-app-root", - CommandUnion: devfilev1.CommandUnion{ - Apply: &devfilev1.ApplyCommand{ - Component: "copy-app-root-container", - }, - }, - }, - }) - if err != nil { - return err - } - initContainer := devfilev1.Component{ - Name: "copy-app-root-container", - ComponentUnion: devfilev1.ComponentUnion{ - Container: &devfilev1.ContainerComponent{ - Container: devfilev1.Container{ - Image: s2iImage, - VolumeMounts: []devfilev1.VolumeMount{ - { - Name: "app-root-volume", - Path: "/mnt/app-root", - }, - }, - Command: []string{ - "/bin/sh", - "-c", - "[ -d /opt/app-root ] && cp -R /opt/app-root /mnt/ || true", - }, - }, - }, - }, - } - return d.AddComponents([]devfilev1.Component{initContainer}) -} - -// setDevfileComponentsForS2I sets the devfile.yaml components field from s2i data. -func setDevfileComponentsForS2I(d data.DevfileData, s2iImage string, localConfig *config.LocalConfigInfo, s2iEnv config.EnvVarList) error { - klog.V(2).Info("Set devfile components from s2i data") - - maxMemory := localConfig.GetMaxMemory() - volumes, err := localConfig.ListStorage() - if err != nil { - return err - } - urls, err := localConfig.ListURLs() - if err != nil { - return err - } - mountSources := true - - var endpoints []devfilev1.Endpoint - var envs []devfilev1.EnvVar - var volumeMounts []devfilev1.VolumeMount - var components []devfilev1.Component - - // convert s2i storage to devfile volumes - for _, vol := range volumes { - volume := devfilev1.Component{ - Name: vol.Name, - ComponentUnion: devfilev1.ComponentUnion{ - Volume: &devfilev1.VolumeComponent{ - Volume: devfilev1.Volume{ - Size: vol.Size, - }, - }, - }, - } - components = append(components, volume) - - volumeMount := devfilev1.VolumeMount{ - Name: vol.Name, - Path: vol.Path, - } - - volumeMounts = append(volumeMounts, volumeMount) - } - - // Add volume for /opt/app-root - volumeAppRoot := devfilev1.Component{ - Name: "app-root-volume", - ComponentUnion: devfilev1.ComponentUnion{ - Volume: &devfilev1.VolumeComponent{ - Volume: devfilev1.Volume{ - Size: "1Gi", - }, - }, - }, - } - components = append(components, volumeAppRoot) - - volumeMountAppRoot := devfilev1.VolumeMount{ - Name: "app-root-volume", - Path: occlient.DefaultAppRootDir, - } - volumeMounts = append(volumeMounts, volumeMountAppRoot) - - // Check to see if ${ODO_S2I_DEPLOYMENT_DIR} directory is inside the /opt/app-root dir. - // If not, we need to have a second Volume - var deploymentDir string - for _, env := range s2iEnv { - if env.Name == "ODO_S2I_DEPLOYMENT_DIR" { - deploymentDir = env.Value - break - } - } - separateDeploymentsMount := len(deploymentDir) > 0 && !strings.HasPrefix(deploymentDir, occlient.DefaultAppRootDir) - - if separateDeploymentsMount { - volumeDeployments := devfilev1.Component{ - Name: "deployments-volume", - ComponentUnion: devfilev1.ComponentUnion{ - Volume: &devfilev1.VolumeComponent{ - Volume: devfilev1.Volume{ - Size: "1Gi", - }, - }, - }, - } - components = append(components, volumeDeployments) - volumeMountDeployments := devfilev1.VolumeMount{ - Name: "deployments-volume", - Path: deploymentDir, - } - volumeMounts = append(volumeMounts, volumeMountDeployments) - } - - sourceMapping := DefaultSourceMappingS2i - - // Add s2i specific env variable in devfile - for _, env := range s2iEnv { - env := devfilev1.EnvVar{ - Name: env.Name, - Value: env.Value, - } - envs = append(envs, env) - } - env := devfilev1.EnvVar{ - Name: envS2iConvertedDevfile, - Value: "true", - } - envs = append(envs, env) - - // convert s2i urls to devfile endpoints - for _, url := range urls { - - endpoint := devfilev1.Endpoint{ - Name: url.Name, - TargetPort: url.Port, - Secure: &url.Secure, - } - - endpoints = append(endpoints, endpoint) - } - - ports, err := localConfig.GetComponentPorts() - if err != nil { - return err - } - // convert s2i ports to devfile endpoints if there are no urls present - for _, port := range ports { - splitPort := strings.Split(port, "/") - protocol := "http" - // protocol provided - if len(splitPort) > 1 { - // technically tcp when exposed is by default using http - if strings.ToLower(splitPort[1]) == "tcp" { - protocol = "http" - } else { - protocol = strings.ToLower(splitPort[1]) - } - } - intPort, err := strconv.Atoi(splitPort[0]) - // we dont fail if the ports are malformed - if err != nil { - continue - } - hasURL := false - for _, url := range urls { - if intPort == url.Port { - hasURL = true - } - } - - if !hasURL { - // every port is an exposed url for now - endpoint := devfilev1.Endpoint{ - Name: fmt.Sprintf("%s-%d", protocol, intPort), - TargetPort: intPort, - Secure: util.GetBoolPtr(false), - } - - endpoints = append(endpoints, endpoint) - } - - } - - container := devfilev1.Component{ - Name: ContainerName, - ComponentUnion: devfilev1.ComponentUnion{ - Container: &devfilev1.ContainerComponent{ - Container: devfilev1.Container{ - Image: s2iImage, - MountSources: &mountSources, - SourceMapping: sourceMapping, - MemoryLimit: maxMemory, - Env: envs, - VolumeMounts: volumeMounts, - }, - Endpoints: endpoints, - }, - }, - } - - components = append(components, container) - - // Ignoring error here as we are writing a new file - _ = d.AddComponents(components) - - return nil - -} diff --git a/pkg/kclient/deployments.go b/pkg/kclient/deployments.go index f6bf3ee601e..24e8d27e506 100644 --- a/pkg/kclient/deployments.go +++ b/pkg/kclient/deployments.go @@ -434,7 +434,7 @@ func (c *Client) UnlinkSecret(secretName, componentName, applicationName string) return c.jsonPatchDeployment(componentlabels.GetSelector(componentName, applicationName), deploymentPatchProvider) } -// this function will look up the appropriate DC, and execute the specified patch +// jsonPatchDeployment will look up the appropriate Deployment, and execute the specified patch // the whole point of using patch is to avoid race conditions where we try to update // deployment while it's being simultaneously updated from another source (for example Kubernetes itself) // this will result in the triggering of a redeployment @@ -467,15 +467,15 @@ func (c *Client) jsonPatchDeployment(deploymentSelector string, deploymentPatchP // returns slice of unique label values func (c *Client) GetDeploymentLabelValues(label string, selector string) ([]string, error) { - // List DeploymentConfig according to selectors - dcList, err := c.appsClient.Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector}) + // List Deployment according to selectors + deploymentList, err := c.appsClient.Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector}) if err != nil { return nil, errors.Wrap(err, "unable to list DeploymentConfigs") } // Grab all the matched strings var values []string - for _, elem := range dcList.Items { + for _, elem := range deploymentList.Items { for key, val := range elem.Labels { if key == label { values = append(values, val) @@ -489,27 +489,6 @@ func (c *Client) GetDeploymentLabelValues(label string, selector string) ([]stri return values, nil } -// GetDeploymentConfigsFromSelector returns an array of Deployment Config -// resources which match the given selector -func (c *Client) GetDeploymentConfigsFromSelector(selector string) ([]appsv1.Deployment, error) { - var dcList *appsv1.DeploymentList - var err error - - if selector != "" { - dcList, err = c.appsClient.Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: selector, - }) - } else { - dcList, err = c.appsClient.Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{ - FieldSelector: fields.Set{"metadata.namespace": c.Namespace}.AsSelector().String(), - }) - } - if err != nil { - return nil, errors.Wrap(err, "unable to list DeploymentConfigs") - } - return dcList.Items, nil -} - // GetDeploymentAPIVersion returns a map with Group, Version, Resource information of Deployment objects // depending on the GVR supported by the cluster func (c *Client) GetDeploymentAPIVersion() (metav1.GroupVersionResource, error) { diff --git a/pkg/kclient/interface.go b/pkg/kclient/interface.go index 52e6913b7b5..61471ee9086 100644 --- a/pkg/kclient/interface.go +++ b/pkg/kclient/interface.go @@ -40,7 +40,6 @@ type ClientInterface interface { LinkSecret(secretName, componentName, applicationName string) error UnlinkSecret(secretName, componentName, applicationName string) error GetDeploymentLabelValues(label string, selector string) ([]string, error) - GetDeploymentConfigsFromSelector(selector string) ([]appsv1.Deployment, error) GetDeploymentAPIVersion() (metav1.GroupVersionResource, error) IsDeploymentExtensionsV1Beta1() (bool, error) diff --git a/pkg/occlient/buildConfigs.go b/pkg/occlient/buildConfigs.go deleted file mode 100644 index 82fbacd19b6..00000000000 --- a/pkg/occlient/buildConfigs.go +++ /dev/null @@ -1,210 +0,0 @@ -package occlient - -import ( - "context" - "fmt" - "io" - "time" - - buildv1 "github.com/openshift/api/build/v1" - buildschema "github.com/openshift/client-go/build/clientset/versioned/scheme" - "github.com/openshift/odo/pkg/kclient" - "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/klog" -) - -// GetBuildConfigFromName get BuildConfig by its name -func (c *Client) GetBuildConfigFromName(name string) (*buildv1.BuildConfig, error) { - klog.V(3).Infof("Getting BuildConfig: %s", name) - bc, err := c.buildClient.BuildConfigs(c.Namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "unable to get BuildConfig %s", name) - } - return bc, nil -} - -// GetLatestBuildName gets the name of the latest build -// buildConfigName is the name of the buildConfig for which we are fetching the build name -// returns the name of the latest build or the error -func (c *Client) GetLatestBuildName(buildConfigName string) (string, error) { - buildConfig, err := c.buildClient.BuildConfigs(c.Namespace).Get(context.TODO(), buildConfigName, metav1.GetOptions{}) - if err != nil { - return "", errors.Wrap(err, "unable to get the latest build name") - } - return fmt.Sprintf("%s-%d", buildConfigName, buildConfig.Status.LastVersion), nil -} - -// CreateBuildConfig creates a buildConfig using the builderImage as well as gitURL. -// envVars is the array containing the environment variables -func (c *Client) CreateBuildConfig(commonObjectMeta metav1.ObjectMeta, builderImage string, gitURL string, gitRef string, envVars []corev1.EnvVar) (buildv1.BuildConfig, error) { - - // Retrieve the namespace, image name and the appropriate tag - imageNS, imageName, imageTag, _, err := ParseImageName(builderImage) - if err != nil { - return buildv1.BuildConfig{}, errors.Wrap(err, "unable to parse image name") - } - imageStream, err := c.GetImageStream(imageNS, imageName, imageTag) - if err != nil { - return buildv1.BuildConfig{}, errors.Wrap(err, "unable to retrieve image stream for CreateBuildConfig") - } - imageNS = imageStream.ObjectMeta.Namespace - - klog.V(3).Infof("Using namespace: %s for the CreateBuildConfig function", imageNS) - - // Use BuildConfig to build the container with Git - bc := generateBuildConfig(commonObjectMeta, gitURL, gitRef, imageName+":"+imageTag, imageNS) - - if len(envVars) > 0 { - bc.Spec.Strategy.SourceStrategy.Env = envVars - } - _, err = c.buildClient.BuildConfigs(c.Namespace).Create(context.TODO(), &bc, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return buildv1.BuildConfig{}, errors.Wrapf(err, "unable to create BuildConfig for %s", commonObjectMeta.Name) - } - - return bc, nil -} - -// UpdateBuildConfig updates the BuildConfig file -// buildConfigName is the name of the BuildConfig file to be updated -// projectName is the name of the project -// gitURL equals to the git URL of the source and is equals to "" if the source is of type dir or binary -// annotations contains the annotations for the BuildConfig file -func (c *Client) UpdateBuildConfig(buildConfigName string, gitURL string, annotations map[string]string) error { - if gitURL == "" { - return errors.New("gitURL for UpdateBuildConfig must not be blank") - } - - // generate BuildConfig - buildSource := buildv1.BuildSource{ - Git: &buildv1.GitBuildSource{ - URI: gitURL, - }, - Type: buildv1.BuildSourceGit, - } - - buildConfig, err := c.GetBuildConfigFromName(buildConfigName) - if err != nil { - return errors.Wrap(err, "unable to get the BuildConfig file") - } - buildConfig.Spec.Source = buildSource - buildConfig.Annotations = annotations - _, err = c.buildClient.BuildConfigs(c.Namespace).Update(context.TODO(), buildConfig, metav1.UpdateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrap(err, "unable to update the component") - } - return nil -} - -// DeleteBuildConfig deletes the given BuildConfig by name using CommonObjectMeta.. -func (c *Client) DeleteBuildConfig(commonObjectMeta metav1.ObjectMeta) error { - - // Convert labels to selector - selector := util.ConvertLabelsToSelector(commonObjectMeta.Labels) - klog.V(3).Infof("DeleteBuildConfig selectors used for deletion: %s", selector) - - // Delete BuildConfig - klog.V(3).Info("Deleting BuildConfigs with DeleteBuildConfig") - return c.buildClient.BuildConfigs(c.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: selector}) -} - -// WaitForBuildToFinish block and waits for build to finish. Returns error if build failed or was canceled. -func (c *Client) WaitForBuildToFinish(buildName string, stdout io.Writer, buildTimeout time.Duration) error { - // following indicates if we have already setup the following logic - following := false - klog.V(3).Infof("Waiting for %s build to finish", buildName) - - // start a watch on the build resources and look for the given build name - w, err := c.buildClient.Builds(c.Namespace).Watch(context.TODO(), metav1.ListOptions{ - FieldSelector: fields.Set{"metadata.name": buildName}.AsSelector().String(), - }) - if err != nil { - return errors.Wrapf(err, "unable to watch build") - } - defer w.Stop() - timeout := time.After(buildTimeout) - for { - select { - // when a event is received regarding the given buildName - case val, ok := <-w.ResultChan(): - if !ok { - break - } - // cast the object returned to a build object and check the phase of the build - if e, ok := val.Object.(*buildv1.Build); ok { - klog.V(3).Infof("Status of %s build is %s", e.Name, e.Status.Phase) - switch e.Status.Phase { - case buildv1.BuildPhaseComplete: - // the build is completed thus return - klog.V(3).Infof("Build %s completed.", e.Name) - return nil - case buildv1.BuildPhaseFailed, buildv1.BuildPhaseCancelled, buildv1.BuildPhaseError: - // the build failed/got cancelled/error occurred thus return with error - return errors.Errorf("build %s status %s", e.Name, e.Status.Phase) - case buildv1.BuildPhaseRunning: - // since the pod is ready and the build is now running, start following the logs - if !following { - // setting following to true as we need to set it up only once - following = true - err := c.FollowBuildLog(buildName, stdout, buildTimeout) - if err != nil { - return err - } - } - } - } - case <-timeout: - // timeout has occurred while waiting for the build to start/complete, so error out - return errors.Errorf("timeout waiting for build %s to start", buildName) - } - } -} - -// StartBuild starts new build as it is, returns name of the build stat was started -func (c *Client) StartBuild(name string) (string, error) { - klog.V(3).Infof("Build %s started.", name) - buildRequest := buildv1.BuildRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - result, err := c.buildClient.BuildConfigs(c.Namespace).Instantiate(context.TODO(), name, &buildRequest, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return "", errors.Wrapf(err, "unable to instantiate BuildConfig for %s", name) - } - klog.V(3).Infof("Build %s for BuildConfig %s triggered.", name, result.Name) - - return result.Name, nil -} - -// FollowBuildLog stream build log to stdout -func (c *Client) FollowBuildLog(buildName string, stdout io.Writer, buildTimeout time.Duration) error { - buildLogOptions := buildv1.BuildLogOptions{ - Follow: true, - NoWait: false, - } - - rd, err := c.buildClient.RESTClient().Get(). - Timeout(buildTimeout). - Namespace(c.Namespace). - Resource("builds"). - Name(buildName). - SubResource("log"). - VersionedParams(&buildLogOptions, buildschema.ParameterCodec). - Stream(context.TODO()) - - if err != nil { - return errors.Wrapf(err, "unable get build log %s", buildName) - } - defer rd.Close() - - if _, err = io.Copy(stdout, rd); err != nil { - return errors.Wrapf(err, "error streaming logs for %s", buildName) - } - - return nil -} diff --git a/pkg/occlient/buildConfigs_test.go b/pkg/occlient/buildConfigs_test.go deleted file mode 100644 index 9d8cacb7247..00000000000 --- a/pkg/occlient/buildConfigs_test.go +++ /dev/null @@ -1,470 +0,0 @@ -package occlient - -import ( - "fmt" - "os" - "reflect" - "testing" - "time" - - "github.com/kylelemons/godebug/pretty" - buildv1 "github.com/openshift/api/build/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - ktesting "k8s.io/client-go/testing" -) - -func TestUpdateBuildConfig(t *testing.T) { - tests := []struct { - name string - buildConfigName string - gitURL string - annotations map[string]string - existingBuildConfig buildv1.BuildConfig - updatedBuildConfig buildv1.BuildConfig - wantErr bool - }{ - { - name: "local to git with proper parameters", - buildConfigName: "nodejs", - gitURL: "https://github.com/sclorg/nodejs-ex", - annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/sclorg/nodejs-ex", - "app.kubernetes.io/component-source-type": "git", - }, - existingBuildConfig: buildv1.BuildConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - }, - Spec: buildv1.BuildConfigSpec{ - CommonSpec: buildv1.CommonSpec{}, - }, - }, - updatedBuildConfig: buildv1.BuildConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/sclorg/nodejs-ex", - "app.kubernetes.io/component-source-type": "git", - }, - }, - Spec: buildv1.BuildConfigSpec{ - CommonSpec: buildv1.CommonSpec{ - Source: buildv1.BuildSource{ - Git: &buildv1.GitBuildSource{ - URI: "https://github.com/sclorg/nodejs-ex", - }, - Type: buildv1.BuildSourceGit, - }, - }, - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - fkclientset.BuildClientset.PrependReactor("get", "buildconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - buildConfigName := action.(ktesting.GetAction).GetName() - if buildConfigName != tt.buildConfigName { - return true, nil, fmt.Errorf("'update' was called with wrong buildConfig name") - } - return true, &tt.existingBuildConfig, nil - }) - - fkclientset.BuildClientset.PrependReactor("update", "buildconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - buildConfig := action.(ktesting.UpdateAction).GetObject().(*buildv1.BuildConfig) - if buildConfig.Name != tt.buildConfigName { - return true, nil, fmt.Errorf("'update' was called with wrong buildConfig name") - } - return true, &tt.updatedBuildConfig, nil - }) - - err := fkclient.UpdateBuildConfig(tt.buildConfigName, tt.gitURL, tt.annotations) - if err == nil && !tt.wantErr { - // Check for validating actions performed - if (len(fkclientset.BuildClientset.Actions()) != 2) && (tt.wantErr != true) { - t.Errorf("expected 2 action in GetBuildConfigFromName got: %v", fkclientset.BuildClientset.Actions()) - } - - updatedDc := fkclientset.BuildClientset.Actions()[1].(ktesting.UpdateAction).GetObject().(*buildv1.BuildConfig) - if !reflect.DeepEqual(updatedDc.Annotations, tt.annotations) { - t.Errorf("deployment Config annotations not matching with expected values, expected: %s, got %s", tt.annotations, updatedDc.Annotations) - } - - if !reflect.DeepEqual(updatedDc.Spec, tt.updatedBuildConfig.Spec) { - t.Errorf("deployment Config Spec not matching with expected values: %v", pretty.Compare(tt.updatedBuildConfig.Spec, updatedDc.Spec)) - } - } else if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } - }) - } -} - -func TestStartBuild(t *testing.T) { - tests := []struct { - name string - bcName string - wantErr bool - }{ - { - name: "Case 1: Testing valid name", - bcName: "ruby", - wantErr: false, - }, - - // TODO: Currently fails. Enable once fixed. - // { - // name: "Case 2: Testing empty name", - // bcName: "", - // wantErr: true, - // }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - fkclient, fkclientset := FakeNew() - - fkclientset.BuildClientset.PrependReactor("create", "buildconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - build := buildv1.Build{ - ObjectMeta: metav1.ObjectMeta{ - Name: tt.bcName, - }, - } - - return true, &build, nil - }) - - _, err := fkclient.StartBuild(tt.bcName) - if !tt.wantErr == (err != nil) { - t.Errorf(" client.StartBuild(string) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if err == nil { - - if len(fkclientset.BuildClientset.Actions()) != 1 { - t.Errorf("expected 1 action in StartBuild got: %v", fkclientset.BuildClientset.Actions()) - } - - startedBuild := fkclientset.BuildClientset.Actions()[0].(ktesting.CreateAction).GetObject().(*buildv1.BuildRequest) - - if startedBuild.Name != tt.bcName { - t.Errorf("buildconfig name is not matching to expected name, expected: %s, got %s", tt.bcName, startedBuild.Name) - } - } - }) - } - -} - -func TestWaitForBuildToFinish(t *testing.T) { - - tests := []struct { - name string - buildName string - status buildv1.BuildPhase - wantErr bool - }{ - { - name: "phase: complete", - buildName: "ruby", - status: buildv1.BuildPhaseComplete, - wantErr: false, - }, - - { - name: "phase: failed", - buildName: "ruby", - status: buildv1.BuildPhaseFailed, - wantErr: true, - }, - - { - name: "phase: cancelled", - buildName: "ruby", - status: buildv1.BuildPhaseCancelled, - wantErr: true, - }, - - { - name: "phase: error", - buildName: "ruby", - status: buildv1.BuildPhaseError, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - fkclient, fkclientset := FakeNew() - fkWatch := watch.NewFake() - - go func() { - fkWatch.Modify(fakeBuildStatus(tt.status, tt.buildName)) - }() - - fkclientset.BuildClientset.PrependWatchReactor("builds", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) { - return true, fkWatch, nil - }) - - err := fkclient.WaitForBuildToFinish(tt.buildName, os.Stdout, time.Second*5) - if !tt.wantErr == (err != nil) { - t.Errorf(" client.WaitForBuildToFinish(string) unexpected error %v, wantErr %v", err, tt.wantErr) - return - } - - if len(fkclientset.BuildClientset.Actions()) != 1 { - t.Errorf("expected 1 action in WaitForBuildToFinish got: %v", fkclientset.BuildClientset.Actions()) - } - - if err == nil { - expectedFields := fields.OneTermEqualSelector("metadata.name", tt.buildName) - gotFields := fkclientset.BuildClientset.Actions()[0].(ktesting.WatchAction).GetWatchRestrictions().Fields - - if !reflect.DeepEqual(expectedFields, gotFields) { - t.Errorf("Fields not matching: expected: %s, got %s", expectedFields, gotFields) - } - } - }) - } -} - -func TestGetBuildConfigFromName(t *testing.T) { - tests := []struct { - name string - buildName string - returnedBuildConfig buildv1.BuildConfig - wantErr bool - }{ - { - name: "buildConfig with existing bc", - buildName: "nodejs", - returnedBuildConfig: buildv1.BuildConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - - fkclientset.BuildClientset.PrependReactor("get", "buildconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - buildName := action.(ktesting.GetAction).GetName() - if buildName != tt.buildName { - return true, nil, fmt.Errorf("'get' was called with wrong buildName") - } - return true, &tt.returnedBuildConfig, nil - }) - - build, err := fkclient.GetBuildConfigFromName(tt.buildName) - if err == nil && !tt.wantErr { - // Check for validating actions performed - if (len(fkclientset.BuildClientset.Actions()) != 1) && (tt.wantErr != true) { - t.Errorf("expected 1 action in GetBuildConfigFromName got: %v", fkclientset.AppsClientset.Actions()) - } - if build.Name != tt.buildName { - t.Errorf("wrong GetBuildConfigFromName got: %v, expected: %v", build.Name, tt.buildName) - } - } else if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } - }) - } -} - -func TestCreateBuildConfig(t *testing.T) { - type args struct { - commonObjectMeta metav1.ObjectMeta - namespace string - builderImage string - gitURL string - gitRef string - envVars []corev1.EnvVar - } - tests := []struct { - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1 - Generate and create the BuildConfig", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - gitRef: "master", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - envVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key1", - Value: "value1", - }, - }, - }, - wantErr: false, - actions: 1, - }, - { - name: "Case 2 - Generate and create the BuildConfig but fail with unable to find image name", - args: args{ - builderImage: "fakeimagename:notlatest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - envVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key1", - Value: "value1", - }, - }, - }, - wantErr: true, - actions: 1, - }, - { - name: "Case 3 - Generate and create the BuildConfig but fail with unable to parse image name", - args: args{ - builderImage: "::", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - envVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key1", - Value: "value1", - }, - }, - }, - wantErr: true, - actions: 1, - }, - { - name: "Case 4 - Generate and create the BuildConfig and pass in no envVars", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - gitRef: "develop", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - envVars: []corev1.EnvVar{}, - }, - wantErr: false, - actions: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.ImageClientset.PrependReactor("get", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStream(tt.args.commonObjectMeta.Name, tt.args.commonObjectMeta.Namespace, []string{"latest"}), nil - }) - - // Run function CreateBuildConfig - bc, err := fakeClient.CreateBuildConfig(tt.args.commonObjectMeta, tt.args.builderImage, tt.args.gitURL, tt.args.gitRef, tt.args.envVars) - - if err == nil && !tt.wantErr { - // Check to see how many actions are being ran - if (len(fakeClientSet.ImageClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in CreateBuildConfig got %v: %v", tt.actions, len(fakeClientSet.ImageClientset.Actions()), fakeClientSet.ImageClientset.Actions()) - } - - // Check to see that names match - if bc.ObjectMeta.Name != tt.args.commonObjectMeta.Name { - t.Errorf("Expected buildConfig name %s, got '%s'", tt.args.commonObjectMeta.Name, bc.ObjectMeta.Name) - } - - // Check to see that labels match - if !reflect.DeepEqual(tt.args.commonObjectMeta.Labels, bc.ObjectMeta.Labels) { - t.Errorf("Expected equal labels, got %+v, expected %+v", tt.args.commonObjectMeta.Labels, bc.ObjectMeta.Labels) - } - - // Check to see that annotations match - if !reflect.DeepEqual(tt.args.commonObjectMeta.Annotations, bc.ObjectMeta.Annotations) { - t.Errorf("Expected equal annotations, got %+v, expected %+v", tt.args.commonObjectMeta.Annotations, bc.ObjectMeta.Annotations) - } - - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} diff --git a/pkg/occlient/deploymentConfigs.go b/pkg/occlient/deploymentConfigs.go deleted file mode 100644 index c7e01aeb524..00000000000 --- a/pkg/occlient/deploymentConfigs.go +++ /dev/null @@ -1,320 +0,0 @@ -package occlient - -import ( - "context" - "encoding/json" - "fmt" - "os" - "sort" - "time" - - appsv1 "github.com/openshift/api/apps/v1" - appsschema "github.com/openshift/client-go/apps/clientset/versioned/scheme" - "github.com/openshift/odo/pkg/kclient" - "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/klog" -) - -func boolPtr(b bool) *bool { - return &b -} - -// IsDeploymentConfigSupported checks if DeploymentConfig type is present on the cluster -func (c *Client) IsDeploymentConfigSupported() (bool, error) { - const Group = "apps.openshift.io" - const Version = "v1" - - return c.GetKubeClient().IsResourceSupported(Group, Version, "deploymentconfigs") -} - -// GetDeploymentConfigFromName returns the Deployment Config resource given -// the Deployment Config name -func (c *Client) GetDeploymentConfigFromName(name string) (*appsv1.DeploymentConfig, error) { - klog.V(3).Infof("Getting DeploymentConfig: %s", name) - deploymentConfig, err := c.appsClient.DeploymentConfigs(c.Namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return deploymentConfig, nil -} - -// GetDeploymentConfigFromSelector returns the Deployment Config object associated -// with the given selector. -// An error is thrown when exactly one Deployment Config is not found for the -// selector. -func (c *Client) GetDeploymentConfigFromSelector(selector string) (*appsv1.DeploymentConfig, error) { - deploymentConfigs, err := c.ListDeploymentConfigs(selector) - if err != nil { - return nil, errors.Wrapf(err, "unable to get DeploymentConfigs for the selector: %v", selector) - } - - numDC := len(deploymentConfigs) - if numDC == 0 { - return nil, fmt.Errorf("no Deployment Config was found for the selector: %v", selector) - } else if numDC > 1 { - return nil, fmt.Errorf("multiple Deployment Configs exist for the selector: %v. Only one must be present", selector) - } - - return &deploymentConfigs[0], nil -} - -// UpdateDCAnnotations updates the DeploymentConfig file -// dcName is the name of the DeploymentConfig file to be updated -// annotations contains the annotations for the DeploymentConfig file -func (c *Client) UpdateDCAnnotations(dcName string, annotations map[string]string) error { - dc, err := c.GetDeploymentConfigFromName(dcName) - if err != nil { - return errors.Wrapf(err, "unable to get DeploymentConfig %s", dcName) - } - - dc.Annotations = annotations - _, err = c.appsClient.DeploymentConfigs(c.Namespace).Update(context.TODO(), dc, metav1.UpdateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to uDeploymentConfig config %s", dcName) - } - return nil -} - -// Define a function that is meant to create patch based on the contents of the DC -type dcPatchProvider func(dc *appsv1.DeploymentConfig) (string, error) - -// this function will look up the appropriate DC, and execute the specified patch -// the whole point of using patch is to avoid race conditions where we try to update -// dc while it's being simultaneously updated from another source (for example Kubernetes itself) -// this will result in the triggering of a redeployment -func (c *Client) patchDC(dcName string, dcPatchProvider dcPatchProvider) error { - dc, err := c.GetDeploymentConfigFromName(dcName) - if err != nil { - return errors.Wrapf(err, "Unable to locate DeploymentConfig %s", dcName) - } - - if dcPatchProvider != nil { - patch, err := dcPatchProvider(dc) - if err != nil { - return errors.Wrap(err, "Unable to create a patch for the DeploymentConfig") - } - - // patch the DeploymentConfig with the secret - _, err = c.appsClient.DeploymentConfigs(c.Namespace).Patch(context.TODO(), dcName, types.JSONPatchType, []byte(patch), metav1.PatchOptions{FieldManager: kclient.FieldManager, Force: boolPtr(true)}) - if err != nil { - return errors.Wrapf(err, "DeploymentConfig not patched %s", dc.Name) - } - } else { - return errors.Wrapf(err, "dcPatch was not properly set") - } - - return nil -} - -// ListDeploymentConfigs returns an array of Deployment Config -// resources which match the given selector -func (c *Client) ListDeploymentConfigs(selector string) ([]appsv1.DeploymentConfig, error) { - var dcList *appsv1.DeploymentConfigList - var err error - - if selector != "" { - dcList, err = c.appsClient.DeploymentConfigs(c.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: selector, - }) - } else { - dcList, err = c.appsClient.DeploymentConfigs(c.Namespace).List(context.TODO(), metav1.ListOptions{ - FieldSelector: fields.Set{"metadata.namespace": c.Namespace}.AsSelector().String(), - }) - } - if err != nil { - return nil, errors.Wrap(err, "unable to list DeploymentConfigs") - } - return dcList.Items, nil -} - -// WaitAndGetDC block and waits until the DeploymentConfig has updated it's annotation -// Parameters: -// name: Name of DC -// timeout: Interval of time.Duration to wait for before timing out waiting for its rollout -// waitCond: Function indicating when to consider dc rolled out -// Returns: -// Updated DC and errors if any -func (c *Client) WaitAndGetDC(name string, desiredRevision int64, timeout time.Duration, waitCond func(*appsv1.DeploymentConfig, int64) bool) (*appsv1.DeploymentConfig, error) { - - w, err := c.appsClient.DeploymentConfigs(c.Namespace).Watch(context.TODO(), metav1.ListOptions{ - FieldSelector: fmt.Sprintf("metadata.name=%s", name), - }) - defer w.Stop() - - if err != nil { - return nil, errors.Wrapf(err, "unable to watch dc") - } - - timeoutChannel := time.After(timeout) - // Keep trying until we're timed out or got a result or got an error - for { - select { - - // Timout after X amount of seconds - case <-timeoutChannel: - return nil, errors.New("Timed out waiting for annotation to update") - - // Each loop we check the result - case val, ok := <-w.ResultChan(): - - if !ok { - break - } - if e, ok := val.Object.(*appsv1.DeploymentConfig); ok { - for _, cond := range e.Status.Conditions { - // using this just for debugging message, so ignoring error on purpose - jsonCond, _ := json.Marshal(cond) - klog.V(3).Infof("DeploymentConfig Condition: %s", string(jsonCond)) - } - // If the annotation has been updated, let's exit - if waitCond(e, desiredRevision) { - return e, nil - } - - } - } - } -} - -// GetDeploymentConfigLabelValues get label values of given label from objects in project that are matching selector -// returns slice of unique label values -func (c *Client) GetDeploymentConfigLabelValues(label string, selector string) ([]string, error) { - - // List DeploymentConfig according to selectors - dcList, err := c.appsClient.DeploymentConfigs(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return nil, errors.Wrap(err, "unable to list DeploymentConfigs") - } - - // Grab all the matched strings - var values []string - for _, elem := range dcList.Items { - for key, val := range elem.Labels { - if key == label { - values = append(values, val) - } - } - } - - // Sort alphabetically - sort.Strings(values) - - return values, nil -} - -// DisplayDeploymentConfigLog logs the deployment config to stdout -func (c *Client) DisplayDeploymentConfigLog(deploymentConfigName string, followLog bool) error { - - // Set standard log options - deploymentLogOptions := appsv1.DeploymentLogOptions{Follow: false, NoWait: true} - - // If the log is being followed, set it to follow / don't wait - if followLog { - // TODO: https://github.com/kubernetes/kubernetes/pull/60696 - // Unable to set to 0, until openshift/client-go updates their Kubernetes vendoring to 1.11.0 - // Set to 1 for now. - tailLines := int64(1) - deploymentLogOptions = appsv1.DeploymentLogOptions{Follow: true, NoWait: false, Previous: false, TailLines: &tailLines} - } - - // RESTClient call to OpenShift - rd, err := c.appsClient.RESTClient().Get(). - Namespace(c.Namespace). - Name(deploymentConfigName). - Resource("deploymentconfigs"). - SubResource("log"). - VersionedParams(&deploymentLogOptions, appsschema.ParameterCodec). - Stream(context.TODO()) - if err != nil { - return errors.Wrapf(err, "unable get deploymentconfigs log %s", deploymentConfigName) - } - if rd == nil { - return errors.New("unable to retrieve DeploymentConfig from OpenShift, does your component exist?") - } - - return util.DisplayLog(followLog, rd, os.Stdout, deploymentConfigName, -1) -} - -// StartDeployment instantiates a given deployment -// deploymentName is the name of the deployment to instantiate -func (c *Client) StartDeployment(deploymentName string) (string, error) { - if deploymentName == "" { - return "", errors.Errorf("deployment name is empty") - } - klog.V(3).Infof("Deployment %s started.", deploymentName) - deploymentRequest := appsv1.DeploymentRequest{ - Name: deploymentName, - // latest is set to true to prevent image name resolution issue - // inspired from https://github.com/openshift/origin/blob/882ed02142fbf7ba16da9f8efeb31dab8cfa8889/pkg/oc/cli/rollout/latest.go#L194 - Latest: true, - Force: true, - } - result, err := c.appsClient.DeploymentConfigs(c.Namespace).Instantiate(context.TODO(), deploymentName, &deploymentRequest, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return "", errors.Wrapf(err, "unable to instantiate Deployment for %s", deploymentName) - } - klog.V(3).Infof("Deployment %s for DeploymentConfig %s triggered.", deploymentName, result.Name) - - return result.Name, nil -} - -// GetPodUsingDeploymentConfig gets the pod using deployment config name -func (c *Client) GetPodUsingDeploymentConfig(componentName, appName string) (*corev1.Pod, error) { - deploymentConfigName, err := util.NamespaceOpenShiftObject(componentName, appName) - if err != nil { - return nil, err - } - - // Find Pod for component - podSelector := fmt.Sprintf("deploymentconfig=%s", deploymentConfigName) - return c.GetKubeClient().GetOnePodFromSelector(podSelector) -} - -// GetEnvVarsFromDC retrieves the env vars from the DC -// dcName is the name of the dc from which the env vars are retrieved -// projectName is the name of the project -func (c *Client) GetEnvVarsFromDC(dcName string) ([]corev1.EnvVar, error) { - dc, err := c.GetDeploymentConfigFromName(dcName) - if err != nil { - return nil, errors.Wrap(err, "error occurred while retrieving the dc") - } - - numContainers := len(dc.Spec.Template.Spec.Containers) - if numContainers != 1 { - return nil, fmt.Errorf("expected exactly one container in Deployment Config %v, got %v", dc.Name, numContainers) - } - - return dc.Spec.Template.Spec.Containers[0].Env, nil -} - -// AddEnvironmentVariablesToDeploymentConfig adds the given environment -// variables to the only container in the Deployment Config and updates in the -// cluster -func (c *Client) AddEnvironmentVariablesToDeploymentConfig(envs []corev1.EnvVar, dc *appsv1.DeploymentConfig) error { - numContainers := len(dc.Spec.Template.Spec.Containers) - if numContainers != 1 { - return fmt.Errorf("expected exactly one container in Deployment Config %v, got %v", dc.Name, numContainers) - } - - dc.Spec.Template.Spec.Containers[0].Env = append(dc.Spec.Template.Spec.Containers[0].Env, envs...) - - _, err := c.appsClient.DeploymentConfigs(c.Namespace).Update(context.TODO(), dc, metav1.UpdateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to update Deployment Config %v", dc.Name) - } - return nil -} - -// GetVolumeMountsFromDC returns a list of all volume mounts in the given DC -func (c *Client) GetVolumeMountsFromDC(dc *appsv1.DeploymentConfig) []corev1.VolumeMount { - var volumeMounts []corev1.VolumeMount - for _, container := range dc.Spec.Template.Spec.Containers { - volumeMounts = append(volumeMounts, container.VolumeMounts...) - } - return volumeMounts -} diff --git a/pkg/occlient/deploymentConfigs_test.go b/pkg/occlient/deploymentConfigs_test.go deleted file mode 100644 index 922d545785e..00000000000 --- a/pkg/occlient/deploymentConfigs_test.go +++ /dev/null @@ -1,617 +0,0 @@ -package occlient - -import ( - "fmt" - "reflect" - "testing" - "time" - - appsv1 "github.com/openshift/api/apps/v1" - applabels "github.com/openshift/odo/pkg/application/labels" - "github.com/openshift/odo/pkg/testingutil" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - ktesting "k8s.io/client-go/testing" -) - -func TestGetDeploymentConfigLabelValues(t *testing.T) { - type args struct { - deploymentConfigList appsv1.DeploymentConfigList - expectedOutput []string - } - tests := []struct { - applicationName string - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1 - Retrieve list", - applicationName: "app", - args: args{ - expectedOutput: []string{"app", "app2"}, - deploymentConfigList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/part-of": "app", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/part-of": "app2", - }, - }, - }, - }, - }, - }, - wantErr: false, - actions: 1, - }, - { - name: "Case 1 - Retrieve list, different order", - applicationName: "app", - args: args{ - expectedOutput: []string{"app", "app2"}, - deploymentConfigList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/part-of": "app2", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/part-of": "app", - }, - }, - }, - }, - }, - }, - wantErr: false, - actions: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.args.deploymentConfigList, nil - }) - - list, err := fakeClient.GetDeploymentConfigLabelValues(applabels.ApplicationLabel, applabels.ApplicationLabel) - - if err == nil && !tt.wantErr { - - // Compare arrays - if !reflect.DeepEqual(list, tt.args.expectedOutput) { - t.Errorf("expected %s output, got %s", tt.args.expectedOutput, list) - } - - if (len(fakeClientSet.AppsClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in TestGetDeploymentConfigLabelValues got %v: %v", tt.actions, len(fakeClientSet.AppsClientset.Actions()), fakeClientSet.AppsClientset.Actions()) - } - - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} - -func TestListDeploymentConfigs(t *testing.T) { - tests := []struct { - name string - selector string - label map[string]string - wantErr bool - }{ - { - name: "true case", - selector: "app.kubernetes.io/part-of=app", - label: map[string]string{ - "app.kubernetes.io/part-of": "app", - }, - wantErr: false, - }, - { - name: "true case", - selector: "app.kubernetes.io/part-of=app1", - label: map[string]string{ - "app.kubernetes.io/part-of": "app", - }, - wantErr: false, - }, - } - - listOfDC := appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/part-of": "app", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if !reflect.DeepEqual(action.(ktesting.ListAction).GetListRestrictions().Labels.String(), tt.selector) { - return true, nil, fmt.Errorf("labels not matching with expected values, expected:%s, got:%s", tt.selector, action.(ktesting.ListAction).GetListRestrictions()) - } - return true, &listOfDC, nil - }) - dc, err := fakeClient.ListDeploymentConfigs(tt.selector) - - if len(fakeClientSet.AppsClientset.Actions()) != 1 { - t.Errorf("expected 1 AppsClientset.Actions() in ListDeploymentConfigs, got: %v", fakeClientSet.AppsClientset.Actions()) - } - - if tt.wantErr == false && err != nil { - t.Errorf("test failed, %#v", dc[0].Labels) - } - - for _, dc1 := range dc { - if !reflect.DeepEqual(dc1.Labels, tt.label) { - t.Errorf("labels are not matching with expected labels, expected: %s, got %s", tt.label, dc1.Labels) - } - } - - }) - } -} - -func TestWaitAndGetDC(t *testing.T) { - type args struct { - name string - annotation string - value string - dc appsv1.DeploymentConfig - timeout time.Duration - } - tests := []struct { - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1 - Check that the function actually works", - args: args{ - name: "foo", - annotation: "app.kubernetes.io/component-source-type", - value: "git", - dc: *fakeDeploymentConfig("foo", "bar", - []corev1.EnvVar{}, []corev1.EnvFromSource{}, t), - timeout: 3 * time.Second, - }, - wantErr: false, - actions: 1, - }, - { - name: "Case 2 - Purposefully timeout / error", - args: args{ - name: "foo", - annotation: "app.kubernetes.io/component-source-type", - value: "foobar", - dc: *fakeDeploymentConfig("foo", "bar", - []corev1.EnvVar{}, []corev1.EnvFromSource{}, t), - timeout: 3 * time.Second, - }, - wantErr: true, - actions: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - fkWatch := watch.NewFake() - go func() { - fkWatch.Modify(&tt.args.dc) - }() - fakeClientSet.AppsClientset.PrependWatchReactor("deploymentconfigs", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) { - return true, fkWatch, nil - }) - // Run function WaitAndGetDC - _, err := fakeClient.WaitAndGetDC(tt.args.name, 0, tt.args.timeout, func(*appsv1.DeploymentConfig, int64) bool { - return !tt.wantErr - }) - // Error checking WaitAndGetDC - if !tt.wantErr == (err != nil) { - t.Errorf(" client.WaitAndGetDC() unexpected error %v, wantErr %v", err, tt.wantErr) - } - if err == nil && !tt.wantErr { - // Check to see how many actions are being ran - if (len(fakeClientSet.AppsClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in WaitAndGetDC got %v: %v", tt.actions, len(fakeClientSet.AppsClientset.Actions()), fakeClientSet.AppsClientset.Actions()) - } - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - }) - } -} - -func TestStartDeployment(t *testing.T) { - tests := []struct { - name string - deploymentName string - wantErr bool - }{ - { - name: "Case 1: Testing valid name", - deploymentName: "ruby", - wantErr: false, - }, - { - name: "Case 2: Testing invalid name", - deploymentName: "", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - fkclient, fkclientset := FakeNew() - - fkclientset.AppsClientset.PrependReactor("create", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - deploymentConfig := appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: tt.deploymentName, - }, - } - return true, &deploymentConfig, nil - }) - - _, err := fkclient.StartDeployment(tt.deploymentName) - if !tt.wantErr == (err != nil) { - t.Errorf(" client.StartDeployment(string) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if err == nil { - - if len(fkclientset.AppsClientset.Actions()) != 1 { - t.Errorf("expected 1 action in StartDeployment got: %v", fkclientset.AppsClientset.Actions()) - } else { - startedDeployment := fkclientset.AppsClientset.Actions()[0].(ktesting.CreateAction).GetObject().(*appsv1.DeploymentRequest) - - if startedDeployment.Name != tt.deploymentName { - t.Errorf("deployment name is not matching to expected name, expected: %s, got %s", tt.deploymentName, startedDeployment.Name) - } - - if startedDeployment.Latest == false { - t.Errorf("deployment is not set to latest") - } - } - } - }) - } -} - -func TestGetDeploymentConfigFromSelector(t *testing.T) { - type args struct { - selector string - } - tests := []struct { - name string - args args - returnedDCList *appsv1.DeploymentConfigList - want *appsv1.DeploymentConfig - wantErr bool - }{ - { - name: "case 1: only one dc returned", - args: args{ - "app=app", - }, - returnedDCList: &appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - *testingutil.OneFakeDeploymentConfigWithMounts("comp-0", "nodejs", "app", nil), - }, - }, - want: testingutil.OneFakeDeploymentConfigWithMounts("comp-0", "nodejs", "app", nil), - }, - { - name: "case 2: no dc returned", - args: args{ - "app=app", - }, - returnedDCList: &appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{}, - }, - wantErr: true, - }, - { - name: "case 3: two dc returned", - args: args{ - "app=app", - }, - returnedDCList: &appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - *testingutil.OneFakeDeploymentConfigWithMounts("comp-0", "nodejs", "app", nil), - *testingutil.OneFakeDeploymentConfigWithMounts("comp-1", "nodejs", "app", nil), - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if !reflect.DeepEqual(action.(ktesting.ListAction).GetListRestrictions().Labels.String(), tt.args.selector) { - return true, nil, fmt.Errorf("labels not matching with expected values, expected:%s, got:%s", tt.args.selector, action.(ktesting.ListAction).GetListRestrictions()) - } - return true, tt.returnedDCList, nil - }) - - got, err := fakeClient.GetDeploymentConfigFromSelector(tt.args.selector) - if (err != nil) != tt.wantErr { - t.Errorf("GetDeploymentConfigFromSelector() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetDeploymentConfigFromSelector() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetEnvVarsFromDC(t *testing.T) { - tests := []struct { - name string - dcName string - projectName string - returnedDC appsv1.DeploymentConfig - returnedEnvVars []corev1.EnvVar - wantErr bool - }{ - { - name: "case 1: with valid existing dc and one valid env var pair", - dcName: "nodejs-app", - projectName: "project", - returnedDC: appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs-app", - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Env: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - }, - }, - }, - }, - }, - }, - }, - returnedEnvVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - }, - wantErr: false, - }, - { - name: "case 2: with valid existing dc and two valid env var pairs", - dcName: "nodejs-app", - projectName: "project", - returnedDC: appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs-app", - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Env: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key-1", - Value: "value-1", - }, - }, - }, - }, - }, - }, - }, - }, - returnedEnvVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key-1", - Value: "value-1", - }, - }, - wantErr: false, - }, - { - name: "case 3: with non valid existing dc", - dcName: "nodejs-app", - projectName: "project", - returnedDC: appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "wildfly-app", - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Env: []corev1.EnvVar{}, - }, - }, - }, - }, - }, - }, - returnedEnvVars: []corev1.EnvVar{}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - dcName := action.(ktesting.GetAction).GetName() - if dcName != tt.dcName { - return true, nil, fmt.Errorf("get dc called with different name, expected: %s, got %s", tt.dcName, dcName) - } - return true, &tt.returnedDC, nil - }) - - envVars, err := fakeClient.GetEnvVarsFromDC(tt.dcName) - - if err == nil && !tt.wantErr { - // Check for validating actions performed - if len(fakeClientSet.AppsClientset.Actions()) != 1 { - t.Errorf("expected 1 action in GetBuildConfigFromName got: %v", fakeClientSet.AppsClientset.Actions()) - } - - if !reflect.DeepEqual(tt.returnedEnvVars, envVars) { - t.Errorf("env vars are not matching with expected values, expected: %s, got %s", tt.returnedEnvVars, envVars) - } - } else if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } - }) - } -} - -func TestUpdateDCAnnotations(t *testing.T) { - tests := []struct { - name string - dcName string - annotations map[string]string - existingDc appsv1.DeploymentConfig - wantErr bool - }{ - { - name: "existing dc", - dcName: "nodejs", - annotations: map[string]string{ - "app.kubernetes.io/component-source-type": "local", - }, - existingDc: appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nodejs", - Annotations: map[string]string{"app.openshift.io/vcs-uri": "https://github.com/sclorg/nodejs-ex", - "app.kubernetes.io/component-source-type": "git", - }, - }, - }, - wantErr: false, - }, - { - name: "non existing dc", - dcName: "nodejs", - annotations: map[string]string{ - "app.kubernetes.io/component-source-type": "local", - }, - existingDc: appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "wildfly", - Annotations: map[string]string{"app.openshift.io/vcs-uri": "https://github.com/sclorg/nodejs-ex", - "app.kubernetes.io/component-source-type": "git", - }, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - fkclientset.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - dcName := action.(ktesting.GetAction).GetName() - if dcName != tt.dcName { - return true, nil, fmt.Errorf("'get' called with a different dcName") - } - - if tt.dcName != tt.existingDc.Name { - return true, nil, fmt.Errorf("got different dc") - } - return true, &tt.existingDc, nil - }) - - fkclientset.AppsClientset.PrependReactor("update", "deploymentconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - dc := action.(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - if dc.Name != tt.existingDc.Name { - return true, nil, fmt.Errorf("got different dc") - } - return true, dc, nil - }) - - err := fkclient.UpdateDCAnnotations(tt.dcName, tt.annotations) - - if err == nil && !tt.wantErr { - // Check for validating actions performed - if (len(fkclientset.AppsClientset.Actions()) != 2) && (tt.wantErr != true) { - t.Errorf("expected 2 action in UpdateDeploymentConfig got: %v", fkclientset.AppsClientset.Actions()) - } - - updatedDc := fkclientset.AppsClientset.Actions()[1].(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - if updatedDc.Name != tt.dcName { - t.Errorf("deploymentconfig name is not matching with expected value, expected: %s, got %s", tt.dcName, updatedDc.Name) - } - - if !reflect.DeepEqual(updatedDc.Annotations, tt.annotations) { - t.Errorf("deployment Config annotations not matching with expected values, expected: %s, got %s", tt.annotations, updatedDc.Annotations) - } - } else if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } - }) - } -} diff --git a/pkg/occlient/imageStreams.go b/pkg/occlient/imageStreams.go deleted file mode 100644 index 4a80fe594aa..00000000000 --- a/pkg/occlient/imageStreams.go +++ /dev/null @@ -1,368 +0,0 @@ -package occlient - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "strings" - - dockerapiv10 "github.com/openshift/api/image/docker10" - imagev1 "github.com/openshift/api/image/v1" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" -) - -// IsImageStreamSupported checks if imagestream resource type is present on the cluster -func (c *Client) IsImageStreamSupported() (bool, error) { - - return c.GetKubeClient().IsResourceSupported("image.openshift.io", "v1", "imagestreams") -} - -// GetImageStream returns the imagestream using image details like imageNS, imageName and imageTag -// imageNS can be empty in which case, this function searches currentNamespace on priority. If -// imagestream of required tag not found in current namespace, then searches openshift namespace. -// If not found, error out. If imageNS is not empty string, then, the requested imageNS only is searched -// for requested imagestream -func (c *Client) GetImageStream(imageNS string, imageName string, imageTag string) (*imagev1.ImageStream, error) { - var err error - var imageStream *imagev1.ImageStream - currentProjectName := c.GetCurrentProjectName() - /* - If User has not chosen image NS then, - 1. Use image from current NS if available - 2. If not 1, use default openshift NS - 3. If not 2, return errors from both 1 and 2 - else - Use user chosen namespace - If image doesn't exist in user chosen namespace, - error out - else - Proceed - */ - // User has not passed any particular ImageStream - if imageNS == "" { - - // First try finding imagestream from current namespace - currentNSImageStream, e := c.imageClient.ImageStreams(currentProjectName).Get(context.TODO(), imageName, metav1.GetOptions{}) - if e != nil { - err = errors.Wrapf(e, "no match found for : %s in namespace %s", imageName, currentProjectName) - } else { - if isTagInImageStream(*currentNSImageStream, imageTag) { - return currentNSImageStream, nil - } - } - - // If not in current namespace, try finding imagestream from openshift namespace - openshiftNSImageStream, e := c.imageClient.ImageStreams(OpenShiftNameSpace).Get(context.TODO(), imageName, metav1.GetOptions{}) - if e != nil { - // The image is not available in current Namespace. - err = errors.Wrapf(e, "no match found for : %s in namespace %s", imageName, OpenShiftNameSpace) - } else { - if isTagInImageStream(*openshiftNSImageStream, imageTag) { - return openshiftNSImageStream, nil - } - } - if e != nil && err != nil { - return nil, fmt.Errorf("component type %q not found", imageName) - } - - // Required tag not in openshift and current namespaces - return nil, fmt.Errorf("image stream %s with tag %s not found in openshift and %s namespaces", imageName, imageTag, currentProjectName) - - } - - // Fetch imagestream from requested namespace - imageStream, err = c.imageClient.ImageStreams(imageNS).Get(context.TODO(), imageName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf( - err, "no match found for %s in namespace %s", imageName, imageNS, - ) - } - if !isTagInImageStream(*imageStream, imageTag) { - return nil, fmt.Errorf("image stream %s with tag %s not found in %s namespaces", imageName, imageTag, currentProjectName) - } - - return imageStream, nil -} - -// GetImageStreamImage returns image and error if any, corresponding to the passed imagestream and image tag -func (c *Client) GetImageStreamImage(imageStream *imagev1.ImageStream, imageTag string) (*imagev1.ImageStreamImage, error) { - imageNS := imageStream.ObjectMeta.Namespace - imageName := imageStream.ObjectMeta.Name - - for _, tag := range imageStream.Status.Tags { - // look for matching tag - if tag.Tag == imageTag { - klog.V(3).Infof("Found exact image tag match for %s:%s", imageName, imageTag) - - if len(tag.Items) > 0 { - tagDigest := tag.Items[0].Image - imageStreamImageName := fmt.Sprintf("%s@%s", imageName, tagDigest) - - // look for imageStreamImage for given tag (reference by digest) - imageStreamImage, err := c.imageClient.ImageStreamImages(imageNS).Get(context.TODO(), imageStreamImageName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "unable to find ImageStreamImage with %s digest", imageStreamImageName) - } - return imageStreamImage, nil - } - - return nil, fmt.Errorf("unable to find tag %s for image %s", imageTag, imageName) - - } - } - - // return error since its an unhandled case if code reaches here - return nil, fmt.Errorf("unable to find tag %s for image %s", imageTag, imageName) -} - -// GetImageStreamTags returns all the ImageStreamTag objects in the given namespace -func (c *Client) GetImageStreamTags(namespace string) ([]imagev1.ImageStreamTag, error) { - imageStreamTagList, err := c.imageClient.ImageStreamTags(namespace).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, errors.Wrap(err, "unable to list imagestreamtags") - } - return imageStreamTagList.Items, nil -} - -// GetPortsFromBuilderImage returns list of available port from given builder image of given component type -func (c *Client) GetPortsFromBuilderImage(componentType string) ([]string, error) { - // checking port through builder image - imageNS, imageName, imageTag, _, err := ParseImageName(componentType) - if err != nil { - return []string{}, err - } - imageStream, err := c.GetImageStream(imageNS, imageName, imageTag) - if err != nil { - return []string{}, err - } - imageStreamImage, err := c.GetImageStreamImage(imageStream, imageTag) - if err != nil { - return []string{}, err - } - containerPorts, err := getExposedPortsFromISI(imageStreamImage) - if err != nil { - return []string{}, err - } - var portList []string - for _, po := range containerPorts { - port := fmt.Sprint(po.ContainerPort) + "/" + string(po.Protocol) - portList = append(portList, port) - } - if len(portList) == 0 { - return []string{}, fmt.Errorf("given component type doesn't expose any ports, please use --port flag to specify a port") - } - return portList, nil -} - -// ListImageStreams returns the Image Stream objects in the given namespace -func (c *Client) ListImageStreams(namespace string) ([]imagev1.ImageStream, error) { - imageStreamList, err := c.imageClient.ImageStreams(namespace).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, errors.Wrap(err, "unable to list imagestreams") - } - return imageStreamList.Items, nil -} - -// ListImageStreamsNames returns the names of the image streams in a given -// namespace -func (c *Client) ListImageStreamsNames(namespace string) ([]string, error) { - imageStreams, err := c.ListImageStreams(namespace) - if err != nil { - return nil, errors.Wrap(err, "unable to get image streams") - } - - var names []string - for _, imageStream := range imageStreams { - names = append(names, imageStream.Name) - } - return names, nil -} - -// getExposedPortsFromISI parse ImageStreamImage definition and return all exposed ports in form of ContainerPorts structs -func getExposedPortsFromISI(image *imagev1.ImageStreamImage) ([]corev1.ContainerPort, error) { - // file DockerImageMetadata - err := imageWithMetadata(&image.Image) - if err != nil { - return nil, err - } - - var ports []corev1.ContainerPort - - var exposedPorts = image.Image.DockerImageMetadata.Object.(*dockerapiv10.DockerImage).ContainerConfig.ExposedPorts - - if image.Image.DockerImageMetadata.Object.(*dockerapiv10.DockerImage).Config != nil { - if exposedPorts == nil { - exposedPorts = make(map[string]struct{}) - } - - // add ports from Config - for exposedPort := range image.Image.DockerImageMetadata.Object.(*dockerapiv10.DockerImage).Config.ExposedPorts { - var emptyStruct struct{} - exposedPorts[exposedPort] = emptyStruct - } - } - - for exposedPort := range exposedPorts { - splits := strings.Split(exposedPort, "/") - if len(splits) != 2 { - return nil, fmt.Errorf("invalid port %s", exposedPort) - } - - portNumberI64, err := strconv.ParseInt(splits[0], 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "invalid port number %s", splits[0]) - } - portNumber := int32(portNumberI64) - - var portProto corev1.Protocol - switch strings.ToUpper(splits[1]) { - case "TCP": - portProto = corev1.ProtocolTCP - case "UDP": - portProto = corev1.ProtocolUDP - default: - return nil, fmt.Errorf("invalid port protocol %s", splits[1]) - } - - port := corev1.ContainerPort{ - Name: fmt.Sprintf("%d-%s", portNumber, strings.ToLower(string(portProto))), - ContainerPort: portNumber, - Protocol: portProto, - } - - ports = append(ports, port) - } - - return ports, nil -} - -// isTagInImageStream takes a imagestream and a tag and checks if the tag is present in the imagestream's status attribute -func isTagInImageStream(is imagev1.ImageStream, imageTag string) bool { - // Loop through the tags in the imagestream's status attribute - for _, tag := range is.Status.Tags { - // look for a matching tag - if tag.Tag == imageTag { - // Return true if found - return true - } - } - // Return false if not found. - return false -} - -// imageWithMetadata mutates the given image. It parses raw DockerImageManifest data stored in the image and -// fills its DockerImageMetadata and other fields. -// Copied from v3.7 github.com/openshift/origin/pkg/image/apis/image/v1/helpers.go -func imageWithMetadata(image *imagev1.Image) error { - // Check if the metadata are already filled in for this image. - meta, hasMetadata := image.DockerImageMetadata.Object.(*dockerapiv10.DockerImage) - if hasMetadata && meta.Size > 0 { - return nil - } - - version := image.DockerImageMetadataVersion - if len(version) == 0 { - version = "1.0" - } - - obj := &dockerapiv10.DockerImage{} - if len(image.DockerImageMetadata.Raw) != 0 { - if err := json.Unmarshal(image.DockerImageMetadata.Raw, obj); err != nil { - return err - } - image.DockerImageMetadata.Object = obj - } - - image.DockerImageMetadataVersion = version - - return nil -} - -// getS2IMetaInfoFromBuilderImg returns script path protocol, S2I scripts path, S2I source or binary expected path, S2I deployment dir and errors(if any) from the passed builder image -func getS2IMetaInfoFromBuilderImg(builderImage *imagev1.ImageStreamImage) (S2IPaths, error) { - - // Define structs for internal un-marshalling of imagestreamimage to extract label from it - type ContainerConfig struct { - Labels map[string]string `json:"Labels"` - WorkingDir string `json:"WorkingDir"` - } - type DockerImageMetaDataRaw struct { - ContainerConfig ContainerConfig `json:"ContainerConfig"` - Config ContainerConfig `json:"Config"` - } - - var dimdr DockerImageMetaDataRaw - - // The label $S2IScriptsURLLabel needs to be extracted from builderImage#Image#DockerImageMetadata#Raw which is byte array - dimdrByteArr := (*builderImage).Image.DockerImageMetadata.Raw - - // Unmarshal the byte array into the struct for ease of access of required fields - err := json.Unmarshal(dimdrByteArr, &dimdr) - if err != nil { - return S2IPaths{}, errors.Wrap(err, "unable to bootstrap supervisord") - } - - labels := make(map[string]string) - - // Put labels from both ContainerConfig and Config into one map - // if there is the same label in both, the Config has priority - for k, v := range dimdr.ContainerConfig.Labels { - labels[k] = v - } - for k, v := range dimdr.Config.Labels { - labels[k] = v - } - - // If by any chance, labels attribute is nil(although ideally not the case for builder images), return - if len(labels) == 0 { - klog.V(3).Infof("No Labels found in %+v in builder image %+v", dimdr, builderImage) - return S2IPaths{}, nil - } - - // Extract the label containing S2I scripts URL - s2iScriptsURL := labels[S2IScriptsURLLabel] - s2iSrcOrBinPath := labels[S2ISrcOrBinLabel] - s2iBuilderImgName := labels[S2IBuilderImageName] - - if s2iSrcOrBinPath == "" { - // In cases like nodejs builder image, where there is no concept of binary and sources are directly run, use destination as source - // s2iSrcOrBinPath = getS2ILabelValue(dimdr.Config.Labels, S2IDeploymentsDir) - s2iSrcOrBinPath = DefaultS2ISrcOrBinPath - } - - s2iDestinationDir := getS2ILabelValue(labels, S2IDeploymentsDir) - // The URL is a combination of protocol and the path to script details of which can be found @ - // https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md#s2i-scripts - // Extract them out into protocol and path separately to minimise the task in - // https://github.com/openshift/odo-init-image/blob/master/assemble-and-restart when custom handling - // for each of the protocols is added - s2iScriptsProtocol := "" - s2iScriptsPath := "" - - switch { - case strings.HasPrefix(s2iScriptsURL, "image://"): - s2iScriptsProtocol = "image://" - s2iScriptsPath = strings.TrimPrefix(s2iScriptsURL, "image://") - case strings.HasPrefix(s2iScriptsURL, "file://"): - s2iScriptsProtocol = "file://" - s2iScriptsPath = strings.TrimPrefix(s2iScriptsURL, "file://") - case strings.HasPrefix(s2iScriptsURL, "http(s)://"): - s2iScriptsProtocol = "http(s)://" - s2iScriptsPath = s2iScriptsURL - default: - return S2IPaths{}, fmt.Errorf("Unknown scripts url %s", s2iScriptsURL) - } - return S2IPaths{ - ScriptsPathProtocol: s2iScriptsProtocol, - ScriptsPath: s2iScriptsPath, - SrcOrBinPath: s2iSrcOrBinPath, - DeploymentDir: s2iDestinationDir, - WorkingDir: dimdr.Config.WorkingDir, - SrcBackupPath: DefaultS2ISrcBackupDir, - BuilderImgName: s2iBuilderImgName, - }, nil -} diff --git a/pkg/occlient/imageStreams_test.go b/pkg/occlient/imageStreams_test.go deleted file mode 100644 index 4577e1ebe27..00000000000 --- a/pkg/occlient/imageStreams_test.go +++ /dev/null @@ -1,1028 +0,0 @@ -package occlient - -import ( - "fmt" - "reflect" - "testing" - - imagev1 "github.com/openshift/api/image/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ktesting "k8s.io/client-go/testing" -) - -func TestGetImageStream(t *testing.T) { - tests := []struct { - name string - imageNS string - imageName string - imageTag string - wantErr bool - want *imagev1.ImageStream - wantActionsCnt int - }{ - { - name: "Case: Valid request for imagestream of latest version and not namespace qualified", - imageNS: "", - imageName: "foo", - imageTag: "latest", - want: fakeImageStream("foo", "testing", []string{"latest"}), - wantActionsCnt: 1, - }, - { - name: "Case: Valid explicit request for specific namespace qualified imagestream of specific version", - imageNS: "openshift", - imageName: "foo", - imageTag: "latest", - want: fakeImageStream("foo", "openshift", []string{"latest", "3.5"}), - wantActionsCnt: 1, - }, - { - name: "Case: Valid request for specific imagestream of specific version not in current namespace", - imageNS: "", - imageName: "foo", - imageTag: "3.5", - want: fakeImageStream("foo", "openshift", []string{"latest", "3.5"}), - wantActionsCnt: 1, // Ideally supposed to be 2 but bcoz prependreactor is not parameter sensitive, the way it is mocked makes it 1 - }, - { - name: "Case: Invalid request for non-current and non-openshift namespace imagestream/Non-existant imagestream", - imageNS: "foo", - imageName: "bar", - imageTag: "3.5", - wantErr: true, - wantActionsCnt: 1, - }, - { - name: "Case: Request for non-existant tag", - imageNS: "", - imageName: "foo", - imageTag: "3.6", - wantErr: true, - wantActionsCnt: 2, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - fkclient.Namespace = "testing" - openshiftIS := fakeImageStream(tt.imageName, "openshift", []string{"latest", "3.5"}) - currentNSIS := fakeImageStream(tt.imageName, "testing", []string{"latest"}) - - fkclientset.ImageClientset.PrependReactor("get", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - if tt.imageNS == "" { - if isTagInImageStream(*fakeImageStream("foo", "testing", []string{"latest"}), tt.imageTag) { - return true, currentNSIS, nil - } else if isTagInImageStream(*fakeImageStream("foo", "openshift", []string{"latest", "3.5"}), tt.imageTag) { - return true, openshiftIS, nil - } - return true, nil, fmt.Errorf("Requested imagestream %s with tag %s not found", tt.imageName, tt.imageTag) - } - if tt.imageNS == "testing" { - return true, currentNSIS, nil - } - if tt.imageNS == "openshift" { - return true, openshiftIS, nil - } - return true, nil, fmt.Errorf("Requested imagestream %s with tag %s not found", tt.imageName, tt.imageTag) - }) - - got, err := fkclient.GetImageStream(tt.imageNS, tt.imageName, tt.imageTag) - if len(fkclientset.ImageClientset.Actions()) != tt.wantActionsCnt { - t.Errorf("expected %d ImageClientset.Actions() in GetImageStream, got %v", tt.wantActionsCnt, fkclientset.ImageClientset.Actions()) - } - if !tt.wantErr == (err != nil) { - t.Errorf("\nclient.GetImageStream(imageNS, imageName, imageTag) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetImageStream() = %#v, want %#v and the current project name is %s\n\n", got, tt, fkclient.GetCurrentProjectName()) - } - }) - } -} - -func TestListImageStreams(t *testing.T) { - - type args struct { - name string - namespace string - } - - tests := []struct { - name string - args args - want []imagev1.ImageStream - wantErr bool - }{ - { - name: "case 1: testing a valid imagestream", - args: args{ - name: "ruby", - namespace: "testing", - }, - want: []imagev1.ImageStream{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Namespace: "testing", - }, - Status: imagev1.ImageStreamStatus{ - Tags: []imagev1.NamedTagEventList{ - { - Tag: "latest", - Items: []imagev1.TagEvent{ - { - DockerImageReference: "example/ruby:latest", - Generation: 1, - Image: "sha256:9579a93ee", - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - - // TODO: Currently fails. Enable once fixed - // { - // name: "case 2: empty namespace", - // args: args{ - // name: "ruby", - // namespace: "", - // }, - // wantErr: true, - // }, - - // { - // name: "case 3: empty name", - // args: args{ - // name: "", - // namespace: "testing", - // }, - // wantErr: true, - // }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - client, fkclientset := FakeNew() - - fkclientset.ImageClientset.PrependReactor("list", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStreams(tt.args.name, tt.args.namespace), nil - }) - - got, err := client.ListImageStreams(tt.args.namespace) - - if (err != nil) != tt.wantErr { - t.Errorf("ListImageStreams() error = %#v, wantErr %#v", err, tt.wantErr) - return - } - - if len(fkclientset.ImageClientset.Actions()) != 1 { - t.Errorf("expected 1 action in ListImageStreams got: %v", fkclientset.ImageClientset.Actions()) - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ListImageStreams() = %#v, want %#v", got, tt.want) - } - - }) - } -} - -func TestGetPortsFromBuilderImage(t *testing.T) { - - type args struct { - componentType string - } - tests := []struct { - name string - imageNamespace string - args args - want []string - wantErr bool - }{ - { - name: "component type: nodejs", - imageNamespace: "openshift", - args: args{componentType: "nodejs"}, - want: []string{"8080/TCP"}, - wantErr: false, - }, - { - name: "component type: php", - imageNamespace: "openshift", - args: args{componentType: "php"}, - want: []string{"8080/TCP", "8443/TCP"}, - wantErr: false, - }, - { - name: "component type: is empty", - imageNamespace: "openshift", - args: args{componentType: ""}, - want: []string{}, - wantErr: true, - }, - { - name: "component type: is invalid", - imageNamespace: "openshift", - args: args{componentType: "abc"}, - want: []string{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - fkclient.Namespace = "testing" - // Fake getting image stream - fkclientset.ImageClientset.PrependReactor("get", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStream(tt.args.componentType, tt.imageNamespace, []string{"latest"}), nil - }) - - fkclientset.ImageClientset.PrependReactor("get", "imagestreamimages", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStreamImage(tt.args.componentType, tt.want, ""), nil - }) - got, err := fkclient.GetPortsFromBuilderImage(tt.args.componentType) - if (err != nil) != tt.wantErr { - t.Errorf("Client.GetPortsFromBuilderImage() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !sliceEqual(got, tt.want) { - t.Errorf("Client.GetPortsFromBuilderImage() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getS2IMetaInfoFromBuilderImg(t *testing.T) { - tests := []struct { - name string - imageStreamImage *imagev1.ImageStreamImage - want S2IPaths - wantErr bool - }{ - { - name: "Case 1: Valid nodejs test case with image protocol access", - imageStreamImage: fakeImageStreamImage( - "nodejs", - []string{"8080/tcp"}, - `{ - "kind": "DockerImage", - "apiVersion": "1.0", - "Id": "sha256:93de1230c12b512ebbaf28b159f450a44c632eda06bdc0754236f403f5876234", - "Created": "2018-10-19T15:43:13Z", - "ContainerConfig": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"/bin/sh\" \"-c\" \"${STI_SCRIPTS_PATH}/usage\"]" - ], - "Image": "sha256:d353b3f467c2d2ff59a1e09bb91cff1c493aedd6c8041ebb273346e892f8ee85", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "image:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "DockerVersion": "18.06.0-ce", - "Config": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "${STI_SCRIPTS_PATH}/usage" - ], - "Image": "57a00ab03a3f8c3af19e91c284e0c499b16d5115c925aa845b5d14eace949c34", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "image:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "Architecture": "amd64", - "Size": 221580439 - }`, - ), - want: S2IPaths{ - ScriptsPathProtocol: "image://", - ScriptsPath: "/usr/libexec/s2i", - SrcOrBinPath: "/tmp", - DeploymentDir: "/opt/app-root/src", - WorkingDir: "/opt/app-root/src", - SrcBackupPath: "/opt/app-root/src-backup", - BuilderImgName: "bucharestgold/centos7-s2i-nodejs", - }, - wantErr: false, - }, - { - name: "Case 2: Valid nodejs test case with file protocol access", - imageStreamImage: fakeImageStreamImage( - "nodejs", - []string{"8080/tcp"}, - `{ - "kind": "DockerImage", - "apiVersion": "1.0", - "Id": "sha256:93de1230c12b512ebbaf28b159f450a44c632eda06bdc0754236f403f5876234", - "Created": "2018-10-19T15:43:13Z", - "ContainerConfig": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=file:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"/bin/sh\" \"-c\" \"${STI_SCRIPTS_PATH}/usage\"]" - ], - "Image": "sha256:d353b3f467c2d2ff59a1e09bb91cff1c493aedd6c8041ebb273346e892f8ee85", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "file:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "file:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "DockerVersion": "18.06.0-ce", - "Config": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "${STI_SCRIPTS_PATH}/usage" - ], - "Image": "57a00ab03a3f8c3af19e91c284e0c499b16d5115c925aa845b5d14eace949c34", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "file:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "Architecture": "amd64", - "Size": 221580439 - }`, - ), - want: S2IPaths{ - ScriptsPathProtocol: "file://", - ScriptsPath: "/usr/libexec/s2i", - SrcOrBinPath: "/tmp", - DeploymentDir: "/opt/app-root/src", - WorkingDir: "/opt/app-root/src", - SrcBackupPath: "/opt/app-root/src-backup", - BuilderImgName: "bucharestgold/centos7-s2i-nodejs", - }, - wantErr: false, - }, - { - name: "Case 3: Valid nodejs test case with http(s) protocol access", - imageStreamImage: fakeImageStreamImage( - "nodejs", - []string{"8080/tcp"}, - `{ - "kind": "DockerImage", - "apiVersion": "1.0", - "Id": "sha256:93de1230c12b512ebbaf28b159f450a44c632eda06bdc0754236f403f5876234", - "Created": "2018-10-19T15:43:13Z", - "ContainerConfig": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=http(s):///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"/bin/sh\" \"-c\" \"${STI_SCRIPTS_PATH}/usage\"]" - ], - "Image": "sha256:d353b3f467c2d2ff59a1e09bb91cff1c493aedd6c8041ebb273346e892f8ee85", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "http(s):///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "DockerVersion": "18.06.0-ce", - "Config": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "${STI_SCRIPTS_PATH}/usage" - ], - "Image": "57a00ab03a3f8c3af19e91c284e0c499b16d5115c925aa845b5d14eace949c34", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "http(s):///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "Architecture": "amd64", - "Size": 221580439 - }`, - ), - want: S2IPaths{ - ScriptsPathProtocol: "http(s)://", - ScriptsPath: "http(s):///usr/libexec/s2i", - SrcOrBinPath: "/tmp", - DeploymentDir: "/opt/app-root/src", - WorkingDir: "/opt/app-root/src", - SrcBackupPath: "/opt/app-root/src-backup", - BuilderImgName: "bucharestgold/centos7-s2i-nodejs", - }, - wantErr: false, - }, - { - name: "Case 4: Valid openjdk test case with image(s) protocol access", - imageStreamImage: fakeImageStreamImage( - "redhat-openjdk18-openshift", - []string{"8080/tcp"}, - `{ - "kind": "DockerImage", - "apiVersion": "1.0", - "Id": "sha256:93de1230c12b512ebbaf28b159f450a44c632eda06bdc0754236f403f5876234", - "Created": "2018-10-19T15:43:13Z", - "ContainerConfig": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=http(s):///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"/bin/sh\" \"-c\" \"${STI_SCRIPTS_PATH}/usage\"]" - ], - "Image": "sha256:d353b3f467c2d2ff59a1e09bb91cff1c493aedd6c8041ebb273346e892f8ee85", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "image:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "DockerVersion": "18.06.0-ce", - "Config": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "${STI_SCRIPTS_PATH}/usage" - ], - "Image": "57a00ab03a3f8c3af19e91c284e0c499b16d5115c925aa845b5d14eace949c34", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "image:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "Architecture": "amd64", - "Size": 221580439 - }`, - ), - want: S2IPaths{ - ScriptsPathProtocol: "image://", - ScriptsPath: "/usr/libexec/s2i", - SrcOrBinPath: "/tmp", - DeploymentDir: "/opt/app-root/src", - WorkingDir: "/opt/app-root/src", - SrcBackupPath: "/opt/app-root/src-backup", - BuilderImgName: "bucharestgold/centos7-s2i-nodejs", - }, - wantErr: false, - }, - { - name: "Case 5: Inalid nodejs test case with invalid protocol access", - imageStreamImage: fakeImageStreamImage( - "nodejs", - []string{"8080/tcp"}, - `{ - "kind": "DockerImage", - "apiVersion": "1.0", - "Id": "sha256:93de1230c12b512ebbaf28b159f450a44c632eda06bdc0754236f403f5876234", - "Created": "2018-10-19T15:43:13Z", - "ContainerConfig": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=something:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"/bin/sh\" \"-c\" \"${STI_SCRIPTS_PATH}/usage\"]" - ], - "Image": "sha256:d353b3f467c2d2ff59a1e09bb91cff1c493aedd6c8041ebb273346e892f8ee85", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "something:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "DockerVersion": "18.06.0-ce", - "Config": { - "Hostname": "8911994b686d", - "User": "1001", - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "SUMMARY=Platform for building and running Node.js 10.12.0 applications", - "DESCRIPTION=Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "STI_SCRIPTS_URL=image:///usr/libexec/s2i", - "STI_SCRIPTS_PATH=/usr/libexec/s2i", - "APP_ROOT=/opt/app-root", - "HOME=/opt/app-root/src", - "BASH_ENV=/opt/app-root/etc/scl_enable", - "ENV=/opt/app-root/etc/scl_enable", - "PROMPT_COMMAND=. /opt/app-root/etc/scl_enable", - "NODEJS_SCL=rh-nodejs8", - "NPM_RUN=start", - "NODE_VERSION=10.12.0", - "NPM_VERSION=6.4.1", - "NODE_LTS=false", - "NPM_CONFIG_LOGLEVEL=info", - "NPM_CONFIG_PREFIX=/opt/app-root/src/.npm-global", - "NPM_CONFIG_TARBALL=/usr/share/node/node-v10.12.0-headers.tar.gz", - "DEBUG_PORT=5858" - ], - "Cmd": [ - "/bin/sh", - "-c", - "${STI_SCRIPTS_PATH}/usage" - ], - "Image": "57a00ab03a3f8c3af19e91c284e0c499b16d5115c925aa845b5d14eace949c34", - "WorkingDir": "/opt/app-root/src", - "Entrypoint": [ - "container-entrypoint" - ], - "Labels": { - "com.redhat.component": "s2i-base-container", - "com.redhat.deployments-dir": "/opt/app-root/src", - "com.redhat.dev-mode": "DEV_MODE:false", - "com.rehdat.dev-mode.port": "DEBUG_PORT:5858", - "description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.description": "Node.js available as docker container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.", - "io.k8s.display-name": "Node.js 10.12.0", - "io.openshift.builder-version": "\"190ef14\"", - "io.openshift.expose-services": "8080:http", - "io.openshift.s2i.scripts-url": "something:///usr/libexec/s2i", - "io.openshift.tags": "builder,nodejs,nodejs-10.12.0", - "io.s2i.scripts-url": "image:///usr/libexec/s2i", - "maintainer": "Lance Ball \u003clball@redhat.com\u003e", - "name": "bucharestgold/centos7-s2i-nodejs", - "org.label-schema.build-date": "20180804", - "org.label-schema.license": "GPLv2", - "org.label-schema.name": "CentOS Base Image", - "org.label-schema.schema-version": "1.0", - "org.label-schema.vendor": "CentOS", - "release": "1", - "summary": "Platform for building and running Node.js 10.12.0 applications", - "version": "10.12.0" - } - }, - "Architecture": "amd64", - "Size": 221580439 - }`, - ), - want: S2IPaths{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s2iPaths, err := getS2IMetaInfoFromBuilderImg(tt.imageStreamImage) - if !reflect.DeepEqual(tt.want, s2iPaths) { - t.Errorf("s2i paths are not matching with expected values, expected: %v, got %v", tt.want, s2iPaths) - } - if !tt.wantErr == (err != nil) { - t.Errorf(" GetS2IScriptsPathFromBuilderImg() unexpected error %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/occlient/occlient.go b/pkg/occlient/occlient.go index 22d97eed6c2..54e3cc3ca75 100644 --- a/pkg/occlient/occlient.go +++ b/pkg/occlient/occlient.go @@ -6,14 +6,11 @@ import ( "fmt" "io" "net" - "path/filepath" - "reflect" "strings" "time" "github.com/pkg/errors" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/preference" "github.com/openshift/odo/pkg/util" @@ -26,15 +23,11 @@ import ( routeclientset "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1" userclientset "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1" - // api resource types - appsv1 "github.com/openshift/api/apps/v1" - imagev1 "github.com/openshift/api/image/v1" oauthv1client "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/version" - "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/clientcmd" "k8s.io/klog" ) @@ -44,7 +37,6 @@ type CreateArgs struct { Name string SourcePath string SourceRef string - SourceType config.SrcType ImageName string EnvVars []string Ports []string @@ -58,73 +50,10 @@ type CreateArgs struct { } const ( - OcUpdateTimeout = 5 * time.Minute - OpenShiftNameSpace = "openshift" - waitForComponentDeletionTimeout = 120 * time.Second - // timeout for waiting for project deletion waitForProjectDeletionTimeOut = 3 * time.Minute - - // The length of the string to be generated for names of resources - nameLength = 5 - - // SupervisordVolumeName Create a custom name and (hope) that users don't use the *exact* same name in their deploymentConfig - SupervisordVolumeName = "odo-supervisord-shared-data" - - // EnvS2IScriptsURL is an env var exposed to https://github.com/openshift/odo-init-image/blob/master/assemble-and-restart to indicate location of s2i scripts in this case assemble script - EnvS2IScriptsURL = "ODO_S2I_SCRIPTS_URL" - - // EnvS2IScriptsProtocol is an env var exposed to https://github.com/openshift/odo-init-image/blob/master/assemble-and-restart to indicate the way to access location of s2i scripts indicated by ${${EnvS2IScriptsURL}} above - EnvS2IScriptsProtocol = "ODO_S2I_SCRIPTS_PROTOCOL" - - // EnvS2ISrcOrBinPath is an env var exposed by s2i to indicate where the builder image expects the component source or binary to reside - EnvS2ISrcOrBinPath = "ODO_S2I_SRC_BIN_PATH" - - // EnvS2ISrcBackupDir is the env var that points to the directory that holds a backup of component source - // This is required bcoz, s2i assemble script moves(hence deletes contents) the contents of $ODO_S2I_SRC_BIN_PATH to $APP_ROOT during which $APP_DIR alo needs to be empty so that mv doesn't complain pushing to an already exisiting dir with same name - EnvS2ISrcBackupDir = "ODO_SRC_BACKUP_DIR" - - // S2IScriptsURLLabel S2I script location Label name - // Ref: https://docs.openshift.com/enterprise/3.2/creating_images/s2i.html#build-process - S2IScriptsURLLabel = "io.openshift.s2i.scripts-url" - - // S2IBuilderImageName is the S2I builder image name - S2IBuilderImageName = "name" - - // S2ISrcOrBinLabel is the label that provides, path where S2I expects component source or binary - S2ISrcOrBinLabel = "io.openshift.s2i.destination" - - // EnvS2IBuilderImageName is the label that provides the name of builder image in component - EnvS2IBuilderImageName = "ODO_S2I_BUILDER_IMG" - - // EnvS2IDeploymentDir is an env var exposed to https://github.com/openshift/odo-init-image/blob/master/assemble-and-restart to indicate s2i deployment directory - EnvS2IDeploymentDir = "ODO_S2I_DEPLOYMENT_DIR" - - // DefaultS2ISrcOrBinPath is the default path where S2I expects source/binary artifacts in absence of $S2ISrcOrBinLabel in builder image - // Ref: https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md#required-image-contents - DefaultS2ISrcOrBinPath = "/tmp" - - // DefaultS2ISrcBackupDir is the default path where odo backs up the component source - DefaultS2ISrcBackupDir = "/opt/app-root/src-backup" - - // EnvS2IWorkingDir is an env var to odo-init-image assemble-and-restart.sh to indicate to it the s2i working directory - EnvS2IWorkingDir = "ODO_S2I_WORKING_DIR" - - DefaultAppRootDir = "/opt/app-root" ) -// S2IPaths is a struct that will hold path to S2I scripts and the protocol indicating access to them, component source/binary paths, artifacts deployments directory -// These are passed as env vars to component pod -type S2IPaths struct { - ScriptsPathProtocol string - ScriptsPath string - SrcOrBinPath string - DeploymentDir string - WorkingDir string - SrcBackupPath string - BuilderImgName string -} - // UpdateComponentParams serves the purpose of holding the arguments to a component update request type UpdateComponentParams struct { // CommonObjectMeta is the object meta containing the labels and annotations expected for the new deployment @@ -133,12 +62,6 @@ type UpdateComponentParams struct { ResourceLimits corev1.ResourceRequirements // EnvVars to be exposed EnvVars []corev1.EnvVar - // ExistingDC is the dc of the existing component that is requested for an update - ExistingDC *appsv1.DeploymentConfig - // DcRollOutWaitCond holds the logic to wait for dc with requested updates to be applied - DcRollOutWaitCond dcRollOutWait - // ImageMeta describes the image to be used in dc(builder image for local/binary and built component image for git deployments) - ImageMeta CommonImageMeta // StorageToBeMounted describes the storage to be mounted // storagePath is the key of the map, the generatedPVC is the value of the map StorageToBeMounted map[string]*corev1.PersistentVolumeClaim @@ -147,14 +70,6 @@ type UpdateComponentParams struct { StorageToBeUnMounted map[string]string } -// S2IDeploymentsDir is a set of possible S2I labels that provides S2I deployments directory -// This label is not uniform across different builder images. This slice is expected to grow as odo adds support to more component types and/or the respective builder images use different labels -var S2IDeploymentsDir = []string{ - "com.redhat.deployments-dir", - "org.jboss.deployments-dir", - "org.jboss.container.deployments-dir", -} - // errorMsg is the message for user when invalid configuration error occurs const errorMsg = ` Please login to your server: @@ -345,824 +260,6 @@ func isServerUp(server string) bool { return true } -// addLabelsToArgs adds labels from map to args as a new argument in format that oc requires -// --labels label1=value1,label2=value2 -func addLabelsToArgs(labels map[string]string, args []string) []string { - if labels != nil { - var labelsString []string - for key, value := range labels { - labelsString = append(labelsString, fmt.Sprintf("%s=%s", key, value)) - } - args = append(args, "--labels") - args = append(args, strings.Join(labelsString, ",")) - } - - return args -} - -func getAppRootVolumeName(dcName string) string { - return fmt.Sprintf("%s-s2idata", dcName) -} - -// NewAppS2I is only used with "Git" as we need Build -// gitURL is the url of the git repo -// inputPorts is the array containing the string port values -// envVars is the array containing the string env var values -func (c *Client) NewAppS2I(params CreateArgs, commonObjectMeta metav1.ObjectMeta) error { - klog.V(3).Infof("Using BuilderImage: %s", params.ImageName) - imageNS, imageName, imageTag, _, err := ParseImageName(params.ImageName) - if err != nil { - return errors.Wrap(err, "unable to parse image name") - } - imageStream, err := c.GetImageStream(imageNS, imageName, imageTag) - if err != nil { - return errors.Wrap(err, "unable to retrieve ImageStream for NewAppS2I") - } - /* - Set imageNS to the commonObjectMeta.Namespace of above fetched imagestream because, the commonObjectMeta.Namespace passed here can potentially be emptystring - in which case, GetImageStream function resolves to correct commonObjectMeta.Namespace in accordance with priorities in GetImageStream - */ - - imageNS = imageStream.ObjectMeta.Namespace - klog.V(3).Infof("Using imageNS: %s", imageNS) - - imageStreamImage, err := c.GetImageStreamImage(imageStream, imageTag) - if err != nil { - return errors.Wrapf(err, "unable to create s2i app for %s", commonObjectMeta.Name) - } - - var containerPorts []corev1.ContainerPort - if len(params.Ports) == 0 { - containerPorts, err = getExposedPortsFromISI(imageStreamImage) - if err != nil { - return errors.Wrapf(err, "unable to get exposed ports for %s:%s", imageName, imageTag) - } - } else { - if err != nil { - return errors.Wrapf(err, "unable to create s2i app for %s", commonObjectMeta.Name) - } - containerPorts, err = util.GetContainerPortsFromStrings(params.Ports) - if err != nil { - return errors.Wrapf(err, "unable to get container ports from %v", params.Ports) - } - } - - inputEnvVars, err := kclient.GetInputEnvVarsFromStrings(params.EnvVars) - if err != nil { - return errors.Wrapf(err, "error adding environment variables to the container") - } - - // generate and create ImageStream - is := imagev1.ImageStream{ - ObjectMeta: commonObjectMeta, - } - _, err = c.imageClient.ImageStreams(c.Namespace).Create(context.TODO(), &is, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to create ImageStream for %s", commonObjectMeta.Name) - } - - // if gitURL is not set, error out - if params.SourcePath == "" { - return errors.New("unable to create buildSource with empty gitURL") - } - - // Deploy BuildConfig to build the container with Git - buildConfig, err := c.CreateBuildConfig(commonObjectMeta, params.ImageName, params.SourcePath, params.SourceRef, inputEnvVars) - if err != nil { - return errors.Wrapf(err, "unable to deploy BuildConfig for %s", commonObjectMeta.Name) - } - - // Generate and create the DeploymentConfig - dc := generateGitDeploymentConfig(commonObjectMeta, buildConfig.Spec.Output.To.Name, containerPorts, inputEnvVars, params.Resources) - err = addOrRemoveVolumeAndVolumeMount(c, &dc, params.StorageToBeMounted, nil) - if err != nil { - return errors.Wrapf(err, "failed to mount and unmount pvc to dc") - } - createdDC, err := c.appsClient.DeploymentConfigs(c.Namespace).Create(context.TODO(), &dc, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to create DeploymentConfig for %s", commonObjectMeta.Name) - } - - ownerReference := GenerateOwnerReference(createdDC) - - // update the owner references for the new storage - for _, storage := range params.StorageToBeMounted { - err := c.GetKubeClient().GetAndUpdateStorageOwnerReference(storage, ownerReference) - if err != nil { - return errors.Wrapf(err, "unable to update owner reference of storage") - } - } - - // Create a service - commonObjectMeta.SetOwnerReferences(append(commonObjectMeta.GetOwnerReferences(), ownerReference)) - service := corev1.Service{ - ObjectMeta: commonObjectMeta, - Spec: generateServiceSpec(commonObjectMeta, dc.Spec.Template.Spec.Containers[0].Ports), - } - svc, err := c.GetKubeClient().CreateService(service) - if err != nil { - return errors.Wrapf(err, "unable to create Service for %s", commonObjectMeta.Name) - } - - // Create secret(s) - err = c.GetKubeClient().CreateSecrets(params.Name, commonObjectMeta, svc, ownerReference) - - return err -} - -// getS2ILabelValue returns the requested S2I label value from the passed set of labels attached to builder image -// and the hard coded possible list(the labels are not uniform across different builder images) of expected labels -func getS2ILabelValue(labels map[string]string, expectedLabelsSet []string) string { - for _, label := range expectedLabelsSet { - if retVal, ok := labels[label]; ok { - return retVal - } - } - return "" -} - -// uniqueAppendOrOverwriteEnvVars appends/overwrites the passed existing list of env vars with the elements from the to-be appended passed list of envs -func uniqueAppendOrOverwriteEnvVars(existingEnvs []corev1.EnvVar, envVars ...corev1.EnvVar) []corev1.EnvVar { - mapExistingEnvs := make(map[string]corev1.EnvVar) - var retVal []corev1.EnvVar - - // Convert slice of existing env vars to map to check for existence - for _, envVar := range existingEnvs { - mapExistingEnvs[envVar.Name] = envVar - } - - // For each new envVar to be appended, Add(if envVar with same name doesn't already exist) / overwrite(if envVar with same name already exists) the map - for _, newEnvVar := range envVars { - mapExistingEnvs[newEnvVar.Name] = newEnvVar - } - - // append the values to the final slice - // don't loop because we need them in order - for _, envVar := range existingEnvs { - if val, ok := mapExistingEnvs[envVar.Name]; ok { - retVal = append(retVal, val) - delete(mapExistingEnvs, envVar.Name) - } - } - - for _, newEnvVar := range envVars { - if val, ok := mapExistingEnvs[newEnvVar.Name]; ok { - retVal = append(retVal, val) - } - } - - return retVal -} - -// deleteEnvVars deletes the passed env var from the list of passed env vars -// Parameters: -// existingEnvs: Slice of existing env vars -// envTobeDeleted: The name of env var to be deleted -// Returns: -// slice of env vars with delete reflected -func deleteEnvVars(existingEnvs []corev1.EnvVar, envTobeDeleted string) []corev1.EnvVar { - retVal := make([]corev1.EnvVar, len(existingEnvs)) - copy(retVal, existingEnvs) - for ind, envVar := range retVal { - if envVar.Name == envTobeDeleted { - retVal = append(retVal[:ind], retVal[ind+1:]...) - break - } - } - return retVal -} - -// BootstrapSupervisoredS2I uses S2I (Source To Image) to inject Supervisor into the application container. -// Odo uses https://github.com/ochinchina/supervisord which is pre-built in a ready-to-deploy InitContainer. -// The supervisord binary is copied over to the application container using a temporary volume and overrides -// the built-in S2I run function for the supervisord run command instead. -// -// Supervisor keeps the pod running (as PID 1), so you it is possible to trigger assembly script inside running pod, -// and than restart application using Supervisor without need to restart the container/Pod. -// -func (c *Client) BootstrapSupervisoredS2I(params CreateArgs, commonObjectMeta metav1.ObjectMeta) error { - imageNS, imageName, imageTag, _, err := ParseImageName(params.ImageName) - - if err != nil { - return errors.Wrap(err, "unable to create new s2i git build ") - } - imageStream, err := c.GetImageStream(imageNS, imageName, imageTag) - if err != nil { - return errors.Wrap(err, "Failed to bootstrap supervisored") - } - /* - Set imageNS to the commonObjectMeta.Namespace of above fetched imagestream because, the commonObjectMeta.Namespace passed here can potentially be emptystring - in which case, GetImageStream function resolves to correct commonObjectMeta.Namespace in accordance with priorities in GetImageStream - */ - imageNS = imageStream.ObjectMeta.Namespace - - imageStreamImage, err := c.GetImageStreamImage(imageStream, imageTag) - if err != nil { - return errors.Wrap(err, "unable to bootstrap supervisord") - } - var containerPorts []corev1.ContainerPort - containerPorts, err = util.GetContainerPortsFromStrings(params.Ports) - if err != nil { - return errors.Wrapf(err, "unable to get container ports from %v", params.Ports) - } - - inputEnvs, err := kclient.GetInputEnvVarsFromStrings(params.EnvVars) - if err != nil { - return errors.Wrapf(err, "error adding environment variables to the container") - } - - // generate and create ImageStream - is := imagev1.ImageStream{ - ObjectMeta: commonObjectMeta, - } - _, err = c.imageClient.ImageStreams(c.Namespace).Create(context.TODO(), &is, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to create ImageStream for %s", commonObjectMeta.Name) - } - - commonImageMeta := CommonImageMeta{ - Name: imageName, - Tag: imageTag, - Namespace: imageNS, - Ports: containerPorts, - } - - // Extract s2i scripts path and path type from imagestream image - //s2iScriptsProtocol, s2iScriptsURL, s2iSrcOrBinPath, s2iDestinationDir - s2iPaths, err := getS2IMetaInfoFromBuilderImg(imageStreamImage) - if err != nil { - return errors.Wrap(err, "unable to bootstrap supervisord") - } - - // Append s2i related parameters extracted above to env - inputEnvs = injectS2IPaths(inputEnvs, s2iPaths) - - if params.SourceType == config.LOCAL { - inputEnvs = uniqueAppendOrOverwriteEnvVars( - inputEnvs, - corev1.EnvVar{ - Name: EnvS2ISrcBackupDir, - Value: s2iPaths.SrcBackupPath, - }, - ) - } - - // Generate the DeploymentConfig that will be used. - dc := generateSupervisordDeploymentConfig( - commonObjectMeta, - commonImageMeta, - inputEnvs, - []corev1.EnvFromSource{}, - params.Resources, - ) - if len(inputEnvs) != 0 { - err = updateEnvVar(&dc, inputEnvs) - if err != nil { - return errors.Wrapf(err, "unable to add env vars to the container") - } - } - - addInitVolumesToDC(&dc, commonObjectMeta.Name, s2iPaths.DeploymentDir) - - err = addOrRemoveVolumeAndVolumeMount(c, &dc, params.StorageToBeMounted, nil) - if err != nil { - return errors.Wrapf(err, "failed to mount and unmount pvc to dc") - } - - createdDC, err := c.appsClient.DeploymentConfigs(c.Namespace).Create(context.TODO(), &dc, metav1.CreateOptions{FieldManager: kclient.FieldManager}) - if err != nil { - return errors.Wrapf(err, "unable to create DeploymentConfig for %s", commonObjectMeta.Name) - } - - var jsonDC []byte - jsonDC, _ = json.Marshal(createdDC) - klog.V(5).Infof("Created new DeploymentConfig:\n%s\n", string(jsonDC)) - - ownerReference := GenerateOwnerReference(createdDC) - - // update the owner references for the new storage - for _, storage := range params.StorageToBeMounted { - err := c.GetKubeClient().GetAndUpdateStorageOwnerReference(storage, ownerReference) - if err != nil { - return errors.Wrapf(err, "unable to update owner reference of storage") - } - } - - // Create a service - commonObjectMeta.SetOwnerReferences(append(commonObjectMeta.GetOwnerReferences(), ownerReference)) - service := corev1.Service{ - ObjectMeta: commonObjectMeta, - Spec: generateServiceSpec(commonObjectMeta, dc.Spec.Template.Spec.Containers[0].Ports), - } - svc, err := c.GetKubeClient().CreateService(service) - if err != nil { - return errors.Wrapf(err, "unable to create Service for %s", commonObjectMeta.Name) - } - - err = c.GetKubeClient().CreateSecrets(params.Name, commonObjectMeta, svc, ownerReference) - if err != nil { - return err - } - - // Setup PVC. - _, err = c.CreatePVC(getAppRootVolumeName(commonObjectMeta.Name), "1Gi", commonObjectMeta.Labels, ownerReference) - if err != nil { - return errors.Wrapf(err, "unable to create PVC for %s", commonObjectMeta.Name) - } - - return nil -} - -// updateEnvVar updates the environmental variables to the container in the DC -// dc is the deployment config to be updated -// envVars is the array containing the corev1.EnvVar values -func updateEnvVar(dc *appsv1.DeploymentConfig, envVars []corev1.EnvVar) error { - numContainers := len(dc.Spec.Template.Spec.Containers) - if numContainers != 1 { - return fmt.Errorf("expected exactly one container in Deployment Config %v, got %v", dc.Name, numContainers) - } - - dc.Spec.Template.Spec.Containers[0].Env = envVars - return nil -} - -// Define a function that is meant to update a DC in place -type dcStructUpdater func(dc *appsv1.DeploymentConfig) error -type dcRollOutWait func(*appsv1.DeploymentConfig, int64) bool - -// PatchCurrentDC "patches" the current DeploymentConfig with a new one -// however... we make sure that configurations such as: -// - volumes -// - environment variables -// are correctly copied over / consistent without an issue. -// if prePatchDCHandler is specified (meaning not nil), then it's applied -// as the last action before the actual call to the Kubernetes API thus giving us the chance -// to perform arbitrary updates to a DC before it's finalized for patching -// isGit indicates if the deployment config belongs to a git component or a local/binary component -func (c *Client) PatchCurrentDC(dc appsv1.DeploymentConfig, prePatchDCHandler dcStructUpdater, existingCmpContainer corev1.Container, ucp UpdateComponentParams, isGit bool) error { - - name := ucp.CommonObjectMeta.Name - currentDC := ucp.ExistingDC - modifiedDC := *currentDC - - waitCond := ucp.DcRollOutWaitCond - - // copy the any remaining volumes and volume mounts - copyVolumesAndVolumeMounts(dc, currentDC, existingCmpContainer) - - if prePatchDCHandler != nil { - err := prePatchDCHandler(&dc) - if err != nil { - return errors.Wrapf(err, "Unable to correctly update dc %s using the specified prePatch handler", name) - } - } - - // now mount/unmount the newly created/deleted pvc - err := addOrRemoveVolumeAndVolumeMount(c, &dc, ucp.StorageToBeMounted, ucp.StorageToBeUnMounted) - if err != nil { - return err - } - - // Replace the current spec with the new one - modifiedDC.Spec = dc.Spec - - // Replace the old annotations with the new ones too - // the reason we do this is because Kubernetes handles metadata such as resourceVersion - // that should not be overridden. - modifiedDC.ObjectMeta.Annotations = dc.ObjectMeta.Annotations - modifiedDC.ObjectMeta.Labels = dc.ObjectMeta.Labels - - // Update the current one that's deployed with the new Spec. - // despite the "patch" function name, we use update since `.Patch` requires - // use to define each and every object we must change. Updating makes it easier. - updatedDc, err := c.appsClient.DeploymentConfigs(c.Namespace).Update(context.TODO(), &modifiedDC, metav1.UpdateOptions{FieldManager: kclient.FieldManager}) - - if err != nil { - return errors.Wrapf(err, "unable to update DeploymentConfig %s", name) - } - - // if isGit is true, the DC belongs to a git component - // since build happens for every push in case of git and a new image is pushed, we need to wait - // so git oriented deployments, we start the deployment before waiting for it to be updated - if isGit { - _, err := c.StartDeployment(updatedDc.Name) - if err != nil { - return errors.Wrapf(err, "unable to start deployment") - } - } else { - // not a git oriented deployment, check before waiting - // we check after the update that the template in the earlier and the new dc are same or not - // if they are same, we don't wait as new deployment won't run and we will wait till timeout - // inspired from https://github.com/openshift/origin/blob/bb1b9b5223dd37e63790d99095eec04bfd52b848/pkg/apps/controller/deploymentconfig/deploymentconfig_controller.go#L609 - if reflect.DeepEqual(updatedDc.Spec.Template, currentDC.Spec.Template) { - return nil - } - currentDCBytes, errCurrent := json.Marshal(currentDC.Spec.Template) - updatedDCBytes, errUpdated := json.Marshal(updatedDc.Spec.Template) - if errCurrent != nil || errUpdated != nil { - return errors.Wrapf(err, "unable to unmarshal dc") - } - klog.V(3).Infof("going to wait for new deployment roll out because updatedDc Spec.Template: %v doesn't match currentDc Spec.Template: %v", string(updatedDCBytes), string(currentDCBytes)) - - } - - // We use the currentDC + 1 for the next revision.. We do NOT use the updated DC (see above code) - // as the "Update" function will not update the Status.LatestVersion quick enough... so we wait until - // the current revision + 1 is available. - desiredRevision := currentDC.Status.LatestVersion + 1 - - // Watch / wait for deploymentconfig to update annotations - // importing "component" results in an import loop, so we do *not* use the constants here. - _, err = c.WaitAndGetDC(name, desiredRevision, OcUpdateTimeout, waitCond) - if err != nil { - return errors.Wrapf(err, "unable to wait for DeploymentConfig %s to update", name) - } - - // update the owner references for the new storage - for _, storage := range ucp.StorageToBeMounted { - err := c.GetKubeClient().GetAndUpdateStorageOwnerReference(storage, GenerateOwnerReference(updatedDc)) - if err != nil { - return errors.Wrapf(err, "unable to update owner reference of storage") - } - } - - return nil -} - -// copies volumes and volume mounts from currentDC to dc, excluding the supervisord related ones -func copyVolumesAndVolumeMounts(dc appsv1.DeploymentConfig, currentDC *appsv1.DeploymentConfig, matchingContainer corev1.Container) { - // Append the existing VolumeMounts to the new DC. We use "range" and find the correct container rather than - // using .spec.Containers[0] *in case* the template ever changes and a new container has been added. - for index, container := range dc.Spec.Template.Spec.Containers { - // Find the container - if container.Name == matchingContainer.Name { - - // create a map of volume mount names for faster searching later - dcVolumeMountsMap := make(map[string]bool) - for _, volumeMount := range container.VolumeMounts { - dcVolumeMountsMap[volumeMount.Name] = true - } - - // Loop through all the volumes - for _, volume := range matchingContainer.VolumeMounts { - // If it's the supervisord volume, ignore it. - if volume.Name == SupervisordVolumeName { - continue - } else { - // check if we are appending the same volume mount again or not - if _, ok := dcVolumeMountsMap[volume.Name]; !ok { - dc.Spec.Template.Spec.Containers[index].VolumeMounts = append(dc.Spec.Template.Spec.Containers[index].VolumeMounts, volume) - } - } - } - - // Break out since we've succeeded in updating the container we were looking for - break - } - } - - // create a map of volume names for faster searching later - dcVolumeMap := make(map[string]bool) - for _, volume := range dc.Spec.Template.Spec.Volumes { - dcVolumeMap[volume.Name] = true - } - - // Now the same with Volumes, again, ignoring the supervisord volume. - for _, volume := range currentDC.Spec.Template.Spec.Volumes { - if volume.Name == SupervisordVolumeName { - continue - } else { - // check if we are appending the same volume again or not - if _, ok := dcVolumeMap[volume.Name]; !ok { - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes, volume) - } - } - } -} - -// UpdateDCToGit replaces / updates the current DeplomentConfig with the appropriate -// generated image from BuildConfig as well as the correct DeploymentConfig triggers for Git. -func (c *Client) UpdateDCToGit(ucp UpdateComponentParams, isDeleteSupervisordVolumes bool) (err error) { - - // Find the container (don't want to use .Spec.Containers[0] in case the user has modified the DC...) - existingCmpContainer, err := FindContainer(ucp.ExistingDC.Spec.Template.Spec.Containers, ucp.CommonObjectMeta.Name) - if err != nil { - return errors.Wrapf(err, "Unable to find container %s", ucp.CommonObjectMeta.Name) - } - - // Fail if blank - if ucp.ImageMeta.Name == "" { - return errors.New("UpdateDCToGit imageName cannot be blank") - } - - dc := generateGitDeploymentConfig(ucp.CommonObjectMeta, ucp.ImageMeta.Name, ucp.ImageMeta.Ports, ucp.EnvVars, &ucp.ResourceLimits) - - if isDeleteSupervisordVolumes { - // Patch the current DC - err = c.PatchCurrentDC( - dc, - removeTracesOfSupervisordFromDC, - existingCmpContainer, - ucp, - true, - ) - - if err != nil { - return errors.Wrapf(err, "unable to update the current DeploymentConfig %s", ucp.CommonObjectMeta.Name) - } - - // Cleanup after the supervisor - err = c.GetKubeClient().DeletePVC(getAppRootVolumeName(ucp.CommonObjectMeta.Name)) - if err != nil { - return errors.Wrapf(err, "unable to delete S2I data PVC from %s", ucp.CommonObjectMeta.Name) - } - } else { - err = c.PatchCurrentDC( - dc, - nil, - existingCmpContainer, - ucp, - true, - ) - } - - if err != nil { - return errors.Wrapf(err, "unable to update the current DeploymentConfig %s", ucp.CommonObjectMeta.Name) - } - - return nil -} - -// UpdateDCToSupervisor updates the current DeploymentConfig to a SupervisorD configuration. -// Parameters: -// commonObjectMeta: dc meta object -// componentImageType: type of builder image -// isToLocal: bool used to indicate if component is to be updated to local in which case a source backup dir will be injected into component env -// isCreatePVC bool used to indicate if a new supervisorD PVC should be created during the update -// Returns: -// errors if any or nil -func (c *Client) UpdateDCToSupervisor(ucp UpdateComponentParams, isToLocal bool, createPVC bool) error { - - existingCmpContainer, err := FindContainer(ucp.ExistingDC.Spec.Template.Spec.Containers, ucp.CommonObjectMeta.Name) - if err != nil { - return errors.Wrapf(err, "Unable to find container %s", ucp.CommonObjectMeta.Name) - } - - // Retrieve the namespace of the corresponding component image - imageStream, err := c.GetImageStream(ucp.ImageMeta.Namespace, ucp.ImageMeta.Name, ucp.ImageMeta.Tag) - if err != nil { - return errors.Wrap(err, "unable to get image stream for CreateBuildConfig") - } - ucp.ImageMeta.Namespace = imageStream.ObjectMeta.Namespace - - imageStreamImage, err := c.GetImageStreamImage(imageStream, ucp.ImageMeta.Tag) - if err != nil { - return errors.Wrap(err, "unable to bootstrap supervisord") - } - - s2iPaths, err := getS2IMetaInfoFromBuilderImg(imageStreamImage) - if err != nil { - return errors.Wrap(err, "unable to bootstrap supervisord") - } - - cmpContainer := ucp.ExistingDC.Spec.Template.Spec.Containers[0] - - // Append s2i related parameters extracted above to env - inputEnvs := injectS2IPaths(ucp.EnvVars, s2iPaths) - - if isToLocal { - inputEnvs = uniqueAppendOrOverwriteEnvVars( - inputEnvs, - corev1.EnvVar{ - Name: EnvS2ISrcBackupDir, - Value: s2iPaths.SrcBackupPath, - }, - ) - } else { - inputEnvs = deleteEnvVars(inputEnvs, EnvS2ISrcBackupDir) - } - - var dc appsv1.DeploymentConfig - // if createPVC is true then we need to create a supervisorD volume and generate a new deployment config - // needed for update from git to local/binary components - // if false, we just update the current deployment config - if createPVC { - // Generate the SupervisorD Config - dc = generateSupervisordDeploymentConfig( - ucp.CommonObjectMeta, - ucp.ImageMeta, - inputEnvs, - cmpContainer.EnvFrom, - &ucp.ResourceLimits, - ) - addInitVolumesToDC(&dc, ucp.CommonObjectMeta.Name, s2iPaths.DeploymentDir) - - ownerReference := GenerateOwnerReference(ucp.ExistingDC) - - // Setup PVC - _, err = c.CreatePVC(getAppRootVolumeName(ucp.CommonObjectMeta.Name), "1Gi", ucp.CommonObjectMeta.Labels, ownerReference) - if err != nil { - return errors.Wrapf(err, "unable to create PVC for %s", ucp.CommonObjectMeta.Name) - } - } else { - dc = updateSupervisorDeploymentConfig( - SupervisorDUpdateParams{ - ucp.ExistingDC.DeepCopy(), ucp.CommonObjectMeta, - ucp.ImageMeta, - inputEnvs, - cmpContainer.EnvFrom, - &ucp.ResourceLimits, - }, - ) - } - - // Patch the current DC with the new one - err = c.PatchCurrentDC( - dc, - nil, - existingCmpContainer, - ucp, - false, - ) - if err != nil { - return errors.Wrapf(err, "unable to update the current DeploymentConfig %s", ucp.CommonObjectMeta.Name) - } - - return nil -} - -func addInitVolumesToDC(dc *appsv1.DeploymentConfig, dcName string, deploymentDir string) { - - // Add the appropriate bootstrap volumes for SupervisorD - addBootstrapVolumeCopyInitContainer(dc, dcName) - addBootstrapSupervisordInitContainer(dc, dcName) - addBootstrapVolume(dc, dcName) - addBootstrapVolumeMount(dc, dcName) - // only use the deployment Directory volume mount if its being used and - // its not a sub directory of src_or_bin_path - if deploymentDir != "" && !isSubDir(DefaultAppRootDir, deploymentDir) { - addDeploymentDirVolumeMount(dc, deploymentDir) - } -} - -// removeTracesOfSupervisordFromDC takes a DeploymentConfig and removes any traces of the supervisord from it -// so it removes things like supervisord volumes, volumes mounts and init containers -func removeTracesOfSupervisordFromDC(dc *appsv1.DeploymentConfig) error { - dcName := dc.Name - - err := removeVolumeFromDC(getAppRootVolumeName(dcName), dc) - if err != nil { - return err - } - - err = removeVolumeMountsFromDC(getAppRootVolumeName(dcName), dc) - if err != nil { - return err - } - - // remove the one bootstrapped init container - for i, container := range dc.Spec.Template.Spec.InitContainers { - if container.Name == "copy-files-to-volume" { - dc.Spec.Template.Spec.InitContainers = append(dc.Spec.Template.Spec.InitContainers[:i], dc.Spec.Template.Spec.InitContainers[i+1:]...) - } - } - - return nil -} - -// Delete takes labels as a input and based on it, deletes respective resource -func (c *Client) Delete(labels map[string]string, wait bool) error { - - // convert labels to selector - selector := util.ConvertLabelsToSelector(labels) - klog.V(3).Infof("Selectors used for deletion: %s", selector) - - var errorList []string - var deletionPolicy = metav1.DeletePropagationBackground - - // for --wait flag, it deletes component dependents first and then delete component - if wait { - deletionPolicy = metav1.DeletePropagationForeground - } - // Delete DeploymentConfig - klog.V(3).Info("Deleting DeploymentConfigs") - err := c.appsClient.DeploymentConfigs(c.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{PropagationPolicy: &deletionPolicy}, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - errorList = append(errorList, "unable to delete deploymentconfig") - } - // Delete BuildConfig - klog.V(3).Info("Deleting BuildConfigs") - err = c.buildClient.BuildConfigs(c.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - errorList = append(errorList, "unable to delete buildconfig") - } - // Delete ImageStream - klog.V(3).Info("Deleting ImageStreams") - err = c.imageClient.ImageStreams(c.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - errorList = append(errorList, "unable to delete imagestream") - } - - // for --wait it waits for component to be deleted - // TODO: Need to modify for `odo app delete`, currently wait flag is added only in `odo component delete` - // so only one component gets passed in selector - if wait { - err = c.WaitForComponentDeletion(selector) - if err != nil { - errorList = append(errorList, err.Error()) - } - } - - // Error string - errString := strings.Join(errorList, ",") - if len(errString) != 0 { - return errors.New(errString) - } - return nil - -} - -// WaitForComponentDeletion waits for component to be deleted -func (c *Client) WaitForComponentDeletion(selector string) error { - - klog.V(3).Infof("Waiting for component to get deleted") - - watcher, err := c.appsClient.DeploymentConfigs(c.Namespace).Watch(context.TODO(), metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return err - } - defer watcher.Stop() - eventCh := watcher.ResultChan() - - for { - select { - case event, ok := <-eventCh: - _, typeOk := event.Object.(*appsv1.DeploymentConfig) - if !ok || !typeOk { - return errors.New("Unable to watch deployment config") - } - if event.Type == watch.Deleted { - klog.V(3).Infof("WaitForComponentDeletion, Event Received:Deleted") - return nil - } else if event.Type == watch.Error { - klog.V(3).Infof("WaitForComponentDeletion, Event Received:Deleted ") - return errors.New("Unable to watch deployment config") - } - case <-time.After(waitForComponentDeletionTimeout): - klog.V(3).Infof("WaitForComponentDeletion, Timeout") - return errors.New("Time out waiting for component to get deleted") - } - } -} - -// LinkSecret links a secret to the DeploymentConfig of a component -func (c *Client) LinkSecret(secretName, componentName, applicationName string) error { - - var dcPatchProvider = func(dc *appsv1.DeploymentConfig) (string, error) { - if len(dc.Spec.Template.Spec.Containers[0].EnvFrom) > 0 { - // we always add the link as the first value in the envFrom array. That way we don't need to know the existing value - return fmt.Sprintf(`[{ "op": "add", "path": "/spec/template/spec/containers/0/envFrom/0", "value": {"secretRef": {"name": "%s"}} }]`, secretName), nil - } - - //in this case we need to add the full envFrom value - return fmt.Sprintf(`[{ "op": "add", "path": "/spec/template/spec/containers/0/envFrom", "value": [{"secretRef": {"name": "%s"}}] }]`, secretName), nil - } - - dcName, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return err - } - - return c.patchDC(dcName, dcPatchProvider) -} - -// UnlinkSecret unlinks a secret to the DeploymentConfig of a component -func (c *Client) UnlinkSecret(secretName, componentName, applicationName string) error { - // Remove the Secret from the container - var dcPatchProvider = func(dc *appsv1.DeploymentConfig) (string, error) { - indexForRemoval := -1 - for i, env := range dc.Spec.Template.Spec.Containers[0].EnvFrom { - if env.SecretRef.Name == secretName { - indexForRemoval = i - break - } - } - - if indexForRemoval == -1 { - return "", fmt.Errorf("DeploymentConfig does not contain a link to %s", secretName) - } - - return fmt.Sprintf(`[{"op": "remove", "path": "/spec/template/spec/containers/0/envFrom/%d"}]`, indexForRemoval), nil - } - - dcName, err := util.NamespaceOpenShiftObject(componentName, applicationName) - if err != nil { - return err - } - - return c.patchDC(dcName, dcPatchProvider) -} - // ServerInfo contains the fields that contain the server's information like // address, OpenShift and Kubernetes versions type ServerInfo struct { @@ -1229,106 +326,3 @@ func (c *Client) GetKubeClient() *kclient.Client { func (c *Client) SetKubeClient(client *kclient.Client) { c.kubeClient = client } - -// FindContainer finds the container -func FindContainer(containers []corev1.Container, name string) (corev1.Container, error) { - - if name == "" { - return corev1.Container{}, errors.New("Invalid parameter for FindContainer, unable to find a blank container") - } - - for _, container := range containers { - if container.Name == name { - return container, nil - } - } - - return corev1.Container{}, errors.New("Unable to find container") -} - -// PropagateDeletes deletes the watch detected deleted files from remote component pod from each of the paths in passed s2iPaths -// Parameters: -// targetPodName: Name of component pod -// delSrcRelPaths: Paths to be deleted on the remote pod relative to component source base path ex: Component src: /abc/src, file deleted: abc/src/foo.lang => relative path: foo.lang -// s2iPaths: Slice of all s2i paths -- deployment dir, destination dir, working dir, etc.. -func (c *Client) PropagateDeletes(targetPodName string, delSrcRelPaths []string, s2iPaths []string) error { - reader, writer := io.Pipe() - var rmPaths []string - if len(s2iPaths) == 0 || len(delSrcRelPaths) == 0 { - return fmt.Errorf("Failed to propagate deletions: s2iPaths: %+v and delSrcRelPaths: %+v", s2iPaths, delSrcRelPaths) - } - for _, s2iPath := range s2iPaths { - for _, delRelPath := range delSrcRelPaths { - // since the paths inside the container are linux oriented - // so we convert the paths accordingly - rmPaths = append(rmPaths, filepath.ToSlash(filepath.Join(s2iPath, delRelPath))) - } - } - klog.V(3).Infof("s2ipaths marked for deletion are %+v", rmPaths) - cmdArr := []string{"rm", "-rf"} - cmdArr = append(cmdArr, rmPaths...) - - err := c.GetKubeClient().ExecCMDInContainer("", targetPodName, cmdArr, writer, writer, reader, false) - if err != nil { - return err - } - return err -} - -func injectS2IPaths(existingVars []corev1.EnvVar, s2iPaths S2IPaths) []corev1.EnvVar { - return uniqueAppendOrOverwriteEnvVars( - existingVars, - corev1.EnvVar{ - Name: EnvS2IScriptsURL, - Value: s2iPaths.ScriptsPath, - }, - corev1.EnvVar{ - Name: EnvS2IScriptsProtocol, - Value: s2iPaths.ScriptsPathProtocol, - }, - corev1.EnvVar{ - Name: EnvS2ISrcOrBinPath, - Value: s2iPaths.SrcOrBinPath, - }, - corev1.EnvVar{ - Name: EnvS2IDeploymentDir, - Value: s2iPaths.DeploymentDir, - }, - corev1.EnvVar{ - Name: EnvS2IWorkingDir, - Value: s2iPaths.WorkingDir, - }, - corev1.EnvVar{ - Name: EnvS2IBuilderImageName, - Value: s2iPaths.BuilderImgName, - }, - ) - -} - -func isSubDir(baseDir, otherDir string) bool { - cleanedBaseDir := filepath.Clean(baseDir) - cleanedOtherDir := filepath.Clean(otherDir) - if cleanedBaseDir == cleanedOtherDir { - return true - } - //matches, _ := filepath.Match(fmt.Sprintf("%s/*", cleanedBaseDir), cleanedOtherDir) - matches, _ := filepath.Match(filepath.Join(cleanedBaseDir, "*"), cleanedOtherDir) - return matches -} - -// GenerateOwnerReference generates an ownerReference which can then be set as -// owner for various OpenShift objects and ensure that when the owner object is -// deleted from the cluster, all other objects are automatically removed by -// OpenShift garbage collector -func GenerateOwnerReference(dc *appsv1.DeploymentConfig) metav1.OwnerReference { - - ownerReference := metav1.OwnerReference{ - APIVersion: "apps.openshift.io/v1", - Kind: "DeploymentConfig", - Name: dc.Name, - UID: dc.UID, - } - - return ownerReference -} diff --git a/pkg/occlient/occlient_test.go b/pkg/occlient/occlient_test.go index 0ef7dd4071d..0d2bfa02ffc 100644 --- a/pkg/occlient/occlient_test.go +++ b/pkg/occlient/occlient_test.go @@ -1,2275 +1 @@ package occlient - -import ( - "encoding/json" - "fmt" - "os" - "reflect" - "sort" - "testing" - - appsv1 "github.com/openshift/api/apps/v1" - buildv1 "github.com/openshift/api/build/v1" - dockerapi "github.com/openshift/api/image/docker10" - imagev1 "github.com/openshift/api/image/v1" - applabels "github.com/openshift/odo/pkg/application/labels" - componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/devfile/adapters/common" - "github.com/openshift/odo/pkg/testingutil" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - ktesting "k8s.io/client-go/testing" -) - -// fakeDeploymentConfig creates a fake DC. -// we "dog food" our own functions by using our templates / functions to generate this fake deployment config -func fakeDeploymentConfig(name string, image string, envVars []corev1.EnvVar, envfrom []corev1.EnvFromSource, t *testing.T) *appsv1.DeploymentConfig { - - // save component type as label - labels := componentlabels.GetLabels(name, name, true) - labels[componentlabels.ComponentTypeLabel] = image - labels[componentlabels.ComponentTypeVersion] = "latest" - labels[applabels.ApplicationLabel] = name - - // save source path as annotation - annotations := map[string]string{ - "app.kubernetes.io/component-source-type": "local", - } - - // Create CommonObjectMeta to be passed in - commonObjectMeta := metav1.ObjectMeta{ - Name: name, - Labels: labels, - Annotations: annotations, - } - - commonImageMeta := CommonImageMeta{ - Name: name, - Tag: "latest", - Namespace: "openshift", - Ports: []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - } - - // Generate the DeploymentConfig that will be used. - dc := generateSupervisordDeploymentConfig( - commonObjectMeta, - commonImageMeta, - envVars, - envfrom, - fakeResourceRequirements(), - ) - - // Add the appropriate bootstrap volumes for SupervisorD - addBootstrapVolumeCopyInitContainer(&dc, commonObjectMeta.Name) - addBootstrapSupervisordInitContainer(&dc, commonObjectMeta.Name) - addBootstrapVolume(&dc, commonObjectMeta.Name) - addBootstrapVolumeMount(&dc, commonObjectMeta.Name) - - return &dc -} - -func fakeDeploymentConfigGit(name string, image string, envVars []corev1.EnvVar, containerPorts []corev1.ContainerPort) *appsv1.DeploymentConfig { - - // save component type as label - labels := componentlabels.GetLabels(name, name, true) - labels[componentlabels.ComponentTypeLabel] = image - labels[componentlabels.ComponentTypeVersion] = "latest" - - // save source path as annotation - annotations := map[string]string{"app.openshift.io/vcs-uri": "github.com/foo/bar.git", - "app.kubernetes.io/component-source-type": "git", - } - - // Create CommonObjectMeta to be passed in - commonObjectMeta := metav1.ObjectMeta{ - Name: name, - Labels: labels, - Annotations: annotations, - } - - commonImageMeta := CommonImageMeta{ - Name: name, - Tag: "latest", - Namespace: "openshift", - Ports: []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - } - - // Generate the DeploymentConfig that will be used. - dc := generateGitDeploymentConfig( - commonObjectMeta, - commonImageMeta.Name, - containerPorts, - envVars, - fakeResourceRequirements(), - ) - - return &dc -} - -func fakeResourceRequirements() *corev1.ResourceRequirements { - var resReq corev1.ResourceRequirements - - limits := make(corev1.ResourceList) - limits[corev1.ResourceCPU], _ = parseResourceQuantity("0.5m") - limits[corev1.ResourceMemory], _ = parseResourceQuantity("300Mi") - resReq.Limits = limits - - requests := make(corev1.ResourceList) - requests[corev1.ResourceCPU], _ = parseResourceQuantity("0.5m") - requests[corev1.ResourceMemory], _ = parseResourceQuantity("300Mi") - resReq.Requests = requests - - return &resReq -} - -// fakeImageStream gets imagestream for the reactor -func fakeImageStream(imageName string, namespace string, strTags []string) *imagev1.ImageStream { - var tags []imagev1.NamedTagEventList - for _, tag := range strTags { - tags = append(tags, imagev1.NamedTagEventList{ - Tag: tag, - Items: []imagev1.TagEvent{ - { - DockerImageReference: "example/" + imageName + ":" + tag, - Generation: 1, - Image: "sha256:9579a93ee", - }, - }, - }) - } - - return &imagev1.ImageStream{ - ObjectMeta: metav1.ObjectMeta{ - Name: imageName, - Namespace: namespace, - }, - - Status: imagev1.ImageStreamStatus{ - Tags: tags, - }, - } -} - -// fakeImageStreams lists the imagestreams for the reactor -func fakeImageStreams(imageName string, namespace string) *imagev1.ImageStreamList { - return &imagev1.ImageStreamList{ - Items: []imagev1.ImageStream{*fakeImageStream(imageName, namespace, []string{"latest"})}, - } -} - -// fakeImageStreamImages gets imagstreamimages for the reactor -func fakeImageStreamImages(imageName string) *imagev1.ImageStreamImage { - mdata := &dockerapi.DockerImage{ - ContainerConfig: dockerapi.DockerConfig{ - Env: []string{ - "STI_SCRIPTS_URL=http://repo/git/" + imageName, - }, - - ExposedPorts: map[string]struct{}{ - "8080/tcp": {}, - }, - }, - } - - mdataRaw, _ := json.Marshal(mdata) - return &imagev1.ImageStreamImage{ - Image: imagev1.Image{ - DockerImageReference: "example/" + imageName + ":latest", - DockerImageMetadata: runtime.RawExtension{Raw: mdataRaw}, - }, - } -} - -// fakeBuildStatus is used to pass fake BuildStatus to watch -func fakeBuildStatus(status buildv1.BuildPhase, buildName string) *buildv1.Build { - return &buildv1.Build{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: buildName, - }, - Status: buildv1.BuildStatus{ - Phase: status, - }, - } -} - -func fakeImageStreamImage(imageName string, ports []string, containerConfig string) *imagev1.ImageStreamImage { - exposedPorts := make(map[string]struct{}) - var s struct{} - for _, port := range ports { - exposedPorts[port] = s - } - builderImage := &imagev1.ImageStreamImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s@@sha256:9579a93ee", imageName), - }, - Image: imagev1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "@sha256:9579a93ee", - }, - DockerImageMetadata: runtime.RawExtension{ - Object: &dockerapi.DockerImage{ - ContainerConfig: dockerapi.DockerConfig{ - ExposedPorts: exposedPorts, - }, - }, - }, - DockerImageReference: fmt.Sprintf("docker.io/centos/%s-36-centos7@s@sha256:9579a93ee", imageName), - }, - } - if containerConfig != "" { - (*builderImage).Image.DockerImageMetadata.Raw = []byte(containerConfig) - } - return builderImage -} - -func TestAddLabelsToArgs(t *testing.T) { - tests := []struct { - name string - argsIn []string - labels map[string]string - argsOut1 []string - argsOut2 []string - }{ - { - name: "one label in empty args", - argsIn: []string{}, - labels: map[string]string{ - "label1": "value1", - }, - argsOut1: []string{ - "--labels", "label1=value1", - }, - }, - { - name: "one label with existing args", - argsIn: []string{ - "--foo", "bar", - }, - labels: map[string]string{ - "label1": "value1", - }, - argsOut1: []string{ - "--foo", "bar", - "--labels", "label1=value1", - }, - }, - { - name: "multiple label with existing args", - argsIn: []string{ - "--foo", "bar", - }, - labels: map[string]string{ - "label1": "value1", - "label2": "value2", - }, - argsOut1: []string{ - "--foo", "bar", - "--labels", "label1=value1,label2=value2", - }, - argsOut2: []string{ - "--foo", "bar", - "--labels", "label2=value2,label1=value1", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - argsGot := addLabelsToArgs(tt.labels, tt.argsIn) - - if !reflect.DeepEqual(argsGot, tt.argsOut1) && !reflect.DeepEqual(argsGot, tt.argsOut2) { - t.Errorf("addLabelsToArgs() \ngot: %#v \nwant: %#v or %#v", argsGot, tt.argsOut1, tt.argsOut2) - } - }) - } -} - -func TestParseImageName(t *testing.T) { - - tests := []struct { - arg string - want1 string - want2 string - want3 string - want4 string - wantErr bool - }{ - { - arg: "nodejs:8", - want1: "", - want2: "nodejs", - want3: "8", - want4: "", - wantErr: false, - }, - { - arg: "nodejs@sha256:7e56ca37d1db225ebff79dd6d9fd2a9b8f646007c2afc26c67962b85dd591eb2", - want2: "nodejs", - want1: "", - want3: "", - want4: "sha256:7e56ca37d1db225ebff79dd6d9fd2a9b8f646007c2afc26c67962b85dd591eb2", - wantErr: false, - }, - { - arg: "nodejs@sha256:asdf@", - wantErr: true, - }, - { - arg: "nodejs@@", - wantErr: true, - }, - { - arg: "nodejs::", - wantErr: true, - }, - { - arg: "nodejs", - want1: "", - want2: "nodejs", - want3: "latest", - want4: "", - wantErr: false, - }, - { - arg: "", - wantErr: true, - }, - { - arg: ":", - wantErr: true, - }, - { - arg: "myproject/nodejs:8", - want1: "myproject", - want2: "nodejs", - want3: "8", - want4: "", - wantErr: false, - }, - } - for _, tt := range tests { - name := fmt.Sprintf("image name: '%s'", tt.arg) - t.Run(name, func(t *testing.T) { - got1, got2, got3, got4, err := ParseImageName(tt.arg) - if (err != nil) != tt.wantErr { - t.Errorf("ParseImageName() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got1 != tt.want1 { - t.Errorf("ParseImageName() got1 = %v, want %v", got1, tt.want1) - } - if got2 != tt.want2 { - t.Errorf("ParseImageName() got2 = %v, want %v", got2, tt.want2) - } - if got3 != tt.want3 { - t.Errorf("ParseImageName() got3 = %v, want %v", got3, tt.want3) - } - if got4 != tt.want4 { - t.Errorf("ParseImageName() got4 = %v, want %v", got4, tt.want4) - } - }) - } -} - -func TestNewAppS2I(t *testing.T) { - type args struct { - commonObjectMeta metav1.ObjectMeta - namespace string - builderImage string - gitURL string - inputPorts []string - envVars []string - storageToBeMounted map[string]*corev1.PersistentVolumeClaim - } - - tests := []struct { - name string - args args - wantedService map[int32]corev1.Protocol - wantErr bool - }{ - { - name: "case 1: with valid gitURL and two env vars and two storage to be mounted", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - envVars: []string{"key=value", "key1=value1"}, - storageToBeMounted: map[string]*corev1.PersistentVolumeClaim{ - "pvc-1": testingutil.FakePVC("pvc-1", "1Gi", map[string]string{}), - "pvc-2": testingutil.FakePVC("pvc-2", "1Gi", map[string]string{}), - }, - }, - wantedService: map[int32]corev1.Protocol{ - 8080: corev1.ProtocolTCP, - }, - wantErr: false, - }, - { - name: "case 2 : binary buildSource with gitURL empty and no env vars", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - inputPorts: []string{"8081/tcp", "9100/udp"}, - }, - wantedService: map[int32]corev1.Protocol{ - 8081: corev1.ProtocolTCP, - 9100: corev1.ProtocolUDP, - }, - wantErr: true, - }, - { - name: "case 3 : with a invalid port protocol", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - inputPorts: []string{"8081", "9100/blah"}, - }, - wantedService: map[int32]corev1.Protocol{ - 8081: corev1.ProtocolTCP, - 9100: corev1.ProtocolUDP, - }, - wantErr: true, - }, - { - name: "case 4 : with a invalid port number", - args: args{ - builderImage: "ruby:latest", - namespace: "testing", - gitURL: "https://github.com/openshift/ruby", - commonObjectMeta: metav1.ObjectMeta{ - Name: "ruby", - Labels: map[string]string{ - "app": "apptmp", - "app.kubernetes.io/instance": "ruby", - "app.kubernetes.io/name": "ruby", - "app.kubernetes.io/part-of": "apptmp", - }, - Annotations: map[string]string{ - "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - "app.kubernetes.io/component-source-type": "git", - }, - }, - inputPorts: []string{"8ad1", "9100/Udp"}, - }, - wantedService: map[int32]corev1.Protocol{ - 8081: corev1.ProtocolTCP, - 9100: corev1.ProtocolUDP, - }, - wantErr: true, - }, - - // TODO: Currently fails. Enable this case once fixed - // { - // name: "case 3: with empty builderImage", - // args: args{ - // name: "ruby", - // builderImage: "", - // gitURL: "https://github.com/openshift/ruby", - // labels: map[string]string{ - // "app": "apptmp", - // "app.kubernetes.io/instance": "ruby", - // "app.kubernetes.io/name": "ruby", - // "app.kubernetes.io/part-of": "apptmp", - // }, - // annotations: map[string]string{ - // "app.openshift.io/vcs-uri": "https://github.com/openshift/ruby", - // "app.kubernetes.io/component-source-type": "git", - // }, - // }, - // wantErr: true, - // }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - - fkclientset.ImageClientset.PrependReactor("list", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStreams(tt.args.commonObjectMeta.Name, tt.args.commonObjectMeta.Namespace), nil - }) - - fkclientset.ImageClientset.PrependReactor("get", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStream(tt.args.commonObjectMeta.Name, tt.args.commonObjectMeta.Namespace, []string{"latest"}), nil - }) - - fkclientset.ImageClientset.PrependReactor("get", "imagestreamimages", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStreamImages(tt.args.commonObjectMeta.Name), nil - }) - - fkclientset.Kubernetes.PrependReactor("get", "persistentvolumeclaims", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - pvcName := action.(ktesting.GetAction).GetName() - for _, pvc := range tt.args.storageToBeMounted { - if pvc.Name == pvcName { - return true, pvc, nil - } - } - return true, nil, nil - }) - - fkclientset.Kubernetes.PrependReactor("update", "persistentvolumeclaims", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - pvc := action.(ktesting.UpdateAction).GetObject().(*corev1.PersistentVolumeClaim) - if pvc.OwnerReferences[0].Name != tt.args.commonObjectMeta.Name { - t.Errorf("owner reference not set for dc %s", tt.args.commonObjectMeta.Name) - } - return true, pvc, nil - }) - - err := fkclient.NewAppS2I( - CreateArgs{ - Name: tt.args.commonObjectMeta.Name, - SourcePath: tt.args.gitURL, - SourceType: config.GIT, - ImageName: tt.args.builderImage, - EnvVars: tt.args.envVars, - Ports: tt.args.inputPorts, - Resources: fakeResourceRequirements(), - StorageToBeMounted: tt.args.storageToBeMounted, - }, - tt.args.commonObjectMeta, - ) - - if (err != nil) != tt.wantErr { - t.Errorf("NewAppS2I() error = %#v, wantErr %#v", err, tt.wantErr) - } - - if err == nil { - - if len(fkclientset.BuildClientset.Actions()) != 1 { - t.Errorf("expected 1 BuildClientset.Actions() in NewAppS2I, got %v: %v", len(fkclientset.BuildClientset.Actions()), fkclientset.BuildClientset.Actions()) - } - - if len(fkclientset.AppsClientset.Actions()) != 1 { - t.Errorf("expected 1 AppsClientset.Actions() in NewAppS2I, got: %v", fkclientset.AppsClientset.Actions()) - } - - if len(tt.args.storageToBeMounted) > 0 { - if len(fkclientset.Kubernetes.Actions()) != len(tt.args.storageToBeMounted)*2+2 { - t.Errorf("expected %v storage action(s) in PatchCurrentDC got : %v", len(tt.args.storageToBeMounted)*2, len(fkclientset.Kubernetes.Actions())) - } - } else { - if len(fkclientset.Kubernetes.Actions()) != 2 { - t.Errorf("expected 2 Kubernetes.Actions() in NewAppS2I, got: %v", fkclientset.Kubernetes.Actions()) - } - } - - var createdIS *imagev1.ImageStream - - if len(tt.args.inputPorts) <= 0 { - if len(fkclientset.ImageClientset.Actions()) != 4 { - t.Errorf("expected 4 ImageClientset.Actions() in NewAppS2I, got %v: %v", len(fkclientset.ImageClientset.Actions()), fkclientset.ImageClientset.Actions()) - } - - // Check for imagestream objects - createdIS = fkclientset.ImageClientset.Actions()[2].(ktesting.CreateAction).GetObject().(*imagev1.ImageStream) - } else { - if len(fkclientset.ImageClientset.Actions()) != 1 { - t.Errorf("expected 3 ImageClientset.Actions() in NewAppS2I, got: %v", fkclientset.ImageClientset.Actions()) - } - - // Check for imagestream objects - createdIS = fkclientset.ImageClientset.Actions()[0].(ktesting.CreateAction).GetObject().(*imagev1.ImageStream) - } - - if createdIS.Name != tt.args.commonObjectMeta.Name { - t.Errorf("imagestream name is not matching with expected name, expected: %s, got %s", tt.args.commonObjectMeta.Name, createdIS.Name) - } - - if !reflect.DeepEqual(createdIS.Labels, tt.args.commonObjectMeta.Labels) { - t.Errorf("imagestream labels not matching with expected values, expected: %s, got %s", tt.args.commonObjectMeta.Labels, createdIS.Labels) - } - - if !reflect.DeepEqual(createdIS.Annotations, tt.args.commonObjectMeta.Annotations) { - t.Errorf("imagestream annotations not matching with expected values, expected: %s, got %s", tt.args.commonObjectMeta.Annotations, createdIS.Annotations) - } - - // Check buildconfig objects - createdBC := fkclientset.BuildClientset.Actions()[0].(ktesting.CreateAction).GetObject().(*buildv1.BuildConfig) - - if tt.args.gitURL != "" { - if createdBC.Spec.CommonSpec.Source.Git.URI != tt.args.gitURL { - t.Errorf("git url is not matching with expected value, expected: %s, got %s", tt.args.gitURL, createdBC.Spec.CommonSpec.Source.Git.URI) - } - - if createdBC.Spec.CommonSpec.Source.Type != "Git" { - t.Errorf("BuildSource type is not Git as expected") - } - } - - // TODO: Enable once Issue #594 fixed - // } else if createdBC.Spec.CommonSpec.Source.Type != "Binary" { - // t.Errorf("BuildSource type is not Binary as expected") - // } - - // Check deploymentconfig objects - createdDC := fkclientset.AppsClientset.Actions()[0].(ktesting.CreateAction).GetObject().(*appsv1.DeploymentConfig) - if createdDC.Spec.Selector["deploymentconfig"] != tt.args.commonObjectMeta.Name { - t.Errorf("deploymentconfig name is not matching with expected value, expected: %s, got %s", tt.args.commonObjectMeta.Name, createdDC.Spec.Selector["deploymentconfig"]) - } - - var createdSvc *corev1.Service - if len(tt.args.storageToBeMounted) > 0 { - // if storage are needed to be mounted, service creation depends on the storage actions in the kubernetes client - // since each storage needs 2 actions thus we multiply 2 to the number of storage to be mounted - createdSvc = fkclientset.Kubernetes.Actions()[len(tt.args.storageToBeMounted)*2].(ktesting.CreateAction).GetObject().(*corev1.Service) - } else { - // no storage action needed thus service creation is the first action in the kubernetes client - createdSvc = fkclientset.Kubernetes.Actions()[0].(ktesting.CreateAction).GetObject().(*corev1.Service) - } - - for port, protocol := range tt.wantedService { - found := false - for _, servicePort := range createdSvc.Spec.Ports { - if servicePort.Port == port { - found = true - if servicePort.Protocol != protocol { - t.Errorf("port protocol not matching, expected: %v, got %v", protocol, servicePort.Protocol) - } - } - } - if !found { - t.Errorf("%v port with %v protocol not found", port, protocol) - break - } - } - } - }) - } -} - -func TestIsTagInImageStream(t *testing.T) { - tests := []struct { - name string - imagestream imagev1.ImageStream - imageTag string - wantErr bool - want bool - }{ - { - name: "Case: Valid image and image tag", - imagestream: *fakeImageStream("foo", "openshift", []string{"latest", "3.5"}), - imageTag: "3.5", - want: true, - }, - { - name: "Case: Invalid image tag", - imagestream: *fakeImageStream("bar", "testing", []string{"latest"}), - imageTag: "0.1", - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - got := isTagInImageStream(tt.imagestream, tt.imageTag) - - if got != tt.want { - t.Errorf("GetImageStream() = %#v, want %#v\n\n", got, tt) - } - }) - } -} - -func Test_getExposedPortsFromISI(t *testing.T) { - tests := []struct { - name string - imageTag string - imageStreamImage *imagev1.ImageStreamImage - wantErr bool - want []corev1.ContainerPort - }{ - { - name: "Case: Valid image ports in ContainerConfig", - imageTag: "3.5", - imageStreamImage: &imagev1.ImageStreamImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name/imagename@@sha256:9579a93ee", - }, - Image: imagev1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "@sha256:9579a93ee", - }, - DockerImageMetadata: runtime.RawExtension{ - Object: &dockerapi.DockerImage{ - ContainerConfig: dockerapi.DockerConfig{ - ExposedPorts: map[string]struct{}{ - "8080/tcp": {}, - }, - }, - }, - }, - }, - }, - want: []corev1.ContainerPort{ - { - Name: "8080-tcp", - ContainerPort: 8080, - Protocol: "TCP", - }, - }, - }, - { - name: "Case: Valid image ports in Config", - imageTag: "3.5", - imageStreamImage: &imagev1.ImageStreamImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name/imagename@@sha256:9579a93ee", - }, - Image: imagev1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "@sha256:9579a93ee", - }, - DockerImageMetadata: runtime.RawExtension{ - Object: &dockerapi.DockerImage{ - Config: &dockerapi.DockerConfig{ - ExposedPorts: map[string]struct{}{ - "8080/tcp": {}, - }, - }, - }, - }, - }, - }, - want: []corev1.ContainerPort{ - { - Name: "8080-tcp", - ContainerPort: 8080, - Protocol: "TCP", - }, - }, - }, - { - name: "Case: Valid image ports in both Config and ContainerConfig", - imageTag: "3.5", - imageStreamImage: &imagev1.ImageStreamImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name/imagename@@sha256:9579a93ee", - }, - Image: imagev1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "@sha256:9579a93ee", - }, - DockerImageMetadata: runtime.RawExtension{ - Object: &dockerapi.DockerImage{ - ContainerConfig: dockerapi.DockerConfig{ - ExposedPorts: map[string]struct{}{ - "8080/tcp": {}, - "9090/tcp": {}, - }, - }, - Config: &dockerapi.DockerConfig{ - ExposedPorts: map[string]struct{}{ - "9090/tcp": {}, - "9191/tcp": {}, - }, - }, - }, - }, - }, - }, - want: []corev1.ContainerPort{ - { - Name: "8080-tcp", - ContainerPort: 8080, - Protocol: "TCP", - }, - { - Name: "9090-tcp", - ContainerPort: 9090, - Protocol: "TCP", - }, - { - Name: "9191-tcp", - ContainerPort: 9191, - Protocol: "TCP", - }, - }, - }, - { - name: "Case: Invalid image tag", - imageTag: "0.1", - imageStreamImage: fakeImageStreamImage("python", []string{"8080---tcp"}, ""), - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, _ := FakeNew() - fkclient.Namespace = "testing" - got, err := getExposedPortsFromISI(tt.imageStreamImage) - - // sort result, map is used behind the scene so the ordering might be different - sort.Slice(got, func(i, j int) bool { - return got[i].ContainerPort < got[j].ContainerPort - }) - - if !tt.wantErr == (err != nil) { - t.Errorf("client.GetExposedPorts(imagestream imageTag) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("client.GetExposedPorts = %#v, want %#v", got, tt.want) - } - }) - } -} - -func TestLinkSecret(t *testing.T) { - tests := []struct { - name string - secretName string - componentName string - applicationName string - existingDC appsv1.DeploymentConfig - expectedUpdatedDC appsv1.DeploymentConfig - wantErr bool - }{ - { - name: "Case 1: Unable to locate DeploymentConfig", - secretName: "foo", - componentName: "foo", - applicationName: "", - wantErr: true, - }, - { - name: "Case 2: Unable to update DeploymentConfig", - secretName: "foo", - componentName: "", - applicationName: "foo", - existingDC: *fakeDeploymentConfig("foo", "", nil, nil, t), - wantErr: true, - }, - { - name: "Case 3: Valid creation of link", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("component-app", "", nil, nil, nil), - expectedUpdatedDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "secret"}, - }, - }, - }, - t, - ), - wantErr: false, - }, - { - name: "Case 4: Creation of link on a component that already has a different link", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other"}, - }, - }, - }, - t), - expectedUpdatedDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other"}, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "secret"}, - }, - }, - }, - t), - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - // Fake getting DC - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if len(tt.applicationName) == 0 { - return true, nil, fmt.Errorf("could not find dc") - } - return true, &tt.existingDC, nil - }) - - // Fake updating DC - fakeClientSet.AppsClientset.PrependReactor("patch", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if len(tt.componentName) == 0 { - return true, nil, fmt.Errorf("could not patch dc") - } - return true, &tt.expectedUpdatedDC, nil - }) - - err := fakeClient.LinkSecret(tt.secretName, tt.componentName, tt.applicationName) - if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } else if err == nil && !tt.wantErr { - if len(fakeClientSet.AppsClientset.Actions()) != 2 { - t.Errorf("expected 1 AppsClientset.Actions() in LinkSecret, got: %v", fakeClientSet.AppsClientset.Actions()) - } - - dcPatched := fakeClientSet.AppsClientset.Actions()[1].(ktesting.PatchAction).GetName() - if dcPatched != tt.existingDC.Name { - t.Errorf("Expected patch to be performed on dc named: %s but instead got: %s", tt.expectedUpdatedDC.Name, dcPatched) - } - } - }) - } -} - -func TestUnlinkSecret(t *testing.T) { - tests := []struct { - name string - secretName string - componentName string - applicationName string - existingDC appsv1.DeploymentConfig - expectedUpdatedDC appsv1.DeploymentConfig - wantErr bool - }{ - { - name: "Case 1: Remove link from dc that has none", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("foo", "", nil, nil, t), - wantErr: true, - }, - { - name: "Case 2: Remove link from dc that has no matching link", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("foo", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other"}, - }, - }, - }, - t), - wantErr: true, - }, - { - name: "Case 3: Remove the only link", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "secret"}, - }, - }, - }, - t), - expectedUpdatedDC: *fakeDeploymentConfig("component-app", "", nil, []corev1.EnvFromSource{}, t), - wantErr: false, - }, - { - name: "Case 4: Remove a link from a dc that contains many", - secretName: "secret", - componentName: "component", - applicationName: "app", - existingDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other1"}, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "secret"}, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other2"}, - }, - }, - }, - t), - expectedUpdatedDC: *fakeDeploymentConfig("component-app", "", nil, - []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other1"}, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "other2"}, - }, - }, - }, - t), - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - // Fake getting DC - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if len(tt.applicationName) == 0 { - return true, nil, fmt.Errorf("could not find dc") - } - return true, &tt.existingDC, nil - }) - - // Fake updating DC - fakeClientSet.AppsClientset.PrependReactor("patch", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - if len(tt.componentName) == 0 { - return true, nil, fmt.Errorf("could not patch dc") - } - return true, &tt.expectedUpdatedDC, nil - }) - - err := fakeClient.UnlinkSecret(tt.secretName, tt.componentName, tt.applicationName) - if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } else if err == nil && !tt.wantErr { - if len(fakeClientSet.AppsClientset.Actions()) != 2 { - t.Errorf("expected 1 AppsClientset.Actions() in LinkSecret, got: %v", fakeClientSet.AppsClientset.Actions()) - } - - dcPatched := fakeClientSet.AppsClientset.Actions()[1].(ktesting.PatchAction).GetName() - if dcPatched != tt.existingDC.Name { - t.Errorf("Expected patch to be performed on dc named: %s but instead got: %s", tt.expectedUpdatedDC.Name, dcPatched) - } - } - }) - } -} - -func TestPatchCurrentDC(t *testing.T) { - dcRollOutWait := func(*appsv1.DeploymentConfig, int64) bool { - return true - } - - type args struct { - ucp UpdateComponentParams - dcPatch appsv1.DeploymentConfig - prePatchDCHandler dcStructUpdater - isGit bool - } - tests := []struct { - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1: Test patching with nil prePatchDCHandler (local/binary to git)", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key1", Value: "value1"}, - {Name: "key2", Value: "value2"}}, []corev1.EnvFromSource{}, t), - DcRollOutWaitCond: dcRollOutWait, - }, - dcPatch: generateGitDeploymentConfig(metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{"app.kubernetes.io/component-source-type": "git"}}, "bar", - []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - fakeResourceRequirements()), - isGit: true, - }, - wantErr: false, - actions: 3, - }, - { - name: "Case 2: Test patching with non-nil prePatchDCHandler (local/binary to git)", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key1", Value: "value1"}, - {Name: "key2", Value: "value2"}}, []corev1.EnvFromSource{}, t), - DcRollOutWaitCond: dcRollOutWait, - }, - dcPatch: generateGitDeploymentConfig(metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{"app.kubernetes.io/component-source-type": "git"}}, "bar", - []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - fakeResourceRequirements()), - prePatchDCHandler: removeTracesOfSupervisordFromDC, - isGit: true, - }, - wantErr: false, - actions: 3, - }, - { - name: "Case 3: Test patching with different dc configuration (local/binary to local/binary)", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key1", Value: "value1"}, - {Name: "key2", Value: "value2"}}, []corev1.EnvFromSource{}, t), - DcRollOutWaitCond: dcRollOutWait, - }, - dcPatch: *fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key1", Value: "value1"}}, []corev1.EnvFromSource{}, t), - prePatchDCHandler: removeTracesOfSupervisordFromDC, - isGit: false, - }, - wantErr: false, - actions: 2, - }, - { - name: "Case 4: Test patching with the wrong name", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - DcRollOutWaitCond: dcRollOutWait, - }, - dcPatch: generateGitDeploymentConfig(metav1.ObjectMeta{Name: "foo2"}, "bar", - []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - fakeResourceRequirements(), - ), - isGit: false, - }, - wantErr: true, - actions: 3, - }, - { - name: "Case 5: Test patching with the dc with same requirements (local/binary to local/binary)", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t, - ), - DcRollOutWaitCond: dcRollOutWait, - }, - dcPatch: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t, - ), - isGit: false, - }, - wantErr: false, - actions: 1, - }, - { - name: "Case 6: Test patching (git to git) with two storage to mount", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfigGit("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.ContainerPort{{Name: "port-1", ContainerPort: 8080}}, - ), - DcRollOutWaitCond: dcRollOutWait, - StorageToBeMounted: map[string]*corev1.PersistentVolumeClaim{ - "pvc-1": testingutil.FakePVC("pvc-1", "1Gi", map[string]string{}), - "pvc-2": testingutil.FakePVC("pvc-2", "1Gi", map[string]string{}), - }, - }, - dcPatch: generateGitDeploymentConfig(metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{"app.kubernetes.io/component-source-type": "git"}}, "bar", - []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - fakeResourceRequirements(), - ), - isGit: true, - }, - wantErr: false, - actions: 3, - }, - { - name: "Case 7: Test patching (git to local/binary) with two storage to mount", - args: args{ - ucp: UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - ExistingDC: fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key1", Value: "value1"}, - {Name: "key2", Value: "value2"}}, []corev1.EnvFromSource{}, t), - DcRollOutWaitCond: dcRollOutWait, - StorageToBeMounted: map[string]*corev1.PersistentVolumeClaim{ - "pvc-1": testingutil.FakePVC("pvc-1", "1Gi", map[string]string{}), - "pvc-2": testingutil.FakePVC("pvc-2", "1Gi", map[string]string{}), - }, - }, - dcPatch: generateGitDeploymentConfig(metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{"app.kubernetes.io/component-source-type": "git"}}, "bar", - []corev1.ContainerPort{{Name: "foo", HostPort: 80, ContainerPort: 80}}, - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - fakeResourceRequirements(), - ), - isGit: false, - }, - wantErr: false, - actions: 2, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - // Fake "watch" - fkWatch := watch.NewFake() - go func() { - fkWatch.Modify(&tt.args.dcPatch) - }() - fakeClientSet.AppsClientset.PrependWatchReactor("deploymentconfigs", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) { - return true, fkWatch, nil - }) - - // Fake getting DC - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, tt.args.ucp.ExistingDC, nil - }) - - fakeClientSet.Kubernetes.PrependReactor("get", "persistentvolumeclaims", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - pvcName := action.(ktesting.GetAction).GetName() - for _, pvc := range tt.args.ucp.StorageToBeMounted { - if pvc.Name == pvcName { - return true, pvc, nil - } - } - return true, nil, nil - }) - - fakeClientSet.Kubernetes.PrependReactor("update", "persistentvolumeclaims", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - pvc := action.(ktesting.UpdateAction).GetObject().(*corev1.PersistentVolumeClaim) - if pvc.OwnerReferences[0].Name != tt.args.ucp.ExistingDC.Name { - t.Errorf("owner reference not set for dc %s", tt.args.ucp.ExistingDC.Name) - } - return true, pvc, nil - }) - - // Fake the "update" - fakeClientSet.AppsClientset.PrependReactor("update", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - dc := action.(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - if dc.Name != tt.args.dcPatch.Name { - return true, nil, fmt.Errorf("got different dc") - } - return true, dc, nil - }) - - fakeClientSet.AppsClientset.PrependReactor("create", "deploymentconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - dc := action.(ktesting.CreateAction).GetObject().(*appsv1.DeploymentRequest) - if dc.Name != tt.args.dcPatch.Name { - return true, nil, fmt.Errorf("got request for different dc") - } - return true, &tt.args.dcPatch, nil - }) - - // Run function PatchCurrentDC - existingContainer, err := FindContainer(tt.args.ucp.ExistingDC.Spec.Template.Spec.Containers, tt.args.ucp.ExistingDC.Name) - if err != nil { - t.Errorf("client.PatchCurrentDC() unexpected error attempting to fetch component container. error %v", err) - } - - err = fakeClient.PatchCurrentDC(tt.args.dcPatch, tt.args.prePatchDCHandler, existingContainer, tt.args.ucp, tt.args.isGit) - - // Error checking PatchCurrentDC - if !tt.wantErr == (err != nil) { - t.Errorf(" client.PatchCurrentDC() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if len(tt.args.ucp.StorageToBeMounted) > 0 { - if len(fakeClientSet.Kubernetes.Actions()) != len(tt.args.ucp.StorageToBeMounted)*2 { - t.Errorf("expected %v storage action(s) in PatchCurrentDC got : %v", len(tt.args.ucp.StorageToBeMounted)*2, len(fakeClientSet.Kubernetes.Actions())) - } - } - - if err == nil && !tt.wantErr { - // Check to see how many actions are being ran - if (len(fakeClientSet.AppsClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in PatchCurrentDC got %v: %v", tt.actions, len(fakeClientSet.AppsClientset.Actions()), fakeClientSet.AppsClientset.Actions()) - } - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} - -func TestUpdateDCToGit(t *testing.T) { - type args struct { - name string - newImage string - dc appsv1.DeploymentConfig - ports []corev1.ContainerPort - componentSettings config.LocalConfigInfo - resourceLimits corev1.ResourceRequirements - envVars []corev1.EnvVar - isDeleteSupervisordVolumes bool - dcRollOutWaitCond dcRollOutWait - } - tests := []struct { - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1: Check the function works", - args: args{ - name: "foo", - newImage: "bar", - - dc: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - ports: []corev1.ContainerPort{}, - componentSettings: fakeComponentSettings("foo", "foo", "foo", config.GIT, "nodejs", t), - resourceLimits: corev1.ResourceRequirements{}, - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - isDeleteSupervisordVolumes: false, - dcRollOutWaitCond: func(*appsv1.DeploymentConfig, int64) bool { - return true - }, - }, - wantErr: false, - actions: 3, - }, - { - name: "Case 2: Fail if the variable passed in is blank", - args: args{ - name: "foo", - newImage: "", - dc: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - ports: []corev1.ContainerPort{}, - componentSettings: fakeComponentSettings("foo", "foo", "foo", config.GIT, "foo", t), - resourceLimits: corev1.ResourceRequirements{}, - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - isDeleteSupervisordVolumes: false, - dcRollOutWaitCond: func(*appsv1.DeploymentConfig, int64) bool { - return true - }, - }, - wantErr: true, - actions: 4, - }, - { - name: "Case 3: Fail if image retrieved doesn't match the one we want to patch", - args: args{ - name: "foo", - newImage: "", - dc: *fakeDeploymentConfig("foo2", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - ports: []corev1.ContainerPort{}, - componentSettings: fakeComponentSettings("foo2", "foo", "foo", config.GIT, "foo2", t), - resourceLimits: corev1.ResourceRequirements{}, - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - isDeleteSupervisordVolumes: false, - dcRollOutWaitCond: func(*appsv1.DeploymentConfig, int64) bool { - return true - }, - }, - wantErr: true, - actions: 3, - }, - { - name: "Case 4: Check we can patch with a tag", - args: args{ - name: "foo", - newImage: "bar:latest", - dc: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - ports: []corev1.ContainerPort{}, - componentSettings: fakeComponentSettings("foo", "foo", "foo", config.GIT, "foo", t), - resourceLimits: corev1.ResourceRequirements{}, - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - isDeleteSupervisordVolumes: false, - dcRollOutWaitCond: func(*appsv1.DeploymentConfig, int64) bool { - return true - }, - }, - wantErr: false, - actions: 3, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - // Fake "watch" - fkWatch := watch.NewFake() - go func() { - fkWatch.Modify(&tt.args.dc) - }() - fakeClientSet.AppsClientset.PrependWatchReactor("deploymentconfigs", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) { - return true, fkWatch, nil - }) - - // Fake getting DC - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.args.dc, nil - }) - - // Fake the "update" - fakeClientSet.AppsClientset.PrependReactor("update", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - dc := action.(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - - // Check name - if dc.Name != tt.args.dc.Name { - return true, nil, fmt.Errorf("got different dc") - } - - // Check that the new patch actually has the new "image" - if !tt.wantErr == (dc.Spec.Template.Spec.Containers[0].Image != "") { - return true, nil, fmt.Errorf("got %s image, suppose to get %s", dc.Spec.Template.Spec.Containers[0].Image, "") - } - - return true, dc, nil - }) - - fakeClientSet.AppsClientset.PrependReactor("create", "deploymentconfigs", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - dc := action.(ktesting.CreateAction).GetObject().(*appsv1.DeploymentRequest) - if dc.Name != tt.args.dc.Name { - return true, nil, fmt.Errorf("got request for different dc") - } - return true, &tt.args.dc, nil - }) - - // Fake the pvc delete - fakeClientSet.Kubernetes.PrependReactor("delete", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - - // Run function UpdateDCToGit - err := fakeClient.UpdateDCToGit(UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{Name: tt.args.name}, - ImageMeta: CommonImageMeta{ - Name: tt.args.newImage, - Ports: tt.args.ports, - }, - ResourceLimits: tt.args.resourceLimits, - EnvVars: tt.args.envVars, - ExistingDC: &(tt.args.dc), - DcRollOutWaitCond: tt.args.dcRollOutWaitCond, - }, - tt.args.isDeleteSupervisordVolumes, - ) - - // Error checking UpdateDCToGit - if !tt.wantErr == (err != nil) { - t.Errorf(" client.UpdateDCToGit() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - if err == nil && !tt.wantErr { - // Check to see how many actions are being ran - if (len(fakeClientSet.AppsClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in UpdateDCToGit got %v: %v", tt.actions, len(fakeClientSet.AppsClientset.Actions()), fakeClientSet.AppsClientset.Actions()) - } - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} - -func TestUniqueAppendOrOverwriteEnvVars(t *testing.T) { - tests := []struct { - name string - existingEnvVars []corev1.EnvVar - envVars []corev1.EnvVar - want []corev1.EnvVar - }{ - { - name: "Case: Overlapping env vars appends", - existingEnvVars: []corev1.EnvVar{ - { - Name: "key1", - Value: "value1", - }, - { - Name: "key2", - Value: "value2", - }, - }, - envVars: []corev1.EnvVar{ - { - Name: "key1", - Value: "value3", - }, - { - Name: "key2", - Value: "value4", - }, - }, - want: []corev1.EnvVar{ - { - Name: "key1", - Value: "value3", - }, - { - Name: "key2", - Value: "value4", - }, - }, - }, - { - name: "New env vars append", - existingEnvVars: []corev1.EnvVar{ - { - Name: "key1", - Value: "value1", - }, - { - Name: "key2", - Value: "value2", - }, - }, - envVars: []corev1.EnvVar{ - { - Name: "key3", - Value: "value3", - }, - { - Name: "key4", - Value: "value4", - }, - }, - want: []corev1.EnvVar{ - { - Name: "key1", - Value: "value1", - }, - { - Name: "key2", - Value: "value2", - }, - { - Name: "key3", - Value: "value3", - }, - { - Name: "key4", - Value: "value4", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotEnvVars := uniqueAppendOrOverwriteEnvVars(tt.existingEnvVars, tt.envVars...) - if len(tt.want) != len(gotEnvVars) { - t.Errorf("Tc: %s, expected %+v, got %+v", tt.name, tt.want, gotEnvVars) - } - matchFound := false - for _, wantEnv := range tt.want { - for _, gotEnv := range gotEnvVars { - if reflect.DeepEqual(wantEnv, gotEnv) { - matchFound = true - } - } - if !matchFound { - t.Errorf("Tc: %s, expected %+v, got %+v", tt.name, tt.want, gotEnvVars) - } - } - }) - } -} - -func TestInjectS2IPaths(t *testing.T) { - tests := []struct { - name string - existingEnvVars []corev1.EnvVar - envVars []corev1.EnvVar - wantLength int - }{ - { - name: "Case: Overlapping env vars appends", - existingEnvVars: []corev1.EnvVar{ - { - Name: EnvS2IScriptsProtocol, - Value: "value1", - }, - { - Name: EnvS2IBuilderImageName, - Value: "value2", - }, - }, - wantLength: 6, - }, - { - name: "New env vars append", - existingEnvVars: []corev1.EnvVar{ - { - Name: "key1", - Value: "value1", - }, - { - Name: "key2", - Value: "value2", - }, - }, - wantLength: 8, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - gotEnvVars := injectS2IPaths(tt.existingEnvVars, S2IPaths{ - "test", "test", "test", "test", "test", "test", "test", - }) - if tt.wantLength != len(gotEnvVars) { - t.Errorf("Tc: %s, expected %+v, got %+v", tt.name, tt.wantLength, len(gotEnvVars)) - } - }) - } -} - -func TestDeleteEnvVars(t *testing.T) { - tests := []struct { - name string - existingEnvs []corev1.EnvVar - envTobeDeleted string - want []corev1.EnvVar - }{ - { - name: "Case 1: valid case of delete", - existingEnvs: []corev1.EnvVar{ - { - Name: "abc", - Value: "123", - }, - { - Name: "def", - Value: "456", - }, - }, - envTobeDeleted: "def", - want: []corev1.EnvVar{ - { - Name: "abc", - Value: "123", - }, - }, - }, - { - name: "Case 2: valid case of delete non-existant env", - existingEnvs: []corev1.EnvVar{ - { - Name: "abc", - Value: "123", - }, - { - Name: "def", - Value: "456", - }, - }, - envTobeDeleted: "ghi", - want: []corev1.EnvVar{ - { - Name: "abc", - Value: "123", - }, - { - Name: "def", - Value: "456", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := deleteEnvVars(tt.existingEnvs, tt.envTobeDeleted) - // Verify the passed param is not changed after call to function - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("got: %+v, want: %+v", got, tt.want) - } - }) - } -} - -func fakeComponentSettings(cmpName string, appName string, projectName string, srcType config.SrcType, cmpType string, t *testing.T) config.LocalConfigInfo { - lci, err := config.NewLocalConfigInfo("") - if err != nil { - t.Errorf("failed to init fake component configuration") - return *lci - } - defer os.Remove(lci.Filename) - err = lci.SetComponentSettings(config.ComponentSettings{ - Name: &cmpName, - Application: &appName, - Project: &projectName, - SourceType: &srcType, - Type: &cmpType, - }) - if err != nil { - t.Errorf("failed to set component settings. Error %+v", err) - } - return *lci -} - -func TestUpdateDCToSupervisor(t *testing.T) { - type args struct { - name string - imageName string - expectedImage string - imageNamespace string - isToLocal bool - dc appsv1.DeploymentConfig - cmpSettings config.LocalConfigInfo - envVars []corev1.EnvVar - } - tests := []struct { - name string - args args - wantErr bool - actions int - }{ - { - name: "Case 1: Check the function works", - args: args{ - name: "foo", - imageName: "nodejs", - expectedImage: "nodejs", - imageNamespace: "openshift", - cmpSettings: fakeComponentSettings("foo", "foo", "foo", config.LOCAL, "nodejs", t), - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - isToLocal: true, - dc: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - }, - wantErr: false, - actions: 2, - }, - { - name: "Case 2: Fail if unable to find container", - args: args{ - name: "testfoo", - imageName: "foo", - expectedImage: "foobar", - imageNamespace: "testing", - isToLocal: false, - cmpSettings: fakeComponentSettings("foo", "foo", "foo", config.LOCAL, "nodejs", t), - envVars: []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - dc: *fakeDeploymentConfig("foo", "foo", - []corev1.EnvVar{{Name: "key1", Value: "value1"}, {Name: "key2", Value: "value2"}}, - []corev1.EnvFromSource{}, t), - }, - wantErr: true, - actions: 3, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - // Fake "watch" - fkWatch := watch.NewFake() - go func() { - fkWatch.Modify(&tt.args.dc) - }() - fakeClientSet.AppsClientset.PrependWatchReactor("deploymentconfigs", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) { - return true, fkWatch, nil - }) - - // Fake getting DC - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.args.dc, nil - }) - - // Fake the "update" - fakeClientSet.AppsClientset.PrependReactor("update", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - dc := action.(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - - // Check name - if dc.Name != tt.args.dc.Name { - return true, nil, fmt.Errorf("got different dc") - } - - // Check that the new patch actually has parts of supervisord in it when it's used.. - - // Check that addBootstrapVolumeCopyInitContainer is the 1st initContainer and it exists - if !tt.wantErr == (dc.Spec.Template.Spec.InitContainers[0].Name != "copy-files-to-volume") { - return true, nil, fmt.Errorf("client.UpdateDCSupervisor() does not contain the copy-files-to-volume container within Spec.Template.Spec.InitContainers, found: %v", dc.Spec.Template.Spec.InitContainers[0].Name) - } - - // Check that addBootstrapVolumeCopyInitContainer is the 2nd initContainer and it exists - if !tt.wantErr == (dc.Spec.Template.Spec.InitContainers[1].Name != "copy-supervisord") { - return true, nil, fmt.Errorf("client.UpdateDCSupervisor() does not contain the copy-supervisord container within Spec.Template.Spec.InitContainers, found: %v", dc.Spec.Template.Spec.InitContainers[1].Name) - } - - return true, dc, nil - }) - - // Fake getting image stream - fakeClientSet.ImageClientset.PrependReactor("get", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStream(tt.args.expectedImage, tt.args.imageNamespace, []string{"latest"}), nil - }) - - fakeClientSet.ImageClientset.PrependReactor("get", "imagestreamimages", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, fakeImageStreamImages(tt.args.imageName), nil - }) - - // Run function UpdateDCToSupervisor - err := fakeClient.UpdateDCToSupervisor( - UpdateComponentParams{ - CommonObjectMeta: metav1.ObjectMeta{Name: tt.args.name}, - ImageMeta: CommonImageMeta{ - Name: tt.args.imageName, - Tag: "latest", - Namespace: "openshift", - }, - ResourceLimits: corev1.ResourceRequirements{}, - EnvVars: tt.args.envVars, - ExistingDC: &(tt.args.dc), - DcRollOutWaitCond: func(e *appsv1.DeploymentConfig, i int64) bool { - return true - }, - }, - tt.args.isToLocal, - false, - ) - - // Error checking UpdateDCToSupervisor - if !tt.wantErr == (err != nil) { - t.Errorf(" client.UpdateDCToSupervisor() unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // Check to see how many actions are being ran - if err == nil && !tt.wantErr { - if (len(fakeClientSet.AppsClientset.Actions()) != tt.actions) && !tt.wantErr { - t.Errorf("expected %v action(s) in UpdateDCToSupervisor got %v: %v", tt.actions, len(fakeClientSet.AppsClientset.Actions()), fakeClientSet.AppsClientset.Actions()) - } - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} - -func TestIsVolumeAnEmptyDir(t *testing.T) { - type args struct { - VolumeName string - dc appsv1.DeploymentConfig - } - tests := []struct { - name string - args args - wantEmptyDir bool - }{ - { - name: "Case 1 - Check that it is an emptyDir", - args: args{ - VolumeName: common.SupervisordVolumeName, - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantEmptyDir: true, - }, - { - name: "Case 2 - Check a non-existent volume", - args: args{ - VolumeName: "foobar", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantEmptyDir: false, - }, - { - name: "Case 3 - Check a volume that exists but is not emptyDir", - args: args{ - VolumeName: "foo-s2idata", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantEmptyDir: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, _ := FakeNew() - - // Run function IsVolumeAnEmptyDir - isVolumeEmpty := fakeClient.IsVolumeAnEmptyDir(tt.args.VolumeName, &tt.args.dc) - - // Error checking IsVolumeAnEmptyDir - if tt.wantEmptyDir != isVolumeEmpty { - t.Errorf(" client.IsVolumeAnEmptyDir() unexpected %v, wantEmptyDir %v", isVolumeEmpty, tt.wantEmptyDir) - } - - }) - } -} - -func Test_updateEnvVar(t *testing.T) { - type args struct { - dc *appsv1.DeploymentConfig - inputEnvVars []corev1.EnvVar - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "test case 1: tests with single container in dc and no existing env vars", - args: args{ - dc: fakeDeploymentConfig("foo", "foo", nil, nil, t), - inputEnvVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key-1", - Value: "value-1", - }, - }, - }, - wantErr: false, - }, - { - name: "test case 2: tests with single container in dc and existing env vars", - args: args{ - dc: fakeDeploymentConfig("foo", "foo", []corev1.EnvVar{{Name: "key-1", Value: "key-1"}}, - []corev1.EnvFromSource{}, t), - inputEnvVars: []corev1.EnvVar{ - { - Name: "key-2", - Value: "value-2", - }, - }, - }, - wantErr: false, - }, - { - name: "test case 3: tests with double container in dc", - args: args{ - dc: &appsv1.DeploymentConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "wildfly-app", - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Env: []corev1.EnvVar{}, - }, - { - Env: []corev1.EnvVar{}, - }, - }, - }, - }, - }, - }, - inputEnvVars: []corev1.EnvVar{ - { - Name: "key", - Value: "value", - }, - { - Name: "key-1", - Value: "value-1", - }, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := updateEnvVar(tt.args.dc, tt.args.inputEnvVars) - - if err == nil && !tt.wantErr { - found := false - for _, inputEnv := range tt.args.inputEnvVars { - for _, foundEnv := range tt.args.dc.Spec.Template.Spec.Containers[0].Env { - if reflect.DeepEqual(inputEnv, foundEnv) { - found = true - } - } - } - if !found { - t.Errorf("update env vars are not matching, expected dc to contain: %v", tt.args.inputEnvVars) - } - - } else if err == nil && tt.wantErr { - t.Error("error was expected, but no error was returned") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, no error was expected, but got unexpected error: %s", err) - } - }) - } -} - -func Test_findContainer(t *testing.T) { - type args struct { - name string - containers []corev1.Container - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Case 1 - Find the container", - args: args{ - name: "foo", - containers: []corev1.Container{ - { - Name: "foo", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "Case 2 - Error if container not found", - args: args{ - name: "foo2", - containers: []corev1.Container{ - { - Name: "foo", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Case 3 - Error when passing in blank container name", - args: args{ - name: "", - containers: []corev1.Container{ - { - Name: "foo", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Case 4 - Check against multiple containers (rather than one)", - args: args{ - name: "foo", - containers: []corev1.Container{ - { - Name: "bar", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - { - Name: "foo", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - // Run function findContainer - container, err := FindContainer(tt.args.containers, tt.args.name) - - // Check that the container matches the name - if err == nil && container.Name != tt.args.name { - t.Errorf("Wrong container returned, wanted container %v, got %v", tt.args.name, container.Name) - } - - if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - - }) - } -} - -// sliceEqual checks equality of two slices irrespective of the element ordering -func sliceEqual(x, y []string) bool { - if len(x) != len(y) { - return false - } - - xc := make([]string, len(x)) - yc := make([]string, len(y)) - - copy(xc, x) - copy(yc, y) - - sort.Strings(xc) - sort.Strings(yc) - - return reflect.DeepEqual(xc, yc) -} - -func TestIsSubDir(t *testing.T) { - - tests := []struct { - name string - baseDir string - otherDir string - matches bool - }{ - { - name: "Case 1: same dirs with slashes", - baseDir: "/abcd/", - otherDir: "/abcd", - matches: true, - }, - { - name: "Case 2: same dirs with slashes order reverse", - baseDir: "/abcd", - otherDir: "/abcd/", - matches: true, - }, - { - name: "Case 3: other dir same prefix", - baseDir: "/abcd", - otherDir: "/abcde/", - matches: false, - }, - { - name: "Case 4: other dir same prefix more complex", - baseDir: "/abcde/fg", - otherDir: "/abcde/fgh", - matches: false, - }, - { - name: "Case 5: other dir same prefix more complex matching", - baseDir: "/abcde/fg", - otherDir: "/abcde/fg/h", - matches: true, - }, - { - name: "Case 6: dirs with ..", - baseDir: "/abcde/fg/../h", - otherDir: "/abcde/h/ij", - matches: true, - }, - { - name: "Case 7: dirs with .. not matching", - baseDir: "/abcde/fg/../h", - otherDir: "/abcde/fg/h", - matches: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if isSubDir(tt.baseDir, tt.otherDir) != tt.matches { - t.Errorf("the outcome for %s and %s is not expected", tt.baseDir, tt.otherDir) - } - }) - } - -} diff --git a/pkg/occlient/routes.go b/pkg/occlient/routes.go index 1578864d369..b161e8389c1 100644 --- a/pkg/occlient/routes.go +++ b/pkg/occlient/routes.go @@ -89,19 +89,3 @@ func (c *Client) GetOneRouteFromSelector(selector string) (*routev1.Route, error return &routes[0], nil } - -// ListRouteNames lists all the names of the routes based on the given label -// selector -func (c *Client) ListRouteNames(labelSelector string) ([]string, error) { - routes, err := c.ListRoutes(labelSelector) - if err != nil { - return nil, err - } - - var routeNames []string - for _, r := range routes { - routeNames = append(routeNames, r.Name) - } - - return routeNames, nil -} diff --git a/pkg/occlient/routes_test.go b/pkg/occlient/routes_test.go index 7a8bed1451f..5ea63d28eba 100644 --- a/pkg/occlient/routes_test.go +++ b/pkg/occlient/routes_test.go @@ -4,7 +4,9 @@ import ( "reflect" "testing" - appsv1 "github.com/openshift/api/apps/v1" + appsV1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + routev1 "github.com/openshift/api/route/v1" "github.com/openshift/odo/pkg/testingutil" "k8s.io/apimachinery/pkg/runtime" @@ -12,17 +14,33 @@ import ( ktesting "k8s.io/client-go/testing" ) +// GenerateOwnerReference generates an ownerReference which can then be set as +// owner for various OpenShift objects and ensure that when the owner object is +// deleted from the cluster, all other objects are automatically removed by +// OpenShift garbage collector +func GenerateOwnerReference(deployment *appsV1.Deployment) metav1.OwnerReference { + + ownerReference := metav1.OwnerReference{ + APIVersion: "apps.openshift.io/v1", + Kind: "Deployment", + Name: deployment.Name, + UID: deployment.UID, + } + + return ownerReference +} + func TestCreateRoute(t *testing.T) { tests := []struct { - name string - urlName string - service string - portNumber intstr.IntOrString - labels map[string]string - wantErr bool - existingDC appsv1.DeploymentConfig - secureURL bool - path string + name string + urlName string + service string + portNumber intstr.IntOrString + labels map[string]string + wantErr bool + existingDeployment *appsV1.Deployment + secureURL bool + path string }{ { name: "Case : mailserver", @@ -34,8 +52,8 @@ func TestCreateRoute(t *testing.T) { "app.kubernetes.io/instance": "backend", "app.kubernetes.io/name": "python", }, - wantErr: false, - existingDC: *fakeDeploymentConfig("mailserver", "", nil, nil, t), + wantErr: false, + existingDeployment: testingutil.CreateFakeDeployment("mailserver"), }, { @@ -48,8 +66,8 @@ func TestCreateRoute(t *testing.T) { "app.kubernetes.io/instance": "backend", "app.kubernetes.io/name": "golang", }, - wantErr: false, - existingDC: *fakeDeploymentConfig("blog", "", nil, nil, t), + wantErr: false, + existingDeployment: testingutil.CreateFakeDeployment("blog"), }, { @@ -62,9 +80,9 @@ func TestCreateRoute(t *testing.T) { "app.kubernetes.io/instance": "backend", "app.kubernetes.io/name": "golang", }, - wantErr: false, - existingDC: *fakeDeploymentConfig("blog", "", nil, nil, t), - secureURL: true, + wantErr: false, + existingDeployment: testingutil.CreateFakeDeployment("blog"), + secureURL: true, }, { @@ -77,9 +95,9 @@ func TestCreateRoute(t *testing.T) { "app.kubernetes.io/instance": "backend", "app.kubernetes.io/name": "golang", }, - wantErr: false, - existingDC: *fakeDeploymentConfig("blog", "", nil, nil, t), - path: "/testpath", + wantErr: false, + existingDeployment: testingutil.CreateFakeDeployment("blog"), + path: "/testpath", }, } for _, tt := range tests { @@ -87,10 +105,10 @@ func TestCreateRoute(t *testing.T) { // initialising the fakeclient fkclient, fkclientset := FakeNew() - ownerReferences := GenerateOwnerReference(&tt.existingDC) + ownerReferences := GenerateOwnerReference(tt.existingDeployment) - fkclientset.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.existingDC, nil + fkclientset.AppsClientset.PrependReactor("get", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) { + return true, tt.existingDeployment, nil }) createdRoute, err := fkclient.CreateRoute(tt.urlName, tt.service, tt.portNumber, tt.labels, tt.secureURL, tt.path, ownerReferences) @@ -200,55 +218,6 @@ func TestListRoutes(t *testing.T) { } } -func TestListRouteNames(t *testing.T) { - type args struct { - labelSelector string - } - tests := []struct { - name string - args args - returnedRoutes routev1.RouteList - want []string - wantErr bool - }{ - { - name: "case 1: list multiple routes", - args: args{ - labelSelector: "app.kubernetes.io/instance", - }, - returnedRoutes: *testingutil.GetRouteListWithMultiple("nodejs", "app"), - want: []string{"example", "example-1"}, - }, - { - name: "case 2: no routes returned", - args: args{ - labelSelector: "app.kubernetes.io/instance", - }, - returnedRoutes: routev1.RouteList{}, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // initialising the fakeclient - fkclient, fkclientset := FakeNew() - - fkclientset.RouteClientset.PrependReactor("list", "routes", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.returnedRoutes, nil - }) - - got, err := fkclient.ListRouteNames(tt.args.labelSelector) - if (err != nil) != tt.wantErr { - t.Errorf("ListRouteNames() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ListRouteNames() got = %v, want %v", got, tt.want) - } - }) - } -} - func TestGetRoute(t *testing.T) { type args struct { name string diff --git a/pkg/occlient/templates.go b/pkg/occlient/templates.go deleted file mode 100644 index 3591b6c12ab..00000000000 --- a/pkg/occlient/templates.go +++ /dev/null @@ -1,453 +0,0 @@ -package occlient - -import ( - "fmt" - - "github.com/pkg/errors" - - "github.com/devfile/library/pkg/devfile/generator" - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/devfile/adapters/common" - - appsv1 "github.com/openshift/api/apps/v1" - buildv1 "github.com/openshift/api/build/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - // appRootSubPath defines the sup-path in the odo's PV where app root will recide - appRootSubPath = "app-root" - // deploymentDirSubPath defines the sup-path in the odo's PV where the deployment dir will recide - deploymentDirSubPath = "deployment" -) - -// CommonImageMeta has all the most common image data that is passed around within Odo -type CommonImageMeta struct { - Name string - Tag string - Namespace string - Ports []corev1.ContainerPort -} - -type SupervisorDUpdateParams struct { - existingDc *appsv1.DeploymentConfig - commonObjectMeta metav1.ObjectMeta - commonImageMeta CommonImageMeta - envVar []corev1.EnvVar - envFrom []corev1.EnvFromSource - resourceRequirements *corev1.ResourceRequirements -} - -// generateSupervisordDeploymentConfig generates dc for local and binary components -// Parameters: -// commonObjectMeta: Contains annotations and labels for dc -// commonImageMeta: Contains details like image NS, name, tag and ports to be exposed -// envVar: env vars to be exposed -// resourceRequirements: Container cpu and memory resource requirements -// Returns: -// deployment config generated using above parameters -func generateSupervisordDeploymentConfig(commonObjectMeta metav1.ObjectMeta, commonImageMeta CommonImageMeta, - envVar []corev1.EnvVar, envFrom []corev1.EnvFromSource, resourceRequirements *corev1.ResourceRequirements) appsv1.DeploymentConfig { - - if commonImageMeta.Namespace == "" { - commonImageMeta.Namespace = "openshift" - } - - // Generates and deploys a DeploymentConfig with an InitContainer to copy over the SupervisorD binary. - dc := appsv1.DeploymentConfig{ - ObjectMeta: commonObjectMeta, - Spec: appsv1.DeploymentConfigSpec{ - Replicas: 1, - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.DeploymentStrategyTypeRecreate, - }, - Selector: map[string]string{ - "deploymentconfig": commonObjectMeta.Name, - }, - Template: &corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "deploymentconfig": commonObjectMeta.Name, - }, - // https://github.com/openshift/odo/pull/622#issuecomment-413410736 - Annotations: map[string]string{ - "alpha.image.policy.openshift.io/resolve-names": "*", - }, - }, - Spec: corev1.PodSpec{ - // The application container - Containers: []corev1.Container{ - { - Image: " ", - Name: commonObjectMeta.Name, - Ports: commonImageMeta.Ports, - // Run the actual supervisord binary that has been mounted into the container - Command: []string{ - "/opt/odo/bin/go-init", - }, - // Using the appropriate configuration file that contains the "run" script for the component. - // either from: /usr/libexec/s2i/assemble or /opt/app-root/src/.s2i/bin/assemble - Args: []string{ - "-pre", - "/opt/odo/bin/s2i-setup", - "-main", - "/opt/odo/bin/supervisord -c /opt/app-root/conf/supervisor.conf", - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: common.SupervisordVolumeName, - MountPath: "/opt/odo/", - }, - }, - Env: envVar, - EnvFrom: envFrom, - }, - }, - - // Create a volume that will be shared between InitContainer and the applicationContainer - // in order to pass over the SupervisorD binary - Volumes: []corev1.Volume{ - { - Name: common.SupervisordVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - }, - }, - }, - // We provide triggers to create an ImageStream so that the application container will use the - // correct and appropriate image that's located internally within the OpenShift commonObjectMeta.Namespace - Triggers: []appsv1.DeploymentTriggerPolicy{ - { - Type: "ConfigChange", - }, - { - Type: "ImageChange", - ImageChangeParams: &appsv1.DeploymentTriggerImageChangeParams{ - Automatic: true, - ContainerNames: []string{ - commonObjectMeta.Name, - "copy-files-to-volume", - }, - From: corev1.ObjectReference{ - Kind: "ImageStreamTag", - Name: fmt.Sprintf("%s:%s", commonImageMeta.Name, commonImageMeta.Tag), - Namespace: commonImageMeta.Namespace, - }, - }, - }, - }, - }, - } - containerIndex := -1 - if resourceRequirements != nil { - for index, container := range dc.Spec.Template.Spec.Containers { - if container.Name == commonObjectMeta.Name { - containerIndex = index - break - } - } - if containerIndex != -1 { - dc.Spec.Template.Spec.Containers[containerIndex].Resources = *resourceRequirements - } - } - return dc -} - -// updateSupervisorDeploymentConfig updates the deploymentConfig during push -// updateParams are the parameters used during the update -func updateSupervisorDeploymentConfig(updateParams SupervisorDUpdateParams) appsv1.DeploymentConfig { - - dc := *updateParams.existingDc - - dc.ObjectMeta = updateParams.commonObjectMeta - - if len(dc.Spec.Template.Spec.Containers) > 0 { - dc.Spec.Template.Spec.Containers[0].Name = updateParams.commonObjectMeta.Name - dc.Spec.Template.Spec.Containers[0].Ports = updateParams.commonImageMeta.Ports - dc.Spec.Template.Spec.Containers[0].Env = updateParams.envVar - dc.Spec.Template.Spec.Containers[0].EnvFrom = updateParams.envFrom - - if updateParams.resourceRequirements != nil && dc.Spec.Template.Spec.Containers[0].Name == updateParams.commonObjectMeta.Name { - dc.Spec.Template.Spec.Containers[0].Resources = *updateParams.resourceRequirements - } - } - - return dc -} - -// FetchContainerResourceLimits returns cpu and memory resource limits of the component container from the passed dc -// Parameter: -// container: Component container -// Returns: -// resource limits from passed component container -func FetchContainerResourceLimits(container corev1.Container) corev1.ResourceRequirements { - return container.Resources -} - -// parseResourceQuantity takes a string representation of quantity/amount of a resource and returns kubernetes representation of it and errors if any -// This is a wrapper around the kube client provided ParseQuantity added to in future support more units and make it more readable -func parseResourceQuantity(resQuantity string) (resource.Quantity, error) { - return resource.ParseQuantity(resQuantity) -} - -// GetResourceRequirementsFromCmpSettings converts the cpu and memory request info from component configuration into format usable in dc -// Parameters: -// cfg: Compoennt configuration/settings -// Returns: -// *corev1.ResourceRequirements: component configuration converted into format usable in dc -func GetResourceRequirementsFromCmpSettings(cfg config.LocalConfigInfo) (*corev1.ResourceRequirements, error) { - var resourceRequirements corev1.ResourceRequirements - requests := make(corev1.ResourceList) - limits := make(corev1.ResourceList) - - cfgMinCPU := cfg.GetMinCPU() - cfgMaxCPU := cfg.GetMaxCPU() - cfgMinMemory := cfg.GetMinMemory() - cfgMaxMemory := cfg.GetMaxMemory() - - if cfgMinCPU != "" { - minCPU, err := parseResourceQuantity(cfgMinCPU) - if err != nil { - return nil, errors.Wrap(err, "failed to parse the min cpu") - } - requests[corev1.ResourceCPU] = minCPU - } - - if cfgMaxCPU != "" { - maxCPU, err := parseResourceQuantity(cfgMaxCPU) - if err != nil { - return nil, errors.Wrap(err, "failed to parse max cpu") - } - limits[corev1.ResourceCPU] = maxCPU - } - - if cfgMinMemory != "" { - minMemory, err := parseResourceQuantity(cfgMinMemory) - if err != nil { - return nil, errors.Wrap(err, "failed to parse min memory") - } - requests[corev1.ResourceMemory] = minMemory - } - - if cfgMaxMemory != "" { - maxMemory, err := parseResourceQuantity(cfgMaxMemory) - if err != nil { - return nil, errors.Wrap(err, "failed to parse max memory") - } - limits[corev1.ResourceMemory] = maxMemory - } - - if len(limits) > 0 { - resourceRequirements.Limits = limits - } - - if len(requests) > 0 { - resourceRequirements.Requests = requests - } - - return &resourceRequirements, nil -} - -func generateGitDeploymentConfig(commonObjectMeta metav1.ObjectMeta, image string, containerPorts []corev1.ContainerPort, envVars []corev1.EnvVar, resourceRequirements *corev1.ResourceRequirements) appsv1.DeploymentConfig { - dc := appsv1.DeploymentConfig{ - ObjectMeta: commonObjectMeta, - Spec: appsv1.DeploymentConfigSpec{ - Replicas: 1, - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.DeploymentStrategyTypeRecreate, - }, - Selector: map[string]string{ - "deploymentconfig": commonObjectMeta.Name, - }, - Template: &corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "deploymentconfig": commonObjectMeta.Name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - // image stream trigger and specifying an image at the same time are mutually exclusive options - // thus we put "" into image field as we are specifying an image stream trigger - Image: "", - Name: commonObjectMeta.Name, - Ports: containerPorts, - Env: envVars, - }, - }, - }, - }, - Triggers: []appsv1.DeploymentTriggerPolicy{ - { - Type: "ImageChange", - ImageChangeParams: &appsv1.DeploymentTriggerImageChangeParams{ - // setting automatic to false so that the trigger is disabled and a new image doesn't trigger deployment - // we don't remove this trigger so that we don't face image resolution issues - Automatic: false, - ContainerNames: []string{ - commonObjectMeta.Name, - }, - From: corev1.ObjectReference{ - Kind: "ImageStreamTag", - Name: image, - }, - }, - }, - }, - }, - } - containerIndex := -1 - if resourceRequirements != nil { - for index, container := range dc.Spec.Template.Spec.Containers { - if container.Name == commonObjectMeta.Name { - containerIndex = index - break - } - } - if containerIndex != -1 { - dc.Spec.Template.Spec.Containers[containerIndex].Resources = *resourceRequirements - } - } - return dc -} - -// generateBuildConfig creates a BuildConfig for Git URL's being passed into Odo -func generateBuildConfig(commonObjectMeta metav1.ObjectMeta, gitURL, gitRef, imageName, imageNamespace string) buildv1.BuildConfig { - - params := generator.BuildConfigParams{ - ObjectMeta: commonObjectMeta, - BuildConfigSpecParams: generator.BuildConfigSpecParams{ - ImageStreamTagName: commonObjectMeta.Name, - GitURL: gitURL, - GitRef: gitRef, - BuildStrategy: generator.GetSourceBuildStrategy(imageName, imageNamespace), - }, - } - - buildConfig := generator.GetBuildConfig(params) - - return *buildConfig -} - -// -// Below is related to SUPERVISORD -// - -// AddBootstrapInitContainer adds the bootstrap init container to the deployment config -// dc is the deployment config to be updated -// dcName is the name of the deployment config -func addBootstrapVolumeCopyInitContainer(dc *appsv1.DeploymentConfig, dcName string) { - dc.Spec.Template.Spec.InitContainers = append(dc.Spec.Template.Spec.InitContainers, - corev1.Container{ - Name: "copy-files-to-volume", - // Using custom image from bootstrapperImage variable for the initial initContainer - Image: dc.Spec.Template.Spec.Containers[0].Image, - Command: []string{ - "sh", - "-c"}, - // Script required to copy over file information from /opt/app-root - // Source https://github.com/jupyter-on-openshift/jupyter-notebooks/blob/master/minimal-notebook/setup-volume.sh - Args: []string{` -SRC=/opt/app-root -DEST=/mnt/app-root - -if [ -f $DEST/.delete-volume ]; then - rm -rf $DEST -fi - if [ -d $DEST ]; then - if [ -f $DEST/.sync-volume ]; then - if ! [[ "$JUPYTER_SYNC_VOLUME" =~ ^(false|no|n|0)$ ]]; then - JUPYTER_SYNC_VOLUME=yes - fi - fi - if [[ "$JUPYTER_SYNC_VOLUME" =~ ^(true|yes|y|1)$ ]]; then - rsync -ar --ignore-existing $SRC/. $DEST - fi - exit -fi - if [ -d $DEST.setup-volume ]; then - rm -rf $DEST.setup-volume -fi - -mkdir -p $DEST.setup-volume -tar -C $SRC -cf - . | tar -C $DEST.setup-volume -xvf - -mv $DEST.setup-volume $DEST - `}, - VolumeMounts: []corev1.VolumeMount{ - { - Name: getAppRootVolumeName(dcName), - MountPath: "/mnt", - }, - }, - }) -} - -// addBootstrapSupervisordInitContainer creates an init container that will copy over -// supervisord to the application image during the start-up process. -func addBootstrapSupervisordInitContainer(dc *appsv1.DeploymentConfig, dcName string) { - - dc.Spec.Template.Spec.InitContainers = append(dc.Spec.Template.Spec.InitContainers, - corev1.Container{ - Name: common.SupervisordInitContainerName, - Image: common.GetBootstrapperImage(), - VolumeMounts: []corev1.VolumeMount{ - { - Name: common.SupervisordVolumeName, - MountPath: common.SupervisordMountPath, - }, - }, - Command: []string{ - "/usr/bin/cp", - }, - Args: []string{ - "-r", - common.OdoInitImageContents, - common.SupervisordMountPath, - }, - }) -} - -// addBootstrapVolume adds the bootstrap volume to the deployment config -// dc is the deployment config to be updated -// dcName is the name of the deployment config -func addBootstrapVolume(dc *appsv1.DeploymentConfig, dcName string) { - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: getAppRootVolumeName(dcName), - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: getAppRootVolumeName(dcName), - }, - }, - }) -} - -// addBootstrapVolumeMount mounts the bootstrap volume to the deployment config -// dc is the deployment config to be updated -// dcName is the name of the deployment config -func addBootstrapVolumeMount(dc *appsv1.DeploymentConfig, dcName string) { - addVolumeMount(dc, getAppRootVolumeName(dcName), DefaultAppRootDir, appRootSubPath) -} - -// addDeploymentDirVolumeMount mounts the bootstrap volume to the deployment config -// in a sub path where the ODO_S2I_DEPLOYMENT_DIR is present for optimisation purposes -// dc is the deployment config to be updated -func addDeploymentDirVolumeMount(dc *appsv1.DeploymentConfig, mountPath string) { - addVolumeMount(dc, getAppRootVolumeName(dc.Name), mountPath, deploymentDirSubPath) -} - -// addVolumeMount adds a volume mount to the deployment config -// dc is the deployment config to be updated -func addVolumeMount(dc *appsv1.DeploymentConfig, name, mountPath, subPath string) { - for i := range dc.Spec.Template.Spec.Containers { - dc.Spec.Template.Spec.Containers[i].VolumeMounts = append(dc.Spec.Template.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{ - Name: name, - MountPath: mountPath, - SubPath: subPath, - }) - } -} diff --git a/pkg/occlient/utils.go b/pkg/occlient/utils.go index f2ca49a7184..0d2bfa02ffc 100644 --- a/pkg/occlient/utils.go +++ b/pkg/occlient/utils.go @@ -1,156 +1 @@ package occlient - -import ( - "fmt" - - appsv1 "github.com/openshift/api/apps/v1" - imagev1 "github.com/openshift/api/image/v1" - "github.com/openshift/library-go/pkg/apps/appsutil" - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/kclient" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/klog" -) - -// HasTag checks to see if there is a tag in a list of over tags.. -func HasTag(tags []string, requiredTag string) bool { - for _, tag := range tags { - if tag == requiredTag { - return true - } - } - return false -} - -// getDeploymentCondition returns the condition with the provided type. -// Borrowed from https://github.com/openshift/origin/blob/64349ed036ed14808124c5b4d8538b3856783b54/pkg/oc/originpolymorphichelpers/deploymentconfigs/status.go -func getDeploymentCondition(status appsv1.DeploymentConfigStatus, condType appsv1.DeploymentConditionType) *appsv1.DeploymentCondition { - for i := range status.Conditions { - c := status.Conditions[i] - if c.Type == condType { - return &c - } - } - return nil -} - -// IsDCRolledOut indicates whether the deployment config is rolled out or not -// Borrowed from https://github.com/openshift/origin/blob/64349ed036ed14808124c5b4d8538b3856783b54/pkg/oc/originpolymorphichelpers/deploymentconfigs/status.go -func IsDCRolledOut(config *appsv1.DeploymentConfig, desiredRevision int64) bool { - - latestRevision := config.Status.LatestVersion - - if latestRevision == 0 { - switch { - case appsutil.HasImageChangeTrigger(config): - klog.V(3).Infof("Deployment config %q waiting on image update", config.Name) - return false - - case len(config.Spec.Triggers) == 0: - fmt.Printf("Deployment config %q waiting on manual update (use 'oc rollout latest %s')", config.Name, config.Name) - return false - } - } - - // We use `<` due to OpenShift at times (in rare cases) updating the DeploymentConfig multiple times via ImageTrigger - if desiredRevision > 0 && latestRevision < desiredRevision { - klog.V(3).Infof("Desired revision (%d) is different from the running revision (%d)", desiredRevision, latestRevision) - return false - } - - // Check the current condition of the deployment config - cond := getDeploymentCondition(config.Status, appsv1.DeploymentProgressing) - if config.Generation <= config.Status.ObservedGeneration { - switch { - case cond != nil && cond.Reason == "NewReplicationControllerAvailable": - return true - - case cond != nil && cond.Reason == "ProgressDeadlineExceeded": - return true - - case cond != nil && cond.Reason == "RolloutCancelled": - return true - - case cond != nil && cond.Reason == "DeploymentConfigPaused": - return true - - case config.Status.UpdatedReplicas < config.Spec.Replicas: - klog.V(3).Infof("Waiting for rollout to finish: %d out of %d new replicas have been updated...", config.Status.UpdatedReplicas, config.Spec.Replicas) - return false - - case config.Status.Replicas > config.Status.UpdatedReplicas: - klog.V(3).Infof("Waiting for rollout to finish: %d old replicas are pending termination...", config.Status.Replicas-config.Status.UpdatedReplicas) - return false - - case config.Status.AvailableReplicas < config.Status.UpdatedReplicas: - klog.V(3).Infof("Waiting for rollout to finish: %d of %d updated replicas are available...", config.Status.AvailableReplicas, config.Status.UpdatedReplicas) - return false - } - } - return false -} - -// GetS2IEnvForDevfile gets environment variable for builder image to be added in devfiles -func GetS2IEnvForDevfile(sourceType string, env config.EnvVarList, imageStreamImage imagev1.ImageStreamImage) (config.EnvVarList, error) { - klog.V(2).Info("Get S2I environment variables to be added in devfile") - - s2iPaths, err := getS2IMetaInfoFromBuilderImg(&imageStreamImage) - if err != nil { - return nil, err - } - - inputEnvs, err := kclient.GetInputEnvVarsFromStrings(env.ToStringSlice()) - if err != nil { - return nil, err - } - // Append s2i related parameters extracted above to env - inputEnvs = injectS2IPaths(inputEnvs, s2iPaths) - - if sourceType == string(config.LOCAL) { - inputEnvs = uniqueAppendOrOverwriteEnvVars( - inputEnvs, - corev1.EnvVar{ - Name: EnvS2ISrcBackupDir, - Value: s2iPaths.SrcBackupPath, - }, - ) - } - - var configEnvs config.EnvVarList - - for _, env := range inputEnvs { - configEnv := config.EnvVar{ - Name: env.Name, - Value: env.Value, - } - - configEnvs = append(configEnvs, configEnv) - } - - return configEnvs, nil -} - -// generateServiceSpec generates the service spec for s2i components -func generateServiceSpec(commonObjectMeta metav1.ObjectMeta, containerPorts []corev1.ContainerPort) corev1.ServiceSpec { - // generate the Service spec - var svcPorts []corev1.ServicePort - for _, containerPort := range containerPorts { - svcPort := corev1.ServicePort{ - - Name: containerPort.Name, - Port: containerPort.ContainerPort, - Protocol: containerPort.Protocol, - TargetPort: intstr.FromInt(int(containerPort.ContainerPort)), - } - svcPorts = append(svcPorts, svcPort) - } - - return corev1.ServiceSpec{ - Ports: svcPorts, - Selector: map[string]string{ - "deploymentconfig": commonObjectMeta.Name, - }, - } -} diff --git a/pkg/occlient/utils_test.go b/pkg/occlient/utils_test.go index 2840e7d9df3..0d2bfa02ffc 100644 --- a/pkg/occlient/utils_test.go +++ b/pkg/occlient/utils_test.go @@ -1,33 +1 @@ package occlient - -import ( - "testing" -) - -func TestHasTag(t *testing.T) { - cases := []struct { - list []string - inputTag string - expected bool - }{ - { - list: []string{"builder", "php", "hidden"}, - inputTag: "hidden", - expected: true, - }, - { - list: []string{"builder", "nodejs", "hidden"}, - inputTag: "php", - expected: false, - }, - } - - for _, testCase := range cases { - outcome := HasTag(testCase.list, testCase.inputTag) - if outcome != testCase.expected { - t.Errorf("HasTag(%v, %v) returned %v, expected %v", - testCase.list, testCase.inputTag, outcome, testCase.expected) - - } - } -} diff --git a/pkg/occlient/volumes.go b/pkg/occlient/volumes.go deleted file mode 100644 index 6261ee48124..00000000000 --- a/pkg/occlient/volumes.go +++ /dev/null @@ -1,295 +0,0 @@ -package occlient - -import ( - "context" - "fmt" - - "github.com/devfile/library/pkg/devfile/generator" - appsv1 "github.com/openshift/api/apps/v1" - "github.com/openshift/odo/pkg/kclient" - "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/util/retry" - "k8s.io/klog" -) - -// CreatePVC creates a PVC resource in the cluster with the given name, size and -// labels -func (c *Client) CreatePVC(name string, size string, labels map[string]string, ownerReference ...metav1.OwnerReference) (*corev1.PersistentVolumeClaim, error) { - quantity, err := resource.ParseQuantity(size) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse size: %v", size) - } - - pvcParams := generator.PVCParams{ - ObjectMeta: generator.GetObjectMeta(name, c.Namespace, labels, nil), - Quantity: quantity, - } - - pvc := generator.GetPVC(pvcParams) - - for _, owRf := range ownerReference { - pvc.SetOwnerReferences(append(pvc.GetOwnerReferences(), owRf)) - } - - return c.kubeClient.CreatePVC(*pvc) -} - -// GetPVCNameFromVolumeMountName returns the PVC associated with the given volume -// An empty string is returned if the volume is not found -func (c *Client) GetPVCNameFromVolumeMountName(volumeMountName string, dc *appsv1.DeploymentConfig) string { - for _, volume := range dc.Spec.Template.Spec.Volumes { - if volume.Name == volumeMountName { - if volume.PersistentVolumeClaim != nil { - return volume.PersistentVolumeClaim.ClaimName - } - } - } - return "" -} - -// AddPVCToDeploymentConfig adds the given PVC to the given Deployment Config -// at the given path -func (c *Client) AddPVCToDeploymentConfig(dc *appsv1.DeploymentConfig, pvc string, path string) error { - volumeName := generateVolumeNameFromPVC(pvc) - - // Validating dc.Spec.Template is present before dereferencing - if dc.Spec.Template == nil { - return fmt.Errorf("TemplatePodSpec in %s DeploymentConfig is empty", dc.Name) - } - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: pvc, - }, - }, - }) - - // Validating dc.Spec.Template.Spec.Containers[] is present before dereferencing - if len(dc.Spec.Template.Spec.Containers) == 0 { - return fmt.Errorf("DeploymentConfig %s doesn't have any Containers defined", dc.Name) - } - dc.Spec.Template.Spec.Containers[0].VolumeMounts = append(dc.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: path, - }, - ) - return nil -} - -// IsAppSupervisorDVolume checks if the volume is a supervisorD volume -func (c *Client) IsAppSupervisorDVolume(volumeName, dcName string) bool { - return volumeName == getAppRootVolumeName(dcName) -} - -// IsVolumeAnEmptyDir returns true if the volume is an EmptyDir, false if not -func (c *Client) IsVolumeAnEmptyDir(volumeMountName string, dc *appsv1.DeploymentConfig) bool { - for _, volume := range dc.Spec.Template.Spec.Volumes { - if volume.Name == volumeMountName { - if volume.EmptyDir != nil { - return true - } - } - } - return false -} - -// IsVolumeAnConfigMap returns true if the volume is an ConfigMap, false if not -func (c *Client) IsVolumeAnConfigMap(volumeMountName string, dc *appsv1.DeploymentConfig) bool { - for _, volume := range dc.Spec.Template.Spec.Volumes { - if volume.Name == volumeMountName { - if volume.ConfigMap != nil { - return true - } - } - } - return false -} - -// RemoveVolumeFromDeploymentConfig removes the volume associated with the -// given PVC from the Deployment Config. Both, the volume entry and the -// volume mount entry in the containers, are deleted. -func (c *Client) RemoveVolumeFromDeploymentConfig(pvc string, dcName string) error { - - retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - - dc, err := c.GetDeploymentConfigFromName(dcName) - if err != nil { - return errors.Wrapf(err, "unable to get Deployment Config: %v", dcName) - } - - volumeNames := getVolumeNamesFromPVC(pvc, dc) - numVolumes := len(volumeNames) - if numVolumes == 0 { - return fmt.Errorf("no volume found for PVC %v in DC %v, expected one", pvc, dc.Name) - } else if numVolumes > 1 { - return fmt.Errorf("found more than one volume for PVC %v in DC %v, expected one", pvc, dc.Name) - } - volumeName := volumeNames[0] - - // Remove volume if volume exists in Deployment Config - err = removeVolumeFromDC(volumeName, dc) - if err != nil { - return err - } - klog.V(3).Infof("Found volume: %v in Deployment Config: %v", volumeName, dc.Name) - - // Remove at max 2 volume mounts if volume mounts exists - err = removeVolumeMountsFromDC(volumeName, dc) - if err != nil { - return err - } - - _, updateErr := c.appsClient.DeploymentConfigs(c.Namespace).Update(context.TODO(), dc, metav1.UpdateOptions{FieldManager: kclient.FieldManager}) - return updateErr - }) - if retryErr != nil { - return errors.Wrapf(retryErr, "updating Deployment Config %v failed", dcName) - } - return nil -} - -// getVolumeNamesFromPVC returns the name of the volume associated with the given -// PVC in the given Deployment Config -func getVolumeNamesFromPVC(pvc string, dc *appsv1.DeploymentConfig) []string { - var volumes []string - for _, volume := range dc.Spec.Template.Spec.Volumes { - - // If PVC does not exist, we skip (as this is either EmptyDir or "shared-data" from SupervisorD - if volume.PersistentVolumeClaim == nil { - klog.V(3).Infof("Volume has no PVC, skipping %s", volume.Name) - continue - } - - // If we find the PVC, add to volumes to be returned - if volume.PersistentVolumeClaim.ClaimName == pvc { - volumes = append(volumes, volume.Name) - } - - } - return volumes -} - -// removeVolumeFromDC removes the volume from the given Deployment Config and -// returns true. If the given volume is not found, it returns false. -func removeVolumeFromDC(vol string, dc *appsv1.DeploymentConfig) error { - - // Error out immediately if there are zero volumes to begin with - if len(dc.Spec.Template.Spec.Volumes) == 0 { - return errors.New("there are *no* volumes in this DeploymentConfig to remove") - } - - found := false - - // If for some reason there is only one volume, let's slice the array to zero length - // or else you will get a "runtime error: slice bounds of of range [2:1] error - if len(dc.Spec.Template.Spec.Volumes) == 1 && vol == dc.Spec.Template.Spec.Volumes[0].Name { - // Mark as found and slice to zero length - found = true - dc.Spec.Template.Spec.Volumes = dc.Spec.Template.Spec.Volumes[:0] - } else { - - for i, volume := range dc.Spec.Template.Spec.Volumes { - - // If we find a match - if volume.Name == vol { - found = true - - // Copy (it takes longer, but maintains volume order) - copy(dc.Spec.Template.Spec.Volumes[i:], dc.Spec.Template.Spec.Volumes[i+1:]) - dc.Spec.Template.Spec.Volumes = dc.Spec.Template.Spec.Volumes[:len(dc.Spec.Template.Spec.Volumes)-1] - - break - } - - } - } - - if found { - return nil - } - - return fmt.Errorf("unable to find volume '%s' within DeploymentConfig '%s'", vol, dc.ObjectMeta.Name) -} - -// removeVolumeMountsFromDC removes the volumeMounts from all the given containers -// in the given Deployment Config and return true. If any of the volumeMount with the name -// is not found, it returns false. -func removeVolumeMountsFromDC(volumeMount string, dc *appsv1.DeploymentConfig) error { - - if len(dc.Spec.Template.Spec.Containers) == 0 { - return errors.New("something went wrong, there are *no* containers available to iterate through") - } - - found := false - - for i, container := range dc.Spec.Template.Spec.Containers { - - if len(dc.Spec.Template.Spec.Containers[i].VolumeMounts) == 1 && dc.Spec.Template.Spec.Containers[i].VolumeMounts[0].Name == volumeMount { - // Mark as found and slice to zero length - found = true - dc.Spec.Template.Spec.Containers[i].VolumeMounts = dc.Spec.Template.Spec.Containers[i].VolumeMounts[:0] - } else { - - for j, volMount := range container.VolumeMounts { - - // If we find a match - if volMount.Name == volumeMount { - found = true - - // Copy (it takes longer, but maintains volume mount order) - copy(dc.Spec.Template.Spec.Containers[i].VolumeMounts[j:], dc.Spec.Template.Spec.Containers[i].VolumeMounts[j+1:]) - dc.Spec.Template.Spec.Containers[i].VolumeMounts = dc.Spec.Template.Spec.Containers[i].VolumeMounts[:len(dc.Spec.Template.Spec.Containers[i].VolumeMounts)-1] - - break - } - } - } - } - - if found { - return nil - } - - return fmt.Errorf("unable to find volume mount '%s'", volumeMount) -} - -// generateVolumeNameFromPVC generates a random volume name based on the name -// of the given PVC -func generateVolumeNameFromPVC(pvc string) string { - return fmt.Sprintf("%v-%v-volume", pvc, util.GenerateRandomString(nameLength)) -} - -// addOrRemoveVolumeAndVolumeMount mounts or unmounts PVCs from the given deploymentConfig -func addOrRemoveVolumeAndVolumeMount(client *Client, dc *appsv1.DeploymentConfig, storageToMount map[string]*corev1.PersistentVolumeClaim, storageUnMount map[string]string) error { - - if len(dc.Spec.Template.Spec.Containers) == 0 || len(dc.Spec.Template.Spec.Containers) > 1 { - return fmt.Errorf("more than one container found in dc") - } - - // find the volume mount to be unmounted from the dc - for i, volumeMount := range dc.Spec.Template.Spec.Containers[0].VolumeMounts { - if _, ok := storageUnMount[volumeMount.MountPath]; ok { - dc.Spec.Template.Spec.Containers[0].VolumeMounts = append(dc.Spec.Template.Spec.Containers[0].VolumeMounts[:i], dc.Spec.Template.Spec.Containers[0].VolumeMounts[i+1:]...) - - // now find the volume to be deleted from the dc - for j, volume := range dc.Spec.Template.Spec.Volumes { - if volume.Name == volumeMount.Name { - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes[:j], dc.Spec.Template.Spec.Volumes[j+1:]...) - } - } - } - } - - for path, pvc := range storageToMount { - err := client.AddPVCToDeploymentConfig(dc, pvc.Name, path) - if err != nil { - return errors.Wrap(err, "unable to add pvc to deployment config") - } - } - return nil -} diff --git a/pkg/occlient/volumes_test.go b/pkg/occlient/volumes_test.go deleted file mode 100644 index 55fd44ee586..00000000000 --- a/pkg/occlient/volumes_test.go +++ /dev/null @@ -1,449 +0,0 @@ -package occlient - -import ( - "reflect" - "strings" - "testing" - - appsv1 "github.com/openshift/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/runtime" - ktesting "k8s.io/client-go/testing" -) - -func TestCreatePVC(t *testing.T) { - tests := []struct { - name string - size string - labels map[string]string - wantErr bool - }{ - { - name: "storage 10Gi", - size: "10Gi", - labels: map[string]string{ - "name": "mongodb", - "namespace": "blog", - }, - wantErr: false, - }, - { - name: "storage 1024", - size: "1024", - labels: map[string]string{ - "name": "PostgreSQL", - "namespace": "backend", - }, - wantErr: false, - }, - { - name: "storage invalid size", - size: "4#0", - labels: map[string]string{ - "name": "MySQL", - "namespace": "", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fkclient, fkclientset := FakeNew() - - _, err := fkclient.CreatePVC(tt.name, tt.size, tt.labels) - - // Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf(" client.CreatePVC(name, size, labels) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // Check for validating actions performed - if (len(fkclientset.Kubernetes.Actions()) != 1) && (tt.wantErr != true) { - t.Errorf("expected 1 action in CreatePVC got: %v", fkclientset.RouteClientset.Actions()) - } - // Checks for return values in positive cases - if err == nil { - createdPVC := fkclientset.Kubernetes.Actions()[0].(ktesting.CreateAction).GetObject().(*corev1.PersistentVolumeClaim) - quantity, err := resource.ParseQuantity(tt.size) - if err != nil { - t.Errorf("failed to create quantity by calling resource.ParseQuantity(%v)", tt.size) - } - - // created PVC should be labeled with labels passed to CreatePVC - if !reflect.DeepEqual(createdPVC.Labels, tt.labels) { - t.Errorf("labels in created route is not matching expected labels, expected: %v, got: %v", tt.labels, createdPVC.Labels) - } - // name, size of createdPVC should be matching to size, name passed to CreatePVC - if !reflect.DeepEqual(createdPVC.Spec.Resources.Requests["storage"], quantity) { - t.Errorf("size of PVC is not matching to expected size, expected: %v, got %v", quantity, createdPVC.Spec.Resources.Requests["storage"]) - } - if createdPVC.Name != tt.name { - t.Errorf("PVC name is not matching to expected name, expected: %s, got %s", tt.name, createdPVC.Name) - } - } - }) - } -} - -func TestAddPVCToDeploymentConfig(t *testing.T) { - type args struct { - dc *appsv1.DeploymentConfig - pvc string - path string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Test case 1: valid dc", - args: args{ - dc: &appsv1.DeploymentConfig{ - Spec: appsv1.DeploymentConfigSpec{ - Selector: map[string]string{ - "deploymentconfig": "nodejs-app", - }, - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test", - }, - }, - }, - }, - }, - }, - }, - }, - pvc: "test volume", - path: "/mnt", - }, - wantErr: false, - }, - { - name: "Test case 2: dc without Containers defined", - args: args{ - dc: &appsv1.DeploymentConfig{ - Spec: appsv1.DeploymentConfigSpec{ - Selector: map[string]string{ - "deploymentconfig": "nodejs-app", - }, - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{}, - }, - }, - }, - pvc: "test-voulme", - path: "/mnt", - }, - wantErr: true, - }, - { - name: "Test case 3: dc without Template defined", - args: args{ - dc: &appsv1.DeploymentConfig{ - Spec: appsv1.DeploymentConfigSpec{ - Selector: map[string]string{ - "deploymentconfig": "nodejs-app", - }, - }, - }, - pvc: "test-voulme", - path: "/mnt", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, _ := FakeNew() - - err := fakeClient.AddPVCToDeploymentConfig(tt.args.dc, tt.args.pvc, tt.args.path) - - // Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf("Client.AddPVCToDeploymentConfig() unexpected error = %v, wantErr %v", err, tt.wantErr) - } - - // Checks for number of actions performed in positive cases - if err == nil { - - found := false // creating a flag - // iterating over the VolumeMounts for finding the one specified during func call - for bb := range tt.args.dc.Spec.Template.Spec.Containers[0].VolumeMounts { - if tt.args.path == tt.args.dc.Spec.Template.Spec.Containers[0].VolumeMounts[bb].MountPath { - found = true - if !strings.Contains(tt.args.dc.Spec.Template.Spec.Containers[0].VolumeMounts[bb].Name, tt.args.pvc) { - t.Errorf("pvc name not matching with the specified value got: %v, expected %v", tt.args.dc.Spec.Template.Spec.Containers[0].VolumeMounts[bb].Name, tt.args.pvc) - } - } - } - if found == false { - t.Errorf("expected Volume mount path %v not found in VolumeMounts", tt.args.path) - } - - found = false // resetting the flag - // iterating over the volume claims to find the one specified during func call - for bb := range tt.args.dc.Spec.Template.Spec.Volumes { - if tt.args.pvc == tt.args.dc.Spec.Template.Spec.Volumes[bb].VolumeSource.PersistentVolumeClaim.ClaimName { - found = true - if !strings.Contains(tt.args.dc.Spec.Template.Spec.Volumes[bb].Name, tt.args.pvc) { - t.Errorf("pvc name not matching in PersistentVolumeClaim, got: %v, expected %v", tt.args.dc.Spec.Template.Spec.Volumes[bb].Name, tt.args.pvc) - } - } - } - if found == false { - t.Errorf("expected volume %s not found in DeploymentConfig.Spec.Template.Spec.Volumes", tt.args.pvc) - } - - } - - }) - } -} - -func TestRemoveVolumeFromDC(t *testing.T) { - type args struct { - volName string - dc appsv1.DeploymentConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Case 1 - Test removing volumes", - args: args{ - volName: "foo-s2idata", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantErr: false, - }, - { - name: "Case 2 - Error out, test removing non-existant volume", - args: args{ - volName: "doesnotexist", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - err := removeVolumeFromDC(tt.args.volName, &tt.args.dc) - - if tt.wantErr && err == nil { - t.Errorf("Wanted an error, got a pass") - } - - if err != nil && !tt.wantErr { - t.Errorf("Got error: %s", err) - } - - // Check that it was actually removed - for _, j := range tt.args.dc.Spec.Template.Spec.Volumes { - if j.Name == tt.args.volName { - t.Errorf("volume %s still exists even after removeVolumeFromDC function, %+v", tt.args.volName, tt.args.dc.Spec.Template.Spec.Containers) - } - } - - }) - } -} - -func Test_removeVolumeMountsFromDC(t *testing.T) { - type args struct { - volName string - dc appsv1.DeploymentConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Case 1 - Test removing volume mount", - args: args{ - volName: "foo-s2idata", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantErr: false, - }, - { - name: "Case 2 - Error out, test removing non-existant volume mount", - args: args{ - volName: "doesnotexist", - dc: *fakeDeploymentConfig("foo", "bar", nil, nil, t), - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - err := removeVolumeMountsFromDC(tt.args.volName, &tt.args.dc) - - if tt.wantErr && err == nil { - t.Errorf("Wanted an error, got a pass") - } - - if err != nil && !tt.wantErr { - t.Errorf("Got error: %s", err) - } - - // Check that it was actually removed - for _, container := range tt.args.dc.Spec.Template.Spec.Containers { - for _, volMount := range container.VolumeMounts { - if volMount.Name == tt.args.volName { - t.Errorf("volume mount %s still exists even after removeVolumeMountsFromDC function, %+v", tt.args.volName, tt.args.dc.Spec.Template.Spec.Containers) - } - } - } - - }) - } -} - -func TestGetPVCNameFromVolumeMountName(t *testing.T) { - dcWithPVC := fakeDeploymentConfig("test", "test", nil, nil, nil) - dcWithPVC.Spec.Template.Spec.Volumes = append(dcWithPVC.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: "test-pvc", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "test-pvc", - }, - }, - }) - - type args struct { - volumeMountName string - dc *appsv1.DeploymentConfig - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Test case : Deployment config with given PVC", - args: args{ - volumeMountName: "test-pvc", - dc: dcWithPVC, - }, - want: "test-pvc", - }, - { - name: "Test case : Deployment config without given PVC", - args: args{ - volumeMountName: "non-existent-pvc", - dc: fakeDeploymentConfig("test", "test", nil, nil, nil), - }, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, _ := FakeNew() - - returnValue := fakeClient.GetPVCNameFromVolumeMountName(tt.args.volumeMountName, tt.args.dc) - - // Check for validating return value - if returnValue != tt.want { - t.Errorf("error in return value got: %v, expected %v", returnValue, tt.want) - } - }) - } -} - -func TestRemoveVolumeFromDeploymentConfig(t *testing.T) { - type args struct { - pvc string - dcName string - } - tests := []struct { - name string - dcBefore *appsv1.DeploymentConfig - args args - wantErr bool - }{ - { - name: "Test case : 1", - dcBefore: &appsv1.DeploymentConfig{ - Spec: appsv1.DeploymentConfigSpec{ - Selector: map[string]string{ - "deploymentconfig": "test", - }, - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/tmp", - Name: "test-pvc", - }, - }, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "test-pvc", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "test-pvc", - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - pvc: "test-pvc", - dcName: "test", - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeClient, fakeClientSet := FakeNew() - - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, tt.dcBefore, nil - }) - fakeClientSet.AppsClientset.PrependReactor("update", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - err := fakeClient.RemoveVolumeFromDeploymentConfig(tt.args.pvc, tt.args.dcName) - - // Checks for error in positive cases - if !tt.wantErr == (err != nil) { - t.Errorf(" client.RemoveVolumeFromDeploymentConfig(pvc, dcName) unexpected error %v, wantErr %v", err, tt.wantErr) - } - // Check for validating number of actions performed - if (len(fakeClientSet.AppsClientset.Actions()) != 2) && (tt.wantErr != true) { - t.Errorf("expected 2 actions in GetPVCFromName got: %v", fakeClientSet.Kubernetes.Actions()) - } - updatedDc := fakeClientSet.AppsClientset.Actions()[1].(ktesting.UpdateAction).GetObject().(*appsv1.DeploymentConfig) - // validating volume got removed from dc - for _, volume := range updatedDc.Spec.Template.Spec.Volumes { - if volume.PersistentVolumeClaim.ClaimName == tt.args.pvc { - t.Errorf("expected volume with name : %v to be removed from dc", tt.args.pvc) - } - } - }) - } -} diff --git a/pkg/odo/cli/application/application.go b/pkg/odo/cli/application/application.go index 3eb3746be3b..476c9b5260a 100644 --- a/pkg/odo/cli/application/application.go +++ b/pkg/odo/cli/application/application.go @@ -58,7 +58,7 @@ func printAppInfo(client *occlient.Client, kClient kclient.ClientInterface, appN if appName != "" { selector = applabels.GetSelector(appName) } - componentList, err := component.List(client, selector, nil) + componentList, err := component.List(client, selector) if err != nil { return errors.Wrap(err, "failed to get Component list") } diff --git a/pkg/odo/cli/application/delete.go b/pkg/odo/cli/application/delete.go index cdbcde69206..07611d0668e 100644 --- a/pkg/odo/cli/application/delete.go +++ b/pkg/odo/cli/application/delete.go @@ -2,7 +2,6 @@ package application import ( "fmt" - "path/filepath" odoUtil "github.com/openshift/odo/pkg/odo/util" @@ -38,11 +37,7 @@ func NewDeleteOptions() *DeleteOptions { // Complete completes DeleteOptions after they've been created func (o *DeleteOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - if util.CheckPathExists(filepath.Join(".odo", "config.yaml")) { - o.Context, err = genericclioptions.NewContext(cmd) - } else { - o.Context, err = genericclioptions.NewDevfileContext(cmd) - } + o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -64,7 +59,7 @@ func (o *DeleteOptions) Validate() (err error) { return fmt.Errorf("given output format %s is not supported", o.OutputFlag) } - exist, err := application.Exists(o.appName, o.Client) + exist, err := application.Exists(o.appName, o.Client.GetKubeClient()) if !exist { return fmt.Errorf("%s app does not exists", o.appName) } @@ -74,7 +69,7 @@ func (o *DeleteOptions) Validate() (err error) { // Run contains the logic for the odo command func (o *DeleteOptions) Run(cmd *cobra.Command) (err error) { if log.IsJSON() { - err = application.Delete(o.Client, o.appName) + err = application.Delete(o.Client.GetKubeClient(), o.appName) if err != nil { return err } @@ -88,7 +83,7 @@ func (o *DeleteOptions) Run(cmd *cobra.Command) (err error) { } if o.force || ui.Proceed(fmt.Sprintf("Are you sure you want to delete the application: %v from project: %v", o.appName, o.Project)) { - err = application.Delete(o.Client, o.appName) + err = application.Delete(o.Client.GetKubeClient(), o.appName) if err != nil { return err } diff --git a/pkg/odo/cli/application/describe.go b/pkg/odo/cli/application/describe.go index 95f02498086..a485ff121d9 100644 --- a/pkg/odo/cli/application/describe.go +++ b/pkg/odo/cli/application/describe.go @@ -2,12 +2,9 @@ package application import ( "fmt" - "path/filepath" applabels "github.com/openshift/odo/pkg/application/labels" - odoutil "github.com/openshift/odo/pkg/util" - "github.com/openshift/odo/pkg/application" "github.com/openshift/odo/pkg/component" "github.com/openshift/odo/pkg/log" @@ -41,11 +38,7 @@ func NewDescribeOptions() *DescribeOptions { // Complete completes DescribeOptions after they've been created func (o *DescribeOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - if odoutil.CheckPathExists(filepath.Join(".odo", "config.yaml")) { - o.Context, err = genericclioptions.NewContext(cmd) - } else { - o.Context, err = genericclioptions.NewDevfileContext(cmd) - } + o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -69,7 +62,7 @@ func (o *DescribeOptions) Validate() (err error) { return fmt.Errorf("There's no active application in project: %v", o.Project) } - exist, err := application.Exists(o.appName, o.Client) + exist, err := application.Exists(o.appName, o.Client.GetKubeClient()) if !exist { return fmt.Errorf("%s app does not exists", o.appName) } @@ -86,7 +79,7 @@ func (o *DescribeOptions) Run(cmd *cobra.Command) (err error) { if o.appName != "" { selector = applabels.GetSelector(o.appName) } - componentList, err := component.List(o.Client, selector, nil) + componentList, err := component.List(o.Client, selector) if err != nil { return err } diff --git a/pkg/odo/cli/application/list.go b/pkg/odo/cli/application/list.go index 47b04271b47..01888bbeee9 100644 --- a/pkg/odo/cli/application/list.go +++ b/pkg/odo/cli/application/list.go @@ -38,7 +38,7 @@ func NewListOptions() *ListOptions { // Complete completes ListOptions after they've been created func (o *ListOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - o.Context, err = genericclioptions.NewDevfileContext(cmd) + o.Context, err = genericclioptions.NewContext(cmd) return } @@ -53,7 +53,7 @@ func (o *ListOptions) Validate() (err error) { // Run contains the logic for the odo command func (o *ListOptions) Run(cmd *cobra.Command) (err error) { - apps, err := application.List(o.Client) + apps, err := application.List(o.Client.GetKubeClient()) if err != nil { return fmt.Errorf("unable to get list of applications: %v", err) } diff --git a/pkg/odo/cli/catalog/describe/component.go b/pkg/odo/cli/catalog/describe/component.go index 1a8179477cf..8cde277cdb0 100644 --- a/pkg/odo/cli/catalog/describe/component.go +++ b/pkg/odo/cli/catalog/describe/component.go @@ -25,7 +25,6 @@ import ( parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/openshift/odo/pkg/devfile" - "k8s.io/klog" ktemplates "k8s.io/kubectl/pkg/util/templates" ) @@ -46,8 +45,6 @@ type DescribeComponentOptions struct { componentName string // if devfile components with name that matches arg[0] devfileComponents []catalog.DevfileComponentType - // if componentName is a classic/odov1 component - component string // generic context options common to all commands *genericclioptions.Context } @@ -62,25 +59,10 @@ func (o *DescribeComponentOptions) Complete(name string, cmd *cobra.Command, arg o.componentName = args[0] tasks := util.NewConcurrentTasks(2) - o.Context, err = genericclioptions.NewContext(cmd, true) + o.Context, err = genericclioptions.NewOfflineContext(cmd) if err != nil { return err } - tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { - catalogList, err := catalog.ListComponents(o.Client) - if err != nil { - // TODO: - // This MAY have to change in the future.. There is no good way to determine whether the user - // wants to list OpenShift or Kubernetes components. So we simply just warn in debug V(4) if - // we are unable to list anything from OpenShift. - klog.V(4).Info("Please log in to an OpenShift cluster to list OpenShift/s2i components") - } - for _, image := range catalogList.Items { - if image.Name == o.componentName { - o.component = image.Name - } - } - }}) tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { catalogDevfileList, err := catalog.ListDevfileComponents("") @@ -98,7 +80,7 @@ func (o *DescribeComponentOptions) Complete(name string, cmd *cobra.Command, arg // Validate validates the DescribeComponentOptions based on completed values func (o *DescribeComponentOptions) Validate() (err error) { - if len(o.devfileComponents) == 0 && o.component == "" { + if len(o.devfileComponents) == 0 { return errors.Wrapf(err, "No components with the name \"%s\" found", o.componentName) } @@ -156,11 +138,6 @@ func (o *DescribeComponentOptions) Run(cmd *cobra.Command) (err error) { } else { fmt.Fprintln(w, "There are no Odo devfile components with the name \""+o.componentName+"\"") } - if o.component != "" { - fmt.Fprintln(w, "\nS2I Based Components:") - fmt.Fprintln(w, "-"+o.component) - } - fmt.Fprintln(w) } return nil diff --git a/pkg/odo/cli/catalog/describe/service.go b/pkg/odo/cli/catalog/describe/service.go index 4c84e0485c9..3b07b74bb8a 100644 --- a/pkg/odo/cli/catalog/describe/service.go +++ b/pkg/odo/cli/catalog/describe/service.go @@ -38,7 +38,7 @@ func NewDescribeServiceOptions() *DescribeServiceOptions { // Complete completes DescribeServiceOptions after they've been created func (o *DescribeServiceOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - o.Context, err = genericclioptions.NewContext(cmd, true) + o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } diff --git a/pkg/odo/cli/catalog/list/components.go b/pkg/odo/cli/catalog/list/components.go index fe74f9be152..2f3345feaef 100644 --- a/pkg/odo/cli/catalog/list/components.go +++ b/pkg/odo/cli/catalog/list/components.go @@ -2,11 +2,8 @@ package list import ( "fmt" - catalogutil "github.com/openshift/odo/pkg/odo/cli/catalog/util" "io" - "k8s.io/klog" "os" - "strings" "text/tabwriter" "github.com/openshift/odo/pkg/catalog" @@ -25,8 +22,6 @@ var componentsExample = ` # Get the supported components // ListComponentsOptions encapsulates the options for the odo catalog list components command type ListComponentsOptions struct { - // list of known images - catalogList catalog.ComponentTypeList // list of known devfiles catalogDevfileList catalog.DevfileComponentTypeList // generic context options common to all commands @@ -40,122 +35,58 @@ func NewListComponentsOptions() *ListComponentsOptions { // Complete completes ListComponentsOptions after they've been created func (o *ListComponentsOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - tasks := util.NewConcurrentTasks(2) - if err = util.CheckKubeConfigPath(); err == nil { o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } - supported, err := o.Client.IsImageStreamSupported() - if err != nil { - klog.V(4).Info("ignoring error while checking imagestream support:", err.Error()) - } - - if supported { - tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { - o.catalogList, err = catalog.ListComponents(o.Client) - if err != nil { - errChannel <- err - } else { - o.catalogList.Items = catalogutil.FilterHiddenComponents(o.catalogList.Items) - } - }}) - } } - tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { - o.catalogDevfileList, err = catalog.ListDevfileComponents("") - if o.catalogDevfileList.DevfileRegistries == nil { - log.Warning("Please run 'odo registry add ' to add registry for listing devfile components\n") - } - if err != nil { - errChannel <- err - } - }}) + o.catalogDevfileList, err = catalog.ListDevfileComponents("") + if err != nil { + return err + } + if o.catalogDevfileList.DevfileRegistries == nil { + log.Warning("Please run 'odo registry add ' to add registry for listing devfile components\n") + } - return tasks.Run() + return } // Validate validates the ListComponentsOptions based on completed values func (o *ListComponentsOptions) Validate() (err error) { - if len(o.catalogList.Items) == 0 && len(o.catalogDevfileList.Items) == 0 { + if len(o.catalogDevfileList.Items) == 0 { return fmt.Errorf("no deployable components found") } return err } -type combinedCatalogList struct { +type catalogList struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - S2iItems []catalog.ComponentType `json:"s2iItems,omitempty"` - DevfileItems []catalog.DevfileComponentType `json:"devfileItems,omitempty"` + Items []catalog.DevfileComponentType `json:"items,omitempty"` } // Run contains the logic for the command associated with ListComponentsOptions func (o *ListComponentsOptions) Run(cmd *cobra.Command) (err error) { if log.IsJSON() { - for i, image := range o.catalogList.Items { - // here we don't care about the unsupported tags (second return value) - supported, _ := catalog.SliceSupportedTags(image) - o.catalogList.Items[i].Spec.SupportedTags = supported - } - combinedList := combinedCatalogList{ + combinedList := catalogList{ TypeMeta: metav1.TypeMeta{ Kind: "List", APIVersion: "odo.dev/v1alpha1", }, - S2iItems: o.catalogList.Items, - DevfileItems: o.catalogDevfileList.Items, + Items: o.catalogDevfileList.Items, } machineoutput.OutputSuccess(combinedList) } else { w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) - var supCatalogList, unsupCatalogList []catalog.ComponentType - var supported string - - for _, image := range o.catalogList.Items { - supported, unsupported := catalog.SliceSupportedTags(image) - - if len(supported) != 0 { - image.Spec.NonHiddenTags = supported - supCatalogList = append(supCatalogList, image) - } - if len(unsupported) != 0 { - image.Spec.NonHiddenTags = unsupported - unsupCatalogList = append(unsupCatalogList, image) - } - } - if len(o.catalogDevfileList.Items) != 0 { fmt.Fprintln(w, "Odo Devfile Components:") fmt.Fprintln(w, "NAME", "\t", "DESCRIPTION", "\t", "REGISTRY") o.printDevfileCatalogList(w, o.catalogDevfileList.Items, "") } - - if len(supCatalogList) != 0 || len(unsupCatalogList) != 0 { - // add a new line of there was something before this - if len(o.catalogDevfileList.Items) != 0 { - fmt.Fprintln(w) - } - fmt.Fprintln(w, "Odo S2I Components:") - fmt.Fprintln(w, "NAME", "\t", "PROJECT", "\t", "TAGS", "\t", "SUPPORTED") - - if len(supCatalogList) != 0 { - supported = "YES" - o.printCatalogList(w, supCatalogList, supported) - } - - if len(unsupCatalogList) != 0 { - supported = "NO" - o.printCatalogList(w, unsupCatalogList, supported) - } - - fmt.Fprint(w) - } - w.Flush() } return @@ -179,26 +110,6 @@ func NewCmdCatalogListComponents(name, fullName string) *cobra.Command { return componentListCmd } -func (o *ListComponentsOptions) printCatalogList(w io.Writer, catalogList []catalog.ComponentType, supported string) { - for _, component := range catalogList { - componentName := component.Name - if component.Namespace == o.Project { - /* - If current namespace is same as the current component namespace, - Loop through every other component, - If there exists a component with same name but in different namespaces, - mark the one from current namespace with (*) - */ - for _, comp := range catalogList { - if comp.ObjectMeta.Name == component.ObjectMeta.Name && component.Namespace != comp.Namespace { - componentName = fmt.Sprintf("%s (*)", component.ObjectMeta.Name) - } - } - } - fmt.Fprintln(w, componentName, "\t", component.ObjectMeta.Namespace, "\t", strings.Join(component.Spec.NonHiddenTags, ","), "\t", supported) - } -} - func (o *ListComponentsOptions) printDevfileCatalogList(w io.Writer, catalogDevfileList []catalog.DevfileComponentType, supported string) { for _, devfileComponent := range catalogDevfileList { if supported != "" { diff --git a/pkg/odo/cli/catalog/search/component.go b/pkg/odo/cli/catalog/search/component.go index 7bbe00dfd6c..d381f1a89c0 100644 --- a/pkg/odo/cli/catalog/search/component.go +++ b/pkg/odo/cli/catalog/search/component.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/openshift/odo/pkg/catalog" + "github.com/openshift/odo/pkg/odo/cli/catalog/util" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/spf13/cobra" diff --git a/pkg/odo/cli/catalog/util/util.go b/pkg/odo/cli/catalog/util/util.go index f0bf8057414..7733c35e572 100644 --- a/pkg/odo/cli/catalog/util/util.go +++ b/pkg/odo/cli/catalog/util/util.go @@ -33,19 +33,6 @@ func DisplayComponents(components []string) { w.Flush() } -// FilterHiddenComponents filters out components that should be hidden from the specified list -func FilterHiddenComponents(input []catalog.ComponentType) []catalog.ComponentType { - inputLength := len(input) - filteredComponents := make([]catalog.ComponentType, 0, inputLength) - for _, component := range input { - // we keep the image if it has tags that are no hidden - if len(component.Spec.NonHiddenTags) > 0 { - filteredComponents = append(filteredComponents, component) - } - } - return filteredComponents -} - // DisplayClusterServiceVersions displays installed Operators in a human friendly manner func DisplayClusterServiceVersions(csvs *olm.ClusterServiceVersionList) { w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) diff --git a/pkg/odo/cli/component/common_push.go b/pkg/odo/cli/component/common_push.go index 0e3f097bbc6..3c50d3fe662 100644 --- a/pkg/odo/cli/component/common_push.go +++ b/pkg/odo/cli/component/common_push.go @@ -1,19 +1,12 @@ package component import ( - "fmt" - "io" - "os" "path/filepath" "strings" "github.com/devfile/library/pkg/devfile/parser" - "github.com/fatih/color" - "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/kclient" - "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/project" "github.com/openshift/odo/pkg/util" @@ -24,20 +17,11 @@ import ( // CommonPushOptions has data needed for all pushes type CommonPushOptions struct { - ignores []string - show bool - - sourceType config.SrcType - sourcePath string + show bool componentContext string - - EnvSpecificInfo *envinfo.EnvSpecificInfo - - pushConfig bool - pushSource bool - forceBuild bool - doesComponentExist bool - + pushConfig bool + pushSource bool + EnvSpecificInfo *envinfo.EnvSpecificInfo *genericclioptions.Context } @@ -71,59 +55,6 @@ func (cpo *CommonPushOptions) ResolveSrcAndConfigFlags() { } } -//ValidateComponentCreate validates if the request to create component is valid -func (cpo *CommonPushOptions) ValidateComponentCreate() error { - var err error - s := log.Spinner("Checking component") - defer s.End(false) - cpo.doesComponentExist, err = component.Exists(cpo.Context.Client, cpo.LocalConfigInfo.GetName(), cpo.LocalConfigInfo.GetApplication()) - if err != nil { - return errors.Wrapf(err, "failed to check if component of name %s exists in application %s", cpo.LocalConfigInfo.GetName(), cpo.LocalConfigInfo.GetApplication()) - } - - if err = component.ValidateComponentCreateRequest(cpo.Context.Client, cpo.LocalConfigInfo.GetComponentSettings(), cpo.componentContext); err != nil { - s.End(false) - log.Italic("\nRun 'odo catalog list components' for a list of supported component types") - return fmt.Errorf("Invalid component type %s, %v", *cpo.LocalConfigInfo.GetComponentSettings().Type, errors.Cause(err)) - } - s.End(true) - return nil -} - -func (cpo *CommonPushOptions) createCmpIfNotExistsAndApplyCmpConfig(stdout io.Writer) error { - if !cpo.pushConfig { - // Not the case of component creation or update (with new config) - // So nothing to do here and hence return from here - return nil - } - - cmpName := cpo.LocalConfigInfo.GetName() - - // Output the "new" section (applying changes) - log.Info("\nConfiguration changes") - - // If the component does not exist, we will create it for the first time. - if !cpo.doesComponentExist { - - // Classic case of component creation - if err := component.CreateComponent(cpo.Context.Client, *cpo.LocalConfigInfo, cpo.componentContext, stdout); err != nil { - log.Errorf( - "Failed to create component with name %s. Please use `odo config view` to view settings used to create component. Error: %v", - cmpName, - err, - ) - return err - } - } - // Apply config - err := component.ApplyConfig(cpo.Context.Client, *cpo.LocalConfigInfo, envinfo.EnvSpecificInfo{}, stdout, cpo.doesComponentExist, true) - if err != nil { - return errors.Wrapf(err, "Failed to update config to component deployed.") - } - - return nil -} - // ResolveProject completes the push options as needed func (cpo *CommonPushOptions) ResolveProject(prjName string) (err error) { @@ -146,195 +77,6 @@ func (cpo *CommonPushOptions) ResolveProject(prjName string) (err error) { return } -// SetSourceInfo sets up source information -func (cpo *CommonPushOptions) SetSourceInfo() (err error) { - cpo.sourceType = cpo.LocalConfigInfo.GetSourceType() - - klog.V(4).Infof("SourceLocation: %s", cpo.LocalConfigInfo.GetSourceLocation()) - - // Get SourceLocation here... - cpo.sourcePath, err = cpo.LocalConfigInfo.GetOSSourcePath() - if err != nil { - return errors.Wrap(err, "unable to retrieve absolute path to source location") - } - - klog.V(4).Infof("Source Path: %s", cpo.sourcePath) - return -} - -// Push pushes changes as per set options -func (cpo *CommonPushOptions) Push() (err error) { - - deletedFiles := []string{} - changedFiles := []string{} - isForcePush := false - - stdout := color.Output - // Ret from Indexer function - var ret util.IndexerRet - - cmpName := cpo.LocalConfigInfo.GetName() - appName := cpo.LocalConfigInfo.GetApplication() - cpo.sourceType = cpo.LocalConfigInfo.GetSourceType() - // force write the content to resolvePath - forceWrite := false - - if cpo.componentContext == "" { - cpo.componentContext = strings.TrimSuffix(filepath.Dir(cpo.LocalConfigInfo.Filename), ".odo") - } - - err = cpo.createCmpIfNotExistsAndApplyCmpConfig(stdout) - if err != nil { - return - } - - if !cpo.pushSource { - // If source is not requested for update, return - return nil - } - - log.Infof("\nPushing to component %s of type %s", cmpName, cpo.sourceType) - - if !cpo.forceBuild && cpo.sourceType != config.GIT { - absIgnoreRules := util.GetAbsGlobExps(cpo.sourcePath, cpo.ignores) - - spinner := log.NewStatus(log.GetStdout()) - defer spinner.End(true) - if cpo.doesComponentExist { - spinner.Start("Checking file changes for pushing", false) - } else { - // if the component doesn't exist, we don't check for changes in the files - // thus we show a different message - spinner.Start("Checking files for pushing", false) - } - - // run the indexer and find the modified/added/deleted/renamed files - ret, err = util.RunIndexer(cpo.componentContext, absIgnoreRules) - spinner.End(true) - - if err != nil { - return errors.Wrap(err, "unable to run indexer") - } - if len(ret.FilesChanged) > 0 || len(ret.FilesDeleted) > 0 { - forceWrite = true - } - - if cpo.doesComponentExist { - // apply the glob rules from the .gitignore/.odoignore file - // and ignore the files on which the rules apply and filter them out - filesChangedFiltered, filesDeletedFiltered := filterIgnores(ret.FilesChanged, ret.FilesDeleted, absIgnoreRules) - - // Remove the relative file directory from the list of deleted files - // in order to make the changes correctly within the OpenShift pod - deletedFiles, err = util.RemoveRelativePathFromFiles(filesDeletedFiltered, cpo.sourcePath) - if err != nil { - return errors.Wrap(err, "unable to remove relative path from list of changed/deleted files") - } - klog.V(4).Infof("List of files to be deleted: +%v", deletedFiles) - changedFiles = filesChangedFiltered - - if len(filesChangedFiltered) == 0 && len(filesDeletedFiltered) == 0 { - // no file was modified/added/deleted/renamed, thus return without building - log.Success("No file changes detected, skipping build. Use the '-f' flag to force the build.") - return nil - } - } - } - - if cpo.forceBuild || !cpo.doesComponentExist { - isForcePush = true - } - - // Get SourceLocation here... - cpo.sourcePath, err = cpo.LocalConfigInfo.GetOSSourcePath() - if err != nil { - return errors.Wrap(err, "unable to retrieve OS source path to source location") - } - - switch cpo.sourceType { - case config.LOCAL: - klog.V(4).Infof("Copying directory %s to pod", cpo.sourcePath) - err = component.PushLocal( - cpo.Context.Client, - cmpName, - appName, - cpo.sourcePath, - os.Stdout, - changedFiles, - deletedFiles, - isForcePush, - util.GetAbsGlobExps(cpo.sourcePath, cpo.ignores), - cpo.show, - ) - - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("Failed to push component: %v", cmpName)) - } - - case config.BINARY: - - // We will pass in the directory, NOT filepath since this is a binary.. - binaryDirectory := filepath.Dir(cpo.sourcePath) - - klog.V(4).Infof("Copying binary file %s to pod", cpo.sourcePath) - err = component.PushLocal( - cpo.Context.Client, - cmpName, - appName, - binaryDirectory, - os.Stdout, - []string{cpo.sourcePath}, - deletedFiles, - isForcePush, - util.GetAbsGlobExps(cpo.sourcePath, cpo.ignores), - cpo.show, - ) - - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("Failed to push component: %v", cmpName)) - } - - // we don't need a case for building git components - // the build happens before deployment - - return errors.Wrapf(err, fmt.Sprintf("failed to push component: %v", cmpName)) - } - if forceWrite { - err = util.WriteFile(ret.NewFileMap, ret.ResolvedPath) - if err != nil { - return errors.Wrapf(err, "Failed to write file") - } - } - - log.Success("Changes successfully pushed to component") - return -} - -// filterIgnores applies the glob rules on the filesChanged and filesDeleted and filters them -// returns the filtered results which match any of the glob rules -func filterIgnores(filesChanged, filesDeleted, absIgnoreRules []string) (filesChangedFiltered, filesDeletedFiltered []string) { - for _, file := range filesChanged { - match, err := util.IsGlobExpMatch(file, absIgnoreRules) - if err != nil { - continue - } - if !match { - filesChangedFiltered = append(filesChangedFiltered, file) - } - } - - for _, file := range filesDeleted { - match, err := util.IsGlobExpMatch(file, absIgnoreRules) - if err != nil { - continue - } - if !match { - filesDeletedFiltered = append(filesDeletedFiltered, file) - } - } - return filesChangedFiltered, filesDeletedFiltered -} - // retrieveKubernetesDefaultNamespace tries to retrieve the current active namespace // to set as a default namespace func retrieveKubernetesDefaultNamespace() (string, error) { diff --git a/pkg/odo/cli/component/component.go b/pkg/odo/cli/component/component.go index 1444d38e491..fadea6bbb8f 100644 --- a/pkg/odo/cli/component/component.go +++ b/pkg/odo/cli/component/component.go @@ -2,9 +2,6 @@ package component import ( "fmt" - "path/filepath" - - "github.com/openshift/odo/pkg/util" "github.com/openshift/odo/pkg/odo/genericclioptions" odoutil "github.com/openshift/odo/pkg/odo/util" @@ -23,19 +20,9 @@ type ComponentOptions struct { // Complete completes component options func (co *ComponentOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - context, err := genericclioptions.GetContextFlagValue(cmd) - if err != nil { - return err - } - - devfilePath := filepath.Join(context, devFile) - if util.CheckPathExists(devfilePath) { - co.Context, err = genericclioptions.NewDevfileContext(cmd) - } else { - co.Context, err = genericclioptions.NewContext(cmd) - } + co.Context, err = genericclioptions.NewContext(cmd) if err != nil { - co.Context, err = genericclioptions.NewOfflineDevfileContext(cmd) + co.Context, err = genericclioptions.NewOfflineContext(cmd) if err != nil { return err } diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index a64e4e0fe67..941356d20d1 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -14,7 +14,6 @@ import ( registryLibrary "github.com/devfile/registry-support/registry-library/library" "github.com/openshift/odo/pkg/catalog" "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/devfile" "github.com/openshift/odo/pkg/devfile/validate" "github.com/openshift/odo/pkg/envinfo" @@ -118,29 +117,27 @@ func NewCreateOptions() *CreateOptions { } } -func createDefaultComponentName(context *genericclioptions.Context, componentType string, sourceType config.SrcType, sourcePath string) (string, error) { +func createDefaultComponentName(componentType string, sourcePath string) (string, error) { // Retrieve the componentName, if the componentName isn't specified, we will use the default image name var err error - finalSourcePath := sourcePath + var finalSourcePath string // we only get absolute path for local source type - if sourceType == config.LOCAL { - if sourcePath == "" { - wd, err := os.Getwd() - if err != nil { - return "", err - } - finalSourcePath = wd - } else { - finalSourcePath, err = filepath.Abs(sourcePath) - if err != nil { - return "", err - } + + if sourcePath == "" { + wd, err := os.Getwd() + if err != nil { + return "", err + } + finalSourcePath = wd + } else { + finalSourcePath, err = filepath.Abs(sourcePath) + if err != nil { + return "", err } } componentName, err := component.GetDefaultComponentName( finalSourcePath, - sourceType, componentType, component.ComponentList{}, ) @@ -156,13 +153,13 @@ func createDefaultComponentName(context *genericclioptions.Context, componentTyp func (co *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { if co.now { - // this populates the LocalConfigInfo as well + // this populates the EnvInfo as well co.Context, err = genericclioptions.NewContextCreatingAppIfNeeded(cmd) if err != nil { return err } } else { - co.Context, err = genericclioptions.NewOfflineDevfileContext(cmd) + co.Context, err = genericclioptions.NewOfflineContext(cmd) if err != nil { return err } @@ -318,9 +315,7 @@ func (co *CreateOptions) Complete(name string, cmd *cobra.Command, args []string } else { var err error componentName, err = createDefaultComponentName( - co.Context, componentType, - config.LOCAL, // always local for devfile co.componentContext, ) if err != nil { diff --git a/pkg/odo/cli/component/create_helpers.go b/pkg/odo/cli/component/create_helpers.go index 2f84ed83fed..cca7a47f83d 100644 --- a/pkg/odo/cli/component/create_helpers.go +++ b/pkg/odo/cli/component/create_helpers.go @@ -52,7 +52,7 @@ func (co *CreateOptions) DevfileJSON() error { return err } - cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(co.Client, co.LocalConfigInfo, envInfo, envInfo.GetName(), envInfo.GetApplication(), co.Project, co.ComponentContext) + cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfigProvider(co.Client, envInfo, envInfo.GetName(), envInfo.GetApplication(), co.Project, co.ComponentContext) if err != nil { return err } diff --git a/pkg/odo/cli/component/delete.go b/pkg/odo/cli/component/delete.go index 9708dcd6e90..9d7d2520286 100644 --- a/pkg/odo/cli/component/delete.go +++ b/pkg/odo/cli/component/delete.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" "k8s.io/klog" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/devfile" "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/log" @@ -89,7 +88,7 @@ func (do *DeleteOptions) Complete(name string, cmd *cobra.Command, args []string do.devfilePath = filepath.Join(do.componentContext, DevfilePath) - do.Context, err = genericclioptions.NewDevfileContext(cmd) + do.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -109,12 +108,6 @@ func (do *DeleteOptions) Validate() (err error) { func (do *DeleteOptions) Run(cmd *cobra.Command) (err error) { klog.V(4).Infof("component delete called") klog.V(4).Infof("args: %#v", do) - return do.DevFileRun() -} - -// DevFileRun has the logic to perform the required actions as part of command for devfiles -func (do *DeleteOptions) DevFileRun() (err error) { - // devfile delete if do.componentForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete the devfile component: %s?", do.EnvSpecificInfo.GetName())) { err = do.DevfileComponentDelete() if err != nil { @@ -143,15 +136,10 @@ func (do *DeleteOptions) DevFileRun() (err error) { if err != nil { return err } - - cfg, err := config.NewLocalConfigInfo(do.componentContext) + err = util.DeletePath(filepath.Join(do.componentContext, util.DotOdoDirectory)) if err != nil { return err } - if err = cfg.DeleteConfigDirIfEmpty(); err != nil { - return err - } - log.Successf("Successfully deleted env file") } else { log.Error("Aborting deletion of env folder") @@ -202,6 +190,29 @@ func (do *DeleteOptions) DevFileRun() (err error) { return fmt.Errorf("devfile.yaml does not exist in the current directory") } + // first remove the uri based files mentioned in the devfile + devfileObj, err := devfile.ParseFromFile(do.devfilePath) + if err != nil { + return err + } + + err = common.RemoveDevfileURIContents(devfileObj, do.componentContext) + if err != nil { + return err + } + + empty, err := util.IsEmpty(filepath.Join(do.componentContext, service.UriFolder)) + if err != nil && !os.IsNotExist(err) { + return err + } + + if !os.IsNotExist(err) && empty { + err = os.RemoveAll(filepath.Join(do.componentContext, service.UriFolder)) + if err != nil { + return err + } + } + err = util.DeletePath(do.devfilePath) if err != nil { return err diff --git a/pkg/odo/cli/component/describe.go b/pkg/odo/cli/component/describe.go index 219063b2637..69e62c7b464 100644 --- a/pkg/odo/cli/component/describe.go +++ b/pkg/odo/cli/component/describe.go @@ -2,10 +2,11 @@ package component import ( "fmt" + "os" + "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/machineoutput" "github.com/openshift/odo/pkg/odo/genericclioptions" - "os" "github.com/openshift/odo/pkg/component" appCmd "github.com/openshift/odo/pkg/odo/cli/application" @@ -54,7 +55,7 @@ func (do *DescribeOptions) Complete(name string, cmd *cobra.Command, args []stri // Validate validates the describe parameters func (do *DescribeOptions) Validate() (err error) { - if !((do.Application != "" && do.Project != "") || do.LocalConfigInfo.Exists() || do.EnvSpecificInfo.Exists()) { + if !((do.Application != "" && do.Project != "") || do.EnvSpecificInfo.Exists()) { return fmt.Errorf("component %v does not exist", do.componentName) } @@ -64,7 +65,7 @@ func (do *DescribeOptions) Validate() (err error) { // Run has the logic to perform the required actions as part of command func (do *DescribeOptions) Run(cmd *cobra.Command) (err error) { - cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(do.Context.Client, do.LocalConfigInfo, do.EnvSpecificInfo, do.componentName, do.Context.Application, do.Context.Project, do.componentContext) + cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfigProvider(do.Context.Client, do.EnvSpecificInfo, do.componentName, do.Context.Application, do.Context.Project, do.componentContext) if err != nil { return err } diff --git a/pkg/odo/cli/component/exec.go b/pkg/odo/cli/component/exec.go index ac75f1f7d3a..10fd7e838c5 100644 --- a/pkg/odo/cli/component/exec.go +++ b/pkg/odo/cli/component/exec.go @@ -61,7 +61,7 @@ Please provide a command to execute, odo exec -- `) eo.devfilePath = filepath.Join(eo.componentContext, devFile) - eo.componentOptions.Context, err = genericclioptions.NewDevfileContext(cmd) + eo.componentOptions.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } diff --git a/pkg/odo/cli/component/link.go b/pkg/odo/cli/component/link.go index ea330585a79..72e92deb069 100644 --- a/pkg/odo/cli/component/link.go +++ b/pkg/odo/cli/component/link.go @@ -7,7 +7,6 @@ import ( servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1" "github.com/spf13/cobra" - "github.com/openshift/odo/pkg/component" "github.com/openshift/odo/pkg/odo/genericclioptions" odoutil "github.com/openshift/odo/pkg/odo/util" svc "github.com/openshift/odo/pkg/service" @@ -83,44 +82,15 @@ func (o *LinkOptions) Complete(name string, cmd *cobra.Command, args []string) ( return err } - if o.csvSupport && o.Context.EnvSpecificInfo != nil { + if o.csvSupport { o.operation = o.KClient.LinkSecret - } else { - o.operation = o.Client.LinkSecret } - return err } // Validate validates the LinkOptions based on completed values func (o *LinkOptions) Validate() (err error) { - err = o.validate() - if err != nil { - return err - } - - if o.Context.EnvSpecificInfo != nil { - return - } - componentName, err := o.Component() - if err != nil { - return err - } - - alreadyLinkedSecretNames, err := component.GetComponentLinkedSecretNames(o.Client, componentName, o.Application) - if err != nil { - return err - } - for _, alreadyLinkedSecretName := range alreadyLinkedSecretNames { - if alreadyLinkedSecretName == o.secretName { - targetType := "component" - if o.isTargetAService { - targetType = "service" - } - return fmt.Errorf("Component %s has previously been linked to %s %s", o.Project, targetType, o.suppliedName) - } - } - return + return o.validate() } // Run contains the logic for the odo link command diff --git a/pkg/odo/cli/component/list.go b/pkg/odo/cli/component/list.go index e2183be09b0..ef197837e48 100644 --- a/pkg/odo/cli/component/list.go +++ b/pkg/odo/cli/component/list.go @@ -7,12 +7,10 @@ import ( "path/filepath" "text/tabwriter" - "github.com/openshift/odo/pkg/application" "github.com/openshift/odo/pkg/devfile" "github.com/openshift/odo/pkg/machineoutput" "github.com/openshift/odo/pkg/project" "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/klog" @@ -42,7 +40,6 @@ type ListOptions struct { allAppsFlag bool componentContext string componentType string - hasDCSupport bool devfilePath string *genericclioptions.Context } @@ -59,11 +56,7 @@ func (lo *ListOptions) Complete(name string, cmd *cobra.Command, args []string) if util.CheckPathExists(lo.devfilePath) { - lo.Context, err = genericclioptions.NewDevfileContext(cmd) - if err != nil { - return err - } - lo.hasDCSupport, err = lo.Client.IsDeploymentConfigSupported() + lo.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -74,28 +67,14 @@ func (lo *ListOptions) Complete(name string, cmd *cobra.Command, args []string) lo.componentType = component.GetComponentTypeFromDevfileMetadata(devObj.Data.GetMetadata()) } else { - // here we use the config.yaml derived context if its present, else we use information from user's kubeconfig + // here we use information from user's kubeconfig // as odo list should work in a non-component directory too - if util.CheckKubeConfigExist() { klog.V(4).Infof("New Context") - lo.Context, err = genericclioptions.NewContext(cmd, false, true) - if err != nil { - return err - } - lo.hasDCSupport, err = lo.Client.IsDeploymentConfigSupported() - if err != nil { - return err - } - - } else { - klog.V(4).Infof("New Config Context") - lo.Context, err = genericclioptions.NewConfigContext(cmd) + lo.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } - // for disconnected situation we just assume we have DC support - lo.hasDCSupport = true } } @@ -119,9 +98,8 @@ func (lo *ListOptions) Validate() (err error) { var project, app string if !util.CheckKubeConfigExist() { - project = lo.LocalConfigInfo.GetProject() - app = lo.LocalConfigInfo.GetApplication() - + project = lo.EnvSpecificInfo.GetNamespace() + app = lo.EnvSpecificInfo.GetApplication() } else { project = lo.Context.Project app = lo.Application @@ -144,12 +122,8 @@ func (lo *ListOptions) Run(cmd *cobra.Command) (err error) { if err != nil { return err } - s2iComps, err := component.ListIfPathGiven(lo.Context.Client, filepath.SplitList(lo.pathFlag)) - if err != nil { - return err - } - combinedComponents := component.NewCombinedComponentList(s2iComps, devfileComps, otherComps) + combinedComponents := component.NewCombinedComponentList(devfileComps, otherComps) if log.IsJSON() { machineoutput.OutputSuccess(combinedComponents) @@ -212,46 +186,6 @@ func (lo *ListOptions) Run(cmd *cobra.Command) (err error) { } } - var s2iComponents []component.Component - // we now check if DC is supported - if lo.hasDCSupport { - - if lo.allAppsFlag { - // retrieve list of application - apps, err := application.List(lo.Client) - if err != nil { - return err - } - - if len(apps) == 0 && lo.LocalConfigInfo.Exists() { - selector = applabels.GetSelector(lo.LocalConfigInfo.GetApplication()) - comps, err := component.ListS2IComponents(lo.Client, selector, lo.LocalConfigInfo) - if err != nil { - return err - } - s2iComponents = append(s2iComponents, comps.Items...) - } - - // iterating over list of application and get list of all components - for _, app := range apps { - selector = applabels.GetSelector(app) - comps, err := component.ListS2IComponents(lo.Client, selector, lo.LocalConfigInfo) - if err != nil { - return err - } - s2iComponents = append(s2iComponents, comps.Items...) - } - } else { - selector = applabels.GetSelector(lo.Application) - componentList, err := component.ListS2IComponents(lo.Client, selector, lo.LocalConfigInfo) - // compat - s2iComponents = componentList.Items - if err != nil { - return errors.Wrapf(err, "failed to fetch component list") - } - } - } - // list components managed by other sources/tools if lo.allAppsFlag { selector = project.GetNonOdoSelector() @@ -259,13 +193,13 @@ func (lo *ListOptions) Run(cmd *cobra.Command) (err error) { selector = applabels.GetNonOdoSelector(lo.Application) } - otherComponents, err := component.List(lo.Client, selector, lo.LocalConfigInfo) + otherComponents, err := component.List(lo.Client, selector) if err != nil { return fmt.Errorf("failed to fetch components not managed by odo: %w", err) } otherComps = otherComponents.Items - combinedComponents := component.NewCombinedComponentList(s2iComponents, devfileComponents, otherComps) + combinedComponents := component.NewCombinedComponentList(devfileComponents, otherComps) if log.IsJSON() { machineoutput.OutputSuccess(combinedComponents) } else { @@ -310,7 +244,7 @@ func HumanReadableOutputInPath(wr io.Writer, o component.CombinedComponentList) defer w.Flush() // if we dont have any components then - if len(o.DevfileComponents) == 0 && len(o.S2IComponents) == 0 { + if len(o.DevfileComponents) == 0 { fmt.Fprintln(w, "No components found") return } @@ -323,22 +257,13 @@ func HumanReadableOutputInPath(wr io.Writer, o component.CombinedComponentList) } fmt.Fprintln(w) } - - if len(o.S2IComponents) != 0 { - fmt.Fprintln(w, "S2I Components: ") - fmt.Fprintln(w, "APP", "\t", "NAME", "\t", "PROJECT", "\t", "TYPE", "\t", "SOURCETYPE", "\t", "STATE", "\t", "CONTEXT") - for _, comp := range o.S2IComponents { - fmt.Fprintln(w, comp.Spec.App, "\t", comp.Name, "\t", comp.Namespace, "\t", comp.Spec.Type, "\t", comp.Spec.SourceType, "\t", comp.Status.State, "\t", comp.Status.Context) - } - } - } func HumanReadableOutput(wr io.Writer, o component.CombinedComponentList) { w := tabwriter.NewWriter(wr, 5, 2, 3, ' ', tabwriter.TabIndent) defer w.Flush() - if len(o.DevfileComponents) == 0 && len(o.S2IComponents) == 0 && len(o.OtherComponents) == 0 { + if len(o.DevfileComponents) == 0 && len(o.OtherComponents) == 0 { log.Info("There are no components deployed.") return } @@ -353,12 +278,4 @@ func HumanReadableOutput(wr io.Writer, o component.CombinedComponentList) { } fmt.Fprintln(w) } - - if len(o.S2IComponents) != 0 { - fmt.Fprintln(w, "S2I Components: ") - fmt.Fprintln(w, "APP", "\t", "NAME", "\t", "PROJECT", "\t", "TYPE", "\t", "SOURCETYPE", "\t", "STATE") - for _, comp := range o.S2IComponents { - fmt.Fprintln(w, comp.Spec.App, "\t", comp.Name, "\t", comp.Namespace, "\t", comp.Spec.Type, "\t", comp.Spec.SourceType, "\t", comp.Status.State) - } - } } diff --git a/pkg/odo/cli/component/log.go b/pkg/odo/cli/component/log.go index c506aa8946d..bf2c0ed2cf1 100644 --- a/pkg/odo/cli/component/log.go +++ b/pkg/odo/cli/component/log.go @@ -42,7 +42,7 @@ func (lo *LogOptions) Complete(name string, cmd *cobra.Command, args []string) ( lo.devfilePath = "devfile.yaml" lo.devfilePath = filepath.Join(lo.componentContext, lo.devfilePath) - lo.ComponentOptions.Context, err = genericclioptions.NewDevfileContext(cmd) + lo.ComponentOptions.Context, err = genericclioptions.NewContext(cmd) return err } diff --git a/pkg/odo/cli/component/push.go b/pkg/odo/cli/component/push.go index 2233df224d7..750158bb7a4 100644 --- a/pkg/odo/cli/component/push.go +++ b/pkg/odo/cli/component/push.go @@ -2,9 +2,10 @@ package component import ( "fmt" - "github.com/openshift/odo/pkg/component" "path/filepath" + "github.com/openshift/odo/pkg/component" + scontext "github.com/openshift/odo/pkg/segment/context" "github.com/openshift/odo/pkg/devfile/validate" @@ -47,6 +48,9 @@ const PushRecommendedCommandName = "push" // PushOptions encapsulates options that push command uses type PushOptions struct { *CommonPushOptions + ignores []string + sourcePath string + forceBuild bool // devfile path DevfilePath string @@ -186,7 +190,7 @@ func (po *PushOptions) Complete(name string, cmd *cobra.Command, args []string) po.EnvSpecificInfo = envFileInfo - po.Context, err = genericclioptions.NewDevfileContext(cmd) + po.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } diff --git a/pkg/odo/cli/component/status.go b/pkg/odo/cli/component/status.go index 6c3c00aa1d2..b396f29d378 100644 --- a/pkg/odo/cli/component/status.go +++ b/pkg/odo/cli/component/status.go @@ -19,7 +19,6 @@ import ( projectCmd "github.com/openshift/odo/pkg/odo/cli/project" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/url" - "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" "github.com/openshift/odo/pkg/odo/util/completion" @@ -48,11 +47,10 @@ type StatusOptions struct { devObj parser.DevfileObj - logFollow bool - EnvSpecificInfo *envinfo.EnvSpecificInfo - localConfig localConfigProvider.LocalConfigProvider + logFollow bool + EnvSpecificInfo *envinfo.EnvSpecificInfo + localConfigProvider localConfigProvider.LocalConfigProvider *genericclioptions.Context - isDevfile bool } // NewStatusOptions returns new instance of StatusOptions @@ -64,46 +62,36 @@ func NewStatusOptions() *StatusOptions { func (so *StatusOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { so.devfilePath = filepath.Join(so.componentContext, DevfilePath) - so.isDevfile = util.CheckPathExists(so.devfilePath) - - // If devfile is present - if so.isDevfile { - so.EnvSpecificInfo, err = envinfo.NewEnvSpecificInfo(so.componentContext) - if err != nil { - return errors.Wrap(err, "unable to retrieve configuration information") - } - so.Context, err = genericclioptions.NewDevfileContext(cmd) - if err != nil { - return err - } - // Get the component name - so.componentName = so.EnvSpecificInfo.GetName() - - devObj, err := devfile.ParseFromFile(so.devfilePath) - if err != nil { - return err - } - so.devObj = devObj - so.EnvSpecificInfo.SetDevfileObj(so.devObj) - - so.localConfig = so.EnvSpecificInfo - - var platformContext interface{} - // The namespace was retrieved from the --project flag (or from the kube client if not set) and stored in kclient when initializing the context - so.namespace = so.KClient.GetCurrentNamespace() - platformContext = kubernetes.KubernetesContext{ - Namespace: so.namespace, - } - - so.devfileHandler, err = adapters.NewComponentAdapter(so.componentName, so.componentContext, so.Application, devObj, platformContext) + so.EnvSpecificInfo, err = envinfo.NewEnvSpecificInfo(so.componentContext) + if err != nil { + return errors.Wrap(err, "unable to retrieve configuration information") + } + so.Context, err = genericclioptions.NewContext(cmd) + if err != nil { + return err + } + // Get the component name + so.componentName = so.EnvSpecificInfo.GetName() + devObj, err := devfile.ParseFromFile(so.devfilePath) + if err != nil { return err } + so.devObj = devObj + so.EnvSpecificInfo.SetDevfileObj(so.devObj) - // Set the correct context - so.Context, err = genericclioptions.NewContextCreatingAppIfNeeded(cmd) + so.localConfigProvider = so.EnvSpecificInfo - return + var platformContext interface{} + // The namespace was retrieved from the --project flag (or from the kube client if not set) and stored in kclient when initializing the context + so.namespace = so.KClient.GetCurrentNamespace() + platformContext = kubernetes.KubernetesContext{ + Namespace: so.namespace, + } + + so.devfileHandler, err = adapters.NewComponentAdapter(so.componentName, so.componentContext, so.Application, devObj, platformContext) + + return err } // Validate validates the status parameters @@ -118,11 +106,6 @@ func (so *StatusOptions) Validate() (err error) { // Run has the logic to perform the required actions as part of command func (so *StatusOptions) Run(cmd *cobra.Command) (err error) { - - if !so.isDevfile { - return errors.New("the status command is only supported for devfiles") - } - if !log.IsJSON() { return errors.New("this command only supports the '-o json' output format") } @@ -140,7 +123,7 @@ func (so *StatusOptions) Run(cmd *cobra.Command) (err error) { oclient.Namespace = so.KClient.GetCurrentNamespace() } - url.StartURLHttpRequestStatusWatchForK8S(oclient, so.KClient, &so.localConfig, loggingClient) + url.StartURLHttpRequestStatusWatchForK8S(oclient, so.KClient, &so.localConfigProvider, loggingClient) // You can call Run() any time you like, but you can never leave. for { diff --git a/pkg/odo/cli/component/test.go b/pkg/odo/cli/component/test.go index e035247a837..00ace94cff1 100644 --- a/pkg/odo/cli/component/test.go +++ b/pkg/odo/cli/component/test.go @@ -47,7 +47,7 @@ func NewTestOptions() *TestOptions { // Complete completes TestOptions after they've been created func (to *TestOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { to.devfilePath = filepath.Join(to.componentContext, DevfilePath) - to.Context, err = genericclioptions.NewDevfileContext(cmd) + to.Context, err = genericclioptions.NewContext(cmd) return } diff --git a/pkg/odo/cli/component/ui/ui.go b/pkg/odo/cli/component/ui/ui.go index e3949f9d94f..bc50e46162e 100644 --- a/pkg/odo/cli/component/ui/ui.go +++ b/pkg/odo/cli/component/ui/ui.go @@ -1,20 +1,13 @@ package ui import ( - "fmt" "sort" - "gopkg.in/AlecAivazis/survey.v1" - "k8s.io/klog" - devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/openshift/odo/pkg/catalog" - "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/odo/cli/ui" - "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/odo/util/validation" - "github.com/openshift/odo/pkg/util" + "gopkg.in/AlecAivazis/survey.v1" ) // SelectStarterProject allows user to select starter project in the prompt @@ -89,18 +82,6 @@ func EnterDevfileComponentProject(defaultComponentNamespace string) string { return name } -// SelectComponentType lets the user to select the builder image (name only) in the prompt -func SelectComponentType(options []catalog.ComponentType) string { - var componentType string - prompt := &survey.Select{ - Message: "Which component type do you wish to create", - Options: getComponentTypeNameCandidates(options), - } - err := survey.AskOne(prompt, &componentType, survey.Required) - ui.HandleError(err) - return componentType -} - func getDevfileComponentTypeNameCandidates(options []catalog.DevfileComponentType) []string { result := make([]string, len(options)) for i, option := range options { @@ -118,207 +99,3 @@ func getProjectNames(projects []devfilev1.StarterProject) []string { sort.Strings(result) return result } - -func getComponentTypeNameCandidates(options []catalog.ComponentType) []string { - result := make([]string, len(options)) - for i, option := range options { - result[i] = option.Name - } - sort.Strings(result) - return result -} - -// SelectImageTag lets the user to select a specific tag for the previously selected builder image in a prompt -func SelectImageTag(options []catalog.ComponentType, selectedComponentType string) string { - var tag string - prompt := &survey.Select{ - Message: fmt.Sprintf("Which version of '%s' component type do you wish to create", selectedComponentType), - Options: getTagCandidates(options, selectedComponentType), - } - err := survey.AskOne(prompt, &tag, survey.Required) - ui.HandleError(err) - return tag -} - -func getTagCandidates(options []catalog.ComponentType, selectedComponentType string) []string { - for _, option := range options { - if option.Name == selectedComponentType { - sort.Strings(option.Spec.NonHiddenTags) - return option.Spec.NonHiddenTags - } - } - klog.V(4).Infof("Selected component type %s was not part of the catalog images", selectedComponentType) - return []string{} -} - -// SelectSourceType lets the user select a specific config.SrcType in a prompty -func SelectSourceType(sourceTypes []config.SrcType) config.SrcType { - options := make([]string, len(sourceTypes)) - for i, sourceType := range sourceTypes { - options[i] = fmt.Sprint(sourceType) - } - - var selectedSourceType string - prompt := &survey.Select{ - Message: "Which input type do you wish to use for the component", - Options: options, - } - err := survey.AskOne(prompt, &selectedSourceType, survey.Required) - ui.HandleError(err) - - for _, sourceType := range sourceTypes { - if selectedSourceType == fmt.Sprint(sourceType) { - return sourceType - } - } - klog.V(4).Infof("Selected source type %s was not part of the source type options", selectedSourceType) - return config.NONE -} - -// EnterInputTypePath allows the user to specify the path on the filesystem in a prompt -func EnterInputTypePath(inputType string, currentDir string, defaultPath ...string) string { - var path string - prompt := &survey.Input{ - Message: fmt.Sprintf("Location of %s component, relative to '%s'", inputType, currentDir), - } - - if len(defaultPath) == 1 { - prompt.Default = defaultPath[0] - } - - err := survey.AskOne(prompt, &path, validation.PathValidator) - ui.HandleError(err) - - return path -} - -// we need this because the validator for the component name needs use info from the Context -// so we effectively return a closure that references the context -func createComponentNameValidator(context *genericclioptions.Context) survey.Validator { - return func(input interface{}) error { - if s, ok := input.(string); ok { - err := validation.ValidateName(s) - if err != nil { - return err - } - - exists, err := component.Exists(context.Client, s, context.Application) - if err != nil { - klog.V(4).Info(err) - return fmt.Errorf("Unable to determine if component '%s' exists or not", s) - } - if exists { - return fmt.Errorf("Component with name '%s' already exists in application '%s'", s, context.Application) - } - - return nil - } - - return fmt.Errorf("can only validate strings, got %v", input) - } -} - -// EnterComponentName allows the user to specify the component name in a prompt -func EnterComponentName(defaultName string, context *genericclioptions.Context) string { - var path string - prompt := &survey.Input{ - Message: "What do you wish to name the new component", - Default: defaultName, - } - err := survey.AskOne(prompt, &path, createComponentNameValidator(context)) - ui.HandleError(err) - return path -} - -// EnterOpenshiftName allows the user to specify the app name in a prompt -func EnterOpenshiftName(defaultName string, message string, context *genericclioptions.Context) string { - var name string - prompt := &survey.Input{ - Message: message, - Default: defaultName, - } - err := survey.AskOne(prompt, &name, validation.NameValidator) - ui.HandleError(err) - return name -} - -// EnterGitInfo will display two prompts, one of the URL of the project and one of the ref -func EnterGitInfo() (string, string) { - gitURL := enterGitInputTypePath() - gitRef := enterGitRef("master") - - return gitURL, gitRef -} - -func enterGitInputTypePath() string { - var path string - prompt := &survey.Input{ - Message: "What is the URL of the git repository you wish the new component to use", - } - err := survey.AskOne(prompt, &path, survey.Required) - ui.HandleError(err) - return path -} - -func enterGitRef(defaultRef string) string { - var path string - prompt := &survey.Input{ - Message: "What git ref (branch, tag, commit) do you wish to use", - Default: defaultRef, - } - err := survey.AskOne(prompt, &path, survey.Required) - ui.HandleError(err) - return path -} - -// EnterPorts allows the user to specify the ports to be used in a prompt -func EnterPorts() []string { - var portsStr string - prompt := &survey.Input{ - Message: "Enter the ports you wish to set (for example: 8080,8100/tcp,9100/udp). Simply press 'Enter' to avoid setting them", - Default: "", - } - err := survey.AskOne(prompt, &portsStr, validation.PortsValidator) - ui.HandleError(err) - - return util.GetSplitValuesFromStr(portsStr) -} - -// EnterEnvVars allows the user to specify the environment variables to be used in a prompt -func EnterEnvVars() []string { - var envVarsStr string - prompt := &survey.Input{ - Message: "Enter the environment variables you would like to set (for example: MY_TYPE=backed,PROFILE=dev). Simply press 'Enter' to avoid setting them", - Default: "", - } - err := survey.AskOne(prompt, &envVarsStr, validation.KeyEqValFormatValidator) - ui.HandleError(err) - - return util.GetSplitValuesFromStr(envVarsStr) -} - -// EnterMemory allows the user to specify the memory limits to be used in a prompt -func EnterMemory(typeStr string, defaultValue string) string { - var result string - prompt := &survey.Input{ - Message: fmt.Sprintf("Enter the %s memory (for example 100Mi)", typeStr), - Default: defaultValue, - } - err := survey.AskOne(prompt, &result, survey.Required) - ui.HandleError(err) - - return result -} - -// EnterCPU allows the user to specify the cpu limits to be used in a prompt -func EnterCPU(typeStr string, defaultValue string) string { - var result string - prompt := &survey.Input{ - Message: fmt.Sprintf("Enter the %s CPU (for example 100m or 2)", typeStr), - Default: defaultValue, - } - err := survey.AskOne(prompt, &result, survey.Required) - ui.HandleError(err) - - return result -} diff --git a/pkg/odo/cli/component/unlink.go b/pkg/odo/cli/component/unlink.go index dd85e38c314..62b7ab9dd61 100644 --- a/pkg/odo/cli/component/unlink.go +++ b/pkg/odo/cli/component/unlink.go @@ -62,10 +62,8 @@ func (o *UnlinkOptions) Complete(name string, cmd *cobra.Command, args []string) return err } - if o.csvSupport && o.Context.EnvSpecificInfo != nil { + if o.csvSupport { o.operation = o.KClient.UnlinkSecret - } else { - o.operation = o.Client.UnlinkSecret } return err } diff --git a/pkg/odo/cli/component/watch.go b/pkg/odo/cli/component/watch.go index 0435039eb73..1179a4ec8a5 100644 --- a/pkg/odo/cli/component/watch.go +++ b/pkg/odo/cli/component/watch.go @@ -70,7 +70,7 @@ func NewWatchOptions() *WatchOptions { func (wo *WatchOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { wo.devfilePath = filepath.Join(wo.componentContext, DevfilePath) - wo.Context, err = genericclioptions.NewDevfileContext(cmd) + wo.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } diff --git a/pkg/odo/cli/config/config.go b/pkg/odo/cli/config/config.go index 3b53ff4a62f..daa4b0ed297 100644 --- a/pkg/odo/cli/config/config.go +++ b/pkg/odo/cli/config/config.go @@ -16,7 +16,6 @@ const RecommendedCommandName = "config" var configLongDesc = ktemplates.LongDesc(`Modifies odo specific configuration settings within the devfile or config file. %[1]s -%[2]s `) // NewCmdConfiguration implements the utils config odo command @@ -27,7 +26,7 @@ func NewCmdConfiguration(name, fullName string) *cobra.Command { configurationCmd := &cobra.Command{ Use: name, Short: "Change or view configuration", - Long: fmt.Sprintf(configLongDesc, config.FormatDevfileSupportedParameters(), config.FormatLocallySupportedParameters()), + Long: fmt.Sprintf(configLongDesc, config.FormatDevfileSupportedParameters()), Example: fmt.Sprintf("%s\n%s\n%s", configurationViewCmd.Example, configurationSetCmd.Example, diff --git a/pkg/odo/cli/config/set.go b/pkg/odo/cli/config/set.go index bdd29d7413a..60c5825746b 100644 --- a/pkg/odo/cli/config/set.go +++ b/pkg/odo/cli/config/set.go @@ -2,9 +2,10 @@ package config import ( "fmt" - "github.com/openshift/odo/pkg/util" "strings" + "github.com/openshift/odo/pkg/util" + "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/log" clicomponent "github.com/openshift/odo/pkg/odo/cli/component" @@ -22,25 +23,7 @@ const setCommandName = "set" var ( setLongDesc = ktemplates.LongDesc(`Set an individual value in the devfile or odo configuration file. %[1]s -%[2]s `) - setExample = ktemplates.Examples(` - # Set a configuration value in the local config - %[1]s %[2]s java - %[1]s %[3]s test - %[1]s %[4]s 50M - %[1]s %[5]s 500M - %[1]s %[6]s 250M - %[1]s %[7]s 4040 - %[1]s %[8]s false - %[1]s %[9]s 0.5 - %[1]s %[10]s 2 - %[1]s %[11]s 1 - %[1]s %[12]s 8080/TCP,8443/TCP - - # Set a env variable in the local config - %[1]s --env KAFKA_HOST=kafka --env KAFKA_PORT=6639 - `) devfileSetExample = ktemplates.Examples(` # Set a configuration value in the devfile @@ -61,7 +44,6 @@ type SetOptions struct { configForceFlag bool envArray []string now bool - IsDevfile bool } // NewSetOptions creates a new SetOptions instance @@ -88,13 +70,8 @@ func (o *SetOptions) Complete(name string, cmd *cobra.Command, args []string) (e } return err } - if o.Context.EnvSpecificInfo != nil { - o.IsDevfile = true - o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath() - o.EnvSpecificInfo = o.Context.EnvSpecificInfo - } else { - o.IsDevfile = false - } + o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath() + o.EnvSpecificInfo = o.Context.EnvSpecificInfo if o.envArray == nil { o.paramName = args[0] @@ -118,17 +95,11 @@ func (o *SetOptions) Validate() (err error) { if !o.Context.LocalConfigProvider.Exists() { return fmt.Errorf("the directory doesn't contain a component. Use 'odo create' to create a component") } - if !o.IsDevfile && o.now { - err = o.ValidateComponentCreate() - if err != nil { - return err - } - } return } -// DevfileRun is ran when the context detects a devfile locally -func (o *SetOptions) DevfileRun() (err error) { +// Run contains the logic for the command +func (o *SetOptions) Run(cmd *cobra.Command) (err error) { if o.envArray != nil { newEnvVarList, err := config.NewEnvVarListFromSlice(o.envArray) if err != nil { @@ -147,7 +118,6 @@ func (o *SetOptions) DevfileRun() (err error) { return err } if !o.configForceFlag { - if config.IsSetInDevfile(o.EnvSpecificInfo.GetDevfileObj(), o.paramName) { if !ui.Proceed(fmt.Sprintf("%v is already set. Do you want to override it in the devfile", o.paramName)) { fmt.Println("Aborted by the user.") @@ -168,72 +138,6 @@ func (o *SetOptions) DevfileRun() (err error) { return err } -// Run contains the logic for the command -func (o *SetOptions) Run(cmd *cobra.Command) (err error) { - if o.IsDevfile { - return o.DevfileRun() - } - - // env variables have been provided - if o.envArray != nil { - newEnvVarList, err := config.NewEnvVarListFromSlice(o.envArray) - if err != nil { - return err - } - - // keeping the old env vars as well - presentEnvVarList := o.LocalConfigInfo.GetEnvVars() - newEnvVarList = presentEnvVarList.Merge(newEnvVarList) - if err := o.LocalConfigInfo.SetEnvVars(newEnvVarList); err != nil { - return err - } - if o.now { - err = o.Push() - if err != nil { - return fmt.Errorf("failed to push changes %w", err) - } - } else { - log.Italic("\nRun `odo push --config` command to apply changes to the cluster") - } - - return nil - } - - if !o.configForceFlag { - - if o.LocalConfigInfo.IsSet(o.paramName) { - if strings.ToLower(o.paramName) == "name" || strings.ToLower(o.paramName) == "project" || strings.ToLower(o.paramName) == "application" { - if !ui.Proceed(fmt.Sprintf("Are you sure you want to change the component's %s?\nThis action might result in the creation of a duplicate component.\nIf your component is already pushed, please delete the component %q after you apply the changes (odo component delete %s --app %s --project %s)", o.paramName, o.LocalConfigInfo.GetName(), o.LocalConfigInfo.GetName(), o.LocalConfigInfo.GetApplication(), o.LocalConfigInfo.GetProject())) { - fmt.Println("Aborted by the user.") - return nil - } - } else { - if !ui.Proceed(fmt.Sprintf("%v is already set. Do you want to override it in the config", o.paramName)) { - fmt.Println("Aborted by the user.") - return nil - } - } - } - - } - - err = o.LocalConfigInfo.SetConfiguration(strings.ToLower(o.paramName), o.paramValue) - if err != nil { - return err - } - - log.Success("Local config successfully updated") - if o.now { - err = o.Push() - if err != nil { - return fmt.Errorf("failed to push changes %w", err) - } - } else { - log.Italic("\nRun `odo push --config` command to apply changes to the cluster") - } - return nil -} - func isValidArgumentList(args []string) error { if len(args) < 2 { @@ -243,7 +147,7 @@ func isValidArgumentList(args []string) error { } var err error - param, ok := config.AsLocallySupportedParameter(args[0]) + param, ok := config.AsDevfileSupportedParameter(args[0]) if !ok { err = errors.Errorf("the provided parameter is not supported, %v", args[0]) @@ -266,21 +170,14 @@ func isValidArgumentList(args []string) error { return err } -func getSetExampleString(fullName string) string { - s2iExample := fmt.Sprintf(fmt.Sprint("\n", setExample), fullName, config.Type, - config.Name, config.MinMemory, config.MaxMemory, config.Memory, config.DebugPort, config.Ignore, config.MinCPU, config.MaxCPU, config.CPU, config.Ports) - devfileExample := fmt.Sprintf("\n"+devfileSetExample, fullName, config.Name, config.Ports, config.Memory) - return devfileExample + "\n" + s2iExample -} - // NewCmdSet implements the config set odo command func NewCmdSet(name, fullName string) *cobra.Command { o := NewSetOptions() configurationSetCmd := &cobra.Command{ Use: name, Short: "Set a value in odo config file", - Long: fmt.Sprintf(setLongDesc, config.FormatDevfileSupportedParameters(), config.FormatLocallySupportedParameters()), - Example: getSetExampleString(fullName), + Long: fmt.Sprintf(setLongDesc, config.FormatDevfileSupportedParameters()), + Example: fmt.Sprintf("\n"+devfileSetExample, fullName, config.Name, config.Ports, config.Memory), Args: func(cmd *cobra.Command, args []string) error { if o.envArray != nil { // no args are needed diff --git a/pkg/odo/cli/config/unset.go b/pkg/odo/cli/config/unset.go index 606748f9276..f5e520fe391 100644 --- a/pkg/odo/cli/config/unset.go +++ b/pkg/odo/cli/config/unset.go @@ -2,9 +2,10 @@ package config import ( "fmt" - "github.com/openshift/odo/pkg/util" "strings" + "github.com/openshift/odo/pkg/util" + "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/log" clicomponent "github.com/openshift/odo/pkg/odo/cli/component" @@ -19,25 +20,7 @@ const unsetCommandName = "unset" var ( unsetLongDesc = ktemplates.LongDesc(`Unset an individual value in the devfile or odo configuration file. %[1]s -%[2]s `) - unsetExample = ktemplates.Examples(` - # Unset a configuration value in the local config - %[1]s %[2]s - %[1]s %[3]s - %[1]s %[4]s - %[1]s %[5]s - %[1]s %[6]s - %[1]s %[7]s - %[1]s %[8]s - %[1]s %[9]s - %[1]s %[10]s - %[1]s %[11]s - - # Unset a env variable in the local config - %[1]s --env KAFKA_HOST --env KAFKA_PORT - `) - devfileUnsetExample = ktemplates.Examples(` # Unset a configuration value in the devfile %[1]s %[2]s @@ -56,7 +39,6 @@ type UnsetOptions struct { configForceFlag bool envArray []string now bool - IsDevfile bool } // NewUnsetOptions creates a new UnsetOptions instance @@ -84,13 +66,8 @@ func (o *UnsetOptions) Complete(name string, cmd *cobra.Command, args []string) } return err } - if o.Context.EnvSpecificInfo != nil { - o.IsDevfile = true - o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath() - o.EnvSpecificInfo = o.Context.EnvSpecificInfo - } else { - o.IsDevfile = false - } + o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath() + o.EnvSpecificInfo = o.Context.EnvSpecificInfo if o.envArray == nil { o.paramName = args[0] @@ -113,17 +90,11 @@ func (o *UnsetOptions) Validate() (err error) { if !o.Context.LocalConfigProvider.Exists() { return fmt.Errorf("the directory doesn't contain a component. Use 'odo create' to create a component") } - if !o.IsDevfile && o.now { - err = o.ValidateComponentCreate() - if err != nil { - return err - } - } return } -// DevfileRun is ran when the context detects a devfile locally -func (o *UnsetOptions) DevfileRun() (err error) { +// Run contains the logic for the command +func (o *UnsetOptions) Run(cmd *cobra.Command) (err error) { if o.envArray != nil { if err := o.EnvSpecificInfo.GetDevfileObj().RemoveEnvVars(o.envArray); err != nil { @@ -151,78 +122,14 @@ func (o *UnsetOptions) DevfileRun() (err error) { return fmt.Errorf("config already unset, cannot unset a configuration which is not set") } -// Run contains the logic for the command -func (o *UnsetOptions) Run(cmd *cobra.Command) (err error) { - - if o.IsDevfile { - return o.DevfileRun() - } - - // env variables have been provided - if o.envArray != nil { - - envList := o.LocalConfigInfo.GetEnvVars() - newEnvList, err := config.RemoveEnvVarsFromList(envList, o.envArray) - if err != nil { - return err - } - - if err = o.LocalConfigInfo.SetEnvVars(newEnvList); err != nil { - return err - } - - log.Success("Environment variables were successfully updated") - if o.now { - err = o.Push() - if err != nil { - return fmt.Errorf("failed to push changes %w", err) - } - } else { - log.Italic("\nRun `odo push --config` command to apply changes to the cluster") - } - return nil - } - - if isSet := o.LocalConfigInfo.IsSet(o.paramName); isSet { - if !o.configForceFlag && !ui.Proceed(fmt.Sprintf("Do you want to unset %s in the config", o.paramName)) { - fmt.Println("Aborted by the user.") - return nil - } - err = o.LocalConfigInfo.DeleteConfiguration(strings.ToLower(o.paramName)) - if err != nil { - return err - } - - log.Success("Local config was successfully updated.") - if o.now { - err = o.Push() - if err != nil { - return fmt.Errorf("failed to push changes %w", err) - } - } else { - log.Italic("\nRun `odo push --config` command to apply changes to the cluster") - } - return nil - } - return fmt.Errorf("config already unset, cannot unset a configuration which is not set") - -} - -func getUnSetExampleString(fullName string) string { - s2iExample := fmt.Sprintf(fmt.Sprint("\n", unsetExample), fullName, config.Type, - config.Name, config.MinMemory, config.MaxMemory, config.Memory, config.DebugPort, config.Ignore, config.MinCPU, config.MaxCPU, config.CPU, config.Ports) - devfileExample := fmt.Sprintf("\n"+devfileUnsetExample, fullName, config.Name, config.Ports, config.Memory) - return devfileExample + "\n" + s2iExample -} - // NewCmdUnset implements the config unset odo command func NewCmdUnset(name, fullName string) *cobra.Command { o := NewUnsetOptions() configurationUnsetCmd := &cobra.Command{ Use: name, Short: "Unset a value in odo config file", - Long: fmt.Sprintf(unsetLongDesc, config.FormatDevfileSupportedParameters(), config.FormatLocallySupportedParameters()), - Example: getUnSetExampleString(fullName), + Long: fmt.Sprintf(unsetLongDesc, config.FormatDevfileSupportedParameters()), + Example: fmt.Sprintf("\n"+devfileUnsetExample, fullName, config.Name, config.Ports, config.Memory), Args: func(cmd *cobra.Command, args []string) error { if o.envArray != nil { // no args are needed diff --git a/pkg/odo/cli/config/view.go b/pkg/odo/cli/config/view.go index 47121f85fbd..e27e248b571 100644 --- a/pkg/odo/cli/config/view.go +++ b/pkg/odo/cli/config/view.go @@ -4,18 +4,14 @@ import ( "fmt" "os" "path/filepath" - "reflect" - "strings" "text/tabwriter" "github.com/devfile/library/pkg/devfile/parser" "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/machineoutput" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ktemplates "k8s.io/kubectl/pkg/util/templates" "sigs.k8s.io/yaml" @@ -31,7 +27,6 @@ var viewExample = ktemplates.Examples(`# For viewing the current configuration f // ViewOptions encapsulates the options for the command type ViewOptions struct { contextDir string - lci *config.LocalConfigInfo devfilePath string devfileObj parser.DevfileObj IsDevfile bool @@ -39,7 +34,6 @@ type ViewOptions struct { // NewViewOptions creates a new ViewOptions instance func NewViewOptions() *ViewOptions { - return &ViewOptions{} } @@ -54,30 +48,16 @@ func (o *ViewOptions) Complete(name string, cmd *cobra.Command, args []string) ( return err } } - - if !o.IsDevfile { - cfg, err := config.NewLocalConfigInfo(o.contextDir) - if err != nil { - return err - } - o.lci = cfg - } return } // Validate validates the ViewOptions based on completed values func (o *ViewOptions) Validate() (err error) { - if !o.IsDevfile { - if !o.lci.Exists() { - return errors.New("the directory doesn't contain a component. Use 'odo create' to create a component") - } - } - return } -// DevfileRun is ran when the context detects a devfile locally -func (o *ViewOptions) DevfileRun() (err error) { +// Run contains the logic for the command +func (o *ViewOptions) Run(cmd *cobra.Command) (err error) { w := tabwriter.NewWriter(os.Stdout, 5, 2, 2, ' ', tabwriter.TabIndent) repr, err := component.ToDevfileRepresentation(o.devfileObj) if err != nil { @@ -95,75 +75,6 @@ func (o *ViewOptions) DevfileRun() (err error) { return err } -// Run contains the logic for the command -func (o *ViewOptions) Run(cmd *cobra.Command) (err error) { - - if o.IsDevfile { - return o.DevfileRun() - } - w := tabwriter.NewWriter(os.Stdout, 5, 2, 2, ' ', tabwriter.TabIndent) - - cs := o.lci.GetComponentSettings() - envVarList := o.lci.GetEnvVars() - if len(envVarList) != 0 { - fmt.Fprintln(w, "ENVIRONMENT VARIABLES") - fmt.Fprintln(w, "------------------------------------------------") - fmt.Fprintln(w, "NAME", "\t", "VALUE") - for _, envVar := range envVarList { - fmt.Fprintln(w, envVar.Name, "\t", envVar.Value) - } - - fmt.Fprintln(w) - - } - fmt.Fprintln(w, "COMPONENT SETTINGS") - fmt.Fprintln(w, "------------------------------------------------") - - fmt.Fprintln(w, "PARAMETER", "\t", "CURRENT_VALUE") - fmt.Fprintln(w, "Type", "\t", showBlankIfNil(cs.Type)) - fmt.Fprintln(w, "Application", "\t", showBlankIfNil(cs.Application)) - fmt.Fprintln(w, "Project", "\t", showBlankIfNil(cs.Project)) - fmt.Fprintln(w, "SourceType", "\t", showBlankIfNil(cs.SourceType)) - fmt.Fprintln(w, "Ref", "\t", showBlankIfNil(cs.Ref)) - fmt.Fprintln(w, "SourceLocation", "\t", showBlankIfNil(cs.SourceLocation)) - fmt.Fprintln(w, "Ports", "\t", formatArray(cs.Ports)) - fmt.Fprintln(w, "Name", "\t", showBlankIfNil(cs.Name)) - fmt.Fprintln(w, "MinMemory", "\t", showBlankIfNil(cs.MinMemory)) - fmt.Fprintln(w, "MaxMemory", "\t", showBlankIfNil(cs.MaxMemory)) - fmt.Fprintln(w, "DebugPort", "\t", showBlankIfNil(cs.DebugPort)) - fmt.Fprintln(w, "Ignore", "\t", showBlankIfNil(cs.Ignore)) - fmt.Fprintln(w, "MinCPU", "\t", showBlankIfNil(cs.MinCPU)) - fmt.Fprintln(w, "MaxCPU", "\t", showBlankIfNil(cs.MaxCPU)) - w.Flush() - return - -} - -func showBlankIfNil(intf interface{}) interface{} { - imm := reflect.ValueOf(intf) - - // if the value is nil then we should return a blank string - if imm.IsNil() { - return "" - } - - // if its a pointer then we should de-ref it because we cant de-ref an interface{} - if imm.Kind() == reflect.Ptr { - return imm.Elem().Interface() - } - - return intf -} -func formatArray(arr *[]string) string { - if arr == nil { - return "" - } - if len(*arr) == 0 { - return "" - } - return strings.Join(*arr, ",") -} - // NewCmdView implements the config view odo command func NewCmdView(name, fullName string) *cobra.Command { o := NewViewOptions() diff --git a/pkg/odo/cli/debug/info.go b/pkg/odo/cli/debug/info.go index ce1c5238e50..ead4a275837 100644 --- a/pkg/odo/cli/debug/info.go +++ b/pkg/odo/cli/debug/info.go @@ -47,7 +47,7 @@ func NewInfoOptions() *InfoOptions { // Complete completes all the required options for port-forward cmd. func (o *InfoOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { if util.CheckPathExists(filepath.Join(o.contextDir, devfile)) { - o.Context, err = genericclioptions.NewDevfileContext(cmd) + o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -57,17 +57,6 @@ func (o *InfoOptions) Complete(name string, cmd *cobra.Command, args []string) ( o.componentName = env.GetName() o.Namespace = env.GetNamespace() - } else { - o.Context, err = genericclioptions.NewContext(cmd) - if err != nil { - return err - } - cfg := o.Context.LocalConfigInfo - o.LocalConfigInfo = cfg - - o.componentName = cfg.GetName() - o.applicationName = cfg.GetApplication() - o.Namespace = cfg.GetProject() } // Using Discard streams because nothing important is logged diff --git a/pkg/odo/cli/debug/portforward.go b/pkg/odo/cli/debug/portforward.go index 648ebb1a858..fec1d703a97 100644 --- a/pkg/odo/cli/debug/portforward.go +++ b/pkg/odo/cli/debug/portforward.go @@ -9,7 +9,6 @@ import ( "strconv" "syscall" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/debug" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/odo/cli/component" @@ -22,6 +21,11 @@ import ( "k8s.io/kubectl/pkg/util/templates" ) +const ( + // DefaultDebugPort is the default port used for debugging on remote pod + DefaultDebugPort = 5858 +) + // PortForwardOptions contains all the options for running the port-forward cli command. type PortForwardOptions struct { componentName string @@ -74,7 +78,7 @@ func (o *PortForwardOptions) Complete(name string, cmd *cobra.Command, args []st var remotePort int if util.CheckPathExists(o.devfilePath) { - o.Context, err = genericclioptions.NewDevfileContext(cmd) + o.Context, err = genericclioptions.NewContext(cmd) if err != nil { return err } @@ -86,20 +90,6 @@ func (o *PortForwardOptions) Complete(name string, cmd *cobra.Command, args []st o.componentName = env.GetName() o.Namespace = env.GetNamespace() - } else { - // this populates the LocalConfigInfo - o.Context, err = genericclioptions.NewContext(cmd) - if err != nil { - return err - } - - // a small shortcut - cfg := o.Context.LocalConfigInfo - remotePort = cfg.GetDebugPort() - - o.componentName = cfg.GetName() - o.applicationName = cfg.GetApplication() - o.Namespace = cfg.GetProject() } // try to listen on the given local port and check if the port is free or not @@ -186,7 +176,7 @@ func NewCmdPortForward(name, fullName string) *cobra.Command { } genericclioptions.AddContextFlag(cmd, &opts.contextDir) - cmd.Flags().IntVarP(&opts.localPort, "local-port", "l", config.DefaultDebugPort, "Set the local port") + cmd.Flags().IntVarP(&opts.localPort, "local-port", "l", DefaultDebugPort, "Set the local port") return cmd } diff --git a/pkg/odo/cli/storage/list.go b/pkg/odo/cli/storage/list.go index 941ebf7e51d..f5c163d09c6 100644 --- a/pkg/odo/cli/storage/list.go +++ b/pkg/odo/cli/storage/list.go @@ -79,7 +79,7 @@ func (o *ListOptions) Run(cmd *cobra.Command) (err error) { if err != nil { return err } - if !o.Context.LocalConfigInfo.Exists() && isContainerDisplay(storageList, localContainers) { + if isContainerDisplay(storageList, localContainers) { printStorageWithContainer(storageList, o.Context.LocalConfigProvider.GetName()) } else { printStorage(storageList, o.Context.LocalConfigProvider.GetName()) diff --git a/pkg/odo/cli/url/create.go b/pkg/odo/cli/url/create.go index c0ea6a19279..8a498fbf935 100644 --- a/pkg/odo/cli/url/create.go +++ b/pkg/odo/cli/url/create.go @@ -144,14 +144,6 @@ func (o *CreateOptions) Validate() (err error) { errorList = append(errorList, err.Error()) } - if o.Context.LocalConfigInfo.Exists() { - if o.now { - if err = o.ValidateComponentCreate(); err != nil { - errorList = append(errorList, err.Error()) - } - } - } - if len(errorList) > 0 { for i := range errorList { errorList[i] = fmt.Sprintf("\t- %s", errorList[i]) @@ -173,14 +165,9 @@ func (o *CreateOptions) Run(cmd *cobra.Command) (err error) { if o.now { // if the now flag is specified, push the changes - if o.Context.LocalConfigInfo.Exists() { - o.LocalConfigInfo = o.Context.LocalConfigInfo - err = o.Push() - } else { - o.CompleteDevfilePath() - o.EnvSpecificInfo = o.Context.EnvSpecificInfo - err = o.DevfilePush() - } + o.CompleteDevfilePath() + o.EnvSpecificInfo = o.Context.EnvSpecificInfo + err = o.DevfilePush() if err != nil { return errors.Wrap(err, "failed to push changes") } diff --git a/pkg/odo/cli/url/delete.go b/pkg/odo/cli/url/delete.go index b6ca8953e39..236c2309789 100644 --- a/pkg/odo/cli/url/delete.go +++ b/pkg/odo/cli/url/delete.go @@ -8,7 +8,6 @@ import ( "github.com/openshift/odo/pkg/odo/cli/ui" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/odo/util/completion" - "github.com/pkg/errors" "github.com/spf13/cobra" ktemplates "k8s.io/kubectl/pkg/util/templates" ) @@ -68,17 +67,6 @@ func (o *DeleteOptions) Validate() (err error) { if url == nil { return fmt.Errorf("the URL %s does not exist within the component %s", o.urlName, o.LocalConfigProvider.GetName()) } - - if o.LocalConfigInfo.Exists() { - if o.now { - o.LocalConfigInfo = o.Context.LocalConfigInfo - err = o.ValidateComponentCreate() - if err != nil { - return err - } - } - } - return } @@ -94,18 +82,11 @@ func (o *DeleteOptions) Run(cmd *cobra.Command) (err error) { log.Successf("URL %s removed from component %s", o.urlName, o.LocalConfigProvider.GetName()) if o.now { - if o.LocalConfigInfo.Exists() { - err = o.Push() - if err != nil { - return errors.Wrap(err, "failed to push changes") - } - } else { - o.CompleteDevfilePath() - o.EnvSpecificInfo = o.Context.EnvSpecificInfo - err = o.DevfilePush() - if err != nil { - return err - } + o.CompleteDevfilePath() + o.EnvSpecificInfo = o.Context.EnvSpecificInfo + err = o.DevfilePush() + if err != nil { + return err } log.Italic("\nTo delete the URL on the cluster, please use `odo push`") } diff --git a/pkg/odo/cli/url/list.go b/pkg/odo/cli/url/list.go index d81f7844f38..d1dfbffdc1e 100644 --- a/pkg/odo/cli/url/list.go +++ b/pkg/odo/cli/url/list.go @@ -81,8 +81,7 @@ func (o *ListOptions) Run(cmd *cobra.Command) (err error) { if log.IsJSON() { machineoutput.OutputSuccess(urls) } else { - isS2i := o.Context.LocalConfigInfo.Exists() - err = HumanReadableOutput(os.Stdout, urls, componentName, isS2i) + err = HumanReadableOutput(os.Stdout, urls, componentName) if err != nil { return err } @@ -116,7 +115,7 @@ func NewCmdURLList(name, fullName string) *cobra.Command { } // HumanReadableOutput outputs the list of projects in a human readable format -func HumanReadableOutput(w io.Writer, urls url.URLList, componentName string, isS2i bool) error { +func HumanReadableOutput(w io.Writer, urls url.URLList, componentName string) error { if len(urls.Items) == 0 { return fmt.Errorf("no URLs found for component %v. Refer `odo url create -h` to add one", componentName) } @@ -132,11 +131,11 @@ func HumanReadableOutput(w io.Writer, urls url.URLList, componentName string, is if u.Status.State == url.StateTypeNotPushed { urlStr = "" } else { - urlStr = url.GetURLString(u.Spec.Protocol, u.Spec.Host, "", isS2i) + urlStr = url.GetURLString(u.Spec.Protocol, u.Spec.Host, "") } fmt.Fprintln(tabWriterURL, u.Name, "\t", u.Status.State, "\t", urlStr, "\t", u.Spec.Port, "\t", u.Spec.Secure, "\t", u.Spec.Kind) } else { - fmt.Fprintln(tabWriterURL, u.Name, "\t", u.Status.State, "\t", url.GetURLString(u.Spec.Protocol, "", u.Spec.Host, false), "\t", u.Spec.Port, "\t", u.Spec.Secure, "\t", u.Spec.Kind) + fmt.Fprintln(tabWriterURL, u.Name, "\t", u.Status.State, "\t", url.GetURLString(u.Spec.Protocol, "", u.Spec.Host), "\t", u.Spec.Port, "\t", u.Spec.Secure, "\t", u.Spec.Kind) } } tabWriterURL.Flush() diff --git a/pkg/odo/cli/utils/convert.go b/pkg/odo/cli/utils/convert.go deleted file mode 100644 index 3551afe2c31..00000000000 --- a/pkg/odo/cli/utils/convert.go +++ /dev/null @@ -1,157 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/fatih/color" - - "github.com/openshift/odo/pkg/config" - "github.com/openshift/odo/pkg/devfile/convert" - "github.com/openshift/odo/pkg/log" - "github.com/openshift/odo/pkg/odo/genericclioptions" - "github.com/pkg/errors" - "github.com/spf13/cobra" - ktemplates "k8s.io/kubectl/pkg/util/templates" -) - -const ( - convertCommandName = "convert-to-devfile" -) - -var convertLongDesc = ktemplates.LongDesc(`Converts odo specific configuration from s2i to devfile. -It generates devfile.yaml and .odo/env/env.yaml for s2i components`) - -//var convertExample = ktemplates.Examples(`odo utils convert-to-devfile`) - -var convertExample = ktemplates.Examples(` # Convert s2i component to devfile component - -Note: Run all commands from s2i component context directory - -1. Generate devfile.yaml and env.yaml for s2i component. -%[1]s - -2. Push the devfile component to the cluster. -odo push - -3. Verify if devfile component is deployed sucessfully. -odo list - -4. Jump to 'rolling back conversion', if devfile component deployment failed. - -5. Delete the s2i component. -odo delete --s2i -a - -Congratulations, you have successfully converted s2i component to devfile component. - -# Rolling back the conversion - -1. If devfile component deployment failed, delete the devfile component with 'odo delete -a'. - It would delete only devfile component, your s2i component should still be running. - - To complete the migration seek help from odo dev community. - -`) - -// ConvertOptions encapsulates the options for the command -type ConvertOptions struct { - context *genericclioptions.Context - componentContext string - componentName string -} - -// NewConvertOptions creates a new ConvertOptions instance -func NewConvertOptions() *ConvertOptions { - return &ConvertOptions{} -} - -// Complete completes ConvertOptions after they've been created -func (co *ConvertOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - co.context, err = genericclioptions.NewContext(cmd) - if err != nil { - return err - } - co.componentName = co.context.LocalConfigInfo.GetName() - return nil - -} - -// Validate validates the ConvertOptions based on completed values -func (co *ConvertOptions) Validate() (err error) { - if co.context.LocalConfigInfo.GetSourceType() == config.GIT { - return errors.New("migration of git type s2i components to devfile is not supported by odo") - } - - return nil -} - -// Run contains the logic for the command -func (co *ConvertOptions) Run(cmd *cobra.Command) (err error) { - - /* NOTE: This data is not used in devfile currently so cannot be converted - minMemory := context.LocalConfigInfo.GetMinMemory() - minCPU := context.LocalConfigInfo.GetMinCPU() - maxCPU := context.LocalConfigInfo.GetMaxCPU() - */ - - err = convert.GenerateDevfileYaml(co.context.Client, co.context.LocalConfigInfo, co.componentContext) - if err != nil { - return errors.Wrap(err, "Error in generating devfile.yaml") - } - - co.context.EnvSpecificInfo, err = convert.GenerateEnvYaml(co.context.Client, co.context.LocalConfigInfo, co.componentContext) - - if err != nil { - return errors.Wrap(err, "Error in generating env.yaml") - } - - printOutput() - - return nil -} - -// NewCmdConvert implements the odo utils convert-to-devfile command -func NewCmdConvert(name, fullName string) *cobra.Command { - o := NewConvertOptions() - convertCmd := &cobra.Command{ - Use: name, - Short: "converts s2i based components to devfile based components", - Long: convertLongDesc, - Example: fmt.Sprintf(convertExample, fullName), - Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { - genericclioptions.GenericRun(o, cmd, args) - }, - } - - genericclioptions.AddContextFlag(convertCmd, &o.componentContext) - - return convertCmd -} - -func printOutput() { - - infoMessage := "devfile.yaml is available in the current directory." - - nextSteps := ` -To complete the conversion, run the following steps: - -NOTE: At all steps your s2i component is running, It would not be deleted until you do 'odo delete --s2i -a' - -1. Deploy devfile component. -$ odo push - -2. Verify if the component gets deployed successfully. -$ odo list - -3. If the devfile component was deployed successfully, your application is up, you can safely delete the s2i component. -$ odo delete --s2i -a - -congratulations you have successfully converted s2i component to devfile component :). -` - - rollBackMessage := ` If you see an error or your application not coming up, delete the devfile component with 'odo delete -a' and report this to odo dev community.` - - log.Infof(infoMessage) - log.Italicf(nextSteps) - yellow := color.New(color.FgYellow).SprintFunc() - log.Warning(yellow(rollBackMessage)) -} diff --git a/pkg/odo/cli/utils/utils.go b/pkg/odo/cli/utils/utils.go index e3961cea129..09fab336731 100644 --- a/pkg/odo/cli/utils/utils.go +++ b/pkg/odo/cli/utils/utils.go @@ -13,7 +13,6 @@ const RecommendedCommandName = "utils" // NewCmdUtils implements the utils odo command func NewCmdUtils(name, fullName string) *cobra.Command { terminalCmd := NewCmdTerminal(terminalCommandName, odoutil.GetFullName(fullName, terminalCommandName)) - convertCmd := NewCmdConvert(convertCommandName, odoutil.GetFullName(fullName, convertCommandName)) utilsCmd := &cobra.Command{ Use: name, Short: "Utilities for terminal commands and modifying odo configurations", @@ -26,6 +25,5 @@ func NewCmdUtils(name, fullName string) *cobra.Command { utilsCmd.SetUsageTemplate(odoutil.CmdUsageTemplate) utilsCmd.AddCommand(terminalCmd) - utilsCmd.AddCommand(convertCmd) return utilsCmd } diff --git a/pkg/odo/genericclioptions/context.go b/pkg/odo/genericclioptions/context.go index 01110cad8a8..54ac3b7710c 100644 --- a/pkg/odo/genericclioptions/context.go +++ b/pkg/odo/genericclioptions/context.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/occlient" @@ -42,7 +41,6 @@ type internalCxt struct { Application string cmp string OutputFlag string - LocalConfigInfo *config.LocalConfigInfo KClient kclient.ClientInterface EnvSpecificInfo *envinfo.EnvSpecificInfo LocalConfigProvider localConfigProvider.LocalConfigProvider @@ -58,11 +56,11 @@ type CreateParameters struct { } // New creates a context based on the given parameters -func New(parameters CreateParameters, toggles ...bool) (context *Context, err error) { +func New(parameters CreateParameters) (context *Context, err error) { parameters.DevfilePath = completeDevfilePath(parameters.ComponentContext, parameters.DevfilePath) isDevfile := odoutil.CheckPathExists(parameters.DevfilePath) if isDevfile { - context, err = NewDevfileContext(parameters.Cmd) + context, err = NewContext(parameters.Cmd) if err != nil { return context, err } @@ -118,26 +116,10 @@ func New(parameters CreateParameters, toggles ...bool) (context *Context, err er } context.ComponentContext = parameters.ComponentContext } - - err = context.InitConfigFromContext() - if err != nil { - return nil, err - } - context.LocalConfigProvider = context.LocalConfigInfo } return context, nil } -//InitConfigFromContext initializes localconfiginfo from the context -func (o *Context) InitConfigFromContext() error { - var err error - o.LocalConfigInfo, err = config.NewLocalConfigInfo(o.ComponentContext) - if err != nil { - return err - } - return nil -} - //InitEnvInfoFromContext initializes envinfo from the context func (o *Context) InitEnvInfoFromContext() (err error) { o.EnvSpecificInfo, err = envinfo.NewEnvSpecificInfo(o.ComponentContext) @@ -157,125 +139,28 @@ func completeDevfilePath(componentContext, devfilePath string) string { } // NewContext creates a new Context struct populated with the current state based on flags specified for the provided command -func NewContext(command *cobra.Command, toggles ...bool) (*Context, error) { - ignoreMissingConfig := false - createApp := false - if len(toggles) == 1 { - ignoreMissingConfig = toggles[0] - } - if len(toggles) == 2 { - createApp = toggles[1] - } - return newContext(command, createApp, ignoreMissingConfig) -} - -// NewDevfileContext creates a new Context struct populated with the current state based on flags specified for the provided command -func NewDevfileContext(command *cobra.Command) (*Context, error) { - return newDevfileContext(command, false) +func NewContext(command *cobra.Command) (*Context, error) { + return newContext(command, false) } // NewContextCreatingAppIfNeeded creates a new Context struct populated with the current state based on flags specified for the // provided command, creating the application if none already exists func NewContextCreatingAppIfNeeded(command *cobra.Command) (*Context, error) { - return newContext(command, true, false) -} - -// NewConfigContext is a special kind of context which only contains local configuration, other information is not retrieved -// from the cluster. This is useful for commands which don't want to connect to cluster. -func NewConfigContext(command *cobra.Command) (*Context, error) { - - // Check for valid config - localConfiguration, err := getValidConfig(command, false) - if err != nil { - return nil, err - } - outputFlag := FlagValueIfSet(command, OutputFlagName) - - ctx := &Context{ - internalCxt{ - LocalConfigInfo: localConfiguration, - OutputFlag: outputFlag, - }, - } - return ctx, nil + return newContext(command, true) } // NewContextCompletion disables checking for a local configuration since when we use autocompletion on the command line, we // couldn't care less if there was a configuration. We only need to check the parameters. func NewContextCompletion(command *cobra.Command) *Context { - ctx, err := newContext(command, false, true) + ctx, err := newContext(command, false) if err != nil { util.LogErrorAndExit(err, "") } return ctx } -// UpdatedContext returns a new context updated from config file -func UpdatedContext(context *Context) (*Context, *config.LocalConfigInfo, error) { - localConfiguration, err := getValidConfig(context.command, false) - if err != nil { - return nil, nil, err - } - ctx, err := newContext(context.command, true, false) - if err != nil { - return nil, localConfiguration, err - } - return ctx, localConfiguration, err -} - -// newContext creates a new context based on the command flags, creating missing app when requested -func newContext(command *cobra.Command, createAppIfNeeded bool, ignoreMissingConfiguration bool) (*Context, error) { - // Create a new occlient - client, err := ocClient() - if err != nil { - return nil, err - } - - // Create a new kclient - KClient, err := kclient.New() - if err != nil { - return nil, err - } - - // Check for valid config - localConfiguration, err := getValidConfig(command, ignoreMissingConfiguration) - if err != nil { - return nil, err - } - - // Resolve output flag - outputFlag := FlagValueIfSet(command, OutputFlagName) - - // Create the internal context representation based on calculated values - internalCxt := internalCxt{ - Client: client, - OutputFlag: outputFlag, - command: command, - LocalConfigInfo: localConfiguration, - KClient: KClient, - } - - err = internalCxt.resolveProject(localConfiguration) - if err != nil { - return nil, err - } - internalCxt.resolveApp(createAppIfNeeded, localConfiguration) - - // Once the component is resolved, add it to the context - _, err = internalCxt.resolveAndSetComponent(command, localConfiguration) - if err != nil { - return nil, err - } - // Create a context from the internal representation - context := &Context{ - internalCxt: internalCxt, - } - - return context, nil -} - -// newDevfileContext creates a new context based on command flags for devfile components -func newDevfileContext(command *cobra.Command, createAppIfNeeded bool) (*Context, error) { +// newContext creates a new context based on command flags for devfile components +func newContext(command *cobra.Command, createAppIfNeeded bool) (*Context, error) { // Resolve output flag outputFlag := FlagValueIfSet(command, OutputFlagName) @@ -284,8 +169,6 @@ func newDevfileContext(command *cobra.Command, createAppIfNeeded bool) (*Context internalCxt := internalCxt{ OutputFlag: outputFlag, command: command, - // this is only so we can make devfile and s2i work together for certain cases - LocalConfigInfo: &config.LocalConfigInfo{}, } // Get valid env information @@ -294,9 +177,6 @@ func newDevfileContext(command *cobra.Command, createAppIfNeeded bool) (*Context return nil, err } - internalCxt.EnvSpecificInfo = envInfo - internalCxt.resolveApp(createAppIfNeeded, envInfo) - // Create a new kubernetes client internalCxt.KClient, err = kClient() if err != nil { @@ -307,17 +187,16 @@ func newDevfileContext(command *cobra.Command, createAppIfNeeded bool) (*Context return nil, err } - // Gather the environment information + // Gather env specific info internalCxt.EnvSpecificInfo = envInfo + internalCxt.resolveApp(createAppIfNeeded, envInfo) - err = internalCxt.resolveNamespace(envInfo) - if err != nil { + if err := internalCxt.resolveNamespace(envInfo); err != nil { return nil, err } // resolve the component - _, err = internalCxt.resolveAndSetComponent(command, envInfo) - if err != nil { + if _, err = internalCxt.resolveAndSetComponent(command, envInfo); err != nil { return nil, err } // Create a context from the internal representation @@ -327,8 +206,8 @@ func newDevfileContext(command *cobra.Command, createAppIfNeeded bool) (*Context return context, nil } -// NewOfflineDevfileContext initializes a context for devfile components without any cluster calls -func NewOfflineDevfileContext(command *cobra.Command) (*Context, error) { +// NewOfflineContext initializes a context for devfile components without any cluster calls +func NewOfflineContext(command *cobra.Command) (*Context, error) { // Resolve output flag outputFlag := FlagValueIfSet(command, OutputFlagName) @@ -336,8 +215,6 @@ func NewOfflineDevfileContext(command *cobra.Command) (*Context, error) { internalCxt := internalCxt{ OutputFlag: outputFlag, command: command, - // this is only so we can make devfile and s2i work together for certain cases - LocalConfigInfo: &config.LocalConfigInfo{}, } // Get valid env information diff --git a/pkg/odo/genericclioptions/localprovider.go b/pkg/odo/genericclioptions/localprovider.go index a39e27d0852..ea72459b892 100644 --- a/pkg/odo/genericclioptions/localprovider.go +++ b/pkg/odo/genericclioptions/localprovider.go @@ -3,10 +3,8 @@ package genericclioptions import ( "fmt" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/envinfo" "github.com/spf13/cobra" - "k8s.io/klog" ) // GetValidEnvInfo is just a wrapper for getValidEnvInfo @@ -46,39 +44,3 @@ func getValidEnvInfo(command *cobra.Command) (*envinfo.EnvSpecificInfo, error) { return envInfo, nil } - -func getValidConfig(command *cobra.Command, ignoreMissingConfiguration bool) (*config.LocalConfigInfo, error) { - - contextDir, err := GetContextFlagValue(command) - if err != nil { - return nil, err - } - - // Access the local configuration - localConfiguration, err := config.NewLocalConfigInfo(contextDir) - if err != nil { - return nil, err - } - - // Now we check to see if we can skip gathering the information. - // If true, we just return. - canWeSkip, err := checkIfConfigurationNeeded(command) - if err != nil { - return nil, err - } - if canWeSkip { - return localConfiguration, nil - } - - // If file does not exist at this point, raise an error - // HOWEVER.. - // When using auto-completion, we should NOT error out, just ignore the fact that there is no configuration - if !localConfiguration.Exists() && ignoreMissingConfiguration { - klog.V(4).Info("There is NO config file that exists, we are however ignoring this as the ignoreMissingConfiguration flag has been passed in as true") - } else if !localConfiguration.Exists() { - return nil, fmt.Errorf("the current directory does not represent an odo component. Use 'odo create' to create component here or switch to directory with a component") - } - - // else simply return the local config info - return localConfiguration, nil -} diff --git a/pkg/odo/genericclioptions/resolve.go b/pkg/odo/genericclioptions/resolve.go index 436a00329c3..3793247da6e 100644 --- a/pkg/odo/genericclioptions/resolve.go +++ b/pkg/odo/genericclioptions/resolve.go @@ -18,51 +18,6 @@ func ResolveAppFlag(command *cobra.Command) string { return DefaultAppName } -// resolveProject resolves project -func (o *internalCxt) resolveProject(localConfiguration localConfigProvider.LocalConfigProvider) error { - var namespace string - command := o.command - projectFlag := FlagValueIfSet(command, ProjectFlagName) - if len(projectFlag) > 0 { - // if project flag was set, check that the specified project exists and use it - project, err := o.Client.GetProject(projectFlag) - if err != nil || project == nil { - return err - } - namespace = projectFlag - } else { - namespace = localConfiguration.GetNamespace() - if namespace == "" { - namespace = o.Client.Namespace - if len(namespace) <= 0 { - errFormat := "Could not get current project. Please create or set a project\n\t%s project create|set " - err := checkProjectCreateOrDeleteOnlyOnInvalidNamespace(command, errFormat) - if err != nil { - return err - } - } - } - - // check that the specified project exists - _, err := o.Client.GetProject(namespace) - if err != nil { - e1 := fmt.Sprintf("You don't have permission to create or set project '%s' or the project doesn't exist. Please create or set a different project\n\t", namespace) - errFormat := fmt.Sprint(e1, "%s project create|set ") - err = checkProjectCreateOrDeleteOnlyOnInvalidNamespace(command, errFormat) - if err != nil { - return err - } - } - } - o.Client.GetKubeClient().Namespace = namespace - o.Client.Namespace = namespace - o.Project = namespace - if o.KClient != nil { - o.KClient.SetNamespace(namespace) - } - return nil -} - // resolveNamespace resolves namespace for devfile component func (o *internalCxt) resolveNamespace(configProvider localConfigProvider.LocalConfigProvider) error { var namespace string diff --git a/pkg/odo/genericclioptions/util.go b/pkg/odo/genericclioptions/util.go index 3ceec2fbda4..7f9ed13b998 100644 --- a/pkg/odo/genericclioptions/util.go +++ b/pkg/odo/genericclioptions/util.go @@ -51,7 +51,7 @@ func checkProjectCreateOrDeleteOnlyOnInvalidNamespaceNoFmt(command *cobra.Comman // checkComponentExistsOrFail checks if the specified component exists with the given context and returns error if not. func (o *internalCxt) checkComponentExistsOrFail(cmp string) error { - exists, err := component.Exists(o.Client, cmp, o.Application) + exists, err := component.Exists(o.KClient, cmp, o.Application) if err != nil { return err } @@ -128,7 +128,7 @@ func checkIfConfigurationNeeded(command *cobra.Command) (bool, error) { return true, nil } // Case 6 : Check if firstChildCommand is catalog and request is to list or search - if firstChildCommand.Name() == "catalog" && (parentCommand.Name() == "list" || parentCommand.Name() == "search") { + if firstChildCommand.Name() == "catalog" { return true, nil } // Case 7: Check if firstChildCommand is component and request is list diff --git a/pkg/odo/util/cmdutils.go b/pkg/odo/util/cmdutils.go index 6dc863c1f7b..6220c7073c8 100644 --- a/pkg/odo/util/cmdutils.go +++ b/pkg/odo/util/cmdutils.go @@ -6,8 +6,9 @@ import ( "strings" "unicode" + "github.com/openshift/odo/pkg/envinfo" + "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/localConfigProvider" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/machineoutput" @@ -108,11 +109,11 @@ func PrintComponentInfo(client *occlient.Client, currentComponentName string, co // Retrieve the storage list storages = storage.StorageList{Items: componentDesc.Spec.StorageSpec} } else { - localConfig, err := config.New() + envInfo, err := envinfo.New() if err != nil { return err } - storageLocal, err := localConfig.ListStorage() + storageLocal, err := envInfo.ListStorage() if err != nil { return err } @@ -157,9 +158,9 @@ func PrintComponentInfo(client *occlient.Client, currentComponentName string, co switch url.Spec.Kind { case localConfigProvider.ROUTE: - urlString = urlPkg.GetURLString(url.Spec.Protocol, url.Spec.Host, "", false) + urlString = urlPkg.GetURLString(url.Spec.Protocol, url.Spec.Host, "") case localConfigProvider.INGRESS: - urlString = urlPkg.GetURLString(url.Spec.Protocol, "", url.Spec.Host, false) + urlString = urlPkg.GetURLString(url.Spec.Protocol, "", url.Spec.Host) default: continue } @@ -238,7 +239,7 @@ func VisitCommands(cmd *cobra.Command, f func(*cobra.Command)) { // as well as changes whether or not machine readable output // has been passed in.. // -// Return the flag usages for the help outout +// Return the flag usages for the help output func ModifyAdditionalFlags(cmd *cobra.Command) string { // Hide the machine readable output if the command diff --git a/pkg/odo/util/completion/completionhandlers.go b/pkg/odo/util/completion/completionhandlers.go index 17f2667deb3..bd9baa20f87 100644 --- a/pkg/odo/util/completion/completionhandlers.go +++ b/pkg/odo/util/completion/completionhandlers.go @@ -2,13 +2,12 @@ package completion import ( applabels "github.com/openshift/odo/pkg/application/labels" + "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/application" "github.com/openshift/odo/pkg/catalog" "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/odo/genericclioptions" - "github.com/openshift/odo/pkg/util" "github.com/posener/complete" "github.com/spf13/cobra" ) @@ -17,7 +16,7 @@ import ( var AppCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { completions = make([]string, 0) - applications, err := application.List(context.Client) + applications, err := application.List(context.Client.GetKubeClient()) if err != nil { return completions } @@ -65,12 +64,12 @@ var URLCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *ge var StorageDeleteCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { completions = make([]string, 0) - localConfig, err := config.New() + envInfo, err := envinfo.New() if err != nil { return completions } - storageList, err := localConfig.ListStorage() + storageList, err := envInfo.ListStorage() if err != nil { return completions } @@ -90,36 +89,15 @@ var StorageDeleteCompletionHandler = func(cmd *cobra.Command, args parsedArgs, c var CreateCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { completions = make([]string, 0) comps := &completions - found := false - - tasks := util.NewConcurrentTasks(2) - tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { - catalogList, _ := catalog.ListComponents(context.Client) - for _, builder := range catalogList.Items { - if args.commands[builder.Name] { - found = true - return - } - if len(builder.Spec.NonHiddenTags) > 0 { - *comps = append(*comps, builder.Name) - } - } - }}) - tasks.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) { - components, _ := catalog.ListDevfileComponents("") - for _, devfile := range components.Items { - if args.commands[devfile.Name] { - found = true - return - } - *comps = append(*comps, devfile.Name) - } - }}) - _ = tasks.Run() - if found { - return nil + components, _ := catalog.ListDevfileComponents("") + for _, devfile := range components.Items { + if args.commands[devfile.Name] { + return nil + } + *comps = append(*comps, devfile.Name) } + return completions } @@ -130,7 +108,7 @@ var ComponentNameCompletionHandler = func(cmd *cobra.Command, args parsedArgs, c if context.Application != "" { selector = applabels.GetSelector(context.Application) } - components, err := component.List(context.Client, selector, nil) + components, err := component.List(context.Client, selector) if err != nil { return completions diff --git a/pkg/storage/kubernetes.go b/pkg/storage/kubernetes.go index aba3fa43e85..bba6b935632 100644 --- a/pkg/storage/kubernetes.go +++ b/pkg/storage/kubernetes.go @@ -188,11 +188,11 @@ func (k kubernetesClient) ListFromCluster() (StorageList, error) { // List lists pvc based Storage and local Storage with respective states func (k kubernetesClient) List() (StorageList, error) { - if k.localConfig == nil { + if k.localConfigProvider == nil { return StorageList{}, fmt.Errorf("no local config was provided") } - localConfigStorage, err := k.localConfig.ListStorage() + localConfigStorage, err := k.localConfigProvider.ListStorage() if err != nil { return StorageList{}, err } diff --git a/pkg/storage/kubernetes_test.go b/pkg/storage/kubernetes_test.go index d54166e68a1..e26f65baa72 100644 --- a/pkg/storage/kubernetes_test.go +++ b/pkg/storage/kubernetes_test.go @@ -311,65 +311,6 @@ func Test_kubernetesClient_ListFromCluster(t *testing.T) { want: StorageList{}, wantErr: false, }, - { - name: "case 11: s2i converted component", - fields: fields{ - generic: generic{ - appName: "app", - componentName: "nodejs", - }, - }, - returnedDeployments: &appsv1.DeploymentList{ - Items: []appsv1.Deployment{ - *testingutil.CreateFakeDeploymentsWithContainers("nodejs", - []corev1.Container{ - testingutil.CreateFakeContainerWithVolumeMounts("s2i-builder", []corev1.VolumeMount{ - { - Name: "odo-projects", - MountPath: "/tmp/projects", - }, - { - Name: "odo-supervisord-shared-data", - MountPath: "/opt/odo/", - }, - { - Name: "app-root-volume-wildfly-wildfly-oupn-app-vol", - MountPath: "/opt/app-root", - }, - { - Name: "deployments-volume-wildfly-wildfly-oupn-app-vol", - MountPath: "/deployments", - }}), - }, []corev1.Container{ - testingutil.CreateFakeContainerWithVolumeMounts("copy-app-root-container-copy-app-root-1", []corev1.VolumeMount{ - { - Name: "app-root-volume-wildfly-wildfly-oupn-app-vol", - MountPath: "/mnt/app-root", - }, - }), - testingutil.CreateFakeContainerWithVolumeMounts("copy-supervisord", []corev1.VolumeMount{ - { - Name: "odo-supervisord-shared-data", - MountPath: "/opt/odo/", - }, - }), - }), - }, - }, - returnedPVCs: &corev1.PersistentVolumeClaimList{ - Items: []corev1.PersistentVolumeClaim{ - *testingutil.FakePVC("app-root-volume-wildfly-wildfly-oupn-app", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "app-root-volume-wildfly-wildfly-oupn-app"}), - *testingutil.FakePVC("deployments-volume-wildfly-wildfly-oupn-app", "10Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "deployments-volume-wildfly-wildfly-oupn-app"}), - }, - }, - want: StorageList{ - Items: []Storage{ - generateStorage(NewStorage("app-root-volume-wildfly-wildfly-oupn-app", "5Gi", "/opt/app-root"), "", "s2i-builder"), - generateStorage(NewStorage("deployments-volume-wildfly-wildfly-oupn-app", "10Gi", "/deployments"), "", "s2i-builder"), - }, - }, - wantErr: false, - }, } for _, tt := range tests { @@ -394,7 +335,7 @@ func Test_kubernetesClient_ListFromCluster(t *testing.T) { mockLocalConfig.EXPECT().GetName().Return(tt.fields.generic.componentName).AnyTimes() mockLocalConfig.EXPECT().GetApplication().Return(tt.fields.generic.appName).AnyTimes() - tt.fields.generic.localConfig = mockLocalConfig + tt.fields.generic.localConfigProvider = mockLocalConfig k := kubernetesClient{ generic: tt.fields.generic, @@ -685,7 +626,7 @@ func Test_kubernetesClient_List(t *testing.T) { mockLocalConfig.EXPECT().GetApplication().Return(tt.fields.generic.appName).AnyTimes() mockLocalConfig.EXPECT().ListStorage().Return(tt.returnedLocalStorage, nil) - tt.fields.generic.localConfig = mockLocalConfig + tt.fields.generic.localConfigProvider = mockLocalConfig k := kubernetesClient{ generic: tt.fields.generic, diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index eda427b3b20..2014e7df47c 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -23,9 +23,9 @@ const ( // generic contains information required for all the Storage clients type generic struct { - appName string - componentName string - localConfig localConfigProvider.LocalConfigProvider + appName string + componentName string + localConfigProvider localConfigProvider.LocalConfigProvider } type ClientOptions struct { @@ -47,9 +47,9 @@ func NewClient(options ClientOptions) Client { if options.LocalConfigProvider != nil { genericInfo = generic{ - appName: options.LocalConfigProvider.GetApplication(), - componentName: options.LocalConfigProvider.GetName(), - localConfig: options.LocalConfigProvider, + appName: options.LocalConfigProvider.GetApplication(), + componentName: options.LocalConfigProvider.GetName(), + localConfigProvider: options.LocalConfigProvider, } } diff --git a/pkg/testingutil/deploymentconfigs.go b/pkg/testingutil/deploymentconfigs.go deleted file mode 100644 index b94ee0c6dab..00000000000 --- a/pkg/testingutil/deploymentconfigs.go +++ /dev/null @@ -1,219 +0,0 @@ -package testingutil - -import ( - "fmt" - - applabels "github.com/openshift/odo/pkg/application/labels" - "github.com/openshift/odo/pkg/util" - - v1 "github.com/openshift/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func getContainer(componentName string, applicationName string, ports []corev1.ContainerPort, - envFromSources []corev1.EnvFromSource) corev1.Container { - return corev1.Container{ - Name: fmt.Sprintf("%v-%v", componentName, applicationName), - Image: fmt.Sprintf("%v-%v", componentName, applicationName), - Ports: ports, - EnvFrom: envFromSources, - } -} - -func getDeploymentConfig(namespace string, componentName string, componentType string, applicationName string, containers []corev1.Container) v1.DeploymentConfig { - return v1.DeploymentConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "DeploymentConfig", - APIVersion: "apps.openshift.io/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%v-%v", componentName, applicationName), - Namespace: namespace, - Labels: map[string]string{ - "app": "app", - "app.kubernetes.io/instance": componentName, - "app.kubernetes.io/name": componentType, - "app.kubernetes.io/part-of": applicationName, - applabels.ManagedBy: "odo", - }, - Annotations: map[string]string{ // Convert into separate function when other source types required in tests - "app.kubernetes.io/component-source-type": "git", - "app.openshift.io/vcs-uri": fmt.Sprintf("https://github.com/%s/%s.git", componentName, applicationName), - }, - }, - Spec: v1.DeploymentConfigSpec{ - Replicas: 1, - Selector: map[string]string{ - "deploymentconfig": fmt.Sprintf("%v-%v", componentName, applicationName), - }, - Template: &corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "deploymentconfig": fmt.Sprintf("%v-%v", componentName, applicationName), - }, - }, - Spec: corev1.PodSpec{ - Containers: containers, - }, - }, - }, - } -} - -func FakeDeploymentConfigs() *v1.DeploymentConfigList { - - var componentName string - var applicationName string - var componentType string - - // DC1 with multiple containers each with multiple ports - componentType = "python" - componentName = "python" - applicationName = "app" - c1 := getContainer(componentName, applicationName, []corev1.ContainerPort{ - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 8080, - Protocol: corev1.ProtocolTCP, - }, - { - Name: fmt.Sprintf("%v-%v-p2", componentName, applicationName), - ContainerPort: 9090, - Protocol: corev1.ProtocolUDP, - }, - }, nil) - c2 := getContainer(componentName, applicationName, []corev1.ContainerPort{ - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 10080, - Protocol: corev1.ProtocolTCP, - }, - { - Name: fmt.Sprintf("%v-%v-p2", componentName, applicationName), - ContainerPort: 10090, - Protocol: corev1.ProtocolUDP, - }, - }, nil) - dc1 := getDeploymentConfig("myproject", componentName, componentType, applicationName, []corev1.Container{c1, c2}) - - // DC2 with single container and single port - componentType = "nodejs" - componentName = "nodejs" - applicationName = "app" - c3 := getContainer(componentName, applicationName, []corev1.ContainerPort{ - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 8080, - Protocol: corev1.ProtocolTCP, - }, - }, []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "s1", - }, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "s2", - }, - }, - }, - }) - dc2 := getDeploymentConfig("myproject", componentName, componentType, applicationName, []corev1.Container{c3}) - - // DC3 with single container and multiple ports - componentType = "wildfly" - componentName = "wildfly" - applicationName = "app" - c4 := getContainer(componentName, applicationName, []corev1.ContainerPort{ - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 8080, - Protocol: corev1.ProtocolTCP, - }, - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 8090, - Protocol: corev1.ProtocolTCP, - }, - }, nil) - dc3 := getDeploymentConfig("myproject", componentName, componentType, applicationName, []corev1.Container{c4}) - - return &v1.DeploymentConfigList{ - Items: []v1.DeploymentConfig{ - dc1, - dc2, - dc3, - }, - } -} - -// mountedStorage is the map of the storage to be mounted -// key is the path for the mount, value is the pvc -func OneFakeDeploymentConfigWithMounts(componentName, componentType, applicationName string, mountedStorage map[string]*corev1.PersistentVolumeClaim) *v1.DeploymentConfig { - c := getContainer(componentName, applicationName, []corev1.ContainerPort{ - { - Name: fmt.Sprintf("%v-%v-p1", componentName, applicationName), - ContainerPort: 8080, - Protocol: corev1.ProtocolTCP, - }, - { - Name: fmt.Sprintf("%v-%v-p2", componentName, applicationName), - ContainerPort: 9090, - Protocol: corev1.ProtocolUDP, - }, - }, nil) - - dc := getDeploymentConfig("myproject", componentName, componentType, applicationName, []corev1.Container{c}) - - supervisorDPVC := FakePVC(getAppRootVolumeName(dc.Name), "1Gi", nil) - - for path, pvc := range mountedStorage { - volumeName := generateVolumeNameFromPVC(pvc.Name) - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: pvc.Name, - }, - }, - }) - dc.Spec.Template.Spec.Containers[0].VolumeMounts = append(dc.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: path, - }) - } - - // now append the supervisorD volume - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes, corev1.Volume{ - Name: getAppRootVolumeName(dc.Name), - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: supervisorDPVC.Name, - }, - }, - }) - - // now append the supervisorD volume mount - dc.Spec.Template.Spec.Containers[0].VolumeMounts = append(dc.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ - Name: getAppRootVolumeName(dc.Name), - MountPath: "/opt/app-root", - SubPath: "app-root", - }) - - return &dc -} - -// generateVolumeNameFromPVC generates a random volume name based on the name -// of the given PVC -func generateVolumeNameFromPVC(pvc string) string { - return fmt.Sprintf("%v-%v-volume", pvc, util.GenerateRandomString(5)) -} - -func getAppRootVolumeName(dcName string) string { - return fmt.Sprintf("%s-s2idata", dcName) -} diff --git a/pkg/testingutil/imagestreams.go b/pkg/testingutil/imagestreams.go deleted file mode 100644 index 41281c77441..00000000000 --- a/pkg/testingutil/imagestreams.go +++ /dev/null @@ -1,85 +0,0 @@ -package testingutil - -import ( - imagev1 "github.com/openshift/api/image/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Function taken from occlient_test.go -// fakeImageStream gets imagestream for the reactor -func fakeImageStream(imageName string, namespace string, tags []string) *imagev1.ImageStream { - image := &imagev1.ImageStream{ - ObjectMeta: metav1.ObjectMeta{ - Name: imageName, - Namespace: namespace, - }, - Status: imagev1.ImageStreamStatus{ - Tags: []imagev1.NamedTagEventList{ - { - Tag: "latest", - Items: []imagev1.TagEvent{ - {DockerImageReference: "example/" + imageName + ":latest"}, - {Generation: 1}, - {Image: imageName + "@sha256:9579a93ee"}, - }, - }, - }, - }, - } - - for _, tag := range tags { - imageTag := imagev1.TagReference{ - Name: tag, - Annotations: map[string]string{"tags": "builder"}, - } - image.Spec.Tags = append(image.Spec.Tags, imageTag) - } - - return image -} - -// FakeImageStreams lists the imagestreams for the reactor -func FakeImageStreams(imageName string, namespace string, tags []string) *imagev1.ImageStreamList { - return &imagev1.ImageStreamList{ - Items: []imagev1.ImageStream{*fakeImageStream(imageName, namespace, tags)}, - } -} - -// fakeImageStreamTag gets imagestreamtag for the reactor -func fakeImageStreamTag(imageName string, version string, namespace string, isHidden bool) *imagev1.ImageStreamTag { - tagsStr := "" - if isHidden { - tagsStr = "hidden" - } - image := &imagev1.ImageStreamTag{ - ObjectMeta: metav1.ObjectMeta{ - Name: imageName + ":" + version, - Namespace: namespace, - Annotations: map[string]string{ - "tags": tagsStr, - "version": version, - }, - }, - } - - return image -} - -// FakeImageStreamTags lists the imagestreams for the reactor -func FakeImageStreamTags(imageName string, namespace string, tags []string, hiddenTags []string) *imagev1.ImageStreamTagList { - var list = []imagev1.ImageStreamTag{} - for _, tag := range tags { - isHidden := false - for _, ht := range hiddenTags { - if ht == tag { - isHidden = true - break - } - } - list = append(list, *fakeImageStreamTag(imageName, tag, namespace, isHidden)) - } - - return &imagev1.ImageStreamTagList{ - Items: list, - } -} diff --git a/pkg/url/kubernetes.go b/pkg/url/kubernetes.go index c014fbfc2fe..15ba202ae93 100644 --- a/pkg/url/kubernetes.go +++ b/pkg/url/kubernetes.go @@ -88,9 +88,9 @@ func (k kubernetesClient) List() (URLList, error) { } localMap := make(map[string]URL) - if k.localConfig != nil { + if k.localConfigProvider != nil { // get the URLs present on the localConfigProvider - localURLS, err := k.localConfig.ListURLs() + localURLS, err := k.localConfigProvider.ListURLs() if err != nil { return URLList{}, err } @@ -324,7 +324,7 @@ func (k kubernetesClient) createRoute(url URL, labels map[string]string) (string } return "", errors.Wrap(err, "unable to create route") } - return GetURLString(GetProtocol(*route, iextensionsv1.Ingress{}), route.Spec.Host, "", true), nil + return GetURLString(GetProtocol(*route, iextensionsv1.Ingress{}), route.Spec.Host, ""), nil } // getResourceName gets the route/ingress resource name diff --git a/pkg/url/kubernetes_test.go b/pkg/url/kubernetes_test.go index d2e631df9af..0636b33799d 100644 --- a/pkg/url/kubernetes_test.go +++ b/pkg/url/kubernetes_test.go @@ -426,7 +426,7 @@ func Test_kubernetesClient_List(t *testing.T) { return true, &tt.returnedRoutes, nil }) - tt.fields.generic.localConfig = mockLocalConfig + tt.fields.generic.localConfigProvider = mockLocalConfig k := kubernetesClient{ generic: tt.fields.generic, isRouteSupported: tt.fields.isRouteSupported, diff --git a/pkg/url/status.go b/pkg/url/status.go index e96af96093f..4402afea0e0 100644 --- a/pkg/url/status.go +++ b/pkg/url/status.go @@ -63,8 +63,8 @@ func startURLTester(urlsToTest [][]statusURL, loggingClient machineoutput.Machin } } -func getURLsForKubernetes(oclient *occlient.Client, client kclient.ClientInterface, localConfig localConfigProvider.LocalConfigProvider, ignoreUnpushed bool) ([]statusURL, error) { - componentName := localConfig.GetName() +func getURLsForKubernetes(oclient *occlient.Client, client kclient.ClientInterface, lcProvider localConfigProvider.LocalConfigProvider, ignoreUnpushed bool) ([]statusURL, error) { + componentName := lcProvider.GetName() routesSupported := false @@ -81,7 +81,7 @@ func getURLsForKubernetes(oclient *occlient.Client, client kclient.ClientInterfa } urlClient := NewClient(ClientOptions{ - LocalConfigProvider: localConfig, + LocalConfigProvider: lcProvider, OCClient: *oclient, IsRouteSupported: routesSupported, }) @@ -103,10 +103,10 @@ func getURLsForKubernetes(oclient *occlient.Client, client kclient.ClientInterfa if u.Spec.Kind != localConfigProvider.ROUTE { protocol = GetProtocol(routev1.Route{}, ConvertExtensionV1IngressURLToIngress(u, componentName)) - properURL = GetURLString(protocol, "", u.Spec.Host, false) + properURL = GetURLString(protocol, "", u.Spec.Host) } else { protocol = u.Spec.Protocol - properURL = GetURLString(protocol, u.Spec.Host, "", false) + properURL = GetURLString(protocol, u.Spec.Host, "") } statusURLVal := statusURL{ diff --git a/pkg/url/url.go b/pkg/url/url.go index fcdf293eebb..63fe0cf522a 100644 --- a/pkg/url/url.go +++ b/pkg/url/url.go @@ -17,9 +17,9 @@ const apiVersion = "odo.dev/v1alpha1" // generic contains information required for all the URL clients type generic struct { - appName string - componentName string - localConfig localConfigProvider.LocalConfigProvider + appName string + componentName string + localConfigProvider localConfigProvider.LocalConfigProvider } type Client interface { @@ -42,9 +42,9 @@ func NewClient(options ClientOptions) Client { if options.LocalConfigProvider != nil { genericInfo = generic{ - appName: options.LocalConfigProvider.GetApplication(), - componentName: options.LocalConfigProvider.GetName(), - localConfig: options.LocalConfigProvider, + appName: options.LocalConfigProvider.GetApplication(), + componentName: options.LocalConfigProvider.GetName(), + localConfigProvider: options.LocalConfigProvider, } } @@ -61,22 +61,22 @@ func NewClient(options ClientOptions) Client { } type PushParameters struct { - LocalConfig localConfigProvider.LocalConfigProvider - URLClient Client - IsRouteSupported bool + LocalConfigProvider localConfigProvider.LocalConfigProvider + URLClient Client + IsRouteSupported bool } // Push creates and deletes the required URLs func Push(parameters PushParameters) error { urlLOCAL := make(map[string]URL) - localConfigURLs, err := parameters.LocalConfig.ListURLs() + localConfigProviderURLs, err := parameters.LocalConfigProvider.ListURLs() if err != nil { return err } // get the local URLs - for _, url := range localConfigURLs { + for _, url := range localConfigProviderURLs { if !parameters.IsRouteSupported && url.Kind == localConfigProvider.ROUTE { // display warning since Host info is missing log.Warningf("Unable to create ingress, missing host information for Endpoint %v, please check instructions on URL creation (refer `odo url create --help`)\n", url.Name) @@ -114,7 +114,7 @@ func Push(parameters PushParameters) error { // the default secret name is used during creation // thus setting it to the local URLs to avoid config mismatch if val.Spec.Secure && val.Spec.TLSSecret == "" { - val.Spec.TLSSecret = getDefaultTLSSecretName(parameters.LocalConfig.GetName(), parameters.LocalConfig.GetApplication()) + val.Spec.TLSSecret = getDefaultTLSSecretName(parameters.LocalConfigProvider.GetName(), parameters.LocalConfigProvider.GetApplication()) } val.Spec.Host = fmt.Sprintf("%v.%v", urlName, val.Spec.Host) } else if val.Spec.Kind == localConfigProvider.ROUTE { diff --git a/pkg/url/url_test.go b/pkg/url/url_test.go index 80f25c22474..4c89eb68e7c 100644 --- a/pkg/url/url_test.go +++ b/pkg/url/url_test.go @@ -671,9 +671,9 @@ func TestPush(t *testing.T) { fakeClient.SetKubeClient(fakeKClient) if err := Push(PushParameters{ - LocalConfig: mockLocalConfigProvider, - URLClient: mockURLClient, - IsRouteSupported: tt.args.isRouteSupported, + LocalConfigProvider: mockLocalConfigProvider, + URLClient: mockURLClient, + IsRouteSupported: tt.args.isRouteSupported, }); (err != nil) != tt.wantErr { t.Errorf("Push() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/pkg/url/utils.go b/pkg/url/utils.go index b0a47c09312..040f2ce1a73 100644 --- a/pkg/url/utils.go +++ b/pkg/url/utils.go @@ -73,16 +73,12 @@ func ConvertEnvInfoURL(envInfoURL localConfigProvider.LocalURL, serviceName stri } // GetURLString returns a string representation of given url -func GetURLString(protocol, URL, ingressDomain string, isS2I bool) string { +func GetURLString(protocol, URL, ingressDomain string) string { if protocol == "" && URL == "" && ingressDomain == "" { return "" } - if !isS2I && URL == "" { - return protocol + "://" + ingressDomain - } - // if we are here we are dealing with s2i if URL == "" { - return protocol + "://" + "" + return protocol + "://" + ingressDomain } return protocol + "://" + URL } diff --git a/pkg/url/utils_test.go b/pkg/url/utils_test.go index e2b4bbd2afb..5fa42d24882 100644 --- a/pkg/url/utils_test.go +++ b/pkg/url/utils_test.go @@ -109,31 +109,13 @@ func TestGetURLString(t *testing.T) { protocol string URL string ingressDomain string - isS2I bool expected string }{ - { - name: "simple s2i case", - protocol: "http", - URL: "example.com", - ingressDomain: "", - isS2I: true, - expected: "http://example.com", - }, - { - name: "all blank with s2i", - protocol: "", - URL: "", - ingressDomain: "", - isS2I: true, - expected: "", - }, { name: "all blank without s2i", protocol: "", URL: "", ingressDomain: "", - isS2I: false, expected: "", }, { @@ -141,17 +123,15 @@ func TestGetURLString(t *testing.T) { protocol: "http", URL: "", ingressDomain: "spring-8080.192.168.39.247.nip.io", - isS2I: false, expected: "http://spring-8080.192.168.39.247.nip.io", }, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { - output := GetURLString(testCase.protocol, testCase.URL, testCase.ingressDomain, testCase.isS2I) + output := GetURLString(testCase.protocol, testCase.URL, testCase.ingressDomain) if output != testCase.expected { t.Errorf("Expected: %v, got %v", testCase.expected, output) - } }) } diff --git a/pkg/util/file_indexer.go b/pkg/util/file_indexer.go index 8d312ed0807..5ae5b85254d 100644 --- a/pkg/util/file_indexer.go +++ b/pkg/util/file_indexer.go @@ -16,7 +16,7 @@ import ( "k8s.io/klog" ) -const fileIndexDirectory = ".odo" +const DotOdoDirectory = ".odo" const fileIndexName = "odo-file-index.json" // FileIndex holds the file index used for storing local file state change @@ -79,11 +79,11 @@ func ResolveIndexFilePath(directory string) (string, error) { switch mode := directoryFi.Mode(); { case mode.IsDir(): // do directory stuff - return filepath.Join(directory, fileIndexDirectory, fileIndexName), nil + return filepath.Join(directory, DotOdoDirectory, fileIndexName), nil case mode.IsRegular(): // do file stuff // for binary files - return filepath.Join(filepath.Dir(directory), fileIndexDirectory, fileIndexName), nil + return filepath.Join(filepath.Dir(directory), DotOdoDirectory, fileIndexName), nil } return directory, nil @@ -91,7 +91,7 @@ func ResolveIndexFilePath(directory string) (string, error) { // GetIndexFileRelativeToContext returns the index file relative to context i.e.; .odo/odo-file-index.json func GetIndexFileRelativeToContext() string { - return filepath.Join(fileIndexDirectory, fileIndexName) + return filepath.Join(DotOdoDirectory, fileIndexName) } // AddOdoFileIndex adds odo-file-index.json to .gitignore @@ -100,7 +100,7 @@ func AddOdoFileIndex(gitIgnoreFile string) error { } func addOdoFileIndex(gitIgnoreFile string, fs filesystem.Filesystem) error { - return addFileToIgnoreFile(gitIgnoreFile, filepath.Join(fileIndexDirectory, fileIndexName), fs) + return addFileToIgnoreFile(gitIgnoreFile, filepath.Join(DotOdoDirectory, fileIndexName), fs) } // CheckGitIgnoreFile checks .gitignore file exists or not, if not then create it @@ -149,111 +149,6 @@ type IndexerRet struct { ResolvedPath string } -// RunIndexer walks the given directory and finds the files which have changed and which were deleted/renamed -// it reads the odo index file from the .odo folder -// if no such file is present, it means it's the first time the folder is being walked and thus returns a empty list -// after the walk, it stores the list of walked files with some information in a odo index file in the .odo folder -// The filemap stores the values as "relative filepath" => FileData but it the FilesChanged and filesDeleted are absolute paths -// to the files -func RunIndexer(directory string, ignoreRules []string) (ret IndexerRet, err error) { - directory = filepath.FromSlash(directory) - ret.ResolvedPath, err = ResolveIndexFilePath(directory) - - if err != nil { - return ret, err - } - - // check for .gitignore file and add odo-file-index.json to .gitignore - gitIgnoreFile, err := CheckGitIgnoreFile(directory) - if err != nil { - return ret, err - } - - // add odo-file-index.json path to .gitignore - err = AddOdoFileIndex(gitIgnoreFile) - if err != nil { - return ret, err - } - - // read the odo index file - existingFileIndex, err := ReadFileIndex(ret.ResolvedPath) - if err != nil { - return ret, err - } - - ret.NewFileMap = make(map[string]FileData) - walk := func(walkFnPath string, fi os.FileInfo, err error) error { - - if err != nil { - return err - } - if fi.IsDir() { - - // if folder is the root folder, don't add it - if walkFnPath == directory { - return nil - } - - match, err := IsGlobExpMatch(walkFnPath, ignoreRules) - if err != nil { - return err - } - // the folder matches a glob rule and thus should be skipped - if match { - return filepath.SkipDir - } - - if fi.Name() == fileIndexDirectory || fi.Name() == ".git" { - klog.V(4).Info(".odo or .git directory detected, skipping it") - return filepath.SkipDir - } - } - - relativeFilename, err := CalculateFileDataKeyFromPath(walkFnPath, directory) - if err != nil { - return err - } - - if _, ok := existingFileIndex.Files[relativeFilename]; !ok { - ret.FilesChanged = append(ret.FilesChanged, walkFnPath) - klog.V(4).Infof("file added: %s", walkFnPath) - } else if !fi.ModTime().Equal(existingFileIndex.Files[relativeFilename].LastModifiedDate) { - ret.FilesChanged = append(ret.FilesChanged, walkFnPath) - klog.V(4).Infof("last modified date changed: %s", walkFnPath) - } else if fi.Size() != existingFileIndex.Files[relativeFilename].Size { - ret.FilesChanged = append(ret.FilesChanged, walkFnPath) - klog.V(4).Infof("size changed: %s", walkFnPath) - } - - ret.NewFileMap[relativeFilename] = FileData{ - Size: fi.Size(), - LastModifiedDate: fi.ModTime(), - } - return nil - } - - err = filepath.Walk(directory, walk) - if err != nil { - return ret, err - } - - // find files which are deleted/renamed - for fileName := range existingFileIndex.Files { - if _, ok := ret.NewFileMap[fileName]; !ok { - klog.V(4).Infof("Deleting file: %s", fileName) - - // Return the *absolute* path to the file) - fileAbsolutePath, err := GetAbsPath(filepath.Join(directory, fileName)) - if err != nil { - return ret, errors.Wrapf(err, "unable to retrieve absolute path of file %s", fileName) - } - ret.FilesDeleted = append(ret.FilesDeleted, fileAbsolutePath) - } - } - - return ret, nil -} - // CalculateFileDataKeyFromPath converts an absolute path to relative (and converts to OS-specific paths) for use // as a map key in IndexerRet and FileIndex func CalculateFileDataKeyFromPath(absolutePath string, rootDirectory string) (string, error) { @@ -580,7 +475,7 @@ func recursiveChecker(pathOptions recursiveCheckerPathOptions, ignoreRules []str if stat.IsDir() { - if stat.Name() == fileIndexDirectory || stat.Name() == ".git" { + if stat.Name() == DotOdoDirectory || stat.Name() == ".git" { return IndexerRet{}, nil } diff --git a/pkg/util/file_indexer_test.go b/pkg/util/file_indexer_test.go index 72120b7b660..b3c846b3532 100644 --- a/pkg/util/file_indexer_test.go +++ b/pkg/util/file_indexer_test.go @@ -265,7 +265,7 @@ func createGitFolderAndFiles(tempDirectoryName string, fs filesystem.Filesystem) return err } - err = fs.MkdirAll(filepath.Join(tempDirectoryName, fileIndexDirectory), 0755) + err = fs.MkdirAll(filepath.Join(tempDirectoryName, DotOdoDirectory), 0755) if err != nil { return err } diff --git a/pkg/util/util.go b/pkg/util/util.go index 48c1b98565c..67a882913bc 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -56,9 +56,8 @@ var httpCacheDir = filepath.Join(os.TempDir(), "odohttpcache") var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") -// 63 is the max length of a DeploymentConfig in Openshift and we also have to take into account -// that each component also gets a volume that uses the component name suffixed with -s2idata -const maxAllowedNamespacedStringLength = 63 - len("-s2idata") - 1 +// 63 is the max length of a DeploymentConfig in Openshift +const maxAllowedNamespacedStringLength = 63 // This value can be provided to set a seperate directory for users 'homedir' resolution // note for mocking purpose ONLY diff --git a/tests/examples/manifests/dc-label.yaml b/tests/examples/manifests/dc-label.yaml deleted file mode 100644 index 9b4ccf2d685..00000000000 --- a/tests/examples/manifests/dc-label.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: apps.openshift.io/v1 -kind: DeploymentConfig -metadata: - name: example-dc - labels: - app: app - app.kubernetes.io/instance: example-dc - app.kubernetes.io/managed-by: console - app.kubernetes.io/managed-by-version: v4.8.0 - app.kubernetes.io/name: example-dc - app.kubernetes.io/part-of: app -spec: - selector: - app: app - replicas: 1 - template: - metadata: - labels: - app: app - app.kubernetes.io/instance: example-dc - app.kubernetes.io/managed-by: console - app.kubernetes.io/managed-by-version: v4.8.0 - app.kubernetes.io/name: example-dc - app.kubernetes.io/part-of: app - spec: - containers: - - name: httpd - image: >- - image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest - ports: - - containerPort: 8080 - diff --git a/tests/helper/helper_configValidate.go b/tests/helper/helper_configValidate.go index 565367ecd27..45f387cbbfd 100644 --- a/tests/helper/helper_configValidate.go +++ b/tests/helper/helper_configValidate.go @@ -1,152 +1,15 @@ package helper import ( - "fmt" - "io/ioutil" "path/filepath" - "reflect" - "strconv" - "strings" . "github.com/onsi/gomega" "github.com/openshift/odo/pkg/envinfo" - "gopkg.in/yaml.v2" ) const configFileDirectory = ".odo" -const configFileName = "config.yaml" const envInfoFile = "env.yaml" -type config struct { - ComponentSettings struct { - Type string `yaml:"Type,omitempty"` - SourceLocation string `yaml:"SourceLocation,omitempty"` - Ref string `yaml:"Ref,omitempty"` - SourceType string `yaml:"SourceType,omitempty"` - Ports []string `yaml:"Ports,omitempty"` - Application string `yaml:"Application,omitempty"` - Project string `yaml:"Project,omitempty"` - Name string `yaml:"Name,omitempty"` - MinMemory string `yaml:"MinMemory,omitempty"` - MaxMemory string `yaml:"MaxMemory,omitempty"` - DebugPort []int `yaml:"DebugPort,omitempty"` - Storage []struct { - Name string `yaml:"Name,omitempty"` - Size string `yaml:"Size,omitempty"` - Path string `yaml:"Path,omitempty"` - } `yaml:"Storage,omitempty"` - Ignore bool `yaml:"Ignore,omitempty"` - MinCPU string `yaml:"MinCPU,omitempty"` - MaxCPU string `yaml:"MaxCPU,omitempty"` - Envs []struct { - Name string `yaml:"Name,omitempty"` - Value string `yaml:"Value,omitempty"` - } `yaml:"Envs,omitempty"` - URL []struct { - // Name of the URL - Name string `yaml:"Name,omitempty"` - // Port number for the url of the component, required in case of components which expose more than one service port - Port int `yaml:"Port,omitempty"` - } `yaml:"Url,omitempty"` - } `yaml:"ComponentSettings,omitempty"` -} - -// VerifyLocalConfig verifies the content of the config.yaml file -func verifyLocalConfig(context string) (config, error) { - var conf config - - yamlFile, err := ioutil.ReadFile(context) - if err != nil { - return conf, err - } - err = yaml.Unmarshal(yamlFile, &conf) - if err != nil { - return conf, err - } - return conf, nil -} - -// Search for the item in cmpfield string array -func Search(cmpField []string, val string) bool { - for _, item := range cmpField { - if item == val { - return true - } - } - return false -} - -// newInterfaceValue takes interface and keyValue of args -// It returns new initialized value -func newInterfaceValue(cmpSetting *config, keyValue ...string) reflect.Value { - indexNum, _ := strconv.Atoi(keyValue[1]) - if keyValue[0] == "URL" { - return reflect.ValueOf(cmpSetting.ComponentSettings.URL[indexNum]) - } - if keyValue[0] == "Storage" { - return reflect.ValueOf(cmpSetting.ComponentSettings.Storage[indexNum]) - } - return reflect.ValueOf(cmpSetting.ComponentSettings.Envs[indexNum]) -} - -// ValidateLocalCmpExist verifies the local config parameter -// It takes context and fieldType,value string as args -// URL and Storage parameter takes key,indexnumber,fieldType,value as args -func ValidateLocalCmpExist(context string, args ...string) { - var interfaceVal reflect.Value - cmpField := []string{"URL", "Storage", "Envs"} - cmpSetting, err := verifyLocalConfig(filepath.Join(context, configFileDirectory, configFileName)) - if err != nil { - Expect(err).To(Equal(nil)) - } - - for i := 0; i < len(args); i++ { - keyValue := strings.Split(args[i], ",") - - // if any of the cmp type like 'URL' is interface and matches cmpField - // New value is initialised for that particular interface in newInterfaceValue - // else New value is initialised for ComponentSettings interface - if Search(cmpField, keyValue[0]) { - interfaceVal = newInterfaceValue(&cmpSetting, keyValue[0], keyValue[1]) - keyValue[0] = keyValue[2] - keyValue[1] = keyValue[3] - } else { - interfaceVal = reflect.ValueOf(cmpSetting.ComponentSettings) - } - - for i := 0; i < interfaceVal.NumField(); i++ { - - // Get the field, returns https://golang.org/pkg/reflect/#StructField - field := interfaceVal.Field(i) - typeField := interfaceVal.Type().Field(i) - - f := field.Interface() - // Get the value of the field - fieldVal := reflect.ValueOf(f) - if typeField.Name == keyValue[0] { - - // validate the corresponding parameters of the type field - // convert the field value into the string - switch fieldVal.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - Expect(strconv.FormatInt(fieldVal.Int(), 10)).To(Equal(keyValue[1])) - case reflect.String: - Expect(fieldVal.String()).To(Equal(keyValue[1])) - case reflect.Slice: - sliceVal := fmt.Sprint(fieldVal) - Expect(sliceVal).To(Equal(keyValue[1])) - default: - fmt.Println("Invalid Kind of the field value") - - } - } - - } - - } - -} - func LocalEnvInfo(context string) *envinfo.EnvSpecificInfo { info, err := envinfo.NewEnvSpecificInfo(filepath.Join(context, configFileDirectory, envInfoFile)) if err != nil { diff --git a/tests/helper/helper_oc.go b/tests/helper/helper_oc.go index 56be747dfdc..f5984ff4598 100644 --- a/tests/helper/helper_oc.go +++ b/tests/helper/helper_oc.go @@ -1,7 +1,6 @@ package helper import ( - "bufio" "fmt" "io/ioutil" "os" @@ -18,8 +17,7 @@ import ( ) const ( - ResourceTypeDeploymentConfig = "dc" - ResourceTypeRoute = "route" + ResourceTypeRoute = "route" ) type OcRunner struct { @@ -119,37 +117,6 @@ func (oc OcRunner) GetComponentRoutes(component string, app string, project stri return "" } -// GetComponentDC run command to get the DeploymentConfig in yaml format for given component -func (oc OcRunner) GetComponentDC(component string, app string, project string) string { - session := CmdRunner(oc.path, "get", "dc", - "-n", project, - "-l", "app.kubernetes.io/instance="+component, - "-l", "app.kubernetes.io/part-of="+app, - "-o", "yaml") - - session.Wait() - if session.ExitCode() == 0 { - return string(session.Out.Contents()) - } - return "" -} - -// SourceTest checks the component-source-type and the source url in the annotation of the bc and dc -// appTestName is the name of the app -// sourceType is the type of the source of the component i.e git/binary/local -// source is the source of the component i.e gitURL or path to the directory or binary file -func (oc OcRunner) SourceTest(appTestName string, sourceType string, source string) { - // checking for source-type in dc - sourceTypeInDc := Cmd(oc.path, "get", "dc", "wildfly-"+appTestName, - "-o", "go-template='{{index .metadata.annotations \"app.kubernetes.io/component-source-type\"}}'").ShouldPass().Out() - Expect(sourceTypeInDc).To(ContainSubstring(sourceType)) - - // checking for source in dc - sourceInDc := Cmd(oc.path, "get", "dc", "wildfly-"+appTestName, - "-o", "go-template='{{index .metadata.annotations \"app.openshift.io/vcs-uri\"}}'").ShouldPass().Out() - Expect(sourceInDc).To(ContainSubstring(source)) -} - // ExecListDir returns dir list in specified location of pod func (oc OcRunner) ExecListDir(podName string, projectName string, dir string) string { stdOut := Cmd(oc.path, "exec", podName, "--namespace", projectName, @@ -202,201 +169,6 @@ func (oc OcRunner) CheckCmdOpInRemoteDevfilePod(podName string, containerName st return checkOp(stdOut, nil) } -// VerifyCmpExists verifies if component was created successfully -func (oc OcRunner) VerifyCmpExists(cmpName string, appName string, prjName string) { - cmpDCName := fmt.Sprintf("%s-%s", cmpName, appName) - Cmd(oc.path, "get", "dc", cmpDCName, "--namespace", prjName).ShouldPass() -} - -// VerifyLabelExistsOfComponent verifies app name of component -func (oc OcRunner) VerifyLabelExistsOfComponent(cmpName string, namespace string, labelName string) { - dcName := oc.GetDcName(cmpName, namespace) - session := Cmd(oc.path, "get", "dc", dcName, "--namespace", namespace, - "--template={{.metadata.labels}}").ShouldPass().Out() - Expect(session).To(ContainSubstring(labelName)) -} - -// VerifyAppNameOfComponent verifies app name of component -func (oc OcRunner) VerifyAppNameOfComponent(cmpName string, appName string, namespace string) { - session := Cmd(oc.path, "get", "dc", cmpName+"-"+appName, "--namespace", namespace, - "--template={{.metadata.labels.app}}").ShouldPass().Out() - Expect(session).To(ContainSubstring(appName)) -} - -// VerifyCmpName verifies the component name -func (oc OcRunner) VerifyCmpName(cmpName string, namespace string) { - dcName := oc.GetDcName(cmpName, namespace) - session := Cmd(oc.path, "get", "dc", dcName, - "--namespace", namespace, - "-L", "app.kubernetes.io/instance").ShouldPass().Out() - Expect(session).To(ContainSubstring(cmpName)) -} - -// GetDcName execute oc command and returns dc name of a deployed -// component by passing component name as a argument -func (oc OcRunner) GetDcName(compName string, namespace string) string { - session := Cmd(oc.path, "get", "dc", "--namespace", namespace).ShouldPass().Out() - re := regexp.MustCompile(compName + `-\S+ `) - dcName := re.FindString(session) - return strings.TrimSpace(dcName) -} - -// DescribeDc execute oc command and returns dc describe as a string -// by passing dcname and namespace as arguments -func (oc OcRunner) DescribeDc(dcName string, namespace string) string { - describeInfo := Cmd(oc.path, "describe", "dc/"+dcName, "-n", namespace).ShouldPass().Out() - return strings.TrimSpace(describeInfo) -} - -// GetDcPorts returns the ports of the component -func (oc OcRunner) GetDcPorts(componentName string, appName string, project string) string { - ports := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{range.spec.template.spec.containers}}{{.ports}}{{end}}'").ShouldPass().Out() - return ports -} - -// MaxMemory returns maximum memory -func (oc OcRunner) MaxMemory(componentName string, appName string, project string) string { - maxMemory := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{range.spec.template.spec.containers}}{{.resources.limits.memory}}{{end}}'").ShouldPass().Out() - return maxMemory -} - -// MinMemory returns minimum memory -func (oc OcRunner) MinMemory(componentName string, appName string, project string) string { - minMemory := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{range.spec.template.spec.containers}}{{.resources.requests.memory}}{{end}}'").ShouldPass().Out() - return minMemory -} - -// MaxCPU returns maximum cpu -func (oc OcRunner) MaxCPU(componentName string, appName string, project string) string { - maxCPU := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{range.spec.template.spec.containers}}{{.resources.limits.cpu}}{{end}}'").ShouldPass().Out() - return maxCPU -} - -// MinCPU returns minimum cpu -func (oc OcRunner) MinCPU(componentName string, appName string, project string) string { - minCPU := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{range.spec.template.spec.containers}}{{.resources.requests.cpu}}{{end}}'").ShouldPass().Out() - return minCPU -} - -// SourceTypeDC returns the source type from the deployment config -func (oc OcRunner) SourceTypeDC(componentName string, appName string, project string) string { - sourceType := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{index .metadata.annotations \"app.kubernetes.io/component-source-type\"}}'").ShouldPass().Out() - return sourceType -} - -// SourceTypeBC returns the source type from the build config -func (oc OcRunner) SourceTypeBC(componentName string, appName string, project string) string { - sourceType := Cmd(oc.path, "get", "bc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{.spec.source.type}}'").ShouldPass().Out() - return sourceType -} - -// SourceLocationDC returns the source location from the deployment config -func (oc OcRunner) SourceLocationDC(componentName string, appName string, project string) string { - sourceLocation := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{index .metadata.annotations \"app.openshift.io/vcs-uri\"}}'").ShouldPass().Out() - return sourceLocation -} - -// SourceLocationBC returns the source location from the build config -func (oc OcRunner) SourceLocationBC(componentName string, appName string, project string) string { - sourceLocation := Cmd(oc.path, "get", "bc", componentName+"-"+appName, "--namespace", project, - "-o", "go-template='{{index .spec.source.git \"uri\"}}'").ShouldPass().Out() - return sourceLocation -} - -// checkForImageStream checks if there is a ImageStream with name and tag in openshift namespace -func (oc OcRunner) checkForImageStream(name string, tag string) bool { - // first check if there is ImageStream with given name - names := strings.Trim(Cmd(oc.path, "get", "is", "-n", "openshift", - "-o", "jsonpath='{range .items[*]}{.metadata.name}{\"\\n\"}{end}'").ShouldPass().Out(), "'") - scanner := bufio.NewScanner(strings.NewReader(names)) - namePresent := false - for scanner.Scan() { - if scanner.Text() == name { - namePresent = true - } - } - tagPresent := false - // if there is a ImageStream check if there is a given tag - if namePresent { - tags := strings.Trim(Cmd(oc.path, "get", "is", name, "-n", "openshift", - "-o", "jsonpath='{range .spec.tags[*]}{.name}{\"\\n\"}{end}'").ShouldPass().Out(), "'") - scanner := bufio.NewScanner(strings.NewReader(tags)) - for scanner.Scan() { - if scanner.Text() == tag { - tagPresent = true - } - } - } - - if tagPresent { - return true - } - return false -} - -// ImportImageFromRegistry import the required image of the respective component type from the specified registry -func (oc OcRunner) ImportImageFromRegistry(registry, image, cmpType, project string) { - Cmd(oc.path, "--request-timeout", "5m", "import-image", cmpType, "--namespace="+project, fmt.Sprintf("--from=%s/%s", registry, image), "--confirm").ShouldPass() - Cmd(oc.path, "annotate", fmt.Sprintf("istag/%s", cmpType), "--namespace="+project, "tags=builder", "--overwrite").ShouldPass() - -} - -// ImportJavaIS import the openjdk image which is used for jars -func (oc OcRunner) ImportJavaIS(project string) { - // if ImageStream already exists, no need to do anything - if oc.checkForImageStream("java", "8") { - return - } - - // we need to import the openjdk image which is used for jars because it's not available by default - Cmd(oc.path, "--request-timeout", "5m", "import-image", "java:8", - "--namespace="+project, "--from=registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift:1.8", - "--confirm").ShouldPass() - Cmd(oc.path, "annotate", "istag/java:8", "--namespace="+project, - "tags=builder", "--overwrite").ShouldPass() -} - -// ImportDotnet20IS import the dotnet image -func (oc OcRunner) ImportDotnet20IS(project string) { - // if ImageStream already exists, no need to do anything - if oc.checkForImageStream("dotnet", "2.0") { - return - } - - // we need to import the openjdk image which is used for jars because it's not available by default - Cmd(oc.path, "--request-timeout", "5m", "import-image", "dotnet:2.0", - "--namespace="+project, "--from=registry.centos.org/dotnet/dotnet-20-centos7", - "--confirm").ShouldPass() - Cmd(oc.path, "annotate", "istag/dotnet:2.0", "--namespace="+project, - "tags=builder", "--overwrite").ShouldPass() -} - -// EnvVarTest checks the component container env vars in the build config for git and deployment config for git/binary/local -// appTestName is the app of the app -// sourceType is the type of the source of the component i.e git/binary/local -func (oc OcRunner) EnvVarTest(resourceName string, sourceType string, envString string) { - - if sourceType == "git" { - // checking the values of the env vars pairs in bc - envVars := Cmd(oc.path, "get", "bc", resourceName, - "-o", "go-template='{{range .spec.strategy.sourceStrategy.env}}{{.name}}{{.value}}{{end}}'").ShouldPass().Out() - Expect(envVars).To(Equal(envString)) - } - - // checking the values of the env vars pairs in dc - envVars := Cmd(oc.path, "get", "dc", resourceName, - "-o", "go-template='{{range .spec.template.spec.containers}}{{range .env}}{{.name}}{{.value}}{{end}}{{end}}'").ShouldPass().Out() - Expect(envVars).To(Equal(envString)) -} - // GetRunningPodNameOfComp executes oc command and returns the running pod name of a deployed // component by passing component name as a argument func (oc OcRunner) GetRunningPodNameOfComp(compName string, namespace string) string { @@ -483,48 +255,11 @@ func (oc OcRunner) GetContainerEnv(podName, containerName, namespace string) str return strings.TrimSpace(containerEnv) } -// GetVolumeMountName returns the name of the volume -func (oc OcRunner) GetVolumeMountName(dcName string, namespace string) string { - volumeName := Cmd(oc.path, "get", "dc", dcName, "--namespace", namespace, - "-o", "go-template='"+ - "{{range .spec.template.spec.containers}}"+ - "{{range .volumeMounts}}{{.name}}{{end}}{{end}}'").ShouldPass().Out() - - return strings.TrimSpace(volumeName) -} - -// GetVolumeMountPath returns the path of the volume mount -func (oc OcRunner) GetVolumeMountPath(dcName string, namespace string) string { - volumePaths := Cmd(oc.path, "get", "dc", dcName, "--namespace", namespace, - "-o", "go-template='"+ - "{{range .spec.template.spec.containers}}"+ - "{{range .volumeMounts}}{{.mountPath}} {{end}}{{end}}'").ShouldPass().Out() - - return strings.TrimSpace(volumePaths) -} - // GetEnvFromEntry returns envFrom entry of the deployment func (oc OcRunner) GetEnvFromEntry(componentName string, appName string, projectName string) string { return GetEnvFromEntry(oc.path, componentName, appName, projectName) } -// GetEnvs returns all env variables in deployment config -func (oc OcRunner) GetEnvs(componentName string, appName string, projectName string) map[string]string { - var mapOutput = make(map[string]string) - - output := Cmd(oc.path, "get", "dc", componentName+"-"+appName, "--namespace", projectName, - "-o", "jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}:{.value}{\"\\n\"}{end}'").ShouldPass().Out() - - for _, line := range strings.Split(output, "\n") { - line = strings.TrimPrefix(line, "'") - splits := strings.Split(line, ":") - name := splits[0] - value := strings.Join(splits[1:], ":") - mapOutput[name] = value - } - return mapOutput -} - func (oc OcRunner) GetEnvsDevFileDeployment(componentName, appName, projectName string) map[string]string { var mapOutput = make(map[string]string) @@ -547,18 +282,6 @@ func (oc OcRunner) GetEnvRefNames(componentName, appName, projectName string) [] return GetEnvRefNames(oc.path, componentName, appName, projectName) } -// WaitForDCRollout wait for DeploymentConfig to finish active rollout -// timeout is a maximum wait time in seconds -func (oc OcRunner) WaitForDCRollout(dcName string, project string, timeout time.Duration) { - session := CmdRunner(oc.path, "rollout", "status", - "-w", - "-n", project, - "dc", dcName) - - Eventually(session).Should(gexec.Exit(0), runningCmd(session.Command)) - session.Wait(timeout) -} - // WaitAndCheckForExistence wait for the given and checks if the given resource type gets deleted on the cluster func (oc OcRunner) WaitAndCheckForExistence(resourceType, namespace string, timeoutMinutes int) bool { pingTimeout := time.After(time.Duration(timeoutMinutes) * time.Minute) diff --git a/tests/integration/devfile/cmd_devfile_catalog_test.go b/tests/integration/devfile/cmd_devfile_catalog_test.go index 7f1c7539ec0..325b9d85631 100644 --- a/tests/integration/devfile/cmd_devfile_catalog_test.go +++ b/tests/integration/devfile/cmd_devfile_catalog_test.go @@ -87,7 +87,7 @@ var _ = Describe("odo devfile catalog command tests", func() { wantOutput := []string{ "odo.dev/v1alpha1", - "devfileItems", + "items", "java-openliberty", "java-springboot", "nodejs", diff --git a/tests/integration/devfile/utils/utils.go b/tests/integration/devfile/utils/utils.go index 0155f8531ff..c9cbe49e0d5 100644 --- a/tests/integration/devfile/utils/utils.go +++ b/tests/integration/devfile/utils/utils.go @@ -250,7 +250,7 @@ func DeleteLocalConfig(args ...string) { // in Devfile Component list func VerifyCatalogListComponent(output string, cmpName []string) error { var data map[string]interface{} - listItems := []string{"devfileItems"} + listItems := []string{"items"} if err := json.Unmarshal([]byte(output), &data); err != nil { return err diff --git a/tests/integration/generic_test.go b/tests/integration/generic_test.go index 7c421bfde83..d3c0487c3f3 100644 --- a/tests/integration/generic_test.go +++ b/tests/integration/generic_test.go @@ -55,7 +55,7 @@ var _ = Describe("odo generic", func() { Context("When executing catalog list without component directory", func() { It("should list all component catalogs", func() { stdOut := helper.Cmd("odo", "catalog", "list", "components").ShouldPass().Out() - helper.MatchAllInOutput(stdOut, []string{"dotnet", "nginx", "php", "ruby", "wildfly"}) + helper.MatchAllInOutput(stdOut, []string{"nodejs", "python", "php", "go", "java"}) }) }) diff --git a/tests/template/template_cleantest_test.go b/tests/template/template_cleantest_test.go index b8df9ca9074..a55927b1ab2 100644 --- a/tests/template/template_cleantest_test.go +++ b/tests/template/template_cleantest_test.go @@ -27,7 +27,7 @@ var _ = Describe("Example of a clean test", func() { // commands like odo push, might take a long time SetDefaultEventuallyTimeout(10 * time.Minute) context = helper.CreateNewContext() - os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) + os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "preference.yaml")) project = helper.CreateRandProject() // we will be testing components that are created from the current directory