diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ba10e23cab1..00000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index d79f9fde90d..3c6651d7b95 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,10 @@ tags # VSCode specific .vscode + +# +# MAC OS SPECIFIC +# + +# Finder Desktop Service +.DS_Store diff --git a/.travis.yml b/.travis.yml index 50d0a736f80..68081c9cf09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -130,7 +130,7 @@ jobs: # scenario of docker devfile url testing needs only Kube config file. So the test has been # added here just to make sure docker devfile url command test gets a proper kube config file. # without creating a separate OpenShift cluster. - name: "devfile catalog, create, push, delete and docker devfile url command integration tests" + name: "devfile catalog, create, push, delete, registry and docker devfile url command integration tests" script: - ./scripts/oc-cluster.sh - make bin @@ -143,6 +143,7 @@ jobs: - travis_wait make test-cmd-devfile-push - travis_wait make test-cmd-devfile-watch - travis_wait make test-cmd-devfile-delete + - travis_wait make test-cmd-devfile-registry - odo logout - <<: *base-test @@ -161,7 +162,7 @@ jobs: - <<: *base-test stage: test - name: "docker devfile push and delete command integration tests" + name: "docker devfile push, url, catalog, delete command integration tests" script: - make bin - sudo cp odo /usr/bin diff --git a/Makefile b/Makefile index 3d6e8636e7a..3914e3da1cf 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,11 @@ test-cmd-devfile-watch: .PHONY: test-cmd-devfile-delete test-cmd-devfile-delete: ginkgo $(GINKGO_FLAGS) -focus="odo devfile delete command tests" tests/integration/devfile/ + +# Run odo devfile registry command tests +.PHONY: test-cmd-devfile-registry +test-cmd-devfile-registry: + ginkgo $(GINKGO_FLAGS) -focus="odo devfile registry command tests" tests/integration/devfile/ # Run odo storage command tests .PHONY: test-cmd-storage diff --git a/README.adoc b/README.adoc index 1097edc0695..e3e91ae9cd0 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ +++ [id="readme"] -= `odo` - Developer-focused CLI for OpenShift += `odo` - Developer-focused CLI for OpenShift and Kubernetes :toc: macro :toc-title: :toclevels: 1 @@ -15,7 +15,7 @@ image:https://img.shields.io/github/license/openshift/odo?style=for-the-badge[Li [[overview]] == Overview -`odo` is a fast, iterative, and straightforward CLI tool for developers who write, build, and deploy applications on OpenShift. +`odo` is a fast, iterative, and straightforward CLI tool for developers who write, build, and deploy applications on OpenShift and Kubernetes. Existing tools such as `oc` are more operations-focused and require a deep-understanding of Kubernetes and OpenShift concepts. `odo` abstracts away complex Kubernetes and OpenShift concepts for the developer. @@ -25,11 +25,11 @@ Existing tools such as `oc` are more operations-focused and require a deep-under `odo` is designed to be simple and concise with the following key features: * Simple syntax and design centered around concepts familiar to developers, such as projects, applications, and components. -* Completely client based. No server is required within the OpenShift cluster for deployment. +* Completely client based. No server is required within the cluster for deployment. * Official support for Node.js and Java components. * Partial compatibility with languages and frameworks such as Ruby, Perl, PHP, and Python. * Detects changes to local code and deploys it to the cluster automatically, giving instant feedback to validate changes in real time. -* Lists all the available components and services from the {product-title} cluster. +* Lists all the available components and services from the cluster. [id="odo-supported-languages-and-images"] === Officially supported languages and corresponding container images @@ -80,13 +80,13 @@ The list of available container images is sourced from the cluster's internal co To list the available components and associated container images for your cluster: -. Log in to the {product-title} cluster with {odo-title}: +. Access your cluster. Authentication with `odo login` for Kubernetes clusters is currently not supported. For OpenShift cluster, you can authenticate with `odo login`: + ---- $ odo login -u developer -p developer ---- -. List the available {odo-title} supported and unsupported components and corresponding container images: +. List the available `odo` supported and unsupported components and corresponding container images: + ---------------------------------------------------- $ odo catalog list components @@ -184,8 +184,7 @@ Want to try out the odo experimental mode? Please read the link:https://github.c *Application:* An application consists of multiple microservices or components that work individually to build the entire application. -*Component:* A component is similar to a microservice. Multiple -components make up an application. A component has different attributes like storage. `odo` supports multiple component types like nodejs, perl, php, python, and ruby. +*Component:* A component is similar to a microservice. Multiple components make up an application. A component has different attributes like storage. `odo` supports multiple component types like nodejs, perl, php, python, and ruby. *Service:* Typically a service is a database or a service that a component links to or depends on. For example: MariaDB, Jenkins, MySQL. diff --git a/cmd/odo/odo.go b/cmd/odo/odo.go index 4431fb1362a..23ec7c820b8 100644 --- a/cmd/odo/odo.go +++ b/cmd/odo/odo.go @@ -3,7 +3,6 @@ package main import ( "flag" "os" - "strings" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/odo/cli" @@ -46,15 +45,6 @@ func main() { } } - // Override the logging level by the value (if set) by the ODO_LOG_LEVEL env - // The "-v" flag set on command line will take precedence over ODO_LOG_LEVEL env - v := flag.CommandLine.Lookup("v").Value.String() - // if the json flag is passed and is valid, we don't turn on ODO_LOG_LEVEL - jsonFlagValue := flag.CommandLine.Lookup("o").Value.String() - if level, ok := os.LookupEnv("ODO_LOG_LEVEL"); ok && v == "0" && strings.ToLower(jsonFlagValue) != "json" { - _ = flag.CommandLine.Set("v", level) - } - // run the completion, in case that the completion was invoked // and ran as a completion script or handled a flag that passed // as argument, the Run method will return true, diff --git a/docs/dev/development.adoc b/docs/dev/development.adoc index f2c2bdf2283..49f2ffeec87 100644 --- a/docs/dev/development.adoc +++ b/docs/dev/development.adoc @@ -513,7 +513,7 @@ Below is working example of how we would implement a "HelloWorld" struct. machineOutput := GenericSuccess{ TypeMeta: metav1.TypeMeta{ Kind: "HelloWorldExample", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "MyProject", diff --git a/docs/dev/machine-output.adoc b/docs/dev/machine-output.adoc index 336f816449c..8adac0d8426 100644 --- a/docs/dev/machine-output.adoc +++ b/docs/dev/machine-output.adoc @@ -170,7 +170,7 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "Application", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "app", "namespace": "myproject", @@ -189,12 +189,12 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "Application", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "app", "namespace": "myproject", @@ -219,7 +219,7 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "Component", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "nodejs-nodejs-ex-xvgz", "namespace": "foobarz", @@ -232,12 +232,12 @@ See the below table for a list of all possible machine readable output commands: "sourceType": "local", "urls": { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "url", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "myurl", "creationTimestamp": null @@ -254,7 +254,7 @@ See the below table for a list of all possible machine readable output commands: }, { "kind": "url", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "json", "creationTimestamp": null @@ -271,12 +271,12 @@ See the below table for a list of all possible machine readable output commands: }, "storages": { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "storage", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "mystorage", "creationTimestamp": null @@ -288,7 +288,7 @@ See the below table for a list of all possible machine readable output commands: }, { "kind": "storage", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "mystorage2", "creationTimestamp": null @@ -323,12 +323,12 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "Component", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "nodejs-nvnh", "creationTimestamp": null @@ -358,12 +358,12 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "Project", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "myproject", "creationTimestamp": null @@ -387,7 +387,7 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "storage", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "mystorage", "creationTimestamp": null @@ -405,12 +405,12 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "List", - "apiVersion": "odo.openshift.io/v1aplha1", + "apiVersion": "odo.dev/v1aplha1", "metadata": {}, "items": [ { "kind": "Storage", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "mystorage", "creationTimestamp": null @@ -430,7 +430,7 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "url", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "foobar-8080", "creationTimestamp": null @@ -449,12 +449,12 @@ See the below table for a list of all possible machine readable output commands: ---- { "kind": "List", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [ { "kind": "url", - "apiVersion": "odo.openshift.io/v1alpha1", + "apiVersion": "odo.dev/v1alpha1", "metadata": { "name": "foobar-8080", "creationTimestamp": null diff --git a/docs/proposals/odo-debug.md b/docs/proposals/odo-debug.md index 983ba755354..3fd05b6ff50 100644 --- a/docs/proposals/odo-debug.md +++ b/docs/proposals/odo-debug.md @@ -32,7 +32,7 @@ Odo sets up port-forwarding to allow a debugger to connect to the running proces ```yaml kind: LocalConfig -apiversion: odo.openshift.io/v1alpha1 +apiversion: odo.dev ComponentSettings: Type: nodejs SourceLocation: ./ @@ -72,7 +72,7 @@ Optional flag, that controls the number of the local port. The value is not stor ##### The `DebugPort` is set in the LocalConfig: ```yaml kind: LocalConfig -apiversion: odo.openshift.io/v1alpha1 +apiversion: odo.dev ComponentSettings: Type: nodejs SourceLocation: ./ @@ -89,7 +89,7 @@ ComponentSettings: ##### If the `DebugPort` is NOT set in the LocalConfig: ```yaml kind: LocalConfig -apiversion: odo.openshift.io/v1alpha1 +apiversion: odo.dev ComponentSettings: Type: nodejs SourceLocation: ./ diff --git a/docs/public/deploying-a-devfile-using-odo.adoc b/docs/public/deploying-a-devfile-using-odo.adoc index cb2e0db9dfe..a7bb91469f1 100644 --- a/docs/public/deploying-a-devfile-using-odo.adoc +++ b/docs/public/deploying-a-devfile-using-odo.adoc @@ -8,21 +8,21 @@ With a devfile you can describe: * The source code being used * Development components such as IDE tools (VSCode) and application runtimes (Yarn / NPM) -* A list of pre-defined commands that can be ran +* A list of pre-defined commands that can be run * Projects to initially clone Odo takes this devfile and transforms it into a workspace of multiple containers running on OpenShift, Kubernetes or Docker. -Devfile's are YAML files with a defined structure, take a look at the general https://github.com/redhat-developer/devfile/blob/master/docs/devfile.md[schema] of devfile. +Devfiles are YAML files with a defined https://github.com/redhat-developer/devfile/blob/master/docs/devfile.md[schema]. == Odo and devfile -When deploying a devfile using odo, odo will automatically look at the default https://github.com/elsony/devfile-registry[devfile] https://github.com/eclipse/che-devfile-registry/[registries]. Interacting with the devfile registries allows a user to pull a standard `devfile.yaml` and begin development immediately. +Odo can now create components from devfiles as recorded in registries. Odo automatically consults the default https://github.com/elsony/devfile-registry[devfile] https://github.com/eclipse/che-devfile-registry/[registries] but users can also add their own registries. Devfiles contribute new component types that users can pull to begin development immediately. An example deployment scenario: -. `odo create` will look at devfile registry and pull down the `devfile.yaml` file -. odo push parses and then deploys the component in the following order: +. `odo create` will consult the recorded devfile registries to offer the user a selection of available component types and pull down the associated `devfile.yaml` file +. `odo push` parses and then deploys the component in the following order: .. Parses and validates the YAML file .. Deploys the development environment to your OpenShift cluster .. Synchronizes your source code to the containers @@ -209,7 +209,7 @@ In this example we will be deploying an https://github.com/odo-devfiles/nodejs-e $ cd ---- -. List the contents of the directory to see that the front end is a Node.js application: +. List the contents of the directory to confirm that the application is indeed a Node.js® application: + [source,sh] ---- @@ -217,7 +217,7 @@ In this example we will be deploying an https://github.com/odo-devfiles/nodejs-e app LICENSE package.json package-lock.json README.md ---- -. Create a component configuration of Node.js component-type named mynodejs: +. Create a component configuration using the `nodejs` component-type named `mynodejs`: + [source,sh] ---- @@ -304,7 +304,7 @@ In this example, we will be deploying the same Java Spring Boot® component we d *Prerequisites:* Docker `17.05` or higher installed -. Enabling the separate pushtarget preference: +. Enabling a separate push target, using the `pushtarget` preference: + [source,sh] ---- @@ -312,9 +312,8 @@ In this example, we will be deploying the same Java Spring Boot® component we d Global preference was successfully updated ---- + -You can configure a separate push target by making use of the `pushtarget` preference. -. Create a component configuration of Spring Boot component-type named mydockerspringboot and download its sample project: +. Create a component configuration using the `java-spring-boot` component-type named `mydockerspringboot` and download its sample project: + [source,sh] ---- @@ -341,7 +340,7 @@ You can configure a separate push target by making use of the `pushtarget` prefe + In order to access the docker application, exposed ports are required and automatically generated by odo. -. Deploy the Spring Boot devfile component to Docker: +. Deploy the Spring Boot® devfile component to Docker: + [source,sh] ---- diff --git a/docs/public/operator-hub.adoc b/docs/public/operator-hub.adoc new file mode 100644 index 00000000000..b8fd03428bf --- /dev/null +++ b/docs/public/operator-hub.adoc @@ -0,0 +1,271 @@ +:source-highlighter: pygments + +# odo integration with Operator Hub +--- + +When working in experimental mode, odo provides the ability to work with +link:https://www.openshift.com/learn/topics/operators[Operators] installed on +the cluster. It allows listing of Operators, creation of services from CRD +(Custom Resource Definitions) provided by the Operators, printing the YAML +definition and providing custom YAML definition to start the service from a +CRD. + +[NOTE] +==== +Installation of Operators is not a part of odo workflow. It is something that +your OpenShift/Kubernetes cluster administrator should be able to do for you. +==== + +=== Prerequisites + +- `odo` is installed. +- Required Operators are installed in the project/namespace by cluster + administrator. +- Experimental mode is enabled. For every command (other than those for + <>) mentioned in this document to work, we + first need to enable the experimental mode. This can be done in + different ways. Make sure that you have installed latest version of odo and + perform any one of the following actions. + +* Enable experimental mode in odo preferences: ++ +[source,shell] +---- +$ odo prefrence set Experimental true +---- + + +* Export the environment variable `ODO_EXPERIMENTAL`: ++ +[source,shell] +---- +$ export ODO_EXPERIMENTAL=true +---- + +* Prefix every command mentioned below with `ODO_EXPERIMENTAL=true`. For + example, to list the Operators installed in current project: ++ +[source,shell] +---- +$ ODO_EXPERIMENTAL=true odo catalog list services +---- + +=== [[create-project]]Creating a project +Create a project to keep your source code, tests, and libraries organized in a +separate single unit. + +1. Log in to the Kubernetes/OpenShift cluster: ++ +[source,shell] +---- +$ odo login -u developer -p developer +---- + +2. Create a project: ++ +[source,shell] +---- +$ odo project create myproject + ✓ Project 'myproject' is ready for use + ✓ New project created and now using project : myproject +---- + +=== [[list-operators]]Listing the Operators + +To list the Operators installed in current project, execute below command: + +[source,shell] +---- +$ odo catalog list services +---- + +It will list the +link:https://docs.openshift.com/container-platform/4.3/operators/olm-what-operators-are.html[Operators] +and the services, or +link:https://docs.openshift.com/container-platform/4.3/operators/crds/crd-extending-api-with-crds.html#crd-custom-resource-definitions_crd-extending-api-with-crds[CRD +(Custom Resource Definitions)], provided by these Operators. For example, we +have installed etcd and MongoDB Operators and the output we get is like below: + +[source,shell] +---- +$ odo catalog list services +Operators available in the cluster +NAME CRDs +etcdoperator.v0.9.4 EtcdCluster, EtcdBackup, EtcdRestore +mongodb-enterprise.v1.4.5 MongoDB, MongoDBUser, MongoDBOpsManager +---- + +In above output, `etcdoperator.v0.9.4` is the Operator while `EtcdCluster`, +`EtcdBackup` and `EtcdRestore` are the CRDs provided by this Operator. + +To start a service from an Operator, we need the Operator name and name of the +service (CRD) to start. Note that these name values are case-sensitive. + +=== [[dry-run]]Print the YAML used to start a service + +odo provides the feature to print the YAML definition of the service (Custom +Resource or CR) provided by the Operator before starting a service off it. This +can be done by: + +[source,shell] +---- +$ odo service create --crd --dry-run +---- + +For example, to print YAML definition of `EtcdCluster` provided by +`etcdoperator.v0.9.4` Operator, you would do: + +[source,shell] +---- +$ odo service create etcdoperator.v0.9.4 --crd EtcdCluster --dry-run +---- + +You can also redirect the output generated above and modify it before starting +a service. We will see this in <>. + +=== [[create-service]]Create a service from an Operator + +[NOTE] +==== +For the commands mentioned in this section to work properly, you need to make +sure that the Operator has a valid definition in its `metadata` to start the +requested service. The commands mentioned here refer the +`metadata.annotations.alm-examples` of an Operator and use it as-is to start +the service. If this YAML has placeholder values or sample values that are not +meant to aid in starting a real service, you will not be able to see a service +start from it. +==== + +To start an `EtcdCluster` service from `etcdoperator.v0.9.4` Operator, you need +to execute: + +[source,shell] +---- +$ odo service create etcdoperator.v0.9.4 --crd EtcdCluster +---- + +This is exactly same command as that shown in <> section above but without the `--dry-run` flag. + +At the moment, `odo` is unable to list services started from an Operator. To +check if the above command succeeded in starting a service, use `kubectl` or +`oc`: + +[source,shell] +---- +$ kubectl get EtcdCluster +---- + +At the time of writing this document, above command worked out of the box +because, as mentioned in the note, `etcdoperator.v0.9.4` 's definition has a +valid example for `EtcdCluster` embedded into it. This can be checked by doing: + + +[source,shell] +---- +$ kubectl get csv/etcdoperator.v0.9.4 -o yaml +---- + +and referring to the `alm-examples` section under `annotations` in the +`metadata` of the `etcdoperator.v0.9.4` Operator. + +If you're using OpenShift, you can replace `kubectl` with `oc` in above +command. + +If there is placeholder/invalid data or no data in the aforementioned section +of the Operator's definition, `odo` won't be able to start the service. As an +example, refer to the YAML definition of `EtcdBackup` in the +`etcdoperator.v0.9.4` 's `metadata`: + +[source,yaml] +---- +apiVersion: etcd.database.coreos.com/v1beta2 +kind: EtcdBackup +metadata: + name: example-etcd-cluster-backup +spec: + etcdEndpoints: + - + s3: + awsSecret: + path: + storageType: S3 +---- + +Here we can see some placeholder data in the form of `` +, `` and `` that the user is expected to set to +appropriate value for the service to start. + +On the other hand, `EtcdCluster` 's definition looks like below: + +[source,yaml] +---- +apiVersion: etcd.database.coreos.com/v1beta2 +kind: EtcdCluster +metadata: + name: example +spec: + size: 3 + version: 3.2.13 +---- + +There's no placeholder data here and it can thus be used to spin a working +service from the Operator. + +=== [[create-service-from-yaml]]Create service from a YAML file + +[NOTE] +==== +This feature is provided on temporary basis while we work on adding support for +link:https://github.com/openshift/odo/issues/2785[passing parameters on the +command line] and link:https://github.com/openshift/odo/issues/2799[using +interactive mode] to create Operator backed services. +==== + +If the YAML definition of the service (or Custom Resource) that you want to +start has placeholder data in its Operator's `metadata`, you can use +<> explained above to get the YAML definition, +replace the placeholder values with correct values and start the service +using the corrected YAML definition. + +For example, if you would like start an `EtcdCluster` service but of a smaller +size than what's configured by default, you could first fetch the YAML +definition of the service: + +[source,shell] +---- +$ odo service create etcdoperator.v0.9.4 --crd EtcdCluster --dry-run +---- + +and then modify the YAML to below: + +[source,yaml] +.etcd.yaml +---- +apiVersion: etcd.database.coreos.com/v1beta2 +kind: EtcdCluster +metadata: + name: my-etcd-cluster // <1> +spec: + size: 1 // <2> + version: 3.2.13 +---- +<1> We changed the name from `example` to `my-etcd-cluster` +<2> We reduced the size from `3` to `1` + +Now we can use the `etcd.yaml` file above to create a service: + +[source,shell] +---- +$ odo service create --from-file etcd.yaml +---- + +This will result in a `EtcdCluster` service with only one pod instead of the +three pods that it's originally configured to create. This can be checked by +doing: + +[source,shell] +---- +$ kubectl get pods | grep my-etcd-cluster +---- diff --git a/pkg/application/application.go b/pkg/application/application.go index ab206ee662d..a67e7740ef1 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -14,7 +14,7 @@ import ( ) const ( - appAPIVersion = "odo.openshift.io/v1alpha1" + appAPIVersion = "odo.dev/v1alpha1" appKind = "Application" appList = "List" ) diff --git a/pkg/catalog/catalog.go b/pkg/catalog/catalog.go index 8a4dc6c8ab0..9c605b70cf8 100644 --- a/pkg/catalog/catalog.go +++ b/pkg/catalog/catalog.go @@ -7,7 +7,9 @@ import ( "strings" imagev1 "github.com/openshift/api/image/v1" + "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" + "github.com/openshift/odo/pkg/preference" "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" "gopkg.in/yaml.v2" @@ -15,12 +17,44 @@ import ( "k8s.io/klog" ) +const ( + apiVersion = "odo.dev/v1alpha1" +) + // DevfileRegistries contains the links of all devfile registries var DevfileRegistries = []string{ "https://raw.githubusercontent.com/elsony/devfile-registry/master", "https://che-devfile-registry.openshift.io/", } +// GetDevfileRegistries gets devfile registries from preference file, +// if registry name is specified return the specific registry, otherwise return all registries +func GetDevfileRegistries(registryName string) (map[string]string, error) { + devfileRegistries := make(map[string]string) + + cfg, err := preference.New() + if err != nil { + return nil, err + } + + if cfg.OdoSettings.RegistryList != nil { + for _, registry := range *cfg.OdoSettings.RegistryList { + if len(registryName) != 0 { + if registryName == registry.Name { + devfileRegistries[registry.Name] = registry.URL + return devfileRegistries, nil + } + } else { + devfileRegistries[registry.Name] = registry.URL + } + } + } else { + return nil, nil + } + + return devfileRegistries, nil +} + // GetDevfileIndex loads the devfile registry index.json func GetDevfileIndex(devfileIndexLink string) ([]DevfileIndexEntry, error) { var devfileIndex []DevfileIndexEntry @@ -103,16 +137,26 @@ func IsDevfileComponentSupported(devfile Devfile) bool { } // ListDevfileComponents lists all the available devfile components -func ListDevfileComponents() (DevfileComponentTypeList, error) { +func ListDevfileComponents(registryName string) (DevfileComponentTypeList, error) { var catalogDevfileList DevfileComponentTypeList - catalogDevfileList.DevfileRegistries = DevfileRegistries + var err error + + // Get devfile registries + catalogDevfileList.DevfileRegistries, err = GetDevfileRegistries(registryName) + if err != nil { + return catalogDevfileList, err + } + if catalogDevfileList.DevfileRegistries == nil { + return catalogDevfileList, nil + } - for _, devfileRegistry := range DevfileRegistries { + for registryName, registryURL := range catalogDevfileList.DevfileRegistries { // Load the devfile registry index.json - devfileIndexLink := devfileRegistry + "/devfiles/index.json" + devfileIndexLink := registryURL + "/devfiles/index.json" devfileIndex, err := GetDevfileIndex(devfileIndexLink) if err != nil { - return DevfileComponentTypeList{}, err + log.Warningf("Registry %s is not set up properly with error: %v", registryName, err) + break } // 1. Load devfiles that indexed in devfile registry index.json @@ -122,14 +166,15 @@ func ListDevfileComponents() (DevfileComponentTypeList, error) { devfileIndexEntryLink := devfileIndexEntry.Links.Link // Load the devfile - devfileLink := devfileRegistry + devfileIndexEntryLink - // TODO: We send http get resquest in this function mutiple times + devfileLink := registryURL + devfileIndexEntryLink + // TODO: We send http get resquest in this function multiple times // since devfile registry uses different links to host different devfiles, // this can reduce the performance especially when we load devfiles from // big registry. We may need to rethink and optimize this in the future devfile, err := GetDevfile(devfileLink) if err != nil { - return DevfileComponentTypeList{}, err + log.Warningf("Registry %s is not set up properly with error: %v", registryName, err) + break } // Populate devfile component with devfile data and form devfile component list @@ -139,7 +184,10 @@ func ListDevfileComponents() (DevfileComponentTypeList, error) { Description: devfileIndexEntry.Description, Link: devfileIndexEntryLink, Support: IsDevfileComponentSupported(devfile), - Registry: devfileRegistry, + Registry: Registry{ + Name: registryName, + URL: registryURL, + }, } catalogDevfileList.Items = append(catalogDevfileList.Items, catalogDevfile) @@ -164,7 +212,7 @@ func ListComponents(client *occlient.Client) (ComponentTypeList, error) { return ComponentTypeList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, Items: catalogList, }, nil @@ -219,7 +267,7 @@ func ListServices(client *occlient.Client) (ServiceTypeList, error) { return ServiceTypeList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, Items: clusterServiceClasses, }, nil @@ -243,7 +291,7 @@ func SearchService(client *occlient.Client, name string) (ServiceTypeList, error return ServiceTypeList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, Items: result, }, nil @@ -274,7 +322,7 @@ func getClusterCatalogServices(client *occlient.Client) ([]ServiceType, error) { classNames = append(classNames, ServiceType{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceType", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: class.Spec.ExternalName, @@ -499,7 +547,7 @@ func getBuildersFromImageStreams(imageStreams []imagev1.ImageStream, imageStream catalogImage := ComponentType{ TypeMeta: metav1.TypeMeta{ Kind: "ComponentType", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: imageStream.Name, diff --git a/pkg/catalog/catalog_test.go b/pkg/catalog/catalog_test.go index ad0e3d44588..e201f2685ef 100644 --- a/pkg/catalog/catalog_test.go +++ b/pkg/catalog/catalog_test.go @@ -1,13 +1,16 @@ package catalog import ( + "io/ioutil" "net/http" "net/http/httptest" + "os" "reflect" "testing" imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/odo/pkg/occlient" + "github.com/openshift/odo/pkg/preference" "github.com/openshift/odo/pkg/testingutil" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -168,6 +171,67 @@ func TestSliceSupportedTags(t *testing.T) { } } +func TestGetDevfileRegistries(t *testing.T) { + tempConfigFile, err := ioutil.TempFile("", "odoconfig") + if err != nil { + t.Fatal("Fail to create temporary config file") + } + defer os.Remove(tempConfigFile.Name()) + defer tempConfigFile.Close() + _, err = tempConfigFile.Write([]byte( + `kind: Preference +apiversion: odo.openshift.io/v1alpha1 +OdoSettings: + Experimental: true + RegistryList: + - Name: CheDevfileRegistry + URL: https://che-devfile-registry.openshift.io/ + - Name: DefaultDevfileRegistry + URL: https://raw.githubusercontent.com/elsony/devfile-registry/master`, + )) + if err != nil { + t.Error(err) + } + + os.Setenv(preference.GlobalConfigEnvName, tempConfigFile.Name()) + defer os.Unsetenv(preference.GlobalConfigEnvName) + + tests := []struct { + name string + registryName string + want map[string]string + }{ + { + name: "Case 1: Test get all devfile registries", + registryName: "", + want: map[string]string{ + "CheDevfileRegistry": "https://che-devfile-registry.openshift.io/", + "DefaultDevfileRegistry": "https://raw.githubusercontent.com/elsony/devfile-registry/master", + }, + }, + { + name: "Case 2: Test get specific devfile registry", + registryName: "CheDevfileRegistry", + want: map[string]string{ + "CheDevfileRegistry": "https://che-devfile-registry.openshift.io/", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetDevfileRegistries(tt.registryName) + if err != nil { + t.Errorf("Error message is %v", err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Got: %v, want: %v", got, tt.want) + } + }) + } +} + func TestGetDevfileIndex(t *testing.T) { // Start a local HTTP server server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/catalog/types.go b/pkg/catalog/types.go index a3266f0d8f6..fd7eef68916 100644 --- a/pkg/catalog/types.go +++ b/pkg/catalog/types.go @@ -12,6 +12,12 @@ type ComponentType struct { Spec ComponentSpec `json:"spec,omitempty"` } +// Registry is the main struct of devfile registry +type Registry struct { + Name string + URL string +} + // DevfileComponentType is the main struct for devfile catalog components type DevfileComponentType struct { Name string @@ -19,7 +25,7 @@ type DevfileComponentType struct { Description string Link string Support bool - Registry string + Registry Registry } // DevfileIndexEntry is the main struct of index.json from devfile registry @@ -66,7 +72,7 @@ type ComponentTypeList struct { // DevfileComponentTypeList lists all the DevfileComponentType's type DevfileComponentTypeList struct { - DevfileRegistries []string + DevfileRegistries map[string]string Items []DevfileComponentType } diff --git a/pkg/component/component.go b/pkg/component/component.go index 6d4e39342fb..ba0bc9f5405 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -45,6 +45,8 @@ const componentRandomNamePartsMaxLen = 12 const componentNameMaxRetries = 3 const componentNameMaxLen = -1 +const apiVersion = "odo.dev/v1alpha1" + // GetComponentDir returns source repo name // Parameters: // path: git url or source path or binary path @@ -1426,7 +1428,7 @@ func getMachineReadableFormat(componentName, componentType string) Component { return Component{ TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: componentName, @@ -1447,7 +1449,7 @@ func GetMachineReadableFormatForList(components []Component) ComponentList { return ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ListMeta: metav1.ListMeta{}, Items: components, diff --git a/pkg/component/component_full_description.go b/pkg/component/component_full_description.go index 5ef8c88880d..8b92dfd725a 100644 --- a/pkg/component/component_full_description.go +++ b/pkg/component/component_full_description.go @@ -95,7 +95,7 @@ func (cfd *ComponentFullDescription) fillEmptyFields(componentDesc Component, co } if len(cfd.APIVersion) <= 0 { - cfd.APIVersion = "odo.openshift.io/v1alpha1" + cfd.APIVersion = apiVersion } if len(cfd.Spec.App) <= 0 { diff --git a/pkg/component/component_test.go b/pkg/component/component_test.go index 3af0861206f..7ef6883f4b9 100644 --- a/pkg/component/component_test.go +++ b/pkg/component/component_test.go @@ -284,7 +284,7 @@ func TestList(t *testing.T) { output: ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: []Component{ @@ -314,7 +314,7 @@ func TestList(t *testing.T) { output: ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: []Component{ @@ -545,7 +545,7 @@ func Test_getMachineReadableFormat(t *testing.T) { want: Component{ TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "frontend", @@ -583,7 +583,7 @@ func Test_getMachineReadableFormatForList(t *testing.T) { { TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "frontend", @@ -596,7 +596,7 @@ func Test_getMachineReadableFormatForList(t *testing.T) { { TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "backend", @@ -611,14 +611,14 @@ func Test_getMachineReadableFormatForList(t *testing.T) { want: ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: []Component{ { TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "frontend", @@ -631,7 +631,7 @@ func Test_getMachineReadableFormatForList(t *testing.T) { { TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "backend", @@ -798,7 +798,7 @@ func TestUnlinkComponents(t *testing.T) { componentList := ComponentList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: tt.childComponents, @@ -866,7 +866,7 @@ func getFakeComponent(compName, namespace, appName, compType string, state State return Component{ TypeMeta: metav1.TypeMeta{ Kind: "Component", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: compName, diff --git a/pkg/config/config.go b/pkg/config/config.go index 8c4381a32c2..9f8bbb149a4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,7 +23,7 @@ const ( localConfigEnvName = "LOCALODOCONFIG" configFileName = "config.yaml" localConfigKind = "LocalConfig" - localConfigAPIVersion = "odo.openshift.io/v1alpha1" + localConfigAPIVersion = "odo.dev/v1alpha1" // DefaultDebugPort is the default port used for debugging on remote pod DefaultDebugPort = 5858 ) diff --git a/pkg/devfile/adapters/common/types.go b/pkg/devfile/adapters/common/types.go index 8b95ee53dea..3a4cb3882fb 100644 --- a/pkg/devfile/adapters/common/types.go +++ b/pkg/devfile/adapters/common/types.go @@ -8,6 +8,7 @@ import ( // AdapterContext is a construct that is common to all adapters type AdapterContext struct { ComponentName string // ComponentName is the odo component name, it is NOT related to any devfile components + Context string // Context is the given directory containing the source code and configs Devfile devfileParser.DevfileObj // Devfile is the object returned by the Devfile parser } diff --git a/pkg/devfile/adapters/common/utils.go b/pkg/devfile/adapters/common/utils.go index e395bc39d6c..7a754804aa4 100644 --- a/pkg/devfile/adapters/common/utils.go +++ b/pkg/devfile/adapters/common/utils.go @@ -29,7 +29,7 @@ const ( // Default Image that will be used containing the supervisord binary and assembly scripts // use GetBootstrapperImage() function instead of this variable - defaultBootstrapperImage = "registry.access.redhat.com/openshiftdo/odo-init-image-rhel7:1.1.2" + defaultBootstrapperImage = "registry.access.redhat.com/openshiftdo/odo-init-image-rhel7:1.1.3" // SupervisordControlCommand sub command which stands for control SupervisordControlCommand = "ctl" diff --git a/pkg/devfile/adapters/docker/component/adapter.go b/pkg/devfile/adapters/docker/component/adapter.go index 9237f120b98..5acf6c468a6 100644 --- a/pkg/devfile/adapters/docker/component/adapter.go +++ b/pkg/devfile/adapters/docker/component/adapter.go @@ -2,6 +2,7 @@ package component import ( "fmt" + "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/mount" @@ -155,25 +156,53 @@ func (a Adapter) Delete(labels map[string]string) error { return errors.New("unable to delete component without a component label") } - list, err := a.Client.GetContainerList() + containers, err := a.Client.GetContainerList() if err != nil { return errors.Wrap(err, "unable to retrieve container list for delete operation") } - componentContainer := a.Client.GetContainersByComponent(componentName, list) + // A unique list of volumes NOT to delete, because they are still mapped into other containers. + // map key is volume name. + volumesNotToDelete := map[string]string{} + + // Go through the containers which are NOT part of this component, and make a list of all + // their volumes so we don't delete them. + for _, container := range containers { + + if container.Labels["component"] == componentName { + continue + } + + for _, mount := range container.Mounts { + volumesNotToDelete[mount.Name] = mount.Name + } + } + + componentContainer := a.Client.GetContainersByComponent(componentName, containers) if len(componentContainer) == 0 { return errors.Errorf("the component %s doesn't exist", a.ComponentName) } - // Get all volumes that match our component label - volumeLabels := utils.GetProjectVolumeLabels(componentName) - vols, err := a.Client.GetVolumesByLabel(volumeLabels) + allVolumes, err := a.Client.GetVolumes() if err != nil { - return errors.Wrapf(err, "unable to retrieve source volume for component "+componentName) + return errors.Wrapf(err, "unable to retrieve list of all Docker volumes") } - if len(vols) == 0 { - return fmt.Errorf("unable to find source volume for component %s", componentName) + + // Look for this component's volumes that contain either a storage-name label or a type label + var vols []types.Volume + for _, vol := range allVolumes { + + if vol.Labels["component"] == componentName { + + if snVal := vol.Labels["storage-name"]; len(strings.TrimSpace(snVal)) > 0 { + vols = append(vols, vol) + } else { + if typeVal := vol.Labels["type"]; typeVal == "projects" { + vols = append(vols, vol) + } + } + } } // A unique list of volumes to delete; map key is volume name. @@ -198,10 +227,20 @@ func (a Adapter) Delete(labels map[string]string) error { } for _, vol := range vols { + + // Don't delete any volumes which are mapped into other containers + if _, exists := volumesNotToDelete[vol.Name]; exists { + klog.V(4).Infof("Skipping volume %s as it is mapped into a non-odo managed container", vol.Name) + continue + } + // If the volume was found to be attached to the component's container, then add the volume // to the deletion list. if _, ok := volumeNames[vol.Name]; ok { + klog.V(4).Infof("Adding volume %s to deletion list", vol.Name) volumesToDelete[vol.Name] = vol.Name + } else { + klog.V(4).Infof("Skipping volume %s as it was not attached to the component's container", vol.Name) } } } diff --git a/pkg/devfile/adapters/docker/component/adapter_test.go b/pkg/devfile/adapters/docker/component/adapter_test.go index c8f4864c0f5..30a502e1ff5 100644 --- a/pkg/devfile/adapters/docker/component/adapter_test.go +++ b/pkg/devfile/adapters/docker/component/adapter_test.go @@ -291,3 +291,315 @@ func TestAdapterDelete(t *testing.T) { }) } } + +func TestAdapterDeleteVolumes(t *testing.T) { + + // Convenience func to create a mock ODO-style container with the given volume mounts + containerWithMount := func(componentName string, mountPoints []types.MountPoint) types.Container { + + return types.Container{ + ID: componentName, + Labels: map[string]string{ + "component": componentName, + }, + Mounts: mountPoints, + } + } + + componentName := "my-component" + anotherComponentName := "other-component" + + // The purpose of these tests is to verify the correctness of container deletion, such as: + // - Only volumes that match the format of an ODO-managed volume (storage or source) are deleted + // - Ensure that bind mounts are never deleted + // - Ensure that other component's volumes are never deleted + // - Ensure that volumes that have only the exact source/storage labels format are deleted + + tests := []struct { + name string + containers []types.Container + volumes []*types.Volume + expectToDelete []string + }{ + { + name: "Case 1: Should delete both storage and source mount", + containers: []types.Container{ + containerWithMount(componentName, + []types.MountPoint{ + { + Name: "my-src-mount", + Type: mount.TypeVolume, + }, + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + }), + }, + volumes: []*types.Volume{ + { + Name: "my-src-mount", + Labels: map[string]string{ + "component": componentName, + "type": "projects", + }, + }, + { + Name: "my-storage-mount", + Labels: map[string]string{ + "component": componentName, + "storage-name": "anyval", + }, + }, + }, + expectToDelete: []string{ + "my-src-mount", + "my-storage-mount", + }, + }, + { + name: "Case 2: Should delete storage mount alone", + containers: []types.Container{ + containerWithMount(componentName, + []types.MountPoint{ + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + }), + }, + volumes: []*types.Volume{ + { + Name: "my-storage-mount", + Labels: map[string]string{ + "component": componentName, + "storage-name": "anyval", + }, + }, + }, + expectToDelete: []string{ + "my-storage-mount", + }, + }, + { + name: "Case 3: Should not delete a bind mount even if it matches src volume labels", + containers: []types.Container{ + containerWithMount(componentName, + []types.MountPoint{ + { + Name: "my-src-mount", + Type: mount.TypeBind, + }, + }), + }, + + volumes: []*types.Volume{ + { + Name: "my-src-mount", + Labels: map[string]string{ + "component": componentName, + "type": "projects", + }, + }, + }, + expectToDelete: []string{}, + }, + { + name: "Case 4: Should not try to delete other component's volumes", + containers: []types.Container{ + containerWithMount(componentName, + []types.MountPoint{ + { + Name: "my-src-mount", + Type: mount.TypeVolume, + }, + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + }), + containerWithMount(anotherComponentName, + []types.MountPoint{ + { + Name: "my-src-mount-other-component", + Type: mount.TypeVolume, + }, + { + Name: "my-storage-mount-other-component", + Type: mount.TypeVolume, + }, + }), + }, + volumes: []*types.Volume{ + { + Name: "my-src-mount", + Labels: map[string]string{ + "component": componentName, + "type": "projects", + }, + }, + { + Name: "my-storage-mount", + Labels: map[string]string{ + "component": componentName, + "storage-name": "anyval", + }, + }, + { + Name: "my-src-mount-other-component", + Labels: map[string]string{ + "component": anotherComponentName, + "type": "projects", + }, + }, + { + Name: "my-storage-mount-other-component", + Labels: map[string]string{ + "component": anotherComponentName, + "storage-name": "anyval", + }, + }, + }, + expectToDelete: []string{ + "my-src-mount", + "my-storage-mount", + }, + }, + { + name: "Case 5: Should not try to delete a component's non-ODO volumes, even if the format is very close to ODO", + containers: []types.Container{containerWithMount("my-component", + []types.MountPoint{ + { + Name: "my-src-mount", + Type: mount.TypeVolume, + }, + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + { + Name: "another-volume-1", + Type: mount.TypeVolume, + }, + { + Name: "another-volume-2", + Type: mount.TypeVolume, + }, + })}, + volumes: []*types.Volume{ + { + Name: "my-src-mount", + Labels: map[string]string{ + "component": componentName, + "type": "projects", + }, + }, + { + Name: "my-storage-mount", + Labels: map[string]string{ + "component": componentName, + "storage-name": "anyval", + }, + }, + { + Name: "another-volume-1", + Labels: map[string]string{ + "component": componentName, + "type": "projects-but-not-really", + }, + }, + { + Name: "another-volume-2", + Labels: map[string]string{ + "component": componentName, + "storage-name-but-not-really": "anyval", + }, + }, + }, + expectToDelete: []string{ + "my-src-mount", + "my-storage-mount", + }, + }, + { + name: "Case 6: Should not delete a volume that is mounted into another container", + containers: []types.Container{ + + containerWithMount("my-component", + []types.MountPoint{ + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + }), + + containerWithMount("a-non-odo-container-for-example", + []types.MountPoint{ + { + Name: "my-storage-mount", + Type: mount.TypeVolume, + }, + }), + }, + volumes: []*types.Volume{ + { + Name: "my-storage-mount", + Labels: map[string]string{ + "component": componentName, + "storage-name": "anyval", + }, + }, + }, + expectToDelete: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + devObj := devfileParser.DevfileObj{ + Data: testingutil.TestDevfileData{ + ComponentType: "nodejs", + }, + } + + adapterCtx := adaptersCommon.AdapterContext{ + ComponentName: componentName, + Devfile: devObj, + } + + fkclient, mockDockerClient := lclient.FakeNewMockClient(ctrl) + + a := Adapter{ + Client: *fkclient, + AdapterContext: adapterCtx, + } + + arg := map[string]string{ + "component": componentName, + } + + mockDockerClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return(tt.containers, nil) + + mockDockerClient.EXPECT().ContainerRemove(gomock.Any(), componentName, gomock.Any()).Return(nil) + + mockDockerClient.EXPECT().VolumeList(gomock.Any(), gomock.Any()).Return(volumeTypes.VolumeListOKBody{ + Volumes: tt.volumes, + }, nil) + + for _, deleteExpected := range tt.expectToDelete { + mockDockerClient.EXPECT().VolumeRemove(gomock.Any(), deleteExpected, gomock.Any()).Return(nil) + } + + err := a.Delete(arg) + if err != nil { + t.Errorf("Delete() unexpected error = %v", err) + } + + }) + + } + +} diff --git a/pkg/devfile/adapters/docker/component/utils.go b/pkg/devfile/adapters/docker/component/utils.go index 29424496d40..9d698278238 100644 --- a/pkg/devfile/adapters/docker/component/utils.go +++ b/pkg/devfile/adapters/docker/component/utils.go @@ -24,8 +24,10 @@ import ( "github.com/openshift/odo/pkg/log" ) -// LocalhostIP is the IP address for localhost -var LocalhostIP = "127.0.0.1" +const ( + localhostIP = "127.0.0.1" + projectSourceVolumeName = "odo-project-source" +) func (a Adapter) createComponent() (err error) { componentName := a.ComponentName @@ -41,7 +43,7 @@ func (a Adapter) createComponent() (err error) { } if len(projectVols) == 0 { // A source volume needs to be created - projectVolumeName, err = storage.GenerateVolNameFromDevfileVol("odo-project-source", a.ComponentName) + projectVolumeName, err = storage.GenerateVolName(projectSourceVolumeName, a.ComponentName) if err != nil { return errors.Wrapf(err, "Unable to generate project source volume name for component %s", componentName) } @@ -154,7 +156,7 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { return componentExists, errors.Wrapf(err, "unable to get the container config for component %s", componentName) } - portMap, err := getPortMap(comp.Endpoints, false) + portMap, err := getPortMap(a.Context, comp.Endpoints, false) if err != nil { return componentExists, errors.Wrapf(err, "unable to get the port map from env.yaml file for component %s", componentName) } @@ -271,7 +273,7 @@ func (a Adapter) generateAndGetContainerConfig(componentName string, comp versio func (a Adapter) generateAndGetHostConfig(endpoints []versionsCommon.DockerimageEndpoint) (container.HostConfig, error) { // Convert the port bindings from env.yaml and generate docker host config - portMap, err := getPortMap(endpoints, true) + portMap, err := getPortMap(a.Context, endpoints, true) if err != nil { return container.HostConfig{}, err } @@ -284,12 +286,21 @@ func (a Adapter) generateAndGetHostConfig(endpoints []versionsCommon.Dockerimage return hostConfig, nil } -func getPortMap(endpoints []versionsCommon.DockerimageEndpoint, show bool) (nat.PortMap, error) { +func getPortMap(context string, endpoints []versionsCommon.DockerimageEndpoint, show bool) (nat.PortMap, error) { // Convert the exposed and internal port pairs saved in env.yaml file to PortMap // Todo: Use context to get the approraite envinfo after context is supported in experimental mode portmap := nat.PortMap{} - dir, err := os.Getwd() + var dir string + var err error + if context == "" { + dir, err = os.Getwd() + if err != nil { + return nil, err + } + } else { + dir = context + } if err != nil { return portmap, err } @@ -309,12 +320,12 @@ func getPortMap(endpoints []versionsCommon.DockerimageEndpoint, show bool) (nat. } portmap[port] = []nat.PortBinding{ nat.PortBinding{ - HostIP: LocalhostIP, + HostIP: localhostIP, HostPort: strconv.Itoa(url.ExposedPort), }, } if show { - log.Successf("URL %v:%v created", LocalhostIP, url.ExposedPort) + log.Successf("URL %v:%v created", localhostIP, url.ExposedPort) } } else if url.ExposedPort > 0 && len(endpoints) > 0 && !common.IsPortPresent(endpoints, url.Port) { return portmap, fmt.Errorf("Error creating url: odo url config's port is not present in the devfile. Please re-create odo url with the new devfile port") diff --git a/pkg/devfile/adapters/docker/component/utils_test.go b/pkg/devfile/adapters/docker/component/utils_test.go index ac0305fc96f..a557c551c26 100644 --- a/pkg/devfile/adapters/docker/component/utils_test.go +++ b/pkg/devfile/adapters/docker/component/utils_test.go @@ -333,7 +333,7 @@ func TestGenerateAndGetHostConfig(t *testing.T) { expectResult: nat.PortMap{ "8080/tcp": []nat.PortBinding{ { - HostIP: LocalhostIP, + HostIP: localhostIP, HostPort: "65432", }, }, @@ -356,19 +356,19 @@ func TestGenerateAndGetHostConfig(t *testing.T) { expectResult: nat.PortMap{ "8080/tcp": []nat.PortBinding{ { - HostIP: LocalhostIP, + HostIP: localhostIP, HostPort: "65432", }, }, "9090/tcp": []nat.PortBinding{ { - HostIP: LocalhostIP, + HostIP: localhostIP, HostPort: "54321", }, }, "9080/tcp": []nat.PortBinding{ { - HostIP: LocalhostIP, + HostIP: localhostIP, HostPort: "45678", }, }, diff --git a/pkg/devfile/adapters/docker/storage/utils.go b/pkg/devfile/adapters/docker/storage/utils.go index 7fbe2f21413..60bf74d5c8a 100644 --- a/pkg/devfile/adapters/docker/storage/utils.go +++ b/pkg/devfile/adapters/docker/storage/utils.go @@ -54,15 +54,20 @@ func Create(Client *lclient.Client, name, componentName, dockerVolName string) ( return &vol, nil } -// GenerateVolNameFromDevfileVol generates a Docker volume name from the Devfile volume name and component name -func GenerateVolNameFromDevfileVol(volName, componentName string) (string, error) { +// GenerateVolName generates a Docker volume name from the Devfile volume name and component name +func GenerateVolName(volName, componentName string) (string, error) { + + if volName == "" { + err := errors.New("unable to generate volume name with an empty name") + return "", err + } dockerVolName := fmt.Sprintf("%v-%v", volName, componentName) dockerVolName = util.TruncateString(dockerVolName, volNameMaxLength) randomChars := util.GenerateRandomString(4) dockerVolName, err := util.NamespaceOpenShiftObject(dockerVolName, randomChars) if err != nil { - return "", errors.Wrapf(err, "unable to create namespaced name") + return "", errors.Wrapf(err, "unable to create namespaced name for volume %s", volName) } return dockerVolName, nil @@ -109,7 +114,7 @@ func ProcessVolumes(client *lclient.Client, componentName string, componentAlias // Generate the volume Names klog.V(3).Infof("Generating Docker volumes name for %v", *vol.Name) - generatedDockerVolName, err := GenerateVolNameFromDevfileVol(*vol.Name, componentName) + generatedDockerVolName, err := GenerateVolName(*vol.Name, componentName) if err != nil { return nil, nil, err } diff --git a/pkg/devfile/adapters/docker/storage/utils_test.go b/pkg/devfile/adapters/docker/storage/utils_test.go index 07de602ab99..6030772914d 100644 --- a/pkg/devfile/adapters/docker/storage/utils_test.go +++ b/pkg/devfile/adapters/docker/storage/utils_test.go @@ -1,6 +1,7 @@ package storage import ( + "strings" "testing" "github.com/openshift/odo/pkg/devfile/adapters/common" @@ -331,3 +332,54 @@ func TestProcessVolumes(t *testing.T) { } } + +func TestGenerateVolName(t *testing.T) { + + tests := []struct { + name string + volName string + cmpName string + wantVolName string + wantErr bool + }{ + { + name: "Case 1: Valid volume and component name", + volName: "myVol", + cmpName: "myCmp", + wantVolName: "myVol-myCmp", + wantErr: false, + }, + { + name: "Case 2: Valid volume name, empty component name", + volName: "myVol", + cmpName: "", + wantVolName: "myVol-", + wantErr: false, + }, + { + name: "Case 3: Long Valid volume and component name", + volName: "myVolmyVolmyVolmyVolmyVolmyVolmyVolmyVolmyVol", + cmpName: "myCmpmyCmpmyCmpmyCmpmyCmpmyCmpmyCmpmyCmpmyCmp", + wantVolName: "myVolmyVolmyVolmyVolmyVolmyVolmyVolmyVolmyVol-", + wantErr: false, + }, + { + name: "Case 4: Empty volume name", + volName: "", + cmpName: "myCmp", + wantVolName: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + generatedVolName, err := GenerateVolName(tt.volName, tt.cmpName) + if !tt.wantErr && err != nil { + t.Errorf("TestGenerateVolName error: unexpected error when generating volume name: %v", err) + } else if !tt.wantErr && !strings.Contains(generatedVolName, tt.wantVolName) { + t.Errorf("TestGenerateVolName error: generating volume name does not semi match wanted volume name, wanted: %s got: %s", tt.wantVolName, generatedVolName) + } + }) + } + +} diff --git a/pkg/devfile/adapters/docker/utils/utils.go b/pkg/devfile/adapters/docker/utils/utils.go index 5125fc66e39..aab022897f7 100644 --- a/pkg/devfile/adapters/docker/utils/utils.go +++ b/pkg/devfile/adapters/docker/utils/utils.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types/mount" adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" + "github.com/openshift/odo/pkg/devfile/adapters/docker/storage" "github.com/openshift/odo/pkg/devfile/parser/data/common" "github.com/openshift/odo/pkg/lclient" "github.com/openshift/odo/pkg/log" @@ -23,6 +24,7 @@ import ( const ( supervisordVolume = "supervisord" projectsVolume = "projects" + volume = "vol" ) // ComponentExists checks if Docker containers labeled with the specified component name exists @@ -234,17 +236,26 @@ func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand co // CreateAndInitSupervisordVolume creates the supervisord volume and initializes // it with supervisord bootstrap image - assembly files and supervisord binary func CreateAndInitSupervisordVolume(client lclient.Client) (string, error) { + log.Info("\nInitialization") + s := log.Spinner("Initializing the component") + defer s.End(false) + + supervisordVolumeName, err := storage.GenerateVolName(adaptersCommon.SupervisordVolumeName, volume) + if err != nil { + return "", errors.Wrapf(err, "unable to generate volume name for supervisord") + } + supervisordLabels := GetSupervisordVolumeLabels() - supervisordVolume, err := client.CreateVolume(adaptersCommon.SupervisordVolumeName, supervisordLabels) + _, err = client.CreateVolume(supervisordVolumeName, supervisordLabels) if err != nil { return "", errors.Wrapf(err, "Unable to create supervisord volume for component") } - supervisordVolumeName := supervisordVolume.Name err = StartBootstrapSupervisordInitContainer(client, supervisordVolumeName) if err != nil { return "", errors.Wrapf(err, "Unable to start supervisord container for component") } + s.End(true) return supervisordVolumeName, nil } diff --git a/pkg/devfile/adapters/helper.go b/pkg/devfile/adapters/helper.go index 7832778f0c0..d375edf3957 100644 --- a/pkg/devfile/adapters/helper.go +++ b/pkg/devfile/adapters/helper.go @@ -13,10 +13,11 @@ import ( ) // NewPlatformAdapter returns a Devfile adapter for the targeted platform -func NewPlatformAdapter(componentName string, devObj devfileParser.DevfileObj, platformContext interface{}) (PlatformAdapter, error) { +func NewPlatformAdapter(componentName string, context string, devObj devfileParser.DevfileObj, platformContext interface{}) (PlatformAdapter, error) { adapterContext := common.AdapterContext{ ComponentName: componentName, + Context: context, Devfile: devObj, } diff --git a/pkg/kclient/fake/ingress.go b/pkg/kclient/fake/ingress.go index e525119954c..6de515cb489 100644 --- a/pkg/kclient/fake/ingress.go +++ b/pkg/kclient/fake/ingress.go @@ -38,7 +38,7 @@ func GetIngressListWithMultiple(componentName string) *extensionsv1.IngressList labels.URLLabel: "example-1", }, }, - Spec: *kclient.GenerateIngressSpec(kclient.IngressParameter{ServiceName: "example-1", PortNumber: intstr.FromInt(8080)}), + Spec: *kclient.GenerateIngressSpec(kclient.IngressParameter{ServiceName: "example-1", PortNumber: intstr.FromInt(9090)}), }, }, } diff --git a/pkg/lclient/storage.go b/pkg/lclient/storage.go index 7289ffd78b7..648f6019c9f 100644 --- a/pkg/lclient/storage.go +++ b/pkg/lclient/storage.go @@ -40,3 +40,18 @@ func (dc *Client) GetVolumesByLabel(labels map[string]string) ([]types.Volume, e return volumes, nil } + +// GetVolumes returns the list of all volumes +func (dc *Client) GetVolumes() ([]types.Volume, error) { + var volumes []types.Volume + volumeList, err := dc.Client.VolumeList(dc.Context, filters.Args{}) + if err != nil { + return nil, errors.Wrapf(err, "unable to get list of docker volumes") + } + + for _, vol := range volumeList.Volumes { + volumes = append(volumes, *vol) + } + + return volumes, nil +} diff --git a/pkg/lclient/storage_test.go b/pkg/lclient/storage_test.go index 8e49cf86c1a..a6b159d1919 100644 --- a/pkg/lclient/storage_test.go +++ b/pkg/lclient/storage_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/docker/docker/api/types" + volume "github.com/docker/docker/api/types/volume" + gomock "github.com/golang/mock/gomock" ) func TestCreateVolume(t *testing.T) { @@ -150,3 +152,63 @@ func TestGetVolumesByLabel(t *testing.T) { }) } } + +func TestGetVolumes(t *testing.T) { + + // removePointer is a utility function to convert []*types.Volume to []types.Volume, to allow use of reflect.DeepEqual + removePointer := func(input []*types.Volume) []types.Volume { + var result []types.Volume + + for _, ptr := range input { + if ptr != nil { + result = append(result, *ptr) + } + } + + return result + } + + tests := []struct { + name string + wantVolumes []*types.Volume + }{ + { + name: "GetVolumes should return empty volume list", + wantVolumes: []*types.Volume{}, + }, + { + name: "GetVolume should return a single volume", + wantVolumes: []*types.Volume{{Name: "One"}}, + }, + { + name: "GetVolume should return multiple volumes", + wantVolumes: []*types.Volume{{Name: "Multiple-1"}, {Name: "Multiple-2"}}, + }, + } + + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + client, mockDockerClient := FakeNewMockClient(ctrl) + + mockDockerClient.EXPECT().VolumeList(gomock.Any(), gomock.Any()).Return(volume.VolumeListOKBody{ + Volumes: tt.wantVolumes, + }, nil) + + vols, err := client.GetVolumes() + if err != nil { + t.Errorf("GetVolumes returned an unexpected error %v", err) + } + + wanted := removePointer(tt.wantVolumes) + if !reflect.DeepEqual(vols, wanted) { + t.Errorf("expected %v, wanted %v", vols, wanted) + } + + }) + } + +} diff --git a/pkg/machineoutput/types.go b/pkg/machineoutput/types.go index cb288505c04..d2603d4180f 100644 --- a/pkg/machineoutput/types.go +++ b/pkg/machineoutput/types.go @@ -13,7 +13,7 @@ import ( const Kind = "Error" // APIVersion is the current API version we are using -const APIVersion = "odo.openshift.io/v1alpha1" +const APIVersion = "odo.dev/v1alpha1" // GenericError for machine readable output error messages type GenericError struct { diff --git a/pkg/occlient/occlient.go b/pkg/occlient/occlient.go index ec55576530c..39b2d7efb81 100644 --- a/pkg/occlient/occlient.go +++ b/pkg/occlient/occlient.go @@ -1686,14 +1686,14 @@ func (c *Client) UpdateDCAnnotations(dcName string, annotations map[string]strin func removeTracesOfSupervisordFromDC(dc *appsv1.DeploymentConfig) error { dcName := dc.Name - found := removeVolumeFromDC(getAppRootVolumeName(dcName), dc) - if !found { - return errors.New("unable to find volume in dc with name: " + dcName) + err := removeVolumeFromDC(getAppRootVolumeName(dcName), dc) + if err != nil { + return err } - found = removeVolumeMountsFromDC(getAppRootVolumeName(dcName), dc) - if !found { - return errors.New("unable to find volume mount in dc with name: " + dcName) + err = removeVolumeMountsFromDC(getAppRootVolumeName(dcName), dc) + if err != nil { + return err } // remove the one bootstrapped init container @@ -2721,15 +2721,18 @@ func (c *Client) RemoveVolumeFromDeploymentConfig(pvc string, dcName string) err 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 - if !removeVolumeFromDC(volumeName, dc) { - return fmt.Errorf("could not find volume '%v' in Deployment Config '%v'", volumeName, dc.Name) + err = removeVolumeFromDC(volumeName, dc) + if err != nil { + return err } klog.V(4).Infof("Found volume: %v in Deployment Config: %v", volumeName, dc.Name) // Remove at max 2 volume mounts if volume mounts exists - if !removeVolumeMountsFromDC(volumeName, dc) { - return fmt.Errorf("could not find volumeMount: %v in Deployment Config: %v", volumeName, dc) + err = removeVolumeMountsFromDC(volumeName, dc) + if err != nil { + return err } _, updateErr := c.appsClient.DeploymentConfigs(c.Namespace).Update(dc) diff --git a/pkg/occlient/volumes.go b/pkg/occlient/volumes.go index 47e429bc6de..047a006c442 100644 --- a/pkg/occlient/volumes.go +++ b/pkg/occlient/volumes.go @@ -120,31 +120,86 @@ func (c *Client) getVolumeNamesFromPVC(pvc string, dc *appsv1.DeploymentConfig) // 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) bool { +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 - for i, volume := range dc.Spec.Template.Spec.Volumes { - if volume.Name == vol { - found = true - dc.Spec.Template.Spec.Volumes = append(dc.Spec.Template.Spec.Volumes[:i], dc.Spec.Template.Spec.Volumes[i+1:]...) + + // 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 + } + } } - return found + + 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(vm string, dc *appsv1.DeploymentConfig) bool { +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 { - for j, volumeMount := range container.VolumeMounts { - if volumeMount.Name == vm { - found = true - dc.Spec.Template.Spec.Containers[i].VolumeMounts = append(dc.Spec.Template.Spec.Containers[i].VolumeMounts[:j], dc.Spec.Template.Spec.Containers[i].VolumeMounts[j+1:]...) + + 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 + } } } } - return found + + if found { + return nil + } + + return fmt.Errorf("unable to find volume mount '%s'", volumeMount) } // generateVolumeNameFromPVC generates a random volume name based on the name diff --git a/pkg/occlient/volumes_test.go b/pkg/occlient/volumes_test.go index 16eee986f87..4ba354b393f 100644 --- a/pkg/occlient/volumes_test.go +++ b/pkg/occlient/volumes_test.go @@ -307,3 +307,107 @@ func Test_updateStorageOwnerReference(t *testing.T) { }) } } + +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 TestRemoveVolumeMountsFromDC(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) + } + } + } + + }) + } +} diff --git a/pkg/odo/cli/catalog/list/components.go b/pkg/odo/cli/catalog/list/components.go index db757f52ece..78e10fc8a02 100644 --- a/pkg/odo/cli/catalog/list/components.go +++ b/pkg/odo/cli/catalog/list/components.go @@ -57,10 +57,14 @@ func (o *ListComponentsOptions) Complete(name string, cmd *cobra.Command, args [ } if experimental.IsExperimentalModeEnabled() { - o.catalogDevfileList, err = catalog.ListDevfileComponents() + 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 @@ -130,7 +134,7 @@ func (o *ListComponentsOptions) Run() (err error) { if len(supDevfileCatalogList) != 0 || (o.listAllDevfileComponents && len(unsupDevfileCatalogList) != 0) { fmt.Fprintln(w, "Odo Devfile Components:") - fmt.Fprintln(w, "NAME", "\t", "DESCRIPTION", "\t", "SUPPORTED") + fmt.Fprintln(w, "NAME", "\t", "DESCRIPTION", "\t", "REGISTRY", "\t", "SUPPORTED") if len(supDevfileCatalogList) != 0 { supported = "YES" @@ -192,6 +196,6 @@ func (o *ListComponentsOptions) printCatalogList(w io.Writer, catalogList []cata func (o *ListComponentsOptions) printDevfileCatalogList(w io.Writer, catalogDevfileList []catalog.DevfileComponentType, supported string) { for _, devfileComponent := range catalogDevfileList { - fmt.Fprintln(w, devfileComponent.Name, "\t", devfileComponent.Description, "\t", supported) + fmt.Fprintln(w, devfileComponent.Name, "\t", devfileComponent.Description, "\t", devfileComponent.Registry.Name, "\t", supported) } } diff --git a/pkg/odo/cli/catalog/util/util_test.go b/pkg/odo/cli/catalog/util/util_test.go index bc13ca6c977..fb24cc0ddce 100644 --- a/pkg/odo/cli/catalog/util/util_test.go +++ b/pkg/odo/cli/catalog/util/util_test.go @@ -29,7 +29,7 @@ func TestFilterHiddenServices(t *testing.T) { input: catalog.ServiceTypeList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "foobar", @@ -72,7 +72,7 @@ func TestFilterHiddenServices(t *testing.T) { expected: catalog.ServiceTypeList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "foobar", diff --git a/pkg/odo/cli/cli.go b/pkg/odo/cli/cli.go index 6c0876f113f..a9c898acdff 100644 --- a/pkg/odo/cli/cli.go +++ b/pkg/odo/cli/cli.go @@ -16,6 +16,7 @@ import ( "github.com/openshift/odo/pkg/odo/cli/plugins" "github.com/openshift/odo/pkg/odo/cli/preference" "github.com/openshift/odo/pkg/odo/cli/project" + "github.com/openshift/odo/pkg/odo/cli/registry" "github.com/openshift/odo/pkg/odo/cli/service" "github.com/openshift/odo/pkg/odo/cli/storage" "github.com/openshift/odo/pkg/odo/cli/url" @@ -23,6 +24,7 @@ import ( "github.com/openshift/odo/pkg/odo/cli/version" "github.com/openshift/odo/pkg/odo/util" odoutil "github.com/openshift/odo/pkg/odo/util" + "github.com/openshift/odo/pkg/odo/util/experimental" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -145,7 +147,7 @@ func odoRootCmd(name, fullName string) *cobra.Command { // We use "flag" in order to make this accessible throughtout ALL of odo, rather than the // above traditional "persistentflags" usage that does not make it a pointer within the 'pflag' // package - flag.CommandLine.String("o", "json", "Specify output format, supported format: json") + flag.CommandLine.String("o", "", "Specify output format, supported format: json") // Here we add the necessary "logging" flags.. However, we choose to hide some of these from the user // as they are not necessarily needed and more for advanced debugging @@ -197,6 +199,12 @@ func odoRootCmd(name, fullName string) *cobra.Command { debug.NewCmdDebug(debug.RecommendedCommandName, util.GetFullName(fullName, debug.RecommendedCommandName)), ) + if experimental.IsExperimentalModeEnabled() { + rootCmd.AddCommand( + registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName)), + ) + } + odoutil.VisitCommands(rootCmd, reconfigureCmdWithSubcmd) return rootCmd diff --git a/pkg/odo/cli/component/create.go b/pkg/odo/cli/component/create.go index 2f8672a2583..02c2863041c 100644 --- a/pkg/odo/cli/component/create.go +++ b/pkg/odo/cli/component/create.go @@ -63,7 +63,7 @@ type DevfileMetadata struct { componentNamespace string devfileSupport bool devfileLink string - devfileRegistry string + devfileRegistry catalog.Registry downloadSource bool } @@ -329,10 +329,13 @@ func (co *CreateOptions) Complete(name string, cmd *cobra.Command, args []string co.CommonPushOptions.componentContext = co.componentContext } - catalogDevfileList, err := catalog.ListDevfileComponents() + catalogDevfileList, err := catalog.ListDevfileComponents(co.devfileMetadata.devfileRegistry.Name) if err != nil { return err } + if catalogDevfileList.DevfileRegistries == nil { + log.Warning("Please run `odo registry add ` to add a registry then create a devfile components\n") + } var componentType string var componentName string @@ -437,6 +440,8 @@ func (co *CreateOptions) Complete(name string, cmd *cobra.Command, args []string } } + registrySpinner := log.Spinnerf("Creating a devfile component from registry: %s", co.devfileMetadata.devfileRegistry.Name) + if co.devfileMetadata.devfileSupport { err = co.InitEnvInfoFromContext() if err != nil { @@ -444,11 +449,13 @@ func (co *CreateOptions) Complete(name string, cmd *cobra.Command, args []string } spinner.End(true) + registrySpinner.End(true) return nil } spinner.End(false) - log.Italic("\nPlease run 'odo catalog list components' for a list of supported devfile component types") + registrySpinner.End(false) + log.Italic("\nPlease run `odo catalog list components` for a list of supported devfile component types") } if len(args) == 0 || !cmd.HasFlags() { @@ -773,7 +780,7 @@ func (co *CreateOptions) Run() (err error) { // Download devfile.yaml file and create env.yaml file if co.devfileMetadata.devfileSupport { if !util.CheckPathExists(DevfilePath) { - err := util.DownloadFile(co.devfileMetadata.devfileRegistry+co.devfileMetadata.devfileLink, DevfilePath) + err := util.DownloadFile(co.devfileMetadata.devfileRegistry.URL+co.devfileMetadata.devfileLink, DevfilePath) if err != nil { return errors.Wrap(err, "Faile to download devfile.yaml for devfile component") } @@ -899,6 +906,7 @@ func NewCmdCreate(name, fullName string) *cobra.Command { componentCreateCmd.Flags().StringSliceVar(&co.componentEnvVars, "env", []string{}, "Environmental variables for the component. For example --env VariableName=Value") if experimental.IsExperimentalModeEnabled() { + componentCreateCmd.Flags().StringVar(&co.devfileMetadata.devfileRegistry.Name, "registry", "", "Create devfile component from specific registry") componentCreateCmd.Flags().BoolVar(&co.devfileMetadata.downloadSource, "downloadSource", false, "Download sample project from devfile. (ex. odo component create [component_name] --downloadSource") } diff --git a/pkg/odo/cli/component/devfile.go b/pkg/odo/cli/component/devfile.go index e2242bea7eb..1a7c13004b2 100644 --- a/pkg/odo/cli/component/devfile.go +++ b/pkg/odo/cli/component/devfile.go @@ -3,7 +3,6 @@ package component import ( "fmt" "os" - "path/filepath" "strings" "github.com/openshift/odo/pkg/envinfo" @@ -41,13 +40,13 @@ func (po *PushOptions) DevfilePush() (err error) { return err } - componentName, err := getComponentName() + componentName, err := getComponentName(po.componentContext) if err != nil { return errors.Wrap(err, "unable to get component name") } // Set the source path to either the context or current working directory (if context not set) - po.sourcePath, err = util.GetAbsPath(filepath.Dir(po.componentContext)) + po.sourcePath, err = util.GetAbsPath(po.componentContext) if err != nil { return errors.Wrap(err, "unable to get source path") } @@ -68,7 +67,7 @@ func (po *PushOptions) DevfilePush() (err error) { platformContext = kc } - devfileHandler, err := adapters.NewPlatformAdapter(componentName, devObj, platformContext) + devfileHandler, err := adapters.NewPlatformAdapter(componentName, po.componentContext, devObj, platformContext) if err != nil { return err @@ -103,12 +102,18 @@ func (po *PushOptions) DevfilePush() (err error) { } // Get component name from env.yaml file -func getComponentName() (string, error) { - // Todo: Use context to get the approraite envinfo after context is supported in experimental mode - dir, err := os.Getwd() - if err != nil { - return "", err +func getComponentName(context string) (string, error) { + var dir string + var err error + if context == "" { + dir, err = os.Getwd() + if err != nil { + return "", err + } + } else { + dir = context } + envInfo, err := envinfo.NewEnvSpecificInfo(dir) if err != nil { return "", err @@ -125,7 +130,7 @@ func (do *DeleteOptions) DevfileComponentDelete() error { return err } - componentName, err := getComponentName() + componentName, err := getComponentName(do.componentContext) if err != nil { return err } @@ -137,7 +142,7 @@ func (do *DeleteOptions) DevfileComponentDelete() error { labels := map[string]string{ "component": componentName, } - devfileHandler, err := adapters.NewPlatformAdapter(componentName, devObj, kc) + devfileHandler, err := adapters.NewPlatformAdapter(componentName, do.componentContext, devObj, kc) if err != nil { return err } diff --git a/pkg/odo/cli/component/push.go b/pkg/odo/cli/component/push.go index 410ce31b11d..fee67195adc 100644 --- a/pkg/odo/cli/component/push.go +++ b/pkg/odo/cli/component/push.go @@ -2,6 +2,7 @@ package component import ( "fmt" + "path/filepath" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/odo/util/pushtarget" @@ -58,6 +59,7 @@ func NewPushOptions() *PushOptions { // Complete completes push args func (po *PushOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + po.DevfilePath = filepath.Join(po.componentContext, po.DevfilePath) // if experimental mode is enabled and devfile is present if experimental.IsExperimentalModeEnabled() && util.CheckPathExists(po.DevfilePath) { @@ -72,6 +74,7 @@ func (po *PushOptions) Complete(name string, cmd *cobra.Command, args []string) // The namespace was retrieved from the --project flag (or from the kube client if not set) and stored in kclient when initalizing the context po.namespace = po.KClient.Namespace } + return nil } diff --git a/pkg/odo/cli/component/watch.go b/pkg/odo/cli/component/watch.go index fbe2d7c522d..3eacffac71c 100644 --- a/pkg/odo/cli/component/watch.go +++ b/pkg/odo/cli/component/watch.go @@ -72,6 +72,8 @@ func NewWatchOptions() *WatchOptions { // Complete completes watch args func (wo *WatchOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + wo.devfilePath = filepath.Join(wo.componentContext, wo.devfilePath) + // if experimental mode is enabled and devfile is present if experimental.IsExperimentalModeEnabled() && util.CheckPathExists(wo.devfilePath) { envinfo, err := envinfo.NewEnvSpecificInfo(wo.componentContext) @@ -82,7 +84,7 @@ func (wo *WatchOptions) Complete(name string, cmd *cobra.Command, args []string) wo.Context = genericclioptions.NewDevfileContext(cmd) // Set the source path to either the context or current working directory (if context not set) - wo.sourcePath, err = util.GetAbsPath(filepath.Dir(wo.componentContext)) + wo.sourcePath, err = util.GetAbsPath(wo.componentContext) if err != nil { return errors.Wrap(err, "unable to get source path") } @@ -94,7 +96,7 @@ func (wo *WatchOptions) Complete(name string, cmd *cobra.Command, args []string) } // Get the component name - wo.componentName, err = getComponentName() + wo.componentName, err = getComponentName(wo.componentContext) if err != nil { return err } @@ -115,7 +117,7 @@ func (wo *WatchOptions) Complete(name string, cmd *cobra.Command, args []string) } else { platformContext = nil } - wo.devfileHandler, err = adapters.NewPlatformAdapter(wo.componentName, devObj, platformContext) + wo.devfileHandler, err = adapters.NewPlatformAdapter(wo.componentName, wo.componentContext, devObj, platformContext) return err } diff --git a/pkg/odo/cli/registry/add.go b/pkg/odo/cli/registry/add.go new file mode 100644 index 00000000000..ca891b3c391 --- /dev/null +++ b/pkg/odo/cli/registry/add.go @@ -0,0 +1,95 @@ +package registry + +import ( + // Built-in packages + "fmt" + + // Third-party packages + "github.com/pkg/errors" + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + // odo packages + "github.com/openshift/odo/pkg/log" + "github.com/openshift/odo/pkg/odo/genericclioptions" + "github.com/openshift/odo/pkg/preference" + "github.com/openshift/odo/pkg/util" +) + +const addCommandName = "add" + +// "odo registry add" command description and examples +var ( + addLongDesc = ktemplates.LongDesc(`Add devfile registry`) + + addExample = ktemplates.Examples(`# Add devfile registry + %[1]s CheRegistry https://che-devfile-registry.openshift.io + + %[1]s CheRegistryFromGitHub https://raw.githubusercontent.com/eclipse/che-devfile-registry/master + `) +) + +// AddOptions encapsulates the options for the "odo registry add" command +type AddOptions struct { + operation string + registryName string + registryURL string + forceFlag bool +} + +// NewAddOptions creates a new AddOptions instance +func NewAddOptions() *AddOptions { + return &AddOptions{} +} + +// Complete completes AddOptions after they've been created +func (o *AddOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + o.operation = "add" + o.registryName = args[0] + o.registryURL = args[1] + return +} + +// Validate validates the AddOptions based on completed values +func (o *AddOptions) Validate() (err error) { + err = util.ValidateURL(o.registryURL) + if err != nil { + return err + } + + return +} + +// Run contains the logic for "odo registry add" command +func (o *AddOptions) Run() (err error) { + + cfg, err := preference.New() + if err != nil { + return errors.Wrap(err, "unable to add registry") + } + + err = cfg.RegistryHandler(o.operation, o.registryName, o.registryURL, o.forceFlag) + if err != nil { + return err + } + + log.Info("New registry successfully added") + return nil +} + +// NewCmdAdd implements the "odo registry add" command +func NewCmdAdd(name, fullName string) *cobra.Command { + o := NewAddOptions() + registryAddCmd := &cobra.Command{ + Use: fmt.Sprintf("%s ", name), + Short: addLongDesc, + Long: addLongDesc, + Example: fmt.Sprintf(fmt.Sprint(addExample), fullName), + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + genericclioptions.GenericRun(o, cmd, args) + }, + } + + return registryAddCmd +} diff --git a/pkg/odo/cli/registry/delete.go b/pkg/odo/cli/registry/delete.go new file mode 100644 index 00000000000..4d3ea5c00f6 --- /dev/null +++ b/pkg/odo/cli/registry/delete.go @@ -0,0 +1,86 @@ +package registry + +import ( + // Built-in packages + "fmt" + + // Third-party packages + "github.com/pkg/errors" + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + // odo packages + "github.com/openshift/odo/pkg/odo/genericclioptions" + "github.com/openshift/odo/pkg/preference" +) + +const deleteCommandName = "delete" + +// "odo registry delete" command description and examples +var ( + deleteLongDesc = ktemplates.LongDesc(`Delete devfile registry`) + + deleteExample = ktemplates.Examples(`# Delete devfile registry + %[1]s CheRegistry + `) +) + +// DeleteOptions encapsulates the options for the "odo registry delete" command +type DeleteOptions struct { + operation string + registryName string + registryURL string + forceFlag bool +} + +// NewDeleteOptions creates a new DeleteOptions instance +func NewDeleteOptions() *DeleteOptions { + return &DeleteOptions{} +} + +// Complete completes DeleteOptions after they've been created +func (o *DeleteOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + o.operation = "delete" + o.registryName = args[0] + o.registryURL = "" + return +} + +// Validate validates the DeleteOptions based on completed values +func (o *DeleteOptions) Validate() (err error) { + return +} + +// Run contains the logic for "odo registry delete" command +func (o *DeleteOptions) Run() (err error) { + cfg, err := preference.New() + if err != nil { + return errors.Wrap(err, "unable to delete registry") + } + + err = cfg.RegistryHandler(o.operation, o.registryName, o.registryURL, o.forceFlag) + if err != nil { + return err + } + + return nil +} + +// NewCmdDelete implements the "odo registry delete" command +func NewCmdDelete(name, fullName string) *cobra.Command { + o := NewDeleteOptions() + registryDeleteCmd := &cobra.Command{ + Use: fmt.Sprintf("%s ", name), + Short: deleteLongDesc, + Long: deleteLongDesc, + Example: fmt.Sprintf(fmt.Sprint(deleteExample), fullName), + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + genericclioptions.GenericRun(o, cmd, args) + }, + } + + registryDeleteCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, delete the registry directly") + + return registryDeleteCmd +} diff --git a/pkg/odo/cli/registry/list.go b/pkg/odo/cli/registry/list.go new file mode 100644 index 00000000000..dac98ba7414 --- /dev/null +++ b/pkg/odo/cli/registry/list.go @@ -0,0 +1,88 @@ +package registry + +import ( + // Built-in packages + "fmt" + "io" + "os" + "text/tabwriter" + + // Third-party packages + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + // odo packages + "github.com/openshift/odo/pkg/odo/genericclioptions" + "github.com/openshift/odo/pkg/odo/util" + "github.com/openshift/odo/pkg/preference" +) + +const listCommandName = "list" + +// "odo registry list" command description and examples +var ( + listDesc = ktemplates.LongDesc(`List devfile registry`) + + listExample = ktemplates.Examples(`# List devfile registry + %[1]s + `) +) + +// ListOptions encapsulates the options for "odo registry list" command +type ListOptions struct { +} + +// NewListOptions creates a new ListOptions instance +func NewListOptions() *ListOptions { + return &ListOptions{} +} + +// Complete completes ListOptions after they've been created +func (o *ListOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + return +} + +// Validate validates the ListOptions based on completed values +func (o *ListOptions) Validate() (err error) { + return +} + +// Run contains the logic for "odo registry list" command +func (o *ListOptions) Run() (err error) { + cfg, err := preference.New() + if err != nil { + util.LogErrorAndExit(err, "") + } + + w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent) + fmt.Fprintln(w, "NAME", "\t", "URL") + o.printRegistryList(w, cfg.OdoSettings.RegistryList) + w.Flush() + return +} + +func (o *ListOptions) printRegistryList(w io.Writer, registryList *[]preference.Registry) { + if registryList == nil { + return + } + + for _, registry := range *registryList { + fmt.Fprintln(w, registry.Name, "\t", registry.URL) + } +} + +// NewCmdList implements the "odo registry list" command +func NewCmdList(name, fullName string) *cobra.Command { + o := NewListOptions() + registryListCmd := &cobra.Command{ + Use: name, + Short: listDesc, + Long: listDesc, + Example: fmt.Sprintf(fmt.Sprint(listExample), fullName), + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + genericclioptions.GenericRun(o, cmd, args) + }, + } + return registryListCmd +} diff --git a/pkg/odo/cli/registry/registry.go b/pkg/odo/cli/registry/registry.go new file mode 100644 index 00000000000..a4c85bac9ff --- /dev/null +++ b/pkg/odo/cli/registry/registry.go @@ -0,0 +1,44 @@ +package registry + +import ( + // Built-in packages + "fmt" + + // Third-party packages + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + // odo packages + "github.com/openshift/odo/pkg/odo/util" +) + +// RecommendedCommandName is the recommended registry command name +const RecommendedCommandName = "registry" + +var registryDesc = ktemplates.LongDesc(`Configure devfile registry`) + +// NewCmdRegistry implements the registry configuration command +func NewCmdRegistry(name, fullName string) *cobra.Command { + registryAddCmd := NewCmdAdd(addCommandName, util.GetFullName(fullName, addCommandName)) + registryListCmd := NewCmdList(listCommandName, util.GetFullName(fullName, listCommandName)) + registryUpdateCmd := NewCmdUpdate(updateCommandName, util.GetFullName(fullName, updateCommandName)) + registryDeleteCmd := NewCmdDelete(deleteCommandName, util.GetFullName(fullName, deleteCommandName)) + + registryCmd := &cobra.Command{ + Use: name, + Short: registryDesc, + Long: registryDesc, + Example: fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s", + registryAddCmd.Example, + registryListCmd.Example, + registryUpdateCmd.Example, + registryDeleteCmd.Example, + ), + } + + registryCmd.AddCommand(registryAddCmd, registryListCmd, registryUpdateCmd, registryDeleteCmd) + registryCmd.SetUsageTemplate(util.CmdUsageTemplate) + registryCmd.Annotations = map[string]string{"command": "main"} + + return registryCmd +} diff --git a/pkg/odo/cli/registry/update.go b/pkg/odo/cli/registry/update.go new file mode 100644 index 00000000000..afdab02de5a --- /dev/null +++ b/pkg/odo/cli/registry/update.go @@ -0,0 +1,92 @@ +package registry + +import ( + // Built-in packages + "fmt" + + // Third-party packages + "github.com/pkg/errors" + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + // odo packages + "github.com/openshift/odo/pkg/odo/genericclioptions" + "github.com/openshift/odo/pkg/preference" + "github.com/openshift/odo/pkg/util" +) + +const updateCommandName = "update" + +// "odo registry update" command description and examples +var ( + updateLongDesc = ktemplates.LongDesc(`Update devfile registry URL`) + + updateExample = ktemplates.Examples(`# Update devfile registry URL + %[1]s CheRegistry https://che-devfile-registry-update.openshift.io + `) +) + +// UpdateOptions encapsulates the options for the "odo registry update" command +type UpdateOptions struct { + operation string + registryName string + registryURL string + forceFlag bool +} + +// NewUpdateOptions creates a new UpdateOptions instance +func NewUpdateOptions() *UpdateOptions { + return &UpdateOptions{} +} + +// Complete completes UpdateOptions after they've been created +func (o *UpdateOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + o.operation = "update" + o.registryName = args[0] + o.registryURL = args[1] + return +} + +// Validate validates the UpdateOptions based on completed values +func (o *UpdateOptions) Validate() (err error) { + err = util.ValidateURL(o.registryURL) + if err != nil { + return err + } + + return +} + +// Run contains the logic for "odo registry update" command +func (o *UpdateOptions) Run() (err error) { + cfg, err := preference.New() + if err != nil { + return errors.Wrap(err, "unable to update registry") + } + + err = cfg.RegistryHandler(o.operation, o.registryName, o.registryURL, o.forceFlag) + if err != nil { + return err + } + + return nil +} + +// NewCmdUpdate implements the "odo registry update" command +func NewCmdUpdate(name, fullName string) *cobra.Command { + o := NewUpdateOptions() + registryUpdateCmd := &cobra.Command{ + Use: fmt.Sprintf("%s ", name), + Short: updateLongDesc, + Long: updateLongDesc, + Example: fmt.Sprintf(fmt.Sprint(updateExample), fullName), + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + genericclioptions.GenericRun(o, cmd, args) + }, + } + + registryUpdateCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, update the registry directly") + + return registryUpdateCmd +} diff --git a/pkg/odo/genericclioptions/runnable.go b/pkg/odo/genericclioptions/runnable.go index eb464fa029b..cab80b6fa38 100644 --- a/pkg/odo/genericclioptions/runnable.go +++ b/pkg/odo/genericclioptions/runnable.go @@ -94,5 +94,12 @@ func CheckMachineReadableOutputCommand(cmd *cobra.Command) { // is not malformed / mixed in with normal logging if log.IsJSON() { _ = flag.Set("v", "0") + } else { + // Override the logging level by the value (if set) by the ODO_LOG_LEVEL env + // The "-v" flag set on command line will take precedence over ODO_LOG_LEVEL env + v := flag.CommandLine.Lookup("v").Value.String() + if level, ok := os.LookupEnv("ODO_LOG_LEVEL"); ok && v == "0" { + _ = flag.CommandLine.Set("v", level) + } } } diff --git a/pkg/preference/preference.go b/pkg/preference/preference.go index 460a7c0f447..5f6239ad1a5 100644 --- a/pkg/preference/preference.go +++ b/pkg/preference/preference.go @@ -12,6 +12,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog" + "github.com/openshift/odo/pkg/log" + "github.com/openshift/odo/pkg/odo/cli/ui" "github.com/openshift/odo/pkg/util" ) @@ -19,7 +21,7 @@ const ( GlobalConfigEnvName = "GLOBALODOCONFIG" configFileName = "preference.yaml" preferenceKind = "Preference" - preferenceAPIVersion = "odo.openshift.io/v1alpha1" + preferenceAPIVersion = "odo.dev/v1alpha1" //DefaultTimeout for openshift server connection check (in seconds) DefaultTimeout = 1 @@ -64,6 +66,18 @@ const ( // KubePushTarget represents the value of the push target when it's set to Kube KubePushTarget = "kube" + + // CheDevfileRegistryName is the name of Che devfile registry + CheDevfileRegistryName = "CheDevfileRegistry" + + // CheDevfileRegistryURL is the URL of Che devfile registry + CheDevfileRegistryURL = "https://che-devfile-registry.openshift.io" + + // DefaultDevfileRegistryName is the name of default devfile registry + DefaultDevfileRegistryName = "DefaultDevfileRegistry" + + // DefaultDevfileRegistryURL is the URL of default devfile registry + DefaultDevfileRegistryURL = "https://raw.githubusercontent.com/elsony/devfile-registry/master" ) // TimeoutSettingDescription is human-readable description for the timeout setting @@ -117,6 +131,15 @@ type OdoSettings struct { // PushTarget for telling odo which platform to push to (either kube or docker) PushTarget *string `yaml:"PushTarget,omitempty"` + + // RegistryList for telling odo to connect to all the registries in the registry list + RegistryList *[]Registry `yaml:"RegistryList,omitempty"` +} + +// Registry includes the registry metadata +type Registry struct { + Name string `yaml:"Name,omitempty"` + URL string `yaml:"URL,omitempty"` } // Preference stores all the preferences related to odo @@ -171,7 +194,8 @@ func NewPreferenceInfo() (*PreferenceInfo, error) { Preference: NewPreference(), Filename: preferenceFile, } - // if the preference file doesn't exist then we dont worry about it and return + + // If the preference file doesn't exist then we return with default preference if _, err = os.Stat(preferenceFile); os.IsNotExist(err) { return &c, nil } @@ -180,9 +204,124 @@ func NewPreferenceInfo() (*PreferenceInfo, error) { if err != nil { return nil, err } + + if c.OdoSettings.Experimental != nil && *c.OdoSettings.Experimental { + if c.OdoSettings.RegistryList == nil { + // Handle user has preference file but doesn't use dynamic registry before + defaultRegistryList := []Registry{ + { + Name: CheDevfileRegistryName, + URL: CheDevfileRegistryURL, + }, + { + Name: DefaultDevfileRegistryName, + URL: DefaultDevfileRegistryURL, + }, + } + c.OdoSettings.RegistryList = &defaultRegistryList + } + } + return &c, nil } +// RegistryHandler handles registry add, update and delete operations +func (c *PreferenceInfo) RegistryHandler(operation string, registryName string, registryURL string, forceFlag bool) error { + var registryList []Registry + var err error + registryExist := false + + // Registry list is empty + if c.OdoSettings.RegistryList == nil { + registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL) + if err != nil { + return err + } + } else { + // The target registry exists in the registry list + registryList = *c.OdoSettings.RegistryList + for index, registry := range registryList { + if registry.Name == registryName { + registryExist = true + registryList, err = handleWithRegistryExist(index, registryList, operation, registryName, registryURL, forceFlag) + if err != nil { + return err + } + } + } + + // The target registry doesn't exist in the registry list + if !registryExist { + registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL) + if err != nil { + return err + } + } + } + + c.OdoSettings.RegistryList = ®istryList + err = util.WriteToFile(&c.Preference, c.Filename) + if err != nil { + return errors.Wrapf(err, "unable to write the configuration of %s operation to preference file", operation) + } + + return nil +} + +func handleWithoutRegistryExist(registryList []Registry, operation string, registryName string, registryURL string) ([]Registry, error) { + switch operation { + + case "add": + registry := Registry{ + Name: registryName, + URL: registryURL, + } + registryList = append(registryList, registry) + + case "update": + return nil, errors.Errorf("failed to update registry: registry %s doesn't exist", registryName) + + case "delete": + return nil, errors.Errorf("failed to delete registry: registry %s doesn't exist", registryName) + } + + return registryList, nil +} + +func handleWithRegistryExist(index int, registryList []Registry, operation string, registryName string, registryURL string, forceFlag bool) ([]Registry, error) { + switch operation { + + case "add": + return nil, errors.Errorf("failed to add registry: registry %s already exists", registryName) + + case "update": + if !forceFlag { + if !ui.Proceed(fmt.Sprintf("Are you sure you want to update registry %s", registryName)) { + log.Info("Aborted by the user") + return registryList, nil + } + } + + registryList[index].URL = registryURL + log.Info("Successfully updated registry") + + case "delete": + if !forceFlag { + if !ui.Proceed(fmt.Sprintf("Are you sure you want to delete registry %s", registryName)) { + log.Info("Aborted by the user") + return registryList, nil + } + } + + copy(registryList[index:], registryList[index+1:]) + registryList[len(registryList)-1] = Registry{} + registryList = registryList[:len(registryList)-1] + log.Info("Successfully deleted registry") + } + + return registryList, nil +} + // SetConfiguration modifies Odo configurations in the config file // as of now being used for nameprefix, timeout, updatenotification // TODO: Use reflect to set parameters diff --git a/pkg/preference/preference_test.go b/pkg/preference/preference_test.go index 73c202c0fb4..d3b5ea22407 100644 --- a/pkg/preference/preference_test.go +++ b/pkg/preference/preference_test.go @@ -703,3 +703,132 @@ func TestMetaTypePopulatedInPreference(t *testing.T) { t.Error("the api version and kind in preference are incorrect") } } + +func TestHandleWithoutRegistryExist(t *testing.T) { + tests := []struct { + name string + registryList []Registry + operation string + registryName string + registryURL string + want []Registry + }{ + { + name: "Case 1: Add registry", + registryList: []Registry{}, + operation: "add", + registryName: "testName", + registryURL: "testURL", + want: []Registry{ + { + Name: "testName", + URL: "testURL", + }, + }, + }, + { + name: "Case 2: Update registry", + registryList: []Registry{}, + operation: "update", + registryName: "testName", + registryURL: "testURL", + want: nil, + }, + { + name: "Case 3: Delete registry", + registryList: []Registry{}, + operation: "delete", + registryName: "testName", + registryURL: "testURL", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := handleWithoutRegistryExist(tt.registryList, tt.operation, tt.registryName, tt.registryURL) + if err != nil { + t.Logf("Error message is %v", err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Got: %v, want %v", got, tt.want) + } + }) + } +} + +func TestHandleWithRegistryExist(t *testing.T) { + tests := []struct { + name string + index int + registryList []Registry + operation string + registryName string + registryURL string + forceFlag bool + want []Registry + }{ + { + name: "Case 1: Add registry", + index: 0, + registryList: []Registry{ + { + Name: "testName", + URL: "testURL", + }, + }, + operation: "add", + registryName: "testName", + registryURL: "addURL", + forceFlag: false, + want: nil, + }, + { + name: "Case 2: update registry", + index: 0, + registryList: []Registry{ + { + Name: "testName", + URL: "testURL", + }, + }, + operation: "update", + registryName: "testName", + registryURL: "updateURL", + forceFlag: true, + want: []Registry{ + { + Name: "testName", + URL: "updateURL", + }, + }, + }, + { + name: "Case 3: Delete registry", + index: 0, + registryList: []Registry{ + { + Name: "testName", + URL: "testURL", + }, + }, + operation: "delete", + registryName: "testName", + registryURL: "", + forceFlag: true, + want: []Registry{}, + }, + } + + for _, tt := range tests { + got, err := handleWithRegistryExist(tt.index, tt.registryList, tt.operation, tt.registryName, tt.registryURL, tt.forceFlag) + if err != nil { + t.Logf("Error message is %v", err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Got: %v, want: %v", got, tt.want) + } + } +} diff --git a/pkg/project/project.go b/pkg/project/project.go index a51d0467c4b..a12a740563e 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -10,6 +10,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const apiVersion = "odo.dev/v1alpha1" + // GetCurrent return current project func GetCurrent(client *occlient.Client) string { project := client.GetCurrentProjectName() @@ -98,7 +100,7 @@ func GetMachineReadableFormat(projectName string, isActive bool) Project { return Project{ TypeMeta: metav1.TypeMeta{ Kind: "Project", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: projectName, @@ -117,7 +119,7 @@ func MachineReadableSuccessOutput(projectName string, message string) { machineOutput := machineoutput.GenericSuccess{ TypeMeta: metav1.TypeMeta{ Kind: "Project", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: projectName, @@ -134,7 +136,7 @@ func getMachineReadableFormatForList(projects []Project) ProjectList { return ProjectList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ListMeta: metav1.ListMeta{}, Items: projects, diff --git a/pkg/service/service.go b/pkg/service/service.go index 2e9806d6fd4..033d3ce4485 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -22,6 +22,7 @@ import ( const provisionedAndBoundStatus = "ProvisionedAndBound" const provisionedAndLinkedStatus = "ProvisionedAndLinked" +const apiVersion = "odo.dev/v1alpha1" // NewServicePlanParameter creates a new ServicePlanParameter instance with the specified state func NewServicePlanParameter(name, typeName, defaultValue string, required bool) ServicePlanParameter { @@ -144,7 +145,7 @@ func List(client *occlient.Client, applicationName string) (ServiceList, error) Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: elem.Labels[componentlabels.ComponentLabel], @@ -157,7 +158,7 @@ func List(client *occlient.Client, applicationName string) (ServiceList, error) return ServiceList{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceList", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, Items: services, }, nil @@ -213,7 +214,7 @@ func ListWithDetailedStatus(client *occlient.Client, applicationName string) (Se return ServiceList{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceList", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, Items: services.Items, }, nil diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index 83692762a12..6ffbc971448 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "fmt" + "github.com/kylelemons/godebug/pretty" "github.com/onsi/gomega/matchers" "github.com/openshift/odo/pkg/testingutil" @@ -390,10 +391,10 @@ func TestListWithDetailedStatus(t *testing.T) { }, }, output: []Service{ - Service{ + { TypeMeta: metav1.TypeMeta{ Kind: "Service", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "mysql-persistent", @@ -406,10 +407,10 @@ func TestListWithDetailedStatus(t *testing.T) { Status: "ProvisionedAndLinked", }, }, - Service{ + { TypeMeta: metav1.TypeMeta{ Kind: "Service", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "postgresql-ephemeral", @@ -422,10 +423,10 @@ func TestListWithDetailedStatus(t *testing.T) { Status: "ProvisionedAndBound", }, }, - Service{ + { TypeMeta: metav1.TypeMeta{ Kind: "Service", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "mongodb", @@ -438,10 +439,10 @@ func TestListWithDetailedStatus(t *testing.T) { Status: "ProvisionedSuccessfully", }, }, - Service{ + { TypeMeta: metav1.TypeMeta{ Kind: "Service", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "jenkins-persistent", diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index e80c756b8bd..b2e05e35de7 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -18,6 +18,8 @@ import ( "k8s.io/klog" ) +const apiVersion = "odo.dev/v1alpha1" + // Get returns Storage defination for given Storage name func (storages StorageList) Get(storageName string) Storage { for _, storage := range storages.Items { @@ -442,7 +444,7 @@ func GetMachineReadableFormatForList(storage []Storage) StorageList { return StorageList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ListMeta: metav1.ListMeta{}, Items: storage, @@ -453,7 +455,7 @@ func GetMachineReadableFormatForList(storage []Storage) StorageList { // storagePath indicates the path to which the storage is mounted to, "" if not mounted func GetMachineReadableFormat(storageName, storageSize, storagePath string) Storage { return Storage{ - TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: "odo.openshift.io/v1alpha1"}, + TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: apiVersion}, ObjectMeta: metav1.ObjectMeta{Name: storageName}, Spec: StorageSpec{ Size: storageSize, diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 50a2cb32e60..75933fab689 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -89,7 +89,7 @@ func TestGetMachineReadableFormat(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "pvc-example", }, - TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: "odo.openshift.io/v1alpha1"}, + TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: "odo.dev/v1alpha1"}, Spec: StorageSpec{ Size: "100Mi", Path: "data", @@ -106,7 +106,7 @@ func TestGetMachineReadableFormat(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "pvc-example", }, - TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: "odo.openshift.io/v1alpha1"}, + TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: "odo.dev/v1alpha1"}, Spec: StorageSpec{ Size: "500Mi", Path: "", @@ -140,7 +140,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "100Mi", @@ -151,7 +151,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { want: StorageList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: []Storage{ @@ -161,7 +161,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "100Mi", @@ -180,7 +180,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "100Mi", @@ -193,7 +193,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "500Mi", @@ -204,7 +204,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { want: StorageList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, ListMeta: metav1.ListMeta{}, Items: []Storage{ @@ -214,7 +214,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "100Mi", @@ -227,7 +227,7 @@ func TestGetMachineReadableFormatForList(t *testing.T) { }, TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: "odo.dev/v1alpha1", }, Spec: StorageSpec{ Size: "500Mi", diff --git a/pkg/url/url.go b/pkg/url/url.go index a73b33b044a..300c027dde2 100644 --- a/pkg/url/url.go +++ b/pkg/url/url.go @@ -29,6 +29,8 @@ import ( "k8s.io/klog" ) +const apiVersion = "odo.dev/v1alpha1" + // Get returns URL definition for given URL name func (urls URLList) Get(urlName string) URL { for _, url := range urls.Items { @@ -383,7 +385,7 @@ func ConvertConfigURL(configURL config.ConfigURL) URL { return URL{ TypeMeta: metav1.TypeMeta{ Kind: "url", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ Name: configURL.Name, @@ -485,7 +487,7 @@ func GetValidExposedPortNumber(exposedPort int) (int, error) { // getMachineReadableFormat gives machine readable URL definition func getMachineReadableFormat(r routev1.Route) URL { return URL{ - TypeMeta: metav1.TypeMeta{Kind: "url", APIVersion: "odo.openshift.io/v1alpha1"}, + TypeMeta: metav1.TypeMeta{Kind: "url", APIVersion: apiVersion}, ObjectMeta: metav1.ObjectMeta{Name: r.Labels[urlLabels.URLLabel]}, Spec: URLSpec{Host: r.Spec.Host, Port: r.Spec.Port.TargetPort.IntValue(), Protocol: GetProtocol(r, iextensionsv1.Ingress{}, experimental.IsExperimentalModeEnabled()), Secure: r.Spec.TLS != nil}, } @@ -496,7 +498,7 @@ func getMachineReadableFormatForList(urls []URL) URLList { return URLList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "odo.openshift.io/v1alpha1", + APIVersion: apiVersion, }, ListMeta: metav1.ListMeta{}, Items: urls, @@ -516,7 +518,7 @@ func getMachineReadableFormatForIngressList(ingresses []iextensionsv1.Ingress) i return iextensionsv1.IngressList{ TypeMeta: metav1.TypeMeta{ Kind: "List", - APIVersion: "udo.udo.io/v1alpha1", + APIVersion: apiVersion, }, ListMeta: metav1.ListMeta{}, Items: ingresses, @@ -575,6 +577,7 @@ func Push(client *occlient.Client, kClient *kclient.Client, parameters PushParam for _, url := range urlList.Items { urlCLUSTER[url.Name] = URL{ Spec: URLSpec{ + Host: url.Spec.Rules[0].Host, Port: int(url.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal), urlKind: envinfo.INGRESS, }, @@ -603,7 +606,21 @@ func Push(client *occlient.Client, kClient *kclient.Client, parameters PushParam // find URLs to delete for urlName, urlSpec := range urlCLUSTER { val, ok := urlLOCAL[urlName] - if !ok { + + configMismatch := false + if ok { + // since the host stored in an ingress + // is the combination of name and host of the url + if val.Spec.urlKind == envinfo.INGRESS { + val.Spec.Host = fmt.Sprintf("%v.%v", urlName, val.Spec.Host) + } + if !reflect.DeepEqual(val.Spec, urlSpec.Spec) { + configMismatch = true + klog.V(4).Infof("config and cluster mismatch for url %s", urlName) + } + } + + if !ok || configMismatch { if urlSpec.Spec.urlKind == envinfo.INGRESS && kClient == nil { continue } @@ -614,11 +631,8 @@ func Push(client *occlient.Client, kClient *kclient.Client, parameters PushParam } log.Successf("URL %s successfully deleted", urlName) urlChange = true + delete(urlCLUSTER, urlName) continue - } else { - if !reflect.DeepEqual(val.Spec, urlSpec.Spec) { - return errors.Errorf("config mismatch for URL with the same name %s", val.Name) - } } } diff --git a/pkg/url/url_test.go b/pkg/url/url_test.go index 4a6869c4649..f24920f922a 100644 --- a/pkg/url/url_test.go +++ b/pkg/url/url_test.go @@ -777,11 +777,33 @@ func TestPush(t *testing.T) { }, { Name: "example-1", - Port: 8080, + Port: 9090, Secure: false, }, }, returnedRoutes: &routev1.RouteList{}, + createdURLs: []URL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-app", + }, + Spec: URLSpec{ + Port: 8080, + Secure: false, + urlKind: envinfo.ROUTE, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-1-app", + }, + Spec: URLSpec{ + Port: 9090, + Secure: false, + urlKind: envinfo.ROUTE, + }, + }, + }, }, { name: "0 url on local config and 2 on openshift cluster", @@ -839,6 +861,27 @@ func TestPush(t *testing.T) { }, }, }, + { + name: "2 url on local config and openshift cluster are in sync", + componentName: "nodejs", + applicationName: "app", + args: args{isRouteSupported: true}, + existingConfigURLs: []config.ConfigURL{ + { + Name: "example", + Port: 8080, + Secure: false, + }, + { + Name: "example-1", + Port: 9100, + Secure: false, + }, + }, + returnedRoutes: testingutil.GetRouteListWithMultiple("nodejs", "app"), + deletedURLs: []URL{}, + createdURLs: []URL{}, + }, { name: "0 urls on env file and cluster", @@ -974,6 +1017,31 @@ func TestPush(t *testing.T) { }, }, }, + { + name: "2 urls on env file and openshift cluster are in sync", + componentName: "wildfly", + args: args{isRouteSupported: true, isExperimentalModeEnabled: true}, + existingEnvInfoURLs: []envinfo.EnvInfoURL{ + { + Name: "example-0", + Port: 8080, + Secure: false, + Host: "com", + Kind: envinfo.INGRESS, + }, + { + Name: "example-1", + Port: 9090, + Secure: false, + Host: "com", + Kind: envinfo.INGRESS, + }, + }, + returnedRoutes: &routev1.RouteList{}, + returnedIngress: fake.GetIngressListWithMultiple("wildfly"), + createdURLs: []URL{}, + deletedURLs: []URL{}, + }, { name: "2 (1 ingress,1 route) urls on env file and 2 on openshift cluster (1 ingress,1 route), but they are different", componentName: "nodejs", @@ -1081,9 +1149,26 @@ func TestPush(t *testing.T) { *fake.GetSingleIngress("example-local-0", "nodejs"), }, }, - createdURLs: []URL{}, - deletedURLs: []URL{}, - wantErr: true, + createdURLs: []URL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-local-0", + }, + Spec: URLSpec{ + Port: 8080, + Secure: false, + urlKind: envinfo.ROUTE, + }, + }, + }, + deletedURLs: []URL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-local-0", + }, + }, + }, + wantErr: false, }, { name: "url with same name exists on config and cluster but with different specs", @@ -1103,9 +1188,26 @@ func TestPush(t *testing.T) { }, }, returnedIngress: &extensionsv1.IngressList{}, - createdURLs: []URL{}, - deletedURLs: []URL{}, - wantErr: true, + createdURLs: []URL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-local-0-app", + }, + Spec: URLSpec{ + Port: 8080, + Secure: false, + urlKind: envinfo.ROUTE, + }, + }, + }, + deletedURLs: []URL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-local-0-app", + }, + }, + }, + wantErr: false, }, { @@ -1339,6 +1441,16 @@ func TestPush(t *testing.T) { t.Errorf("route is not supproted, total actions on the routeClient should be 0") } } + + if len(tt.createdURLs) == 0 && len(tt.deletedURLs) == 0 { + if len(fakeClientSet.RouteClientset.Actions()) > 1 { + t.Errorf("when urls are in sync, total action for route client set should be less than 1") + } + + if len(fakeClientSet.Kubernetes.Actions()) > 1 { + t.Errorf("when urls are in snyc, total action for kubernetes client set should be less than 1") + } + } } }) } diff --git a/pkg/util/util.go b/pkg/util/util.go index 2dce6cf36c6..2c7746a9749 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -33,7 +33,10 @@ import ( ) // HTTPRequestTimeout configures timeout of all HTTP requests -const HTTPRequestTimeout = 10 * time.Second +const ( + HTTPRequestTimeout = 20 * time.Second // HTTPRequestTimeout configures timeout of all HTTP requests + ResponseHeaderTimeout = 10 * time.Second // ResponseHeaderTimeout is the timeout to retrieve the server's response headers +) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") @@ -680,7 +683,10 @@ func GetRemoteFilesMarkedForDeletion(delSrcRelPaths []string, remoteFolder strin // HTTPGetRequest uses url to get file contents func HTTPGetRequest(url string) ([]byte, error) { - var httpClient = &http.Client{Timeout: HTTPRequestTimeout} + var httpClient = &http.Client{Transport: &http.Transport{ + ResponseHeaderTimeout: ResponseHeaderTimeout, + }, + Timeout: HTTPRequestTimeout} resp, err := httpClient.Get(url) if err != nil { return nil, err @@ -924,7 +930,9 @@ func DownloadFile(url string, filepath string) error { defer out.Close() // #nosec G307 // Get the data - var httpClient = &http.Client{Timeout: HTTPRequestTimeout} + var httpClient = &http.Client{Transport: &http.Transport{ + ResponseHeaderTimeout: ResponseHeaderTimeout, + }, Timeout: HTTPRequestTimeout} resp, err := httpClient.Get(url) if err != nil { return err @@ -982,3 +990,17 @@ func CheckKubeConfigExist() bool { return false } + +// ValidateURL validates the URL +func ValidateURL(sourceURL string) error { + u, err := url.Parse(sourceURL) + if err != nil { + return err + } + + if len(u.Host) == 0 || len(u.Scheme) == 0 { + return errors.New("URL is invalid") + } + + return nil +} diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index 94f5b88c941..1dae55a141f 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -1614,3 +1614,41 @@ func TestGetGitHubZipURL(t *testing.T) { } } */ + +func TestValidateURL(t *testing.T) { + tests := []struct { + name string + url string + wantErr bool + }{ + { + name: "Case 1: Valid URL", + url: "http://www.example.com/", + wantErr: false, + }, + { + name: "Case 2: Invalid URL - No host", + url: "http://", + wantErr: true, + }, + { + name: "Case 3: Invalid URL - No scheme", + url: "://www.example.com/", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotErr := false + got := ValidateURL(tt.url) + if got != nil { + gotErr = true + } + + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("Got %v, want %v", got, tt.wantErr) + } + }) + } +} diff --git a/tests/examples/source/devfiles/nodejs/project/app/app.js b/tests/examples/source/devfiles/nodejs/project/app/app.js new file mode 100644 index 00000000000..42ff4183f17 --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/project/app/app.js @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012-2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +/*eslint-env node*/ + +var express = require('express'); +var app = express(); + +app.get('/', function (req, res) { + res.send('Hello World!'); +}); + +app.listen(3000, function () { + console.log('Example app listening on port 3000!'); +}); diff --git a/tests/examples/source/devfiles/nodejs/project/package.json b/tests/examples/source/devfiles/nodejs/project/package.json new file mode 100644 index 00000000000..2c28053506f --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/project/package.json @@ -0,0 +1,14 @@ +{ + "name": "nodejs-sample", + "version": "1.0.0", + "description": "", + "main": "app/app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "EPL-2.0", + "dependencies": { + "express": "^4.13.4" + } +} diff --git a/tests/examples/source/devfiles/springboot/project/.gitignore b/tests/examples/source/devfiles/springboot/project/.gitignore new file mode 100644 index 00000000000..18d2ca6cdc6 --- /dev/null +++ b/tests/examples/source/devfiles/springboot/project/.gitignore @@ -0,0 +1,4 @@ +target/ +.classpath +.project +.settings/ diff --git a/tests/examples/source/devfiles/springboot/project/pom.xml b/tests/examples/source/devfiles/springboot/project/pom.xml new file mode 100644 index 00000000000..5dc6fa1723a --- /dev/null +++ b/tests/examples/source/devfiles/springboot/project/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + projects + sprtest1 + 1.0-SNAPSHOT + + + UTF-8 + UTF-8 + 1.8 + 1.8 + 1.8 + Dalston.SR4 + + sprtest1 + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.15.RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-actuator + + + org.springframework.cloud + spring-cloud-starter-hystrix + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.ibm.runtimetools + javametrics-spring + [1.1,2.0) + + + com.ibm.runtimetools + javametrics-agent + [1.1,2.0) + + + org.glassfish + javax.json + 1.0.4 + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/tests/examples/source/devfiles/springboot/project/src/main/java/application/Info.java b/tests/examples/source/devfiles/springboot/project/src/main/java/application/Info.java new file mode 100644 index 00000000000..7cb2fb1b983 --- /dev/null +++ b/tests/examples/source/devfiles/springboot/project/src/main/java/application/Info.java @@ -0,0 +1,17 @@ +package application; + +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.boot.context.event.ApplicationReadyEvent; + +@Component +public class Info { + + @EventListener(ApplicationReadyEvent.class) + public void contextRefreshedEvent() { + System.out.println("The following endpoints are available by default :-"); + System.out.println(" Health : http://localhost:8080/health"); + System.out.println(" Application : http://localhost:8080/v1/"); + } + +} diff --git a/tests/examples/source/devfiles/springboot/project/src/main/java/application/SBApplication.java b/tests/examples/source/devfiles/springboot/project/src/main/java/application/SBApplication.java new file mode 100644 index 00000000000..1b28be333d6 --- /dev/null +++ b/tests/examples/source/devfiles/springboot/project/src/main/java/application/SBApplication.java @@ -0,0 +1,14 @@ +package application; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = {"application", "com.ibm.javametrics.spring"}) +public class SBApplication { + + public static void main(String[] args) { + SpringApplication.run(SBApplication.class, args); + } +} diff --git a/tests/examples/source/devfiles/springboot/project/src/main/java/application/rest/v1/Example.java b/tests/examples/source/devfiles/springboot/project/src/main/java/application/rest/v1/Example.java new file mode 100644 index 00000000000..99819f264d1 --- /dev/null +++ b/tests/examples/source/devfiles/springboot/project/src/main/java/application/rest/v1/Example.java @@ -0,0 +1,23 @@ +package application.rest.v1; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ResponseBody; +import java.util.ArrayList; +import java.util.List; + +@RestController +public class Example { + + + @RequestMapping("v1") + public @ResponseBody ResponseEntity example() { + List list = new ArrayList<>(); + //return a simple list of strings + list.add("Congratulations, your application is up and running."); + return new ResponseEntity(list.toString(), HttpStatus.OK); + } + +} diff --git a/tests/examples/source/devfiles/springboot/project/src/main/resources/application-local.properties b/tests/examples/source/devfiles/springboot/project/src/main/resources/application-local.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/examples/source/devfiles/springboot/project/src/main/resources/application.properties b/tests/examples/source/devfiles/springboot/project/src/main/resources/application.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/examples/source/openjdk-sb-postgresql/.gitignore b/tests/examples/source/openjdk-sb-postgresql/.gitignore new file mode 100644 index 00000000000..18d2ca6cdc6 --- /dev/null +++ b/tests/examples/source/openjdk-sb-postgresql/.gitignore @@ -0,0 +1,4 @@ +target/ +.classpath +.project +.settings/ diff --git a/tests/examples/source/openjdk/.gitignore b/tests/examples/source/openjdk/.gitignore new file mode 100644 index 00000000000..18d2ca6cdc6 --- /dev/null +++ b/tests/examples/source/openjdk/.gitignore @@ -0,0 +1,4 @@ +target/ +.classpath +.project +.settings/ diff --git a/tests/examples/source/wildfly/.gitignore b/tests/examples/source/wildfly/.gitignore new file mode 100644 index 00000000000..18d2ca6cdc6 --- /dev/null +++ b/tests/examples/source/wildfly/.gitignore @@ -0,0 +1,4 @@ +target/ +.classpath +.project +.settings/ diff --git a/tests/helper/helper_docker.go b/tests/helper/helper_docker.go index f5fbfcfc64b..11940692b41 100644 --- a/tests/helper/helper_docker.go +++ b/tests/helper/helper_docker.go @@ -1,6 +1,7 @@ package helper import ( + "encoding/json" "fmt" "strings" @@ -84,6 +85,19 @@ func (d *DockerRunner) GetVolumesByLabel(label string) []string { return containers } +// VolumeExists returns true if a volume with the given name exists, false otherwise. +func (d *DockerRunner) VolumeExists(name string) bool { + vols := d.ListVolumes() + + for _, vol := range vols { + if vol == name { + return true + } + } + return false + +} + // GetVolumesByCompStorageName returns the list of volumes associated with a specific devfile volume in a component func (d *DockerRunner) GetVolumesByCompStorageName(component string, storageName string) []string { fmt.Fprintf(GinkgoWriter, "Listing Docker volumes with comp %s and storage name %s", component, storageName) @@ -96,7 +110,20 @@ func (d *DockerRunner) GetVolumesByCompStorageName(component string, storageName return containers } -// IsVolumeMountedInContainer returns true if the specified volume is moutned in the container associated with specified component and alias +// InspectVolume returns a map-representation of the JSON returned by the 'docker inspect volume' command +func (d *DockerRunner) InspectVolume(volumeName string) []map[string]interface{} { + + fmt.Fprintf(GinkgoWriter, "Inspecting volume %s", volumeName) + output := CmdShouldPass(d.path, "inspect", volumeName) + + var result []map[string]interface{} + err := json.Unmarshal([]byte(output), &result) + Expect(err).NotTo(HaveOccurred()) + + return result +} + +// IsVolumeMountedInContainer returns true if the specified volume is mounted in the container associated with specified component and alias func (d *DockerRunner) IsVolumeMountedInContainer(volumeName string, component string, alias string) bool { // Get the container ID of the specified component and alias containers := d.GetRunningContainersByCompAlias(component, alias) @@ -108,47 +135,74 @@ func (d *DockerRunner) IsVolumeMountedInContainer(volumeName string, component s return strings.Contains(mounts, volumeName) } -// ListVolumesOfComponentAndType lists all volumes that match the expected component/type labels -func (d *DockerRunner) ListVolumesOfComponentAndType(componentLabel string, typeLabel string) []string { +// GetSourceAndStorageVolumesByComponent lists only the volumes that are associated with this component +// and contain either the 'type' or 'storage-name' fields. +func (d *DockerRunner) GetSourceAndStorageVolumesByComponent(componentLabel string) []string { - fmt.Fprintf(GinkgoWriter, "Listing volumes with component label %s and type label %s", componentLabel, typeLabel) - session := CmdRunner(d.path, "volume", "ls", "-q", "--filter", "label=component="+componentLabel, "--filter", "label=type="+typeLabel) + result := []string{} - session.Wait() - if session.ExitCode() == 0 { + volumeList := d.GetVolumesByLabel("component=" + componentLabel) + if len(volumeList) == 0 { + return result + } + + fmt.Fprintf(GinkgoWriter, "Removing volumes with component label %s", componentLabel) - volumes := strings.Fields(strings.TrimSpace(string(session.Out.Contents()))) + for _, volumeName := range volumeList { + + // Only return volumes that contain the component label, and either 'type' or 'storage-name' + volumeJSON := d.InspectVolume(volumeName) + volumeLabels := (volumeJSON[0]["Labels"]).(map[string]interface{}) + + match := false + if typeValue, ok := volumeLabels["type"]; ok { + match = match || typeValue == "projects" + } + if _, ok := volumeLabels["storage-name"]; ok { + match = true + } - return volumes + if match { + result = append(result, volumeName) + } } - return []string{} + + return result + } -// RemoveVolumesByComponentAndType removes any volumes that match specified component and type labels -func (d *DockerRunner) RemoveVolumesByComponentAndType(componentLabel string, typeLabel string) string { +// RemoveVolumeByName removes a specific volume by name +func (d *DockerRunner) RemoveVolumeByName(volumeName string) *gexec.Session { + fmt.Fprintf(GinkgoWriter, "Removing volume with ID %s", volumeName) - volumes := d.ListVolumesOfComponentAndType(componentLabel, typeLabel) + session := CmdRunner(d.path, "volume", "rm", "-f", volumeName) + session.Wait() - if len(volumes) == 0 { + return session +} + +// RemoveVolumesByComponent removes source/storage volumes that match specified component +func (d *DockerRunner) RemoveVolumesByComponent(componentLabel string) string { + + volumeList := d.GetSourceAndStorageVolumesByComponent(componentLabel) + if len(volumeList) == 0 { return "" } - fmt.Fprintf(GinkgoWriter, "Removing volumes with component label %s and type label %s", componentLabel, typeLabel) - output := "" + fmt.Fprintf(GinkgoWriter, "Removing volumes with component label %s", componentLabel) - for _, volume := range volumes { + output := "" - fmt.Fprintf(GinkgoWriter, "Removing volume with ID %s", volume) + for _, volumeName := range volumeList { - session := CmdRunner(d.path, "volume", "rm", "-f", volume) - session.Wait() + session := d.RemoveVolumeByName(volumeName) sessionOut := strings.TrimSpace(string(session.Out.Contents())) if session.ExitCode() == 0 { output += sessionOut + " " } else { - fmt.Fprintf(GinkgoWriter, "Non-zero error code on removing volume with component label %s and type label %s, output: %s", componentLabel, typeLabel, sessionOut) + fmt.Fprintf(GinkgoWriter, "Non-zero error code on removing volume with component label %s, output: %s", componentLabel, sessionOut) } } @@ -170,3 +224,19 @@ func (d *DockerRunner) StopContainers(label string) { } } + +// CreateVolume creates an empty volume with the given name and labels +func (d *DockerRunner) CreateVolume(volumeName string, labels []string) { + fmt.Fprintf(GinkgoWriter, "Creating volume %s with labels %v", volumeName, labels) + + args := []string{"volume", "create"} + + for _, label := range labels { + args = append(args, "--label", label) + } + + args = append(args, volumeName) + + CmdShouldPass(d.path, args...) + +} diff --git a/tests/integration/cmd_app_test.go b/tests/integration/cmd_app_test.go index 016a8c3ee0a..75f010b366f 100644 --- a/tests/integration/cmd_app_test.go +++ b/tests/integration/cmd_app_test.go @@ -51,7 +51,7 @@ var _ = Describe("odo app command tests", func() { appList := helper.CmdShouldPass("odo", "app", "list", "--project", project) Expect(appList).To(ContainSubstring("There are no applications deployed")) actual := helper.CmdShouldPass("odo", "app", "list", "-o", "json", "--project", project) - desired := `{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[]}` + desired := `{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[]}` Expect(desired).Should(MatchJSON(actual)) appDelete := helper.CmdShouldFail("odo", "app", "delete", "test", "--project", project, "-f") @@ -78,11 +78,11 @@ var _ = Describe("odo app command tests", func() { Expect(appListOutput).To(ContainSubstring(appName)) actualCompListJSON := helper.CmdShouldPass("odo", "list", "-o", "json") - desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"nodejs","creationTimestamp":null, "namespace":"%s"},"spec":{"type":"nodejs","app":"app","sourceType": "local","env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}]}`, project) + desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"nodejs","creationTimestamp":null, "namespace":"%s"},"spec":{"type":"nodejs","app":"app","sourceType": "local","env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}]}`, project) Expect(desiredCompListJSON).Should(MatchJSON(actualCompListJSON)) helper.CmdShouldPass("odo", "app", "describe") - desiredDesAppJSON := fmt.Sprintf(`{"kind":"Application","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"myapp","namespace":"%s","creationTimestamp":null},"spec":{}}`, project) + desiredDesAppJSON := fmt.Sprintf(`{"kind":"Application","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"myapp","namespace":"%s","creationTimestamp":null},"spec":{}}`, project) actualDesAppJSON := helper.CmdShouldPass("odo", "app", "describe", "myapp", "-o", "json") Expect(desiredDesAppJSON).Should(MatchJSON(actualDesAppJSON)) @@ -113,12 +113,12 @@ var _ = Describe("odo app command tests", func() { appListOutput := helper.CmdShouldPass("odo", "app", "list", "--project", project) Expect(appListOutput).To(ContainSubstring(appName)) actualCompListJSON := helper.CmdShouldPass("odo", "app", "list", "-o", "json", "--project", project) - //desiredCompListJSON := `{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[]}` - desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"Application","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"app","namespace":"%s","creationTimestamp":null},"spec":{"components":["%s"]}}]}`, project, cmpName) + //desiredCompListJSON := `{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[]}` + desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"Application","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"app","namespace":"%s","creationTimestamp":null},"spec":{"components":["%s"]}}]}`, project, cmpName) Expect(desiredCompListJSON).Should(MatchJSON(actualCompListJSON)) helper.CmdShouldPass("odo", "app", "describe", appName, "--project", project) - desiredDesAppJSON := fmt.Sprintf(`{"kind":"Application","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"spec":{"components":["%s"]}}`, appName, project, cmpName) + desiredDesAppJSON := fmt.Sprintf(`{"kind":"Application","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"spec":{"components":["%s"]}}`, appName, project, cmpName) actualDesAppJSON := helper.CmdShouldPass("odo", "app", "describe", appName, "--project", project, "-o", "json") Expect(desiredDesAppJSON).Should(MatchJSON(actualDesAppJSON)) diff --git a/tests/integration/cmd_storage_test.go b/tests/integration/cmd_storage_test.go index 93d86533e6b..1ad9a4dc4fb 100644 --- a/tests/integration/cmd_storage_test.go +++ b/tests/integration/cmd_storage_test.go @@ -130,11 +130,11 @@ var _ = Describe("odo storage command tests", func() { helper.CopyExample(filepath.Join("source", "wildfly"), context) helper.CmdShouldPass("odo", "component", "create", "wildfly", "wildfly", "--app", "wildflyapp", "--project", project, "--context", context) actualJSONStorage := helper.CmdShouldPass("odo", "storage", "create", "mystorage", "--path=/opt/app-root/src/storage/", "--size=1Gi", "--context", context, "-o", "json") - desiredJSONStorage := `{"kind":"storage","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"mystorage","creationTimestamp":null},"spec":{"size":"1Gi","path":"/opt/app-root/src/storage/"}}` + desiredJSONStorage := `{"kind":"storage","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"mystorage","creationTimestamp":null},"spec":{"size":"1Gi","path":"/opt/app-root/src/storage/"}}` Expect(desiredJSONStorage).Should(MatchJSON(actualJSONStorage)) actualStorageList := helper.CmdShouldPass("odo", "storage", "list", "--context", context, "-o", "json") - desiredStorageList := `{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"storage","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"mystorage","creationTimestamp":null},"spec":{"size":"1Gi","path":"/opt/app-root/src/storage/"},"status":"Not Pushed"}]}` + desiredStorageList := `{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"storage","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"mystorage","creationTimestamp":null},"spec":{"size":"1Gi","path":"/opt/app-root/src/storage/"},"status":"Not Pushed"}]}` Expect(desiredStorageList).Should(MatchJSON(actualStorageList)) helper.CmdShouldPass("odo", "storage", "delete", "mystorage", "--context", context, "-f") diff --git a/tests/integration/cmd_url_test.go b/tests/integration/cmd_url_test.go index 7d29e1697f9..d72879f92f3 100644 --- a/tests/integration/cmd_url_test.go +++ b/tests/integration/cmd_url_test.go @@ -131,7 +131,7 @@ var _ = Describe("odo url command tests", func() { actualURLListJSON := helper.CmdShouldPass("odo", "url", "list", "-o", "json") fullURLPath := helper.DetermineRouteURL("") pathNoHTTP := strings.Split(fullURLPath, "//")[1] - desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"url","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"myurl","creationTimestamp":null},"spec":{"host":"%s","protocol":"http","port":8080,"secure":false},"status":{"state": "Pushed"}}]}`, pathNoHTTP) + desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"url","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"myurl","creationTimestamp":null},"spec":{"host":"%s","protocol":"http","port":8080,"secure":false},"status":{"state": "Pushed"}}]}`, pathNoHTTP) Expect(desiredURLListJSON).Should(MatchJSON(actualURLListJSON)) }) @@ -144,7 +144,7 @@ var _ = Describe("odo url command tests", func() { actualURLListJSON := helper.CmdShouldPass("odo", "url", "list", "-o", "json") fullURLPath := helper.DetermineRouteURL("") pathNoHTTP := strings.Split(fullURLPath, "//")[1] - desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"url","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"myurl","creationTimestamp":null},"spec":{"host":"%s","protocol":"https","port":8080,"secure":true},"status":{"state": "Pushed"}}]}`, pathNoHTTP) + desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"url","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"myurl","creationTimestamp":null},"spec":{"host":"%s","protocol":"https","port":8080,"secure":true},"status":{"state": "Pushed"}}]}`, pathNoHTTP) Expect(desiredURLListJSON).Should(MatchJSON(actualURLListJSON)) }) }) diff --git a/tests/integration/component.go b/tests/integration/component.go index 485fd858502..cd459d5241f 100644 --- a/tests/integration/component.go +++ b/tests/integration/component.go @@ -135,7 +135,7 @@ func componentTests(args ...string) { contextPath = strings.TrimSpace(context) } // this orders the json - desired, err := helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Not Pushed"}}`, project, contextPath)) + desired, err := helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Not Pushed"}}`, project, contextPath)) Expect(err).Should(BeNil()) actual, err := helper.Unindented(helper.CmdShouldPass("odo", append(args, "list", "-o", "json", "--path", filepath.Dir(context))...)) @@ -175,11 +175,11 @@ func componentTests(args ...string) { helper.DeleteDir(context2) helper.DeleteProject(project2) // this orders the json - expected, err := helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Pushed"}}`, project, contextPath)) + expected, err := helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Pushed"}}`, project, contextPath)) Expect(err).Should(BeNil()) Expect(actual).Should(ContainSubstring(expected)) // this orders the json - expected, err = helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"python","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"python","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Pushed"}}`, project2, contextPath2)) + expected, err = helper.Unindented(fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"python","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"python","sourceType": "local","ports":["8080/TCP"]},"status":{"context":"%s","state":"Pushed"}}`, project2, contextPath2)) Expect(err).Should(BeNil()) Expect(actual).Should(ContainSubstring(expected)) @@ -199,7 +199,7 @@ func componentTests(args ...string) { cmpList := helper.CmdShouldPass("odo", append(args, "list", "--project", project)...) Expect(cmpList).To(ContainSubstring("cmp-git")) actualCompListJSON := helper.CmdShouldPass("odo", append(args, "list", "--project", project, "-o", "json")...) - desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.openshift.io/v1alpha1","metadata":{},"items":[{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"cmp-git","namespace":"%s","creationTimestamp":null},"spec":{"app":"testing","type":"nodejs","source":"https://github.com/openshift/nodejs-ex","sourceType": "git","env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}]}`, project) + desiredCompListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"cmp-git","namespace":"%s","creationTimestamp":null},"spec":{"app":"testing","type":"nodejs","source":"https://github.com/openshift/nodejs-ex","sourceType": "git","env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}]}`, project) Expect(desiredCompListJSON).Should(MatchJSON(actualCompListJSON)) cmpAllList := helper.CmdShouldPass("odo", append(args, "list", "--all-apps")...) Expect(cmpAllList).To(ContainSubstring("cmp-git")) @@ -249,7 +249,7 @@ func componentTests(args ...string) { cmpDescribeJSON, err := helper.Unindented(helper.CmdShouldPass("odo", append(args, "describe", "-o", "json", "--context", context)...)) Expect(err).Should(BeNil()) - expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.openshift.io/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","source": "https://github.com/openshift/nodejs-ex","sourceType": "git","urls": {"kind": "List", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {}, "items": [{"kind": "url", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {"name": "url-1", "creationTimestamp": null}, "spec": {"port": 8080, "secure": false}, "status": {"state": "Not Pushed"}}, {"kind": "url", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {"name": "url-2", "creationTimestamp": null}, "spec": {"port": 8080, "secure": false}, "status": {"state": "Not Pushed"}}]},"storages": {"kind": "List", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {}, "items": [{"kind": "storage", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {"name": "storage-1", "creationTimestamp": null}, "spec": {"size": "1Gi", "path": "/data1"}}]},"ports": ["8080/TCP", "8080/TCP"]},"status": {"state": "Not Pushed"}}`) + expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.dev/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","source": "https://github.com/openshift/nodejs-ex","sourceType": "git","urls": {"kind": "List", "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [{"kind": "url", "apiVersion": "odo.dev/v1alpha1", "metadata": {"name": "url-1", "creationTimestamp": null}, "spec": {"port": 8080, "secure": false}, "status": {"state": "Not Pushed"}}, {"kind": "url", "apiVersion": "odo.dev/v1alpha1", "metadata": {"name": "url-2", "creationTimestamp": null}, "spec": {"port": 8080, "secure": false}, "status": {"state": "Not Pushed"}}]},"storages": {"kind": "List", "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": [{"kind": "storage", "apiVersion": "odo.dev/v1alpha1", "metadata": {"name": "storage-1", "creationTimestamp": null}, "spec": {"size": "1Gi", "path": "/data1"}}]},"ports": ["8080/TCP", "8080/TCP"]},"status": {"state": "Not Pushed"}}`) Expect(err).Should(BeNil()) Expect(cmpDescribeJSON).To(Equal(expected)) @@ -264,7 +264,7 @@ func componentTests(args ...string) { helper.CopyExample(filepath.Join("source", "nodejs"), context) cmpDescribeJSON, err := helper.Unindented(helper.CmdShouldPass("odo", append(args, "create", "nodejs", "cmp-git", "--project", project, "--context", context, "--app", "testing", "-o", "json")...)) Expect(err).Should(BeNil()) - expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.openshift.io/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","source": "file://./","sourceType": "local","ports": ["8080/TCP"]}, "status": {"state": "Not Pushed"}}`) + expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.dev/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","source": "file://./","sourceType": "local","ports": ["8080/TCP"]}, "status": {"state": "Not Pushed"}}`) Expect(err).Should(BeNil()) Expect(cmpDescribeJSON).To(Equal(expected)) helper.CmdShouldPass("odo", append(args, "delete", "-f", "--all", "--context", context)...) @@ -274,7 +274,7 @@ func componentTests(args ...string) { helper.CopyExample(filepath.Join("source", "nodejs"), context) cmpDescribeJSON, err := helper.Unindented(helper.CmdShouldPass("odo", append(args, "create", "nodejs", "cmp-git", "--project", project, "--context", context, "--app", "testing", "-o", "json", "--now")...)) Expect(err).Should(BeNil()) - expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.openshift.io/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","sourceType": "local","env": [{"name": "DEBUG_PORT","value": "5858"}],"ports": ["8080/TCP"]}, "status": {"state": "Pushed"}}`) + expected, err := helper.Unindented(`{"kind": "Component","apiVersion": "odo.dev/v1alpha1","metadata": {"name": "cmp-git","namespace": "` + project + `","creationTimestamp": null},"spec":{"app": "testing","type":"nodejs","sourceType": "local","env": [{"name": "DEBUG_PORT","value": "5858"}],"ports": ["8080/TCP"]}, "status": {"state": "Pushed"}}`) Expect(err).Should(BeNil()) Expect(cmpDescribeJSON).To(Equal(expected)) helper.CmdShouldPass("odo", append(args, "delete", "-f", "--all", "--context", context)...) @@ -702,7 +702,7 @@ func componentTests(args ...string) { actualDesCompJSON := helper.CmdShouldPass("odo", append(args, "describe", cmpName, "--app", appName, "--project", project, "-o", "json")...) - desiredDesCompJSON := fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local", "urls": {"kind": "List", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {}, "items": null}, "storages": {"kind": "List", "apiVersion": "odo.openshift.io/v1alpha1", "metadata": {}, "items": null}, "env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}`, project) + desiredDesCompJSON := fmt.Sprintf(`{"kind":"Component","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"nodejs","namespace":"%s","creationTimestamp":null},"spec":{"app":"app","type":"nodejs","sourceType": "local", "urls": {"kind": "List", "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": null}, "storages": {"kind": "List", "apiVersion": "odo.dev/v1alpha1", "metadata": {}, "items": null}, "env":[{"name":"DEBUG_PORT","value":"5858"}]},"status":{"state":"Pushed"}}`, project) Expect(desiredDesCompJSON).Should(MatchJSON(actualDesCompJSON)) helper.CmdShouldPass("odo", append(args, "delete", cmpName, "--app", appName, "--project", project, "-f")...) diff --git a/tests/integration/devfile/cmd_devfile_catalog_test.go b/tests/integration/devfile/cmd_devfile_catalog_test.go index 4b9c0cdebda..d2aa5f7a85a 100644 --- a/tests/integration/devfile/cmd_devfile_catalog_test.go +++ b/tests/integration/devfile/cmd_devfile_catalog_test.go @@ -48,14 +48,33 @@ var _ = Describe("odo devfile catalog command tests", func() { Context("When executing catalog list components", func() { It("should list all supported devfile components", func() { output := helper.CmdShouldPass("odo", "catalog", "list", "components") - helper.MatchAllInOutput(output, []string{"Odo Devfile Components", "java-spring-boot", "openLiberty"}) + wantOutput := []string{ + "Odo Devfile Components", + "NAME", + "java-spring-boot", + "openLiberty", + "DESCRIPTION", + "REGISTRY", + "SUPPORTED", + } + helper.MatchAllInOutput(output, wantOutput) }) }) Context("When executing catalog list components with -a flag", func() { It("should list all supported and unsupported devfile components", func() { output := helper.CmdShouldPass("odo", "catalog", "list", "components", "-a") - helper.MatchAllInOutput(output, []string{"Odo Devfile Components", "java-spring-boot", "java-maven", "php-mysql"}) + wantOutput := []string{ + "Odo Devfile Components", + "NAME", + "java-spring-boot", + "java-maven", + "php-mysql", + "DESCRIPTION", + "REGISTRY", + "SUPPORTED", + } + helper.MatchAllInOutput(output, wantOutput) }) }) }) diff --git a/tests/integration/devfile/cmd_devfile_create_test.go b/tests/integration/devfile/cmd_devfile_create_test.go index e9e58702a59..0bfdc16bfac 100644 --- a/tests/integration/devfile/cmd_devfile_create_test.go +++ b/tests/integration/devfile/cmd_devfile_create_test.go @@ -79,6 +79,13 @@ var _ = Describe("odo devfile create command tests", func() { }) }) + Context("When executing odo create with devfile component type argument and --registry flag", func() { + It("should successfully create the devfile component", func() { + componentRegistry := "DefaultDevfileRegistry" + helper.CmdShouldPass("odo", "create", "openLiberty", "--registry", componentRegistry) + }) + }) + Context("When executing odo create with devfile component type argument and --context flag", func() { It("should successfully create the devfile component in the context", func() { newContext := path.Join(context, "newContext") diff --git a/tests/integration/devfile/cmd_devfile_delete_test.go b/tests/integration/devfile/cmd_devfile_delete_test.go index d4819a416b8..5e67256e8eb 100644 --- a/tests/integration/devfile/cmd_devfile_delete_test.go +++ b/tests/integration/devfile/cmd_devfile_delete_test.go @@ -12,8 +12,7 @@ import ( ) var _ = Describe("odo devfile delete command tests", func() { - var namespace, context, currentWorkingDirectory, componentName, projectDirPath string - var projectDir = "/projectDir" + var namespace, context, currentWorkingDirectory, componentName string // TODO: all oc commands in all devfile related test should get replaced by kubectl // TODO: to goal is not to use "oc" @@ -25,7 +24,6 @@ var _ = Describe("odo devfile delete command tests", func() { namespace = helper.CreateRandProject() context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() - projectDirPath = context + projectDir componentName = helper.RandString(6) helper.Chdir(context) @@ -48,12 +46,10 @@ var _ = Describe("odo devfile delete command tests", func() { Context("when devfile delete command is executed", func() { It("should delete the component created from the devfile and also the owned resources", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "url", "create", "example", "--host", "1.2.3.4.nip.io") @@ -71,22 +67,20 @@ var _ = Describe("odo devfile delete command tests", func() { Context("when devfile delete command is executed with all flag", func() { It("should delete the component created from the devfile and also the env and odo folders and the odo-index-file.json file", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) - helper.CmdShouldPass("odo", "url", "create", "example", "--host", "1.2.3.4.nip.io", "--context", projectDirPath) + helper.CmdShouldPass("odo", "url", "create", "example", "--host", "1.2.3.4.nip.io", "--context", context) helper.CmdShouldPass("odo", "delete", "--devfile", "devfile.yaml", "--project", namespace, "-f", "--all") oc.WaitAndCheckForExistence("deployments", namespace, 1) - files := helper.ListFilesInDir(projectDirPath) + files := helper.ListFilesInDir(context) Expect(files).To(Not(ContainElement(".odo"))) }) }) diff --git a/tests/integration/devfile/cmd_devfile_push_test.go b/tests/integration/devfile/cmd_devfile_push_test.go index 7e54b07b4ea..4fdeb66a0f8 100644 --- a/tests/integration/devfile/cmd_devfile_push_test.go +++ b/tests/integration/devfile/cmd_devfile_push_test.go @@ -14,8 +14,7 @@ import ( ) var _ = Describe("odo devfile push command tests", func() { - var namespace, context, cmpName, currentWorkingDirectory, projectDirPath string - var projectDir = "/projectDir" + var namespace, context, cmpName, currentWorkingDirectory string var sourcePath = "/projects/nodejs-web-app" // TODO: all oc commands in all devfile related test should get replaced by kubectl @@ -28,7 +27,6 @@ var _ = Describe("odo devfile push command tests", func() { namespace = helper.CreateRandProject() context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() - projectDirPath = context + projectDir cmpName = helper.RandString(6) helper.Chdir(context) @@ -51,15 +49,12 @@ var _ = Describe("odo devfile push command tests", func() { Context("Verify devfile push works", func() { It("should have no errors when no endpoints within the devfile, should create a service when devfile has endpoints", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) - + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) helper.RenameFile("devfile.yaml", "devfile-old.yaml") - helper.RenameFile("devfile-no-endpoints.yaml", "devfile.yaml") + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-no-endpoints.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) output := oc.GetServices(namespace) Expect(output).NotTo(ContainSubstring(cmpName)) @@ -73,12 +68,10 @@ var _ = Describe("odo devfile push command tests", func() { }) It("checks that odo push works with a devfile", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) Expect(output).To(ContainSubstring("Changes successfully pushed to component")) @@ -88,18 +81,26 @@ var _ = Describe("odo devfile push command tests", func() { helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) }) - }) + It("checks that odo push works outside of the context directory", func() { + helper.Chdir(currentWorkingDirectory) + + helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, "--context", context, cmpName) - Context("When devfile push command is executed", func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--context", context) + Expect(output).To(ContainSubstring("Changes successfully pushed to component")) + }) It("should not build when no changes are detected in the directory and build when a file change is detected", func() { - utils.ExecPushToTestFileChanges(projectDirPath, cmpName, namespace) + utils.ExecPushToTestFileChanges(context, cmpName, namespace) }) It("should be able to create a file, push, delete, then push again propagating the deletions", func() { - newFilePath := filepath.Join(projectDirPath, "foobar.txt") - newDirPath := filepath.Join(projectDirPath, "testdir") - utils.ExecPushWithNewFileAndDir(projectDirPath, cmpName, namespace, newFilePath, newDirPath) + newFilePath := filepath.Join(context, "foobar.txt") + newDirPath := filepath.Join(context, "testdir") + utils.ExecPushWithNewFileAndDir(context, cmpName, namespace, newFilePath, newDirPath) // Check to see if it's been pushed (foobar.txt abd directory testdir) podName := oc.GetRunningPodNameByComponent(cmpName, namespace) @@ -120,12 +121,10 @@ var _ = Describe("odo devfile push command tests", func() { }) It("should delete the files from the container if its removed locally", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) @@ -144,7 +143,7 @@ var _ = Describe("odo devfile push command tests", func() { }, ) Expect(statErr).ToNot(HaveOccurred()) - Expect(os.Remove(filepath.Join(projectDirPath, "app", "app.js"))).NotTo(HaveOccurred()) + Expect(os.Remove(filepath.Join(context, "app", "app.js"))).NotTo(HaveOccurred()) helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) oc.CheckCmdOpInRemoteDevfilePod( @@ -162,11 +161,11 @@ var _ = Describe("odo devfile push command tests", func() { }) It("should build when no changes are detected in the directory and force flag is enabled", func() { - utils.ExecPushWithForceFlag(projectDirPath, cmpName, namespace) + utils.ExecPushWithForceFlag(context, cmpName, namespace) }) It("should execute the default devbuild and devrun commands if present", func() { - utils.ExecDefaultDevfileCommands(projectDirPath, cmpName, namespace) + utils.ExecDefaultDevfileCommands(context, cmpName, namespace) // Check to see if it's been pushed (foobar.txt abd directory testdir) podName := oc.GetRunningPodNameByComponent(cmpName, namespace) @@ -189,61 +188,51 @@ var _ = Describe("odo devfile push command tests", func() { }) It("should execute devinit command if present", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "java-spring-boot", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "springboot"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile-init.yaml"), filepath.Join(context, "devfile.yaml")) - output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init.yaml", "--namespace", namespace) + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) Expect(output).To(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devbuild command \"/artifacts/bin/build-container-full.sh\"")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) }) It("should execute devinit and devrun commands if present", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "java-spring-boot", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "springboot"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile-init-without-build.yaml"), filepath.Join(context, "devfile.yaml")) - output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init-without-build.yaml", "--namespace", namespace) + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) Expect(output).To(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) }) It("should only execute devinit command once if component is already created", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "java-spring-boot", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "springboot"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile-init.yaml"), filepath.Join(context, "devfile.yaml")) - output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init.yaml", "--namespace", namespace) + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) Expect(output).To(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devbuild command \"/artifacts/bin/build-container-full.sh\"")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) // Need to force so build and run get triggered again with the component already created. - output = helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init.yaml", "--namespace", namespace, "-f") + output = helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace, "-f") Expect(output).NotTo(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devbuild command \"/artifacts/bin/build-container-full.sh\"")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) }) It("should be able to handle a missing devinit command", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) - helper.RenameFile("devfile.yaml", "devfile-old.yaml") - helper.RenameFile("devfile-without-devinit.yaml", "devfile.yaml") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-without-devinit.yaml"), filepath.Join(context, "devfile.yaml")) output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) Expect(output).NotTo(ContainSubstring("Executing devinit command")) @@ -252,34 +241,30 @@ var _ = Describe("odo devfile push command tests", func() { }) It("should be able to handle a missing devbuild command", func() { - utils.ExecWithMissingBuildCommand(projectDirPath, cmpName, namespace) + utils.ExecWithMissingBuildCommand(context, cmpName, namespace) }) It("should error out on a missing devrun command", func() { - utils.ExecWithMissingRunCommand(projectDirPath, cmpName, namespace) + utils.ExecWithMissingRunCommand(context, cmpName, namespace) }) It("should be able to push using the custom commands", func() { - utils.ExecWithCustomCommand(projectDirPath, cmpName, namespace) + utils.ExecWithCustomCommand(context, cmpName, namespace) }) It("should error out on a wrong custom commands", func() { - utils.ExecWithWrongCustomCommand(projectDirPath, cmpName, namespace) + utils.ExecWithWrongCustomCommand(context, cmpName, namespace) }) It("should not restart the application if restart is false", func() { - utils.ExecWithRestartAttribute(projectDirPath, cmpName, namespace) + utils.ExecWithRestartAttribute(context, cmpName, namespace) }) It("should create pvc and reuse if it shares the same devfile volume name", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) - helper.RenameFile("devfile.yaml", "devfile-old.yaml") - helper.RenameFile("devfile-with-volumes.yaml", "devfile.yaml") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volumes.yaml"), filepath.Join(context, "devfile.yaml")) output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) Expect(output).To(ContainSubstring("Executing devinit command")) @@ -345,7 +330,6 @@ var _ = Describe("odo devfile push command tests", func() { } Expect(volumesMatched).To(Equal(true)) }) - }) }) diff --git a/tests/integration/devfile/cmd_devfile_registry_test.go b/tests/integration/devfile/cmd_devfile_registry_test.go new file mode 100644 index 00000000000..85c27e0297c --- /dev/null +++ b/tests/integration/devfile/cmd_devfile_registry_test.go @@ -0,0 +1,93 @@ +package devfile + +import ( + "os" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/openshift/odo/tests/helper" +) + +var _ = Describe("odo devfile registry command tests", func() { + var project string + var context string + var currentWorkingDirectory string + const registryName string = "RegistryName" + const addRegistryURL string = "https://raw.githubusercontent.com/GeekArthur/registry/master" + const updateRegistryURL string = "http://www.example.com/update" + + // This is run after every Spec (It) + var _ = BeforeEach(func() { + SetDefaultEventuallyTimeout(10 * time.Minute) + context = helper.CreateNewContext() + os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) + helper.CmdShouldPass("odo", "preference", "set", "Experimental", "true") + if os.Getenv("KUBERNETES") == "true" { + project = helper.CreateRandNamespace(context) + } else { + project = helper.CreateRandProject() + } + currentWorkingDirectory = helper.Getwd() + helper.Chdir(context) + }) + + // This is run after every Spec (It) + var _ = AfterEach(func() { + if os.Getenv("KUBERNETES") == "true" { + helper.DeleteNamespace(project) + } else { + helper.DeleteProject(project) + } + helper.Chdir(currentWorkingDirectory) + helper.DeleteDir(context) + }) + + Context("When executing registry list", func() { + It("Should list all default registries", func() { + output := helper.CmdShouldPass("odo", "registry", "list") + helper.MatchAllInOutput(output, []string{"CheDevfileRegistry", "DefaultDevfileRegistry"}) + }) + }) + + Context("When executing registry commands with the registry is not present", func() { + It("Should successfully add the registry", func() { + helper.CmdShouldPass("odo", "registry", "add", registryName, addRegistryURL) + output := helper.CmdShouldPass("odo", "registry", "list") + helper.MatchAllInOutput(output, []string{registryName, addRegistryURL}) + helper.CmdShouldPass("odo", "create", "nodejs", "--registry", registryName) + helper.CmdShouldPass("odo", "registry", "delete", registryName, "-f") + }) + + It("Should fail to update the registry", func() { + helper.CmdShouldFail("odo", "registry", "update", registryName, updateRegistryURL, "-f") + }) + + It("Should fail to delete the registry", func() { + helper.CmdShouldFail("odo", "registry", "delete", registryName, "-f") + }) + }) + + Context("When executing registry commands with the registry is present", func() { + It("Should fail to add the registry", func() { + helper.CmdShouldPass("odo", "registry", "add", registryName, addRegistryURL) + helper.CmdShouldFail("odo", "registry", "add", registryName, addRegistryURL) + helper.CmdShouldPass("odo", "registry", "delete", registryName, "-f") + }) + + It("Should successfully update the registry", func() { + helper.CmdShouldPass("odo", "registry", "add", registryName, addRegistryURL) + helper.CmdShouldPass("odo", "registry", "update", registryName, updateRegistryURL, "-f") + output := helper.CmdShouldPass("odo", "registry", "list") + helper.MatchAllInOutput(output, []string{registryName, updateRegistryURL}) + helper.CmdShouldPass("odo", "registry", "delete", registryName, "-f") + }) + + It("Should successfully delete the registry", func() { + helper.CmdShouldPass("odo", "registry", "add", registryName, addRegistryURL) + helper.CmdShouldPass("odo", "registry", "delete", registryName, "-f") + helper.CmdShouldFail("odo", "create", "maven", "--registry", registryName) + }) + }) +}) diff --git a/tests/integration/devfile/cmd_devfile_url_test.go b/tests/integration/devfile/cmd_devfile_url_test.go index 0bada66ad17..f430f93c2c6 100644 --- a/tests/integration/devfile/cmd_devfile_url_test.go +++ b/tests/integration/devfile/cmd_devfile_url_test.go @@ -14,8 +14,7 @@ import ( ) var _ = Describe("odo devfile url command tests", func() { - var namespace, context, componentName, currentWorkingDirectory, projectDirPath string - var projectDir = "/projectDir" + var namespace, context, componentName, currentWorkingDirectory string // This is run after every Spec (It) var _ = BeforeEach(func() { @@ -24,7 +23,6 @@ var _ = Describe("odo devfile url command tests", func() { namespace = helper.CreateRandProject() context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() - projectDirPath = context + projectDir componentName = helper.RandString(6) helper.Chdir(context) @@ -50,12 +48,10 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) host := helper.RandString(5) + ".com" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) stdout = helper.CmdShouldFail("odo", "url", "list") Expect(stdout).To(ContainSubstring("no URLs found")) @@ -86,19 +82,17 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) host := helper.RandString(5) + ".com" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000", "--host", host, "--ingress") helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) // odo url list -o json helper.WaitForCmdOut("odo", []string{"url", "list", "-o", "json"}, 1, true, func(output string) bool { - desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"udo.udo.io/v1alpha1","metadata":{},"items":[{"kind":"Ingress","apiVersion":"extensions/v1beta1","metadata":{"name":"%s","creationTimestamp":null},"spec":{"rules":[{"host":"%s","http":{"paths":[{"path":"/","backend":{"serviceName":"%s","servicePort":3000}}]}}]},"status":{"loadBalancer":{}}}]}`, url1, url1+"."+host, componentName) + desiredURLListJSON := fmt.Sprintf(`{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"Ingress","apiVersion":"extensions/v1beta1","metadata":{"name":"%s","creationTimestamp":null},"spec":{"rules":[{"host":"%s","http":{"paths":[{"path":"/","backend":{"serviceName":"%s","servicePort":3000}}]}}]},"status":{"loadBalancer":{}}}]}`, url1, url1+"."+host, componentName) if strings.Contains(output, url1) { Expect(desiredURLListJSON).Should(MatchJSON(output)) return true @@ -114,12 +108,10 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) host := helper.RandString(5) + ".com" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000", "--host", host, "--secure", "--ingress") @@ -139,12 +131,10 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) host := helper.RandString(5) + ".com" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) stdout = helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000", "--host", host, "--now", "--ingress", "--devfile", "devfile.yaml") helper.MatchAllInOutput(stdout, []string{"URL " + url1 + " created for component", "http:", url1 + "." + host}) @@ -158,22 +148,28 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "url", "create", url1) helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) + pushStdOut := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) + Expect(pushStdOut).NotTo(ContainSubstring("successfully deleted")) + Expect(pushStdOut).NotTo(ContainSubstring("created")) + Expect(pushStdOut).To(ContainSubstring("URLs are synced with the cluster, no changes are required")) output := helper.CmdShouldPass("oc", "get", "routes", "--namespace", namespace) Expect(output).Should(ContainSubstring(url1)) helper.CmdShouldPass("odo", "url", "delete", url1, "-f") helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) + pushStdOut = helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--namespace", namespace) + Expect(pushStdOut).NotTo(ContainSubstring("successfully deleted")) + Expect(pushStdOut).NotTo(ContainSubstring("created")) + Expect(pushStdOut).To(ContainSubstring("URLs are synced with the cluster, no changes are required")) output = helper.CmdShouldPass("oc", "get", "routes", "--namespace", namespace) Expect(output).ShouldNot(ContainSubstring(url1)) @@ -194,6 +190,33 @@ var _ = Describe("odo devfile url command tests", func() { output := helper.CmdShouldPass("oc", "get", "routes", "--namespace", namespace) Expect(output).Should(ContainSubstring(url1)) }) + + It("should be able to push again twice after creating and deleting a url", func() { + var stdOut string + url1 := helper.RandString(5) + host := helper.RandString(5) + ".com" + + helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + + helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000", "--host", host, "--ingress") + + helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) + stdOut = helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) + Expect(stdOut).NotTo(ContainSubstring("successfully deleted")) + Expect(stdOut).NotTo(ContainSubstring("created")) + Expect(stdOut).To(ContainSubstring("URLs are synced with the cluster, no changes are required")) + + helper.CmdShouldPass("odo", "url", "delete", url1, "-f") + + helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) + stdOut = helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml", "--project", namespace) + Expect(stdOut).NotTo(ContainSubstring("successfully deleted")) + Expect(stdOut).NotTo(ContainSubstring("created")) + Expect(stdOut).To(ContainSubstring("URLs are synced with the cluster, no changes are required")) + }) }) Context("Describing urls", func() { @@ -202,12 +225,10 @@ var _ = Describe("odo devfile url command tests", func() { url1 := helper.RandString(5) host := helper.RandString(5) + ".com" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, componentName) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000", "--host", host, "--ingress") diff --git a/tests/integration/devfile/cmd_devfile_watch_test.go b/tests/integration/devfile/cmd_devfile_watch_test.go index d05adef7e09..71c34a7db98 100644 --- a/tests/integration/devfile/cmd_devfile_watch_test.go +++ b/tests/integration/devfile/cmd_devfile_watch_test.go @@ -63,9 +63,9 @@ var _ = Describe("odo devfile watch command tests", func() { // Devfile push requires experimental mode to be set helper.CmdShouldPass("odo", "preference", "set", "Experimental", "true") cmpName := helper.RandString(6) + helper.Chdir(currentWorkingDirectory) helper.CmdShouldPass("odo", "create", "nodejs", "--project", namespace, "--context", context, cmpName) - - output := helper.CmdShouldFail("odo", "watch", "--devfile", filepath.Join(context, "devfile.yaml"), "--context", context) + output := helper.CmdShouldFail("odo", "watch", "--context", context) Expect(output).To(ContainSubstring("component does not exist. Please use `odo push` to create your component")) }) }) diff --git a/tests/integration/devfile/docker/cmd_docker_devfile_delete_test.go b/tests/integration/devfile/docker/cmd_docker_devfile_delete_test.go index 6a4084de2ea..5a6589f0245 100644 --- a/tests/integration/devfile/docker/cmd_docker_devfile_delete_test.go +++ b/tests/integration/devfile/docker/cmd_docker_devfile_delete_test.go @@ -14,8 +14,8 @@ var _ = Describe("odo docker devfile delete command tests", func() { var context string var currentWorkingDirectory string var cmpName string - var projectDir = "/projectDir" - var projectDirPath string + + var fakeVolumes []string dockerClient := helper.NewDockerRunner("docker") @@ -25,24 +25,49 @@ var _ = Describe("odo docker devfile delete command tests", func() { context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() cmpName = helper.RandString(6) - projectDirPath = context + projectDir helper.Chdir(context) os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) // Devfile commands require experimental mode to be set helper.CmdShouldPass("odo", "preference", "set", "Experimental", "true") helper.CmdShouldPass("odo", "preference", "set", "pushtarget", "docker") + + // With our docker delete code, we want to avoid deleting volumes that we didn't create. So + // next we create a set of fake volumes, none of which should be deleted (eg they are out of scope) by any of the tests. + + // 1) Create volume with fake component but valid type + volname1 := cmpName + "-fakevol1" + fakeVolumes = append(fakeVolumes, volname1) + dockerClient.CreateVolume(volname1, []string{"component=fake", "type=projects"}) + + // 2) Create volume with fake component but valid storage + volname2 := cmpName + "-fakevol2" + fakeVolumes = append(fakeVolumes, volname2) + dockerClient.CreateVolume(volname2, []string{"component=fake", "storage-name=" + volname2}) + + // 3) Create volume with real component but neither valid source ("type") nor valid storage + volname3 := cmpName + "-fakevol3" + fakeVolumes = append(fakeVolumes, volname3) + dockerClient.CreateVolume(volname3, []string{"component=" + cmpName, "type=not-projects", "storage-name-fake=" + volname3}) + }) // Clean up after the test // This is run after every Spec (It) var _ = AfterEach(func() { + // Ensure that our fake volumes all still exist, then clean them up. + for _, fakeVolume := range fakeVolumes { + Expect(dockerClient.VolumeExists(fakeVolume)).To(Equal(true)) + dockerClient.RemoveVolumeByName(fakeVolume) + } + fakeVolumes = []string{} + // Stop all containers labeled with the component name label := "component=" + cmpName dockerClient.StopContainers(label) - dockerClient.RemoveVolumesByComponentAndType(cmpName, "projects") + dockerClient.RemoveVolumesByComponent(cmpName) helper.Chdir(currentWorkingDirectory) helper.DeleteDir(context) @@ -53,49 +78,78 @@ var _ = Describe("odo docker devfile delete command tests", func() { Context("when docker devfile delete command is executed", func() { It("should delete the component created from the devfile and also the owned resources", func() { - - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") Expect(dockerClient.GetRunningContainersByLabel("component=" + cmpName)).To(HaveLen(1)) - Expect(dockerClient.ListVolumesOfComponentAndType(cmpName, "projects")).To(HaveLen(1)) + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(1)) + + helper.CmdShouldPass("odo", "delete", "--devfile", "devfile.yaml", "-f") + + Expect(dockerClient.GetRunningContainersByLabel("component=" + cmpName)).To(HaveLen(0)) + + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(0)) + + }) + + It("should delete all the mounted volume types listed in the devfile", func() { + + helper.CmdShouldPass("odo", "create", "nodejs", "--context", context, cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), context) + helper.RenameFile("devfile.yaml", "devfile-old.yaml") + helper.RenameFile("devfile-with-volumes.yaml", "devfile.yaml") + + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") + Expect(output).To(ContainSubstring("Changes successfully pushed to component")) + + // Retrieve the volume from one of the aliases in the devfile + volumes := dockerClient.GetVolumesByCompStorageName(cmpName, "myvol") + Expect(len(volumes)).To(Equal(1)) + vol := volumes[0] + + // Verify the volume is mounted + volMounted := dockerClient.IsVolumeMountedInContainer(vol, cmpName, "runtime") + Expect(volMounted).To(Equal(true)) + + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(3)) helper.CmdShouldPass("odo", "delete", "--devfile", "devfile.yaml", "-f") Expect(dockerClient.GetRunningContainersByLabel("component=" + cmpName)).To(HaveLen(0)) - Expect(dockerClient.ListVolumesOfComponentAndType(cmpName, "projects")).To(HaveLen(0)) + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(0)) }) + }) Context("when docker devfile delete command is executed with all flag", func() { It("should delete the component created from the devfile and also the env folder", func() { - - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") Expect(dockerClient.GetRunningContainersByLabel("component=" + cmpName)).To(HaveLen(1)) - Expect(dockerClient.ListVolumesOfComponentAndType(cmpName, "projects")).To(HaveLen(1)) + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(1)) helper.CmdShouldPass("odo", "delete", "--devfile", "devfile.yaml", "-f", "--all") Expect(dockerClient.GetRunningContainersByLabel("component=" + cmpName)).To(HaveLen(0)) - Expect(dockerClient.ListVolumesOfComponentAndType(cmpName, "projects")).To(HaveLen(0)) + Expect(dockerClient.GetSourceAndStorageVolumesByComponent(cmpName)).To(HaveLen(0)) - files := helper.ListFilesInDir(projectDirPath) + files := helper.ListFilesInDir(context) Expect(files).To(Not(ContainElement(".odo"))) }) diff --git a/tests/integration/devfile/docker/cmd_docker_devfile_push_test.go b/tests/integration/devfile/docker/cmd_docker_devfile_push_test.go index 6b64745d4e4..de4c43f3e48 100644 --- a/tests/integration/devfile/docker/cmd_docker_devfile_push_test.go +++ b/tests/integration/devfile/docker/cmd_docker_devfile_push_test.go @@ -13,8 +13,7 @@ import ( ) var _ = Describe("odo docker devfile push command tests", func() { - var context, currentWorkingDirectory, cmpName, projectDirPath string - var projectDir = "/projectDir" + var context, currentWorkingDirectory, cmpName string var sourcePath = "/projects/nodejs-web-app" dockerClient := helper.NewDockerRunner("docker") @@ -24,7 +23,6 @@ var _ = Describe("odo docker devfile push command tests", func() { SetDefaultEventuallyTimeout(10 * time.Minute) context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() - projectDirPath = context + projectDir cmpName = helper.RandString(6) helper.Chdir(context) os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) @@ -109,13 +107,13 @@ var _ = Describe("odo docker devfile push command tests", func() { }) It("should not build when no changes are detected in the directory and build when a file change is detected", func() { - utils.ExecPushToTestFileChanges(projectDirPath, cmpName, "") + utils.ExecPushToTestFileChanges(context, cmpName, "") }) It("should be able to create a file, push, delete, then push again propagating the deletions", func() { - newFilePath := filepath.Join(projectDirPath, "foobar.txt") - newDirPath := filepath.Join(projectDirPath, "testdir") - utils.ExecPushWithNewFileAndDir(projectDirPath, cmpName, "", newFilePath, newDirPath) + newFilePath := filepath.Join(context, "foobar.txt") + newDirPath := filepath.Join(context, "testdir") + utils.ExecPushWithNewFileAndDir(context, cmpName, "", newFilePath, newDirPath) // Check to see if it's been pushed (foobar.txt abd directory testdir) containers := dockerClient.GetRunningContainersByCompAlias(cmpName, "runtime") @@ -137,11 +135,11 @@ var _ = Describe("odo docker devfile push command tests", func() { }) It("should build when no changes are detected in the directory and force flag is enabled", func() { - utils.ExecPushWithForceFlag(projectDirPath, cmpName, "") + utils.ExecPushWithForceFlag(context, cmpName, "") }) It("should execute the default devbuild and devrun commands if present", func() { - utils.ExecDefaultDevfileCommands(projectDirPath, cmpName, "") + utils.ExecDefaultDevfileCommands(context, cmpName, "") // Check to see if it's been pushed (foobar.txt abd directory testdir) containers := dockerClient.GetRunningContainersByCompAlias(cmpName, "runtime") @@ -152,15 +150,12 @@ var _ = Describe("odo docker devfile push command tests", func() { }) It("should execute the optional devinit, and devrun commands if present", func() { - - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "java-spring-boot", cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "springboot"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile-init.yaml"), filepath.Join(context, "devfile.yaml")) - output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init.yaml") + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") Expect(output).To(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devbuild command \"/artifacts/bin/build-container-full.sh\"")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) @@ -174,15 +169,12 @@ var _ = Describe("odo docker devfile push command tests", func() { }) It("should execute devinit and devrun commands if present", func() { - - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "java-spring-boot", cmpName) - helper.CopyExample(filepath.Join("source", "devfiles", "springboot"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile-init-without-build.yaml"), filepath.Join(context, "devfile.yaml")) - output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile-init-without-build.yaml") + output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") Expect(output).To(ContainSubstring("Executing devinit command \"echo hello")) Expect(output).To(ContainSubstring("Executing devrun command \"/artifacts/bin/start-server.sh\"")) @@ -195,19 +187,19 @@ var _ = Describe("odo docker devfile push command tests", func() { }) It("should be able to handle a missing devbuild command", func() { - utils.ExecWithMissingBuildCommand(projectDirPath, cmpName, "") + utils.ExecWithMissingBuildCommand(context, cmpName, "") }) It("should error out on a missing devrun command", func() { - utils.ExecWithMissingRunCommand(projectDirPath, cmpName, "") + utils.ExecWithMissingRunCommand(context, cmpName, "") }) It("should be able to push using the custom commands", func() { - utils.ExecWithCustomCommand(projectDirPath, cmpName, "") + utils.ExecWithCustomCommand(context, cmpName, "") }) It("should error out on a wrong custom commands", func() { - utils.ExecWithWrongCustomCommand(projectDirPath, cmpName, "") + utils.ExecWithWrongCustomCommand(context, cmpName, "") }) }) diff --git a/tests/integration/devfile/docker/cmd_docker_devfile_url_test.go b/tests/integration/devfile/docker/cmd_docker_devfile_url_test.go index bc8a2757fc0..bf5c5fcc8c8 100644 --- a/tests/integration/devfile/docker/cmd_docker_devfile_url_test.go +++ b/tests/integration/devfile/docker/cmd_docker_devfile_url_test.go @@ -13,8 +13,7 @@ import ( ) var _ = Describe("odo docker devfile url command tests", func() { - var projectDirPath, context, currentWorkingDirectory, cmpName string - var projectDir = "/projectDir" + var context, currentWorkingDirectory, cmpName string dockerClient := helper.NewDockerRunner("docker") // This is run after every Spec (It) @@ -22,7 +21,6 @@ var _ = Describe("odo docker devfile url command tests", func() { SetDefaultEventuallyTimeout(10 * time.Minute) context = helper.CreateNewContext() currentWorkingDirectory = helper.Getwd() - projectDirPath = context + projectDir cmpName = helper.RandString(6) helper.Chdir(context) os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) @@ -45,10 +43,11 @@ var _ = Describe("odo docker devfile url command tests", func() { Context("Creating urls", func() { It("create should pass", func() { var stdout string - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + stdout = helper.CmdShouldPass("odo", "url", "create") helper.MatchAllInOutput(stdout, []string{cmpName + "-3000", "created for component"}) stdout = helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") @@ -58,10 +57,12 @@ var _ = Describe("odo docker devfile url command tests", func() { It("create with now flag should pass", func() { var stdout string url1 := helper.RandString(5) - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + stdout = helper.CmdShouldPass("odo", "url", "create", url1, "--now") helper.MatchAllInOutput(stdout, []string{url1, "created for component", "Changes successfully pushed to component"}) }) @@ -69,10 +70,12 @@ var _ = Describe("odo docker devfile url command tests", func() { It("create with same url name should fail", func() { var stdout string url1 := helper.RandString(5) - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "url", "create", url1) stdout = helper.CmdShouldFail("odo", "url", "create", url1) @@ -81,11 +84,11 @@ var _ = Describe("odo docker devfile url command tests", func() { }) It("should be able to do a GET on the URL after a successful push", func() { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "url", "create", cmpName) output := helper.CmdShouldPass("odo", "push", "--devfile", "devfile.yaml") @@ -100,10 +103,12 @@ var _ = Describe("odo docker devfile url command tests", func() { Context("Switching pushtarget", func() { It("switch from docker to kube, odo push should display warning", func() { var stdout string - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "url", "create") helper.CmdShouldPass("odo", "preference", "set", "pushtarget", "kube", "-f") @@ -116,10 +121,12 @@ var _ = Describe("odo docker devfile url command tests", func() { It("switch from kube to docker, odo push should display warning", func() { var stdout string - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) helper.CmdShouldPass("odo", "preference", "set", "pushtarget", "kube", "-f") helper.CmdShouldPass("odo", "create", "nodejs", cmpName) + + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml")) + helper.CmdShouldPass("odo", "url", "create", "--host", "1.2.3.4.com", "--ingress") helper.CmdShouldPass("odo", "preference", "set", "pushtarget", "docker", "-f") diff --git a/tests/integration/devfile/utils/utils.go b/tests/integration/devfile/utils/utils.go index 68950b780a4..8aa0aff94b6 100644 --- a/tests/integration/devfile/utils/utils.go +++ b/tests/integration/devfile/utils/utils.go @@ -19,13 +19,13 @@ func useProjectIfAvailable(args []string, project string) []string { // ExecDefaultDevfileCommands executes the default devfile commands func ExecDefaultDevfileCommands(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/maysunfaisal/springboot.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "java-spring-boot", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) + helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "springboot", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) + args = []string{"push", "--devfile", "devfile.yaml"} args = useProjectIfAvailable(args, namespace) output := helper.CmdShouldPass("odo", args...) @@ -35,13 +35,11 @@ func ExecDefaultDevfileCommands(projectDirPath, cmpName, namespace string) { // ExecWithMissingBuildCommand executes odo push with a missing build command func ExecWithMissingBuildCommand(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-without-devbuild.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) args = []string{"push", "--devfile", "devfile.yaml"} @@ -53,14 +51,12 @@ func ExecWithMissingBuildCommand(projectDirPath, cmpName, namespace string) { // ExecWithMissingRunCommand executes odo push with a missing run command func ExecWithMissingRunCommand(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) // Rename the devrun command helper.ReplaceString(filepath.Join(projectDirPath, "devfile.yaml"), "devrun", "randomcommand") @@ -74,14 +70,12 @@ func ExecWithMissingRunCommand(projectDirPath, cmpName, namespace string) { // ExecWithCustomCommand executes odo push with a custom command func ExecWithCustomCommand(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) args = []string{"push", "--devfile", "devfile.yaml", "--build-command", "build", "--run-command", "run"} args = useProjectIfAvailable(args, namespace) @@ -94,14 +88,12 @@ func ExecWithCustomCommand(projectDirPath, cmpName, namespace string) { func ExecWithWrongCustomCommand(projectDirPath, cmpName, namespace string) { garbageCommand := "buildgarbage" - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) args = []string{"push", "--devfile", "devfile.yaml", "--build-command", garbageCommand} args = useProjectIfAvailable(args, namespace) @@ -112,13 +104,13 @@ func ExecWithWrongCustomCommand(projectDirPath, cmpName, namespace string) { // ExecPushToTestFileChanges executes odo push with and without a file change func ExecPushToTestFileChanges(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) + args = []string{"push", "--devfile", "devfile.yaml"} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) @@ -133,14 +125,12 @@ func ExecPushToTestFileChanges(projectDirPath, cmpName, namespace string) { // ExecPushWithForceFlag executes odo push with a force flag func ExecPushWithForceFlag(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) args = []string{"push", "--devfile", "devfile.yaml"} args = useProjectIfAvailable(args, namespace) @@ -155,14 +145,12 @@ func ExecPushWithForceFlag(projectDirPath, cmpName, namespace string) { // ExecPushWithNewFileAndDir executes odo push after creating a new file and dir func ExecPushWithNewFileAndDir(projectDirPath, cmpName, namespace, newFilePath, newDirPath string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs"), projectDirPath) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) // Create a new file that we plan on deleting later... if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil { @@ -180,13 +168,11 @@ func ExecPushWithNewFileAndDir(projectDirPath, cmpName, namespace, newFilePath, // ExecWithRestartAttribute executes odo push with a command attribute restart func ExecWithRestartAttribute(projectDirPath, cmpName, namespace string) { - helper.CmdShouldPass("git", "clone", "https://github.com/che-samples/web-nodejs-sample.git", projectDirPath) - helper.Chdir(projectDirPath) - args := []string{"create", "nodejs", cmpName} args = useProjectIfAvailable(args, namespace) helper.CmdShouldPass("odo", args...) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), projectDirPath) helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-restart.yaml"), filepath.Join(projectDirPath, "devfile.yaml")) args = []string{"push", "--devfile", "devfile.yaml"} diff --git a/tests/integration/generic_test.go b/tests/integration/generic_test.go index 73764d6e33b..ae9220d211c 100644 --- a/tests/integration/generic_test.go +++ b/tests/integration/generic_test.go @@ -80,7 +80,7 @@ var _ = Describe("odo generic", func() { // odo project create foobar -o json It("should be able to create project and show output in json format", func() { actual := helper.CmdShouldPass("odo", "project", "create", projectName, "-o", "json") - desired := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"message":"Project '%s' is ready for use"}`, projectName, projectName, projectName) + desired := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"message":"Project '%s' is ready for use"}`, projectName, projectName, projectName) Expect(desired).Should(MatchJSON(actual)) }) }) @@ -97,7 +97,7 @@ var _ = Describe("odo generic", func() { It("should fail along with proper machine readable output", func() { helper.CmdShouldPass("odo", "project", "create", projectName) actual := helper.CmdShouldFail("odo", "project", "create", projectName, "-o", "json") - desired := fmt.Sprintf(`{"kind":"Error","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"creationTimestamp":null},"message":"unable to create new project: unable to create new project %s: project.project.openshift.io \"%s\" already exists"}`, projectName, projectName) + desired := fmt.Sprintf(`{"kind":"Error","apiVersion":"odo.dev/v1alpha1","metadata":{"creationTimestamp":null},"message":"unable to create new project: unable to create new project %s: project.project.openshift.io \"%s\" already exists"}`, projectName, projectName) Expect(desired).Should(MatchJSON(actual)) }) }) @@ -113,7 +113,7 @@ var _ = Describe("odo generic", func() { helper.CmdShouldPass("odo", "project", "create", projectName, "-o", "json") actual := helper.CmdShouldPass("odo", "project", "delete", projectName, "-o", "json") - desired := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"message":"Deleted project : %s"}`, projectName, projectName, projectName) + desired := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"%s","namespace":"%s","creationTimestamp":null},"message":"Deleted project : %s"}`, projectName, projectName, projectName) Expect(desired).Should(MatchJSON(actual)) }) }) @@ -132,7 +132,7 @@ var _ = Describe("odo generic", func() { }) It("should be able to return project list", func() { actualProjectListJSON := helper.CmdShouldPass("odo", "project", "list", "-o", "json") - partOfProjectListJSON := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"%s","creationTimestamp":null},`, project) + partOfProjectListJSON := fmt.Sprintf(`{"kind":"Project","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"%s","creationTimestamp":null},`, project) Expect(actualProjectListJSON).To(ContainSubstring(partOfProjectListJSON)) }) })*/ diff --git a/tests/integration/project/cmd_project_test.go b/tests/integration/project/cmd_project_test.go index 0e89d3c960d..8cd650492fb 100644 --- a/tests/integration/project/cmd_project_test.go +++ b/tests/integration/project/cmd_project_test.go @@ -57,7 +57,7 @@ var _ = Describe("odo project command tests", func() { // project deletion doesn't happen immediately and older projects still might exist // so we test subset of the string - expected, err := helper.Unindented(`{"kind":"Project","apiVersion":"odo.openshift.io/v1alpha1","metadata":{"name":"` + project + `","namespace":"` + project + `","creationTimestamp":null},"spec":{},"status":{"active":true}}`) + expected, err := helper.Unindented(`{"kind":"Project","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"` + project + `","namespace":"` + project + `","creationTimestamp":null},"spec":{},"status":{"active":true}}`) Expect(err).Should(BeNil()) helper.WaitForCmdOut("odo", []string{"project", "list", "-o", "json"}, 1, true, func(output string) bool {