├── .dockerignore ├── .editorconfig ├── .gitignore ├── CONTRIBUTING.md ├── DCO ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── charts └── workflow-e2e │ ├── .gitignore │ ├── Chart.yaml │ ├── requirements.yaml │ ├── templates │ ├── NOTES.txt │ └── workflow-e2e-pod.yaml │ └── values.yaml ├── docker-test-integration.sh ├── glide.lock ├── glide.yaml ├── shims └── system.go └── tests ├── apps_test.go ├── auth_test.go ├── buildpacks_test.go ├── buildprocfile_test.go ├── builds_test.go ├── certs_test.go ├── cmd ├── apps │ └── commands.go ├── auth │ └── commands.go ├── builds │ └── commands.go ├── certs │ └── commands.go ├── configs │ └── commands.go ├── domains │ └── commands.go ├── git │ └── commands.go ├── helper.go ├── keys │ └── commands.go └── perms │ └── commands.go ├── config_test.go ├── dockerfiles_test.go ├── domains_test.go ├── files └── certs │ ├── bar.com.cert │ ├── bar.com.csr │ ├── bar.com.key │ ├── foo.com.cert │ ├── foo.com.csr │ ├── foo.com.key │ ├── wildcard.foo.com.cert │ ├── wildcard.foo.com.csr │ ├── wildcard.foo.com.key │ ├── www.foo.com.cert │ ├── www.foo.com.csr │ └── www.foo.com.key ├── git_push_test.go ├── healthcheck_test.go ├── keys_test.go ├── labels_test.go ├── limits_test.go ├── maintenance_test.go ├── model └── model.go ├── perms_test.go ├── ps_test.go ├── registry_test.go ├── releases_test.go ├── routing_test.go ├── settings └── settings.go ├── tags_test.go ├── tests_suite_test.go ├── tls_test.go ├── users_test.go ├── util └── util.go └── whitelist_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | _scripts 3 | *.md 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [Makefile] 14 | indent_style = tab 15 | 16 | [*.go] 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # local binaries, installers, and artifacts 2 | tests/tests.test 3 | tests/example-*/ 4 | 5 | # vendored go source code 6 | vendor/ 7 | 8 | # generated bintray scripts during ci 9 | _scripts/ci/bintray-ci.json 10 | 11 | junit.xml 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | The Deis project is Apache 2.0 licensed and accepts contributions via Github pull 4 | requests. This document outlines some of the conventions on commit message formatting, 5 | contact points for developers and other resources to make getting your contribution 6 | accepted. 7 | 8 | # Certificate of Origin 9 | 10 | By contributing to this project you agree to the 11 | [Developer Certificate of Origin (DCO)][dco]. This document was created by the Linux 12 | Kernel community and is a simple statement that you, as a contributor, have the legal 13 | right to make the contribution. 14 | 15 | # Support Channels 16 | 17 | Before opening a new issue, it's helpful to search the project - it's likely that another user 18 | has already reported the issue you're facing, or it's a known issue that we're already aware of. 19 | 20 | Additionally, see the [Troubleshooting Deis][troubleshooting] documentation for common issues. 21 | 22 | Our official support channels are: 23 | 24 | - GitHub issues: https://github.com/deis/deis/issues/new 25 | - IRC: #[deis](irc://irc.freenode.org:6667/#deis) IRC channel on freenode.org 26 | 27 | ## Getting Started 28 | 29 | - Fork the repository on GitHub 30 | - Read [the documentation](http://docs.deis.io/en/latest/contributing/hacking/) for build instructions 31 | 32 | ## Contribution Flow 33 | 34 | This is a rough outline of what a contributor's workflow looks like: 35 | 36 | - Create a topic branch from where you want to base your work. This is usually master. 37 | - Make commits of logical units. 38 | - Make sure your commit messages are in the proper format, see below 39 | - Push your changes to a topic branch in your fork of the repository. 40 | - Submit a pull request 41 | 42 | Thanks for your contributions! 43 | 44 | ### Design Documents 45 | 46 | Most substantial changes to Deis should follow a [Design Document](http://docs.deis.io/en/latest/contributing/design-documents/) 47 | describing the proposed changes and how they are tested and verified before they 48 | are accepted into the project. 49 | 50 | ### Commit Style Guideline 51 | 52 | We follow a rough convention for commit messages borrowed from CoreOS, who borrowed theirs 53 | from AngularJS. This is an example of a commit: 54 | 55 | feat(scripts/test-cluster): add a cluster test command 56 | 57 | this uses tmux to setup a test cluster that you can easily kill and 58 | start for debugging. 59 | 60 | To make it more formal, it looks something like this: 61 | 62 | 63 | {type}({scope}): {subject} 64 | 65 | {body} 66 | 67 | {footer} 68 | 69 | The {scope} can be anything specifying place of the commit change. 70 | 71 | The {subject} needs to use imperative, present tense: “change”, not “changed” nor 72 | “changes”. The first letter should not be capitalized, and there is no dot (.) at the end. 73 | 74 | Just like the {subject}, the message {body} needs to be in the present tense, and includes 75 | the motivation for the change, as well as a contrast with the previous behavior. The first 76 | letter in a paragraph must be capitalized. 77 | 78 | All breaking changes need to be mentioned in the {footer} with the description of the 79 | change, the justification behind the change and any migration notes required. 80 | 81 | Any line of the commit message cannot be longer than 72 characters, with the subject line 82 | limited to 50 characters. This allows the message to be easier to read on github as well 83 | as in various git tools. 84 | 85 | The allowed {types} are as follows: 86 | 87 | feat -> feature 88 | fix -> bug fix 89 | docs -> documentation 90 | style -> formatting 91 | ref -> refactoring code 92 | test -> adding missing tests 93 | chore -> maintenance 94 | 95 | ### More Details on Commits 96 | 97 | For more details see the [commit style guide][style-guide]. 98 | 99 | [dco]: DCO 100 | [style-guide]: http://docs.deis.io/en/latest/contributing/standards/#commit-style-guide 101 | [troubleshooting]: http://docs.deis.io/en/latest/troubleshooting_deis/ 102 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | 12 | Developer's Certificate of Origin 1.1 13 | 14 | By making a contribution to this project, I certify that: 15 | 16 | (a) The contribution was created in whole or in part by me and I 17 | have the right to submit it under the open source license 18 | indicated in the file; or 19 | 20 | (b) The contribution is based upon previous work that, to the best 21 | of my knowledge, is covered under an appropriate open source 22 | license and I have the right under that license to submit that 23 | work with modifications, whether created in whole or in part 24 | by me, under the same open source license (unless I am 25 | permitted to submit under a different license), as indicated 26 | in the file; or 27 | 28 | (c) The contribution was provided directly to me by some other 29 | person who certified (a), (b) or (c) and I have not modified 30 | it. 31 | 32 | (d) I understand and agree that this project and the contribution 33 | are public and that a record of the contribution (including all 34 | personal information I submit with it, including my sign-off) is 35 | maintained indefinitely and may be redistributed consistent with 36 | this project or the open source license(s) involved. 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/deis/go-dev:0.19.0 2 | 3 | ENV K8S_VERSION=1.6.3 4 | 5 | RUN go get -u -v github.com/onsi/ginkgo/ginkgo \ 6 | && curl -o /usr/local/bin/kubectl -Os https://storage.googleapis.com/kubernetes-release/release/v$K8S_VERSION/bin/linux/amd64/kubectl \ 7 | && chmod +x /usr/local/bin/kubectl 8 | 9 | COPY . /go/src/github.com/deis/workflow-e2e 10 | 11 | WORKDIR /go/src/github.com/deis/workflow-e2e 12 | 13 | RUN glide install 14 | 15 | CMD ["./docker-test-integration.sh"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Deis Maintainers 2 | 3 | This document serves to describe the leadership structure of the Deis project, and list the current 4 | project maintainers. 5 | 6 | # What is a maintainer? 7 | 8 | (Unabashedly stolen from the [Docker](https://github.com/docker/docker/blob/master/MAINTAINERS) project) 9 | 10 | There are different types of maintainers, with different responsibilities, but 11 | all maintainers have 3 things in common: 12 | 13 | 1. They share responsibility in the project's success. 14 | 2. They have made a long-term, recurring time investment to improve the project. 15 | 3. They spend that time doing whatever needs to be done, not necessarily what 16 | is the most interesting or fun. 17 | 18 | Maintainers are often under-appreciated, because their work is harder to appreciate. 19 | It's easy to appreciate a really cool and technically advanced feature. It's harder 20 | to appreciate the absence of bugs, the slow but steady improvement in stability, 21 | or the reliability of a release process. But those things distinguish a good 22 | project from a great one. 23 | 24 | # Deis maintainers 25 | 26 | Deis has two groups of maintainers in addition to our beloved Benevolent Dictator for Life. 27 | 28 | ## BDFL 29 | 30 | Deis follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator 31 | for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life). 32 | 33 | Gabriel Monroy ([@gabrtv](https://github.com/gabrtv)), as creator of the Deis project, serves as our 34 | project's BDFL. While the day-to-day project management is carried out by the maintainers, Gabriel 35 | serves as the final arbiter of any disputes and has the final say on project direction. 36 | 37 | ## Core maintainers 38 | 39 | Core maintainers are exceptionally knowledgeable about all areas of Deis. Some maintainers work on Deis 40 | full-time, although this is not a requirement. 41 | 42 | The duties of a core maintainer include: 43 | * Classify and respond to GitHub issues and review pull requests 44 | * Help to shape the Deis roadmap and lead efforts to accomplish roadmap milestones 45 | * Participate actively in feature development and bug fixing 46 | * Answer questions and help users in IRC 47 | 48 | The current core maintainers of Deis: 49 | * Chris Armstrong - ([@carmstrong](https://github.com/carmstrong)) 50 | * Kent Rancourt - ([@krancour](https://github.com/krancour)) 51 | * Matt Boersma - ([@mboersma](https://github.com/mboersma)) 52 | * Matt Butcher - ([@technosophos](https://github.com/technosophos)) 53 | * Matthew Fisher - ([@bacongobbler](https://github.com/bacongobbler)) 54 | 55 | No pull requests can be merged until at least one core maintainer signs off with an 56 | [LGTM](http://docs.deis.io/en/latest/contributing/standards/#merge-approval). The other LGTM can 57 | come from either a core maintainer or contributing maintainer. 58 | 59 | ## Contributing maintainers 60 | 61 | Contributing maintainers are exceptionally knowledgeable about some but not necessarily all areas 62 | of Deis, and are often selected due to specific domain knowledge that complements the project (but 63 | a willingness to continually contribute to the project is most important!). Often, 64 | core maintainers will ask a contributing maintainer to weigh in on issues, pull requests, or 65 | conversations where the contributing maintainer is knowledgeable. 66 | 67 | The duties of a contributing maintainer are very similar to those of a core maintainer, but they are limited to areas of the Deis project where the contributing maintainer is knowledgeable. 68 | 69 | Contributing maintainers are defined in practice as those who have write access to the Deis repository. All maintainers can review pull requests and add LGTM labels as appropriate. 70 | 71 | ## Becoming a maintainer 72 | 73 | The Deis project wouldn't be where it is today without its community. Many of the project's 74 | community members embody the spirit of maintainership, and have contributed substantially to 75 | the project. 76 | 77 | The contributing maintainers group was created in part so that exceptional members of the community 78 | who have an interest in the continued success of the project have the opportunity to join the 79 | core maintainers in guiding the future of Deis. 80 | 81 | Generally, potential contributing maintainers are selected by the Deis core maintainers based in 82 | part on the following criteria: 83 | * Sustained contributions to the project over a period of time (usually months) 84 | * A willingness to help Deis users on GitHub and in IRC 85 | * A friendly attitude :) 86 | 87 | The Deis core maintainers must unanimously agree before inviting a community member to join as a 88 | contributing maintainer, although in many cases the candidate has already been acting in the 89 | capacity of a contributing maintainer for some time, and has been consulted on issues, pull requests, 90 | etc. 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHORT_NAME := workflow-e2e 2 | 3 | SRC_PATH := /go/src/github.com/deis/workflow-e2e 4 | 5 | MUTABLE_VERSION ?= canary 6 | VERSION ?= git-$(shell git rev-parse --short HEAD) 7 | 8 | CLI_VERSION ?= latest 9 | 10 | ifdef GINKGO_NODES 11 | export GINKO_NODES_ARG=-nodes=${GINKGO_NODES} 12 | else 13 | export GINKO_NODES_ARG=-p 14 | endif 15 | 16 | ifdef FOCUS_TEST 17 | FOCUS_OPTS := --focus="\"${FOCUS_TEST}\"" 18 | endif 19 | 20 | ifdef SKIP_TEST 21 | SKIP_OPTS := --skip="${SKIP_TEST}" 22 | else # Skip the lengthy "all buildpacks" and "all dockerfiles" specs by default 23 | SKIP_OPTS := --skip="all (buildpack|dockerfile) apps" 24 | endif 25 | 26 | TEST_OPTS := -slowSpecThreshold=120.00 -noisyPendings=false ${GINKO_NODES_ARG} ${SKIP_OPTS} ${FOCUS_OPTS} 27 | 28 | DEIS_REGISTRY ?= quay.io/ 29 | IMAGE_PREFIX ?= deis 30 | IMAGE := ${DEIS_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} 31 | MUTABLE_IMAGE := ${DEIS_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} 32 | 33 | DEV_IMG := quay.io/deis/go-dev:0.20.0 34 | DEV_CMD_ARGS := --rm -v ${CURDIR}:${SRC_PATH} -w ${SRC_PATH} ${DEV_IMG} 35 | DEV_CMD := docker run ${DEV_CMD_ARGS} 36 | DEV_CMD_INT := docker run -it ${DEV_CMD_ARGS} 37 | RUN_CMD := docker run --rm -e GINKGO_NODES=${GINKGO_NODES} \ 38 | -e SKIP_OPTS=${SKIP_OPTS} \ 39 | -e FOCUS_OPTS=${FOCUS_OPTS} \ 40 | -e DEIS_CONTROLLER_URL=${DEIS_CONTROLLER_URL} \ 41 | -e DEIS_ROUTER_SERVICE_HOST=${DEIS_ROUTER_SERVICE_HOST} \ 42 | -e DEIS_ROUTER_SERVICE_PORT=${DEIS_ROUTER_SERVICE_PORT} \ 43 | -e DEFAULT_EVENTUALLY_TIMEOUT=${DEFAULT_EVENTUALLY_TIMEOUT} \ 44 | -e MAX_EVENTUALLY_TIMEOUT=${MAX_EVENTUALLY_TIMEOUT} \ 45 | -e JUNIT=${JUNIT} \ 46 | -e DEBUG=${DEBUG} \ 47 | -e CLI_VERSION=${CLI_VERSION} \ 48 | -v ${HOME}/.kube:/root/.kube \ 49 | -w ${SRC_PATH} ${IMAGE} 50 | 51 | dev-env: 52 | ${DEV_CMD_INT} bash 53 | 54 | bootstrap: 55 | glide install 56 | 57 | docker-bootstrap: 58 | ${DEV_CMD} make bootstrap 59 | 60 | test-integration: 61 | ginkgo ${TEST_OPTS} tests/ 62 | 63 | test-buildpacks: 64 | ginkgo --focus="all buildpack apps" tests 65 | 66 | test-dockerfiles: 67 | ginkgo --focus="all dockerfile apps" tests 68 | 69 | test: test-style 70 | 71 | test-style: 72 | docker run --rm -v ${CURDIR}:/bash -w /bash \ 73 | quay.io/deis/shell-dev shellcheck $(wildcard *.sh) 74 | 75 | docker-build: 76 | docker build -t ${IMAGE} ${CURDIR} 77 | docker tag ${IMAGE} ${MUTABLE_IMAGE} 78 | 79 | docker-push: docker-immutable-push docker-mutable-push 80 | 81 | docker-immutable-push: 82 | docker push ${IMAGE} 83 | 84 | docker-mutable-push: 85 | docker push ${MUTABLE_IMAGE} 86 | 87 | # run tests in parallel inside of a container 88 | docker-test-integration: 89 | ${RUN_CMD} ./docker-test-integration.sh 90 | 91 | .PHONY: dev-env \ 92 | bootstrap \ 93 | docker-bootstrap \ 94 | test-integration \ 95 | test \ 96 | test-style \ 97 | docker-build \ 98 | docker-push \ 99 | docker-immutable-push \ 100 | docker-mutable-push \ 101 | docker-test-integration 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | |![](https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Warning.svg/156px-Warning.svg.png) | Deis Workflow is no longer maintained.
Please [read the announcement](https://deis.com/blog/2017/deis-workflow-final-release/) for more detail. | 3 | |---:|---| 4 | | 09/07/2017 | Deis Workflow [v2.18][] final release before entering maintenance mode | 5 | | 03/01/2018 | End of Workflow maintenance: critical patches no longer merged | 6 | | | [Hephy](https://github.com/teamhephy/workflow) is a fork of Workflow that is actively developed and accepts code contributions. | 7 | 8 | # Deis Workflow End to End Tests v2 9 | 10 | [![Build Status](https://ci.deis.io/job/workflow-e2e/badge/icon)](https://ci.deis.io/job/workflow-e2e) 11 | [![Go Report Card](https://goreportcard.com/badge/github.com/deis/workflow-e2e)](https://goreportcard.com/report/github.com/deis/workflow-e2e) 12 | [![Docker Repository on Quay](https://quay.io/repository/deisci/deis-e2e/status "Docker Repository on Quay")](https://quay.io/repository/deisci/deis-e2e) 13 | 14 | Deis (pronounced DAY-iss) Workflow is an open source Platform as a Service (PaaS) that adds a developer-friendly layer to any [Kubernetes](http://kubernetes.io) cluster, making it easy to deploy and manage applications on your own servers. 15 | 16 | For more information about the Deis Workflow, please visit the main project page at https://github.com/deis/workflow. 17 | 18 | We welcome your input! If you have feedback, please [submit an issue][issues]. If you'd like to participate in development, please read the "Development" section below and [submit a pull request][prs]. 19 | 20 | # About 21 | 22 | The code in this repository is a set of [Ginkgo](http://onsi.github.io/ginkgo) and [Gomega](http://onsi.github.io/gomega) based integration tests that execute commands against a running Deis cluster using the Deis CLI. 23 | 24 | # Development 25 | 26 | The Deis project welcomes contributions from all developers. The high level process for development matches many other open source projects. See below for an outline. 27 | 28 | * Fork this repository 29 | * Make your changes 30 | * [Submit a pull request][prs] (PR) to this repository with your changes, and unit tests whenever possible. 31 | * If your PR fixes any [issues][issues], make sure you write Fixes #1234 in your PR description (where #1234 is the number of the issue you're closing) 32 | * The Deis core contributors will review your code. After each of them sign off on your code, they'll label your PR with LGTM1 and LGTM2 (respectively). Once that happens, the contributors will merge it 33 | 34 | ## Prerequisities 35 | 36 | Before you run the tests, you'll need a full Deis cluster up and running in Kubernetes. Follow the instructions [here](https://github.com/deis/charts#installation) to get one running. 37 | 38 | ## Run the Tests 39 | 40 | There are three options for how to execute the tests. These include two options for executing the tests against Deis Workflow installed on a _remote_ Kubernetes cluster, and one option for installing the same tests directly into a Kubernetes cluster and executing them there. 41 | 42 | ### Remote Execution 43 | 44 | Either of two options for remote execution of the test suite require the `DEIS_CONTROLLER_URL` environment variable to be exported. Its value should be the the controller endpoint you would normally use with the `deis register` or `deis login` commands: 45 | 46 | ```console 47 | $ export DEIS_CONTROLLER_URL=http://deis.your.cluster 48 | ``` 49 | 50 | Tests execute in parallel by default. If you wish to control the number of executors, export a value for the `GINKGO_NODES` environment variable: 51 | 52 | ```console 53 | $ export GINKGO_NODES=5 54 | ``` 55 | 56 | If this is not set, Ginkgo will automatically choose a number of test nodes (executors) based on the number of CPU cores _on the machine executing the tests_. It is important to note, however, that test execution is constrained more significantly by the resources of the cluster under test than by the resources of the machine executing the tests. The number of test nodes, therefore, should be explicitly set and scaled in proportion to the resources available in the cluster. 57 | 58 | For reference, Workflow's own CI pipeline uses the following: 59 | 60 | | Test Nodes | Kubernetes Worker Nodes | Worker Node CPU | Worker Node Memory | 61 | |------------|-------------------------|-----------------|--------------------| 62 | | 5 | 3 | 4 vCPUs | 15 GB | 63 | 64 | Setting the `GINKGO_NODES` environment variable to a value of `1` will allow serialized execution of all tests in the suite. 65 | 66 | #### Native Execution 67 | 68 | If you have Go 1.5 or greater already installed and working properly and also have the [Glide](https://github.com/Masterminds/glide) dependency management tool for Go installed, you may clone this repository into your `$GOPATH`: 69 | 70 | ```console 71 | git clone git@github.com:deis/workflow-e2e.git $GOPATH/src/github.com/deis/workflow-e2e 72 | ``` 73 | 74 | One-time execution of the following will resolve the test suite's own dependencies: 75 | 76 | ```console 77 | $ make bootstrap 78 | ``` 79 | 80 | To execute the entire test suite: 81 | 82 | ```console 83 | $ make test-integration 84 | ``` 85 | 86 | To run a single test or set of tests, you'll need the [Ginkgo](https://github.com/onsi/ginkgo) tool installed on your machine: 87 | 88 | ```console 89 | $ go get github.com/onsi/ginkgo/ginkgo 90 | ``` 91 | 92 | You can then use the `--focus` option to run subsets of the test suite: 93 | 94 | ```console 95 | $ ginkgo --focus="deis apps" tests 96 | ``` 97 | 98 | #### Containerized Execution 99 | 100 | If you do not have Go 1.5 or greater installed locally, but do have a Docker daemon running locally (or are using docker-machine), you can quite easily execute tests against a remote cluster from within a container. 101 | 102 | In this case, you may clone this repository into a path of your own choosing (does not need to be on your `$GOPATH`): 103 | 104 | ```console 105 | git clone git@github.com:deis/workflow-e2e.git /path/of/your/choice 106 | ``` 107 | 108 | Then build the test image and execute the test suite: 109 | 110 | ```console 111 | $ make docker-build docker-test-integration 112 | ``` 113 | 114 | ### Within the Cluster 115 | 116 | A third option is to run the test suite from within the very cluster that is under test. 117 | 118 | To install the [helm](https://github.com/kubernetes/helm) chart and start the tests, assuming helm and its corresponding server component tiller are [installed](https://github.com/kubernetes/helm/blob/master/docs/install.md): 119 | 120 | ```console 121 | helm repo add workflow-e2e https://charts.deis.com/workflow-e2e 122 | helm install --verify workflow-e2e/workflow-e2e --namespace deis 123 | ``` 124 | 125 | To monitor tests as they execute: 126 | 127 | ```console 128 | $ kubectl --namespace=deis logs -f workflow-e2e tests 129 | ``` 130 | 131 | ## Special Note on Resetting Cluster State 132 | 133 | All tests clean up after themselves, however, in the case of test failures or interruptions, automatic cleanup may not always proceed as intended. This may leave projects, users or other state behind, which may impact future executions of the test suite against the same cluster. (Often all tests will fail.) If you see this behavior, run these commands to clean up. (Replace `deis-workflow-qoxhz` with the name of the deis/workflow pod in your cluster.) 134 | 135 | ```console 136 | $ kubectl exec -it deis-workflow-qoxhz python manage.py shell 137 | Python 2.7.10 (default, Aug 13 2015, 12:27:27) 138 | [GCC 4.9.2] on linux2 139 | >>> from django.contrib.auth import get_user_model 140 | >>> m = get_user_model() 141 | >>> m.objects.exclude(username='AnonymousUser').delete() 142 | >>> m.objects.all() 143 | ``` 144 | 145 | Note that this is an ongoing issue for which we're planning [a more comprehensive fix](https://github.com/deis/workflow-e2e/issues/12). 146 | 147 | 148 | [install-k8s]: http://kubernetes.io/gettingstarted/ 149 | [issues]: https://github.com/deis/workflow-e2e/issues 150 | [prs]: https://github.com/deis/workflow-e2e/pulls 151 | [v2.18]: https://github.com/deis/workflow/releases/tag/v2.18.0 152 | -------------------------------------------------------------------------------- /charts/workflow-e2e/.gitignore: -------------------------------------------------------------------------------- 1 | charts 2 | requirements.lock -------------------------------------------------------------------------------- /charts/workflow-e2e/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: workflow-e2e 2 | home: https://github.com/deis/workflow-e2e 3 | version: 4 | description: End-to-end tests for Deis Workflow, executed in parallel. 5 | maintainers: 6 | - name: Deis Team 7 | email: engineering@deis.com 8 | -------------------------------------------------------------------------------- /charts/workflow-e2e/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: spotify-docker-gc 3 | version: 0.1.0 4 | repository: https://kubernetes-charts.storage.googleapis.com -------------------------------------------------------------------------------- /charts/workflow-e2e/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | To monitor tests as they execute: 2 | 3 | ```console 4 | $ kubectl --namespace={{ .Release.Namespace }} logs -f workflow-e2e tests 5 | ``` 6 | -------------------------------------------------------------------------------- /charts/workflow-e2e/templates/workflow-e2e-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{.Values.pod_name}} 5 | labels: 6 | heritage: deis 7 | spec: 8 | restartPolicy: Never 9 | containers: 10 | - name: tests 11 | image: quay.io/{{.Values.org}}/workflow-e2e:{{.Values.docker_tag}} 12 | imagePullPolicy: {{.Values.pull_policy}} 13 | env: 14 | - name: GINKGO_NODES 15 | value: "{{.Values.ginkgo_nodes}}" 16 | - name: JUNIT 17 | value: "true" 18 | - name: CLI_VERSION 19 | value: "{{.Values.cli_version}}" 20 | # set TEST env variable to run appropriate tests in e2e suite 21 | - name: TEST 22 | value: "{{.Values.test}}" 23 | - name: DEBUG_MODE 24 | value: "{{.Values.debug_mode}}" 25 | volumeMounts: 26 | - name: artifact-volume 27 | mountPath: /root 28 | - name: artifacts 29 | image: busybox 30 | imagePullPolicy: Always 31 | command: ["tail", "-f", "/dev/null"] 32 | volumeMounts: 33 | - name: artifact-volume 34 | mountPath: /root 35 | volumes: 36 | - name: artifact-volume 37 | emptyDir: {} 38 | -------------------------------------------------------------------------------- /charts/workflow-e2e/values.yaml: -------------------------------------------------------------------------------- 1 | org: "deisci" 2 | docker_tag: "canary" 3 | pull_policy: "Always" 4 | pod_name: "workflow-e2e" 5 | ginkgo_nodes: 15 6 | cli_version: "latest" 7 | test: "e2e" 8 | debug_mode: "false" 9 | 10 | spotify-docker-gc: 11 | cron: 12 | schedule: "*/1 * * * *" 13 | env: 14 | dockerAPIVersion: "1.23" 15 | -------------------------------------------------------------------------------- /docker-test-integration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This program is suppsoed to be run inside the workflow-e2e docker container 4 | # to download the workflow CLI at runtime and run the integration tests. 5 | set -eo pipefail 6 | 7 | function debug { 8 | if [ "${DEBUG_MODE}" == "true" ]; then 9 | filename="/tmp/deis_debug" 10 | touch "${filename}" 11 | echo "Sleeping until ${filename} is deleted" 12 | 13 | while [ -f "${filename}" ] 14 | do 15 | sleep 2 16 | done 17 | fi 18 | } 19 | 20 | trap debug ERR 21 | 22 | curl-cli-from-gcs-bucket() { 23 | local gcs_bucket="${1}" 24 | local base_url="https://storage.googleapis.com/${gcs_bucket}" 25 | local url 26 | 27 | case "${CLI_VERSION}" in 28 | "latest" | "stable") 29 | url="${base_url}" 30 | ;; 31 | *) 32 | url="${base_url}/${CLI_VERSION}" 33 | ;; 34 | esac 35 | url="${url}/deis-${CLI_VERSION}-linux-amd64" 36 | 37 | # Download CLI, retry up to 5 times with 10 second delay between each 38 | echo "Installing Workflow CLI version '${CLI_VERSION}' via url '${url}'" 39 | curl -f --silent --show-error -I "${url}" 40 | curl -f --silent --show-error --retry 5 --retry-delay 10 -o /usr/local/bin/deis "${url}" 41 | } 42 | 43 | # try multiple buckets for specific CLI_VERSION 44 | curl-cli-from-gcs-bucket "workflow-cli-master" || \ 45 | curl-cli-from-gcs-bucket "workflow-cli-pr" || \ 46 | curl-cli-from-gcs-bucket "workflow-cli-release" 47 | chmod +x /usr/local/bin/deis 48 | 49 | echo "Workflow CLI Version '$(deis --version)' installed." 50 | 51 | if [ "$TEST" == "bps" ]; then 52 | make test-buildpacks 53 | make test-dockerfiles 54 | else 55 | make test-integration 56 | fi 57 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: d4df07af09ac276ba107aff25a4019af3f670a70d5b6b453bddf6828b0a5c376 2 | updated: 2017-01-09T13:13:07.891110078-08:00 3 | imports: 4 | - name: github.com/deis/controller-sdk-go 5 | version: 9520b6c460202f376856d042ac4864ee951cdf3a 6 | - name: github.com/onsi/ginkgo 7 | version: a23f924ce96d61f963fb6219b8ff069d8d768cc2 8 | subpackages: 9 | - config 10 | - extensions/table 11 | - internal/codelocation 12 | - internal/containernode 13 | - internal/failer 14 | - internal/leafnodes 15 | - internal/remote 16 | - internal/spec 17 | - internal/specrunner 18 | - internal/suite 19 | - internal/testingtproxy 20 | - internal/writer 21 | - reporters 22 | - reporters/stenographer 23 | - reporters/stenographer/support/go-colorable 24 | - reporters/stenographer/support/go-isatty 25 | - types 26 | - name: github.com/onsi/gomega 27 | version: f1f0f388b31eca4e2cbe7a6dd8a3a1dfddda5b1c 28 | subpackages: 29 | - format 30 | - gbytes 31 | - gexec 32 | - internal/assertion 33 | - internal/asyncassertion 34 | - internal/oraclematcher 35 | - internal/testingtsupport 36 | - matchers 37 | - matchers/support/goraph/bipartitegraph 38 | - matchers/support/goraph/edge 39 | - matchers/support/goraph/node 40 | - matchers/support/goraph/util 41 | - types 42 | - name: github.com/ThomasRooney/gexpect 43 | version: 5482f03509440585d13d8f648989e05903001842 44 | - name: golang.org/x/sys 45 | version: d75a52659825e75fff6158388dddc6a5b04f9ba5 46 | subpackages: 47 | - unix 48 | - name: gopkg.in/yaml.v2 49 | version: a5b47d31c556af34a302ce5d659e6fea44d90de0 50 | testImports: 51 | - name: github.com/goware/urlx 52 | version: 8bb4a2e4339f55b15164907177e96e9faf885504 53 | - name: github.com/kballard/go-shellquote 54 | version: d8ec1a69a250a17bb0e419c386eac1f3711dc142 55 | - name: github.com/kr/pty 56 | version: ce7fa45920dc37a92de8377972e52bc55ffa8d57 57 | - name: github.com/PuerkitoBio/purell 58 | version: 0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4 59 | - name: github.com/PuerkitoBio/urlesc 60 | version: 5bd2802263f21d8788851d5305584c82a5c75d7e 61 | - name: golang.org/x/net 62 | version: c2528b2dd8352441850638a8bb678c2ad056fd3e 63 | subpackages: 64 | - idna 65 | - name: golang.org/x/text 66 | version: 47a200a05c8b3fd1b698571caecbb68beb2611ec 67 | subpackages: 68 | - transform 69 | - unicode/norm 70 | - width 71 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/deis/workflow-e2e 2 | import: 3 | - package: github.com/onsi/ginkgo 4 | - package: github.com/onsi/gomega 5 | subpackages: 6 | - gbytes 7 | - gexec 8 | - package: github.com/ThomasRooney/gexpect 9 | - package: github.com/deis/controller-sdk-go 10 | -------------------------------------------------------------------------------- /shims/system.go: -------------------------------------------------------------------------------- 1 | package shims 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type Shim struct { 11 | OutFile *os.File 12 | ShimFile *os.File 13 | } 14 | 15 | func CreateSystemShim(toShim string) (Shim, error) { 16 | tempDir := strings.TrimSuffix(os.TempDir(), "/") 17 | // create out file for shim to write to 18 | outFile, err := ioutil.TempFile(tempDir, fmt.Sprintf("%s.out", toShim)) 19 | if err != nil { 20 | return Shim{}, err 21 | } 22 | 23 | // create shim file 24 | shimLogic := []byte(fmt.Sprintf("#!/bin/sh\necho $@ > %s", outFile.Name())) 25 | shimFileName := fmt.Sprintf("%s/%s", tempDir, toShim) 26 | err = ioutil.WriteFile(shimFileName, shimLogic, 0777) 27 | if err != nil { 28 | return Shim{}, err 29 | } 30 | shimFile, err := os.Open(shimFileName) 31 | if err != nil { 32 | return Shim{}, err 33 | } 34 | 35 | return Shim{OutFile: outFile, ShimFile: shimFile}, nil 36 | } 37 | 38 | func RemoveShim(shim Shim) { 39 | os.Remove(shim.OutFile.Name()) 40 | os.Remove(shim.ShimFile.Name()) 41 | } 42 | 43 | func SubstituteEnvVar(env []string, envKey string, envValue string) []string { 44 | // create clone of env provided 45 | newEnv := make([]string, len(env)) 46 | copy(newEnv, env) 47 | 48 | // find and delete key/value in question 49 | for i, e := range newEnv { 50 | pair := strings.Split(e, "=") 51 | if pair[0] == envKey { 52 | newEnv = append(newEnv[:i], newEnv[i+1:]...) 53 | } 54 | } 55 | // substitute new key/value 56 | newEnv = append(newEnv, fmt.Sprintf("%s=%s", envKey, envValue)) 57 | 58 | return newEnv 59 | } 60 | 61 | func PrependPath(env []string, prefix string) []string { 62 | path := os.Getenv("PATH") 63 | return SubstituteEnvVar(env, "PATH", fmt.Sprintf("%s:%s", prefix, path)) 64 | } 65 | -------------------------------------------------------------------------------- /tests/apps_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | deis "github.com/deis/controller-sdk-go" 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/builds" 12 | "github.com/deis/workflow-e2e/tests/model" 13 | "github.com/deis/workflow-e2e/tests/settings" 14 | "github.com/deis/workflow-e2e/tests/util" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | . "github.com/onsi/gomega/gbytes" 19 | . "github.com/onsi/gomega/gexec" 20 | ) 21 | 22 | var _ = Describe("deis apps", func() { 23 | 24 | Context("with an existing user", func() { 25 | 26 | var user model.User 27 | 28 | BeforeEach(func() { 29 | user = auth.RegisterAndLogin() 30 | }) 31 | 32 | AfterEach(func() { 33 | auth.Cancel(user) 34 | }) 35 | 36 | Specify("that user can create an app without a git remote", func() { 37 | app := apps.Create(user, "--no-remote") 38 | apps.Destroy(user, app) 39 | }) 40 | 41 | Specify("that user can create an app that uses a custom buildpack", func() { 42 | app := apps.Create(user, "--no-remote", "--buildpack https://weird-buildpacks.io/lisp") 43 | defer apps.Destroy(user, app) 44 | sess, err := cmd.Start("deis config:list -a %s", &user, app.Name) 45 | Eventually(sess).Should(Say("BUILDPACK_URL")) 46 | Expect(err).NotTo(HaveOccurred()) 47 | Eventually(sess).Should(Exit(0)) 48 | }) 49 | 50 | Context("and an app that does not exist", func() { 51 | 52 | bogusAppName := "bogus-app-name" 53 | 54 | Specify("that user cannot get information about that app", func() { 55 | sess, err := cmd.Start("deis info -a %s", &user, bogusAppName) 56 | Eventually(sess.Err).Should(Say(util.PrependError(apps.ErrNoAppMatch))) 57 | Expect(err).NotTo(HaveOccurred()) 58 | Eventually(sess).Should(Exit(1)) 59 | }) 60 | 61 | Specify("that user cannot retrieve logs for that app", func() { 62 | sess, err := cmd.Start("deis logs -a %s", &user, bogusAppName) 63 | Eventually(sess.Err).Should(Say(`Error: There are currently no log messages. Please check the following things:`)) 64 | Expect(err).NotTo(HaveOccurred()) 65 | Eventually(sess).Should(Exit(1)) 66 | }) 67 | 68 | Specify("that user cannot open that app", func() { 69 | sess, err := cmd.Start("deis open -a %s", &user, bogusAppName) 70 | Eventually(sess.Err).Should(Say(util.PrependError(apps.ErrNoAppMatch))) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Eventually(sess).Should(Exit(1)) 73 | }) 74 | 75 | Specify("that user cannot run a command in that app's environment", func() { 76 | sess, err := cmd.Start("deis apps:run -a %s echo Hello, 世界", &user, bogusAppName) 77 | Eventually(sess).Should(Say("Running 'echo Hello, 世界'...")) 78 | Eventually(sess.Err).Should(Say(util.PrependError(apps.ErrNoAppMatch))) 79 | Expect(err).NotTo(HaveOccurred()) 80 | Eventually(sess).ShouldNot(Exit(0)) 81 | }) 82 | 83 | }) 84 | 85 | Context("who owns an existing app", func() { 86 | 87 | var app model.App 88 | 89 | BeforeEach(func() { 90 | app = apps.Create(user, "--no-remote") 91 | }) 92 | 93 | AfterEach(func() { 94 | apps.Destroy(user, app) 95 | }) 96 | 97 | Specify("that user cannot create a new app with the same name", func() { 98 | sess, err := cmd.Start("deis apps:create %s", &user, app.Name) 99 | Eventually(sess.Err).Should(Say("Application with this id already exists.")) 100 | Expect(err).NotTo(HaveOccurred()) 101 | Eventually(sess).ShouldNot(Exit(0)) 102 | }) 103 | 104 | Context("and another user also exists", func() { 105 | 106 | var otherUser model.User 107 | 108 | BeforeEach(func() { 109 | otherUser = auth.RegisterAndLogin() 110 | }) 111 | 112 | AfterEach(func() { 113 | auth.Cancel(otherUser) 114 | }) 115 | 116 | Specify("that first user can transfer ownership to the other user", func() { 117 | sess, err := cmd.Start("deis apps:transfer --app=%s %s", &user, app.Name, otherUser.Username) 118 | Expect(err).NotTo(HaveOccurred()) 119 | Eventually(sess).Should(Exit(0)) 120 | sess, err = cmd.Start("deis info -a %s", &user, app.Name) 121 | Eventually(sess.Err).Should(Say(util.PrependError(deis.ErrForbidden))) 122 | Expect(err).NotTo(HaveOccurred()) 123 | Eventually(sess).Should(Exit(1)) 124 | // Transer back or else cleanup will fail. 125 | sess, err = cmd.Start("deis apps:transfer --app=%s %s", &otherUser, app.Name, user.Username) 126 | Expect(err).NotTo(HaveOccurred()) 127 | Eventually(sess).Should(Exit(0)) 128 | }) 129 | 130 | }) 131 | 132 | }) 133 | 134 | Context("who has a local git repo containing source code", func() { 135 | 136 | BeforeEach(func() { 137 | output, err := cmd.Execute(`git clone https://github.com/deis/example-go.git`) 138 | Expect(err).NotTo(HaveOccurred(), output) 139 | }) 140 | 141 | Specify("that user can create an app with a git remote", func() { 142 | os.Chdir("example-go") 143 | app := apps.Create(user) 144 | apps.Destroy(user, app) 145 | }) 146 | 147 | }) 148 | 149 | Context("who owns an existing app that has already been deployed", func() { 150 | 151 | uuidRegExp := `[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}` 152 | procsRegexp := `(%s-[\w-]+) up \(v\d+\)` 153 | var app model.App 154 | 155 | BeforeEach(func() { 156 | app = apps.Create(user, "--no-remote") 157 | builds.Create(user, app) 158 | }) 159 | 160 | AfterEach(func() { 161 | apps.Destroy(user, app) 162 | }) 163 | 164 | Specify("that user can get information about that app", func() { 165 | sess, err := cmd.Start("deis info -a %s", &user, app.Name) 166 | Eventually(sess).Should(Say("=== %s Application", app.Name)) 167 | Eventually(sess).Should(Say(`uuid:\s*%s`, uuidRegExp)) 168 | Eventually(sess).Should(Say(`url:\s*%s`, strings.Replace(app.URL, "http://", "", 1))) 169 | Eventually(sess).Should(Say(`owner:\s*%s`, user.Username)) 170 | Eventually(sess).Should(Say(`id:\s*%s`, app.Name)) 171 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 172 | Eventually(sess).Should(Say(procsRegexp, app.Name)) 173 | Eventually(sess).Should(Say("=== %s Domains", app.Name)) 174 | Eventually(sess).Should(Say("%s", app.Name)) 175 | Expect(err).NotTo(HaveOccurred()) 176 | Eventually(sess).Should(Exit(0)) 177 | }) 178 | 179 | Specify("that user can retrieve logs for that app", func() { 180 | sess, err := cmd.Start("deis logs -a %s", &user, app.Name) 181 | Eventually(sess).Should(SatisfyAll( 182 | Say(`(.+) (deis\[controller\]: INFO config test\-.* updated)`), 183 | Say(`(.*) (deis\[controller\]: INFO test\-.* created initial release)`), 184 | Say(`(.*) (deis\[controller\]: INFO appsettings test\-.* updated)`), 185 | Say(`(.*) (deis\[controller\]: INFO domain test\-.* added)`), 186 | Say(`(.*) (deis\[controller\]: INFO build test\-.* created)`))) 187 | Expect(err).NotTo(HaveOccurred()) 188 | Eventually(sess).Should(Exit(0)) 189 | }) 190 | 191 | Specify("that user can open that app", func() { 192 | apps.Open(user, app) 193 | }) 194 | 195 | Specify("that user can run a command in that app's environment", func() { 196 | sess, err := cmd.Start("deis apps:run --app=%s echo Hello, 世界", &user, app.Name) 197 | Expect(err).NotTo(HaveOccurred()) 198 | Eventually(sess, (settings.MaxEventuallyTimeout)).Should(Say("Hello, 世界")) 199 | Eventually(sess).Should(Exit(0)) 200 | }) 201 | 202 | Specify("that user can run a command with dashes in that app's environment", func() { 203 | sess, err := cmd.Start("deis apps:run --app=%s -- ls -alh", &user, app.Name) 204 | Expect(err).NotTo(HaveOccurred()) 205 | // Can't assume too much about arbitrary "ls" output 206 | Eventually(sess, (settings.MaxEventuallyTimeout)).Should(Say("total ")) 207 | Eventually(sess, (settings.MaxEventuallyTimeout)).Should(Say(" ..")) 208 | Eventually(sess).Should(Exit(0)) 209 | }) 210 | 211 | Specify("that user can run a command with quotes in that app's environment", func() { 212 | sess, err := cmd.Start("deis apps:run --app=%s echo 'Hello, \\\"高座\\\"'", &user, app.Name) 213 | Expect(err).NotTo(HaveOccurred()) 214 | Eventually(sess, (settings.MaxEventuallyTimeout)).Should(Say("Hello, \"高座\"")) 215 | Eventually(sess).Should(Exit(0)) 216 | }) 217 | 218 | // TODO: Test is broken on CI in GKE 219 | XSpecify("that user can run a command with lengthy output in that app's environment", func() { 220 | sess, err := cmd.Start("deis apps:run --app=%s dd if=/dev/urandom bs=3072 count=1000", &user, app.Name) 221 | Expect(err).NotTo(HaveOccurred()) 222 | Eventually(sess, (settings.MaxEventuallyTimeout)).Should(Exit(0)) 223 | Expect(len(sess.Out.Contents())).To(BeNumerically(">=", 3072000)) 224 | }) 225 | 226 | Specify("that user can't run a bogus command in that app's environment", func() { 227 | sess, err := cmd.Start("deis apps:run --app=%s /usr/bin/boguscmd", &user, app.Name) 228 | Expect(err).NotTo(HaveOccurred()) 229 | Eventually(sess.Err, (settings.MaxEventuallyTimeout)).Should(Say("No such file or directory")) 230 | Eventually(sess).ShouldNot(Exit(0)) 231 | }) 232 | 233 | }) 234 | 235 | }) 236 | 237 | }) 238 | -------------------------------------------------------------------------------- /tests/auth_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/cmd/auth" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | . "github.com/onsi/gomega/gbytes" 14 | . "github.com/onsi/gomega/gexec" 15 | ) 16 | 17 | var _ = Describe("deis auth", func() { 18 | 19 | Context("with no user logged in", func() { 20 | 21 | BeforeEach(func() { 22 | // Important: All the tests use profiles. In theory, no client.json containing a token 23 | // exists because of this. However, in order to future-proof this test against any fallout 24 | // from any test added in the future that might deliberately or accidentally behave 25 | // differently, we explicitly log out, without specifying a profile. This is meant to 26 | // GUARANTEE that client.json does not exist. 27 | sess, err := cmd.Start("deis auth:logout", nil) 28 | Eventually(sess).Should(Say("Logged out\n")) 29 | Expect(err).NotTo(HaveOccurred()) 30 | Eventually(sess).Should(Exit(0)) 31 | }) 32 | 33 | Specify("information on the current user cannot be printed", func() { 34 | sess, err := cmd.Start("deis auth:whoami", nil) 35 | Eventually(sess.Err).Should(Say("Error: Client configuration file not found")) 36 | Expect(err).NotTo(HaveOccurred()) 37 | Eventually(sess).Should(Exit(1)) 38 | }) 39 | 40 | }) 41 | 42 | Context("with a non-admin user", func() { 43 | 44 | var user model.User 45 | 46 | BeforeEach(func() { 47 | user = model.NewUser() 48 | os.Setenv("DEIS_PROFILE", user.Username) 49 | }) 50 | 51 | AfterEach(func() { 52 | sess, err := cmd.Start("deis auth:cancel --username=%s --password=%s --yes", &user, user.Username, user.Password) 53 | Expect(err).To(BeNil()) 54 | Eventually(sess).Should(Exit(1)) 55 | Expect(err).NotTo(HaveOccurred()) 56 | os.Unsetenv("DEIS_PROFILE") 57 | }) 58 | 59 | Specify("that user cannot register when registration mode is 'admin_only', as is the default", func() { 60 | sess, err := cmd.Start("deis auth:register %s --username=%s --password=%s --email=%s", nil, settings.DeisControllerURL, user.Username, user.Password, user.Email) 61 | Expect(err).NotTo(HaveOccurred()) 62 | Eventually(sess.Err).Should(Say("Registration failed: Error: You do not have permission to perform this action.")) 63 | Eventually(sess).Should(Exit(1)) 64 | }) 65 | 66 | }) 67 | 68 | Context("with an existing user", func() { 69 | admin := model.Admin 70 | var user model.User 71 | 72 | BeforeEach(func() { 73 | user = auth.RegisterAndLogin() 74 | }) 75 | 76 | AfterEach(func() { 77 | auth.Cancel(user) 78 | }) 79 | 80 | Specify("that user can log out", func() { 81 | auth.Logout(user) 82 | auth.Login(user) // Log back in so cleanup won't fail. 83 | }) 84 | 85 | Specify("a new user cannot be registered using the same details", func() { 86 | sess, err := cmd.Start("deis auth:register %s --username=%s --password=%s --email=%s", &admin, settings.DeisControllerURL, user.Username, user.Password, user.Email) 87 | Eventually(sess.Err).Should(Say("Registration failed")) 88 | Expect(err).NotTo(HaveOccurred()) 89 | Eventually(sess).Should(Exit(1)) 90 | }) 91 | 92 | Specify("that user can print information about themself", func() { 93 | auth.Whoami(user) 94 | }) 95 | 96 | Specify("that user can print extensive information about themself", func() { 97 | auth.WhoamiAll(user) 98 | }) 99 | 100 | Specify("that user can regenerates their own token", func() { 101 | auth.Regenerate(user) 102 | }) 103 | 104 | }) 105 | 106 | Context("with an existing admin", func() { 107 | 108 | admin := model.Admin 109 | 110 | Specify("that admin can list admins", func() { 111 | sess, err := cmd.Start("deis perms:list --admin", &admin) 112 | Eventually(sess).Should(Say("=== Administrators")) 113 | Eventually(sess).Should(Say(admin.Username)) 114 | Expect(err).NotTo(HaveOccurred()) 115 | Eventually(sess).Should(Exit(0)) 116 | }) 117 | 118 | Context("and another existing user", func() { 119 | 120 | var otherUser model.User 121 | 122 | BeforeEach(func() { 123 | otherUser = auth.RegisterAndLogin() 124 | }) 125 | 126 | AfterEach(func() { 127 | auth.Cancel(otherUser) 128 | }) 129 | 130 | Specify("that admin can regenerate the token for the other user", func() { 131 | sess, err := cmd.Start("deis auth:regenerate -u %s", &admin, otherUser.Username) 132 | Eventually(sess).Should(Say("Token Regenerated")) 133 | Expect(err).NotTo(HaveOccurred()) 134 | Eventually(sess).Should(Exit(0)) 135 | auth.Login(otherUser) // Log back in so cleanup won't fail. 136 | }) 137 | 138 | }) 139 | 140 | // TODO: This is marked pending because it resets all user auth tokens. Because we run the 141 | // tests in parallel, this can wreak havoc on tests that may be in flight. We will need to 142 | // reevaluate how we want to test this functionality. 143 | XSpecify("that admin can regenerate the tokens of all other users", func() { 144 | sess, err := cmd.Start("deis auth:regenerate --all", &admin) 145 | Eventually(sess).Should(Say("Token Regenerated")) 146 | Expect(err).NotTo(HaveOccurred()) 147 | Eventually(sess).Should(Exit(0)) 148 | }) 149 | 150 | }) 151 | 152 | }) 153 | -------------------------------------------------------------------------------- /tests/buildpacks_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/git" 12 | "github.com/deis/workflow-e2e/tests/cmd/keys" 13 | "github.com/deis/workflow-e2e/tests/model" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/ginkgo/extensions/table" 17 | . "github.com/onsi/gomega" 18 | ) 19 | 20 | var _ = Describe("all buildpack apps", func() { 21 | 22 | Context("with an existing user", func() { 23 | 24 | var user model.User 25 | var keyPath string 26 | 27 | BeforeEach(func() { 28 | user = auth.RegisterAndLogin() 29 | }) 30 | 31 | AfterEach(func() { 32 | auth.Cancel(user) 33 | }) 34 | 35 | Context("who has added their public key", func() { 36 | 37 | BeforeEach(func() { 38 | _, keyPath = keys.Add(user) 39 | }) 40 | 41 | DescribeTable("can deploy an example buildpack app", 42 | func(url, buildpack, banner string) { 43 | 44 | var app model.App 45 | 46 | output, err := cmd.Execute(`git clone %s`, url) 47 | Expect(err).NotTo(HaveOccurred(), output) 48 | // infer app directory from URL 49 | splits := strings.Split(url, "/") 50 | dir := strings.TrimSuffix(splits[len(splits)-1], ".git") 51 | os.Chdir(dir) 52 | // create with custom buildpack if needed 53 | var args []string 54 | if buildpack != "" { 55 | args = append(args, fmt.Sprintf("--buildpack %s", buildpack)) 56 | } 57 | app = apps.Create(user, args...) 58 | defer apps.Destroy(user, app) 59 | git.Push(user, keyPath, app, banner) 60 | 61 | }, 62 | 63 | // NOTE: Keep this list up-to-date with any example apps that are added 64 | // under the github/deis org, or any third-party apps that increase coverage 65 | // or prevent regressions. 66 | Entry("Clojure", "https://github.com/deis/example-clojure-ring.git", "", 67 | "Powered by Deis"), 68 | Entry("Go", "https://github.com/deis/example-go.git", "", 69 | "Powered by Deis"), 70 | Entry("Java", "https://github.com/deis/example-java-jetty.git", "", 71 | "Powered by Deis"), 72 | Entry("Multi", "https://github.com/deis/example-multi", "", 73 | "Heroku Multipack Test"), 74 | Entry("NodeJS", "https://github.com/deis/example-nodejs-express.git", "", 75 | "Powered by Deis"), 76 | Entry("Perl", "https://github.com/deis/example-perl.git", 77 | "https://github.com/miyagawa/heroku-buildpack-perl.git", 78 | "Powered by Deis"), 79 | Entry("PHP", "https://github.com/deis/example-php.git", "", 80 | "Powered by Deis"), 81 | Entry("Java (Play)", "https://github.com/deis/example-play.git", "", 82 | "Powered by Deis"), 83 | Entry("Python (Django)", "https://github.com/deis/example-python-django.git", "", 84 | "Powered by Deis"), 85 | Entry("Python (Flask)", "https://github.com/deis/example-python-flask.git", "", 86 | "Powered by Deis"), 87 | Entry("Ruby", "https://github.com/deis/example-ruby-sinatra.git", "", 88 | "Powered by Deis"), 89 | Entry("Scala", "https://github.com/deis/example-scala.git", "", 90 | "Powered by Deis"), 91 | ) 92 | 93 | }) 94 | 95 | }) 96 | 97 | }) 98 | -------------------------------------------------------------------------------- /tests/buildprocfile_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/cmd/apps" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/model" 8 | "github.com/deis/workflow-e2e/tests/settings" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | . "github.com/onsi/gomega/gbytes" 13 | . "github.com/onsi/gomega/gexec" 14 | ) 15 | 16 | var _ = Describe("deis builds procfile", func() { 17 | 18 | Context("with an existing user", func() { 19 | 20 | var user model.User 21 | 22 | BeforeEach(func() { 23 | user = auth.RegisterAndLogin() 24 | }) 25 | 26 | AfterEach(func() { 27 | auth.Cancel(user) 28 | }) 29 | 30 | Context("who owns an existing app that has not been deployed", func() { 31 | 32 | var app model.App 33 | 34 | BeforeEach(func() { 35 | app = apps.Create(user, "--no-remote") 36 | }) 37 | 38 | AfterEach(func() { 39 | apps.Destroy(user, app) 40 | }) 41 | Specify("that user can create a new build of that app with a different procfile", func() { 42 | Image := "smothiki/exampleapp:latest" 43 | procfile := "web: /bin/boot" 44 | sess, err := cmd.Start("deis pull %s --app=%s ", &user, Image, app.Name) 45 | Expect(err).NotTo(HaveOccurred()) 46 | Eventually(sess).Should(Say("Creating build...")) 47 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 48 | sess, err = cmd.Start("deis builds:create %s -a %s --procfile \"%s\"", &user, Image, app.Name, procfile) 49 | Expect(err).NotTo(HaveOccurred()) 50 | Eventually(sess).Should(Say("Creating build...")) 51 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 52 | sess, err = cmd.Start("deis ps:scale web=1 -a %s", &user, app.Name) 53 | Expect(err).NotTo(HaveOccurred()) 54 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Scaling processes... but first,")) 55 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`)) 56 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 57 | Eventually(sess).Should(Say(`(--- [\w]+)`)) 58 | Eventually(sess).Should(Say(`(%s-[\w-]+) up \(v\d+\)`, app.Name)) 59 | Eventually(sess).Should(Say(`(--- [\w]+)`)) 60 | Eventually(sess).Should(Say(`(%s-[\w-]+) up \(v\d+\)`, app.Name)) 61 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 62 | }) 63 | 64 | }) 65 | 66 | }) 67 | 68 | }) 69 | -------------------------------------------------------------------------------- /tests/builds_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/cmd/apps" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/cmd/builds" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | "github.com/deis/workflow-e2e/tests/util" 11 | 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/gomega" 14 | . "github.com/onsi/gomega/gbytes" 15 | . "github.com/onsi/gomega/gexec" 16 | ) 17 | 18 | var _ = Describe("deis builds", func() { 19 | 20 | Context("with an existing user", func() { 21 | 22 | uuidRegExp := `[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}` 23 | 24 | var user model.User 25 | 26 | BeforeEach(func() { 27 | user = auth.RegisterAndLogin() 28 | }) 29 | 30 | AfterEach(func() { 31 | auth.Cancel(user) 32 | }) 33 | 34 | Context("and an app that does not exist", func() { 35 | 36 | bogusAppName := "bogus-app-name" 37 | 38 | Specify("that user cannot create a build for that app", func() { 39 | sess, err := cmd.Start("deis builds:create -a %s %s", &user, bogusAppName, builds.ExampleImage) 40 | Eventually(sess.Err).Should(Say(util.PrependError(apps.ErrNoAppMatch))) 41 | Expect(err).NotTo(HaveOccurred()) 42 | Eventually(sess).Should(Exit(1)) 43 | }) 44 | 45 | }) 46 | 47 | Context("who owns an existing app that has not been deployed", func() { 48 | 49 | var app model.App 50 | 51 | BeforeEach(func() { 52 | app = apps.Create(user, "--no-remote") 53 | }) 54 | 55 | AfterEach(func() { 56 | apps.Destroy(user, app) 57 | }) 58 | 59 | Specify("that user can list that app's builds", func() { 60 | sess, err := cmd.Start("deis builds:list -a %s", &user, app.Name) 61 | Eventually(sess).ShouldNot(Say(uuidRegExp)) 62 | Eventually(sess).Should(Exit(0)) 63 | Expect(err).NotTo(HaveOccurred()) 64 | }) 65 | 66 | Specify("that user can create a new build of that app from an existing image", func() { 67 | builds.Create(user, app) 68 | }) 69 | 70 | Specify("that user can create a new build of that app from an existing image using `deis pull`", func() { 71 | builds.Pull(user, app) 72 | }) 73 | 74 | Specify("that user can't create a new build of that app from a nonexistent image using `deis pull`", func() { 75 | builds.Create(user, app) 76 | // Docker Hub gives a "not found" 400 error 77 | nonexistentImage := "deis/nonexistent:dummy" 78 | sess, err := cmd.Start("deis pull --app=%s %s", &user, app.Name, nonexistentImage) 79 | Expect(err).NotTo(HaveOccurred()) 80 | Eventually(sess).Should(Say("Creating build...")) 81 | Eventually(sess.Err).Should(Say(`image .* not found`)) 82 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(1)) 83 | // quay.io gives a "permission denied" 400 error 84 | nonexistentImage = "quay.io/deis/nonexistent:dummy" 85 | sess, err = cmd.Start("deis pull --app=%s %s", &user, app.Name, nonexistentImage) 86 | Expect(err).NotTo(HaveOccurred()) 87 | Eventually(sess).Should(Say("Creating build...")) 88 | Eventually(sess.Err).Should(Say("Permission Denied attempting to pull image")) 89 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(1)) 90 | 91 | // test that the old rc is not deleted after a failed build 92 | procsListing := listProcs(user, app, "").Out.Contents() 93 | procs := scrapeProcs(app.Name, procsListing) 94 | Expect(len(procs)).To(Equal(1)) 95 | }) 96 | 97 | Specify("that user can create multiple builds of that app with DEPLOY_BATCHES set to 5", func() { 98 | builds.Pull(user, app) 99 | 100 | // scale to 11 101 | sess, err := cmd.Start("deis ps:scale cmd=11 --app=%s", &user, app.Name) 102 | Eventually(sess).Should(Say("Scaling processes... but first,")) 103 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`)) 104 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 105 | Expect(err).NotTo(HaveOccurred()) 106 | Eventually(sess).Should(Exit(0)) 107 | 108 | // configure 5 pods being rolled at once 109 | sess, err = cmd.Start("deis config:set -a %s DEPLOY_BATCHES=5", &user, app.Name) 110 | Expect(err).NotTo(HaveOccurred()) 111 | Eventually(sess).Should(Say("Creating config")) 112 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Config", app.Name)) 113 | Eventually(sess).Should(Say(`DEPLOY_BATCHES\s+5`)) 114 | Expect(err).NotTo(HaveOccurred()) 115 | Eventually(sess).Should(Exit(0)) 116 | 117 | }) 118 | 119 | }) 120 | 121 | Context("who owns an existing app that has already been deployed", func() { 122 | 123 | var app model.App 124 | 125 | BeforeEach(func() { 126 | app = apps.Create(user, "--no-remote") 127 | builds.Create(user, app) 128 | }) 129 | 130 | AfterEach(func() { 131 | apps.Destroy(user, app) 132 | }) 133 | 134 | Specify("that user can list that app's builds", func() { 135 | sess, err := cmd.Start("deis builds:list -a %s", &user, app.Name) 136 | Eventually(sess).Should(Say(uuidRegExp)) 137 | Eventually(sess).Should(Exit(0)) 138 | Expect(err).NotTo(HaveOccurred()) 139 | }) 140 | 141 | Specify("that user can create a new build of that app from an existing image", func() { 142 | builds.Create(user, app) 143 | }) 144 | 145 | Specify("that user can create a new build of that app from an existing image using `deis pull`", func() { 146 | builds.Pull(user, app) 147 | }) 148 | 149 | }) 150 | 151 | }) 152 | 153 | }) 154 | -------------------------------------------------------------------------------- /tests/certs_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | deis "github.com/deis/controller-sdk-go" 10 | "github.com/deis/workflow-e2e/tests/cmd" 11 | "github.com/deis/workflow-e2e/tests/cmd/apps" 12 | "github.com/deis/workflow-e2e/tests/cmd/auth" 13 | "github.com/deis/workflow-e2e/tests/cmd/builds" 14 | "github.com/deis/workflow-e2e/tests/cmd/certs" 15 | "github.com/deis/workflow-e2e/tests/cmd/domains" 16 | "github.com/deis/workflow-e2e/tests/model" 17 | "github.com/deis/workflow-e2e/tests/util" 18 | 19 | . "github.com/onsi/ginkgo" 20 | . "github.com/onsi/gomega" 21 | . "github.com/onsi/gomega/gbytes" 22 | . "github.com/onsi/gomega/gexec" 23 | ) 24 | 25 | var _ = Describe("deis certs", func() { 26 | 27 | nonExistentCertName := "non-existent-cert" 28 | 29 | var cert model.Cert 30 | 31 | BeforeEach(func() { 32 | cert = model.NewCert() 33 | }) 34 | 35 | Context("with an existing user", func() { 36 | 37 | var user model.User 38 | 39 | BeforeEach(func() { 40 | user = auth.RegisterAndLogin() 41 | }) 42 | 43 | AfterEach(func() { 44 | auth.Cancel(user) 45 | }) 46 | 47 | Specify("that user cannot add a cert with a malformed name", func() { 48 | sess, err := cmd.Start("deis certs:add %s %s %s", &user, "bogus.cert.name", cert.CertPath, cert.KeyPath) 49 | // TODO: Figure out spacing issues that necessitate this workaround. 50 | output := sess.Wait().Err.Contents() 51 | Expect(strings.TrimSpace(string(output))).To(Equal(util.PrependError(deis.ErrInvalidName))) 52 | Expect(err).NotTo(HaveOccurred()) 53 | Eventually(sess).Should(Exit(1)) 54 | }) 55 | 56 | Specify("that user cannot add a cert using a non-existent cert file", func() { 57 | nonExistentCertFile := "non.existent.cert" 58 | sess, err := cmd.Start("deis certs:add %s %s %s", &user, cert.Name, nonExistentCertFile, cert.KeyPath) 59 | Eventually(sess.Err).Should(Say("open %s: no such file or directory", nonExistentCertFile)) 60 | Expect(err).NotTo(HaveOccurred()) 61 | Eventually(sess).Should(Exit(1)) 62 | }) 63 | 64 | Specify("that user cannot add a cert using a non-existent key file", func() { 65 | nonExistentKeyFile := "non.existent.key" 66 | sess, err := cmd.Start("deis certs:add %s %s %s", &user, cert.Name, cert.CertPath, nonExistentKeyFile) 67 | Eventually(sess.Err).Should(Say("open %s: no such file or directory", nonExistentKeyFile)) 68 | Expect(err).NotTo(HaveOccurred()) 69 | Eventually(sess).Should(Exit(1)) 70 | }) 71 | 72 | Specify("that user cannot add a cert with the key and cert files swapped", func() { 73 | sess, err := cmd.Start("deis certs:add %s %s %s", &user, cert.Name, cert.KeyPath, cert.CertPath) 74 | Eventually(sess.Err).Should(Say(util.PrependError(deis.ErrInvalidCertificate))) 75 | Expect(err).NotTo(HaveOccurred()) 76 | Eventually(sess).Should(Exit(1)) 77 | }) 78 | 79 | Specify("that user cannot get info on a non-existent cert", func() { 80 | sess, err := cmd.Start("deis certs:info %s", &user, nonExistentCertName) 81 | Eventually(sess.Err).Should(Say(util.PrependError(certs.ErrNoCertMatch))) 82 | Expect(err).NotTo(HaveOccurred()) 83 | Eventually(sess).Should(Exit(1)) 84 | }) 85 | 86 | Specify("that user cannot remove a non-existent cert", func() { 87 | sess, err := cmd.Start("deis certs:remove %s", &user, nonExistentCertName) 88 | Eventually(sess.Err).Should(Say(util.PrependError(certs.ErrNoCertMatch))) 89 | Expect(err).NotTo(HaveOccurred()) 90 | Eventually(sess).Should(Exit(1)) 91 | }) 92 | 93 | Context("who owns an existing app", func() { 94 | 95 | var app model.App 96 | 97 | BeforeEach(func() { 98 | app = apps.Create(user, "--no-remote") 99 | }) 100 | 101 | AfterEach(func() { 102 | apps.Destroy(user, app) 103 | }) 104 | 105 | Context("with a domain added to it", func() { 106 | 107 | var domain string 108 | 109 | BeforeEach(func() { 110 | domain = getRandDomain() 111 | domains.Add(user, app, domain) 112 | }) 113 | 114 | AfterEach(func() { 115 | domains.Remove(user, app, domain) 116 | }) 117 | 118 | Specify("that user cannot attach a non-existent cert to that domain", func() { 119 | sess, err := cmd.Start("deis certs:attach %s %s", &user, nonExistentCertName, domain) 120 | Eventually(sess.Err).Should(Say(util.PrependError(certs.ErrNoCertMatch))) 121 | Expect(err).NotTo(HaveOccurred()) 122 | Eventually(sess).Should(Exit(1)) 123 | }) 124 | 125 | Specify("that user cannot detatch a non-existent cert from that domain", func() { 126 | sess, err := cmd.Start("deis certs:detach %s %s", &user, nonExistentCertName, domain) 127 | Eventually(sess.Err).Should(Say(util.PrependError(certs.ErrNoCertMatch))) 128 | Expect(err).NotTo(HaveOccurred()) 129 | Eventually(sess).Should(Exit(1)) 130 | }) 131 | 132 | }) 133 | 134 | }) 135 | 136 | Context("who owns an existing cert", func() { 137 | 138 | nonExistentDomain := "non.existent.domain" 139 | 140 | BeforeEach(func() { 141 | certs.Add(user, cert) 142 | }) 143 | 144 | AfterEach(func() { 145 | certs.Remove(user, cert) 146 | }) 147 | 148 | Specify("that user cannot attach a cert to a non-existent domain", func() { 149 | sess, err := cmd.Start("deis certs:attach %s %s", &user, cert.Name, nonExistentDomain) 150 | Eventually(sess.Err).Should(Say(util.PrependError(domains.ErrNoDomainMatch))) 151 | Expect(err).NotTo(HaveOccurred()) 152 | Eventually(sess).Should(Exit(1)) 153 | }) 154 | 155 | Specify("that user cannot detach a cert from a non-existent domain", func() { 156 | sess, err := cmd.Start("deis certs:detach %s %s", &user, cert.Name, nonExistentDomain) 157 | Eventually(sess.Err).Should(Say(util.PrependError(domains.ErrNoDomainMatch))) 158 | Expect(err).NotTo(HaveOccurred()) 159 | Eventually(sess).Should(Exit(1)) 160 | }) 161 | 162 | }) 163 | 164 | Context("who owns two existing certs", func() { 165 | 166 | var cert1, cert2 model.Cert 167 | 168 | BeforeEach(func() { 169 | cert1 = model.NewCert() 170 | cert2 = model.NewCert() 171 | certs.Add(user, cert1) 172 | certs.Add(user, cert2) 173 | }) 174 | 175 | AfterEach(func() { 176 | certs.Remove(user, cert1) 177 | certs.Remove(user, cert2) 178 | }) 179 | 180 | Specify("that user can limit the number of certs returned by certs:list", func() { 181 | randCertRegExp := `\d{0,9}-cert` 182 | 183 | // limit=0 is invalid as of DRF 3.4 184 | // https://github.com/tomchristie/django-rest-framework/pull/4194 185 | sess, err := cmd.Start("deis certs:list --limit=0", &user) 186 | Eventually(sess).Should(Say(randCertRegExp)) 187 | Eventually(sess).Should(Say(randCertRegExp)) 188 | Expect(err).NotTo(HaveOccurred()) 189 | Eventually(sess).Should(Exit(0)) 190 | 191 | sess, err = cmd.Start("deis certs:list --limit=1", &user) 192 | Eventually(sess).Should(Say(randCertRegExp)) 193 | Eventually(sess).Should(Not(Say(randCertRegExp))) 194 | Expect(err).NotTo(HaveOccurred()) 195 | Eventually(sess).Should(Exit(0)) 196 | 197 | sess, err = cmd.Start("deis certs:list", &user) 198 | Eventually(sess).Should(Say(randCertRegExp)) 199 | Eventually(sess).Should(Say(randCertRegExp)) 200 | Expect(err).NotTo(HaveOccurred()) 201 | Eventually(sess).Should(Exit(0)) 202 | }) 203 | 204 | }) 205 | 206 | Context("who owns an existing app that has already been deployed", func() { 207 | 208 | var app model.App 209 | 210 | BeforeEach(func() { 211 | app = apps.Create(user, "--no-remote") 212 | builds.Create(user, app) 213 | }) 214 | 215 | AfterEach(func() { 216 | apps.Destroy(user, app) 217 | }) 218 | 219 | Context("with a domain added to it", func() { 220 | 221 | domain := "www.foo.com" 222 | 223 | BeforeEach(func() { 224 | domains.Add(user, app, domain) 225 | }) 226 | 227 | AfterEach(func() { 228 | domains.Remove(user, app, domain) 229 | }) 230 | 231 | Context("and that user also owns an existing cert", func() { 232 | 233 | BeforeEach(func() { 234 | certs.Add(user, cert) 235 | }) 236 | 237 | AfterEach(func() { 238 | certs.Remove(user, cert) 239 | }) 240 | 241 | Specify("that user can attach/detach that cert to/from that domain", func() { 242 | certs.Attach(user, cert, domain) 243 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -k -H "Host: %s" -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, domain, app.URL)} 244 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusOK), 60)).Should(BeTrue()) 245 | certs.Detach(user, cert, domain) 246 | }) 247 | 248 | }) 249 | 250 | }) 251 | 252 | }) 253 | 254 | }) 255 | 256 | }) 257 | -------------------------------------------------------------------------------- /tests/cmd/apps/commands.go: -------------------------------------------------------------------------------- 1 | package apps 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/deis/workflow-e2e/shims" 12 | "github.com/deis/workflow-e2e/tests/cmd" 13 | "github.com/deis/workflow-e2e/tests/model" 14 | "github.com/deis/workflow-e2e/tests/settings" 15 | 16 | . "github.com/onsi/gomega" 17 | . "github.com/onsi/gomega/gbytes" 18 | . "github.com/onsi/gomega/gexec" 19 | ) 20 | 21 | var ErrNoAppMatch = errors.New("\"No App matches the given query.\"") 22 | 23 | // The functions in this file implement SUCCESS CASES for commonly used `deis apps` subcommands. 24 | // This allows each of these to be re-used easily in multiple contexts. 25 | 26 | // Create executes `deis apps:create` as the specified user with the specified, arvitrary options. 27 | func Create(user model.User, options ...string) model.App { 28 | noRemote := false 29 | app := model.NewApp() 30 | sess, err := cmd.Start("deis apps:create %s %s", &user, app.Name, strings.Join(options, " ")) 31 | Expect(err).NotTo(HaveOccurred()) 32 | sess.Wait(settings.MaxEventuallyTimeout) 33 | Eventually(sess).Should(Say("created %s", app.Name)) 34 | 35 | for _, option := range options { 36 | if option == "--no-remote" { 37 | noRemote = true 38 | break 39 | } 40 | } 41 | 42 | if noRemote { 43 | Eventually(sess).Should(Say("If you want to add a git remote for this app later, use ")) 44 | } else { 45 | Eventually(sess).Should(Say("Git remote deis successfully created for app")) 46 | } 47 | Eventually(sess).Should(Exit(0)) 48 | return app 49 | } 50 | 51 | // Open executes `deis apps:open` on the specified app as the specified user. A shim is used to 52 | // intercept the execution of `open` (Darwin) or `xdg-open` (Linux) and verify that the browser 53 | // would have navigated to the correct address. 54 | func Open(user model.User, app model.App) { 55 | // The underlying utility that `deis open` looks for: 56 | toShim := "open" //darwin 57 | if runtime.GOOS == "linux" { 58 | toShim = "xdg-open" 59 | } 60 | myShim, err := shims.CreateSystemShim(toShim) 61 | if err != nil { 62 | panic(err) 63 | } 64 | defer shims.RemoveShim(myShim) 65 | 66 | // Create custom env with location of open shim prepended to the PATH env var. 67 | env := shims.PrependPath(os.Environ(), os.TempDir()) 68 | 69 | sess, err := cmd.StartCmd(model.Cmd{Env: env, CommandLineString: fmt.Sprintf("DEIS_PROFILE=%s deis open -a %s", user.Username, app.Name)}) 70 | Expect(err).NotTo(HaveOccurred()) 71 | Eventually(sess).Should(Exit(0)) 72 | 73 | output, err := ioutil.ReadFile(myShim.OutFile.Name()) 74 | Expect(err).NotTo(HaveOccurred()) 75 | Expect(strings.TrimSpace(string(output))).To(ContainSubstring(app.URL)) 76 | } 77 | 78 | // Destroy executes `deis apps:destroy` on the specified app as the specified user. 79 | func Destroy(user model.User, app model.App) *Session { 80 | sess, err := cmd.Start("deis apps:destroy --app=%s --confirm=%s", &user, app.Name, app.Name) 81 | Expect(err).NotTo(HaveOccurred()) 82 | sess.Wait(settings.MaxEventuallyTimeout) 83 | Eventually(sess).Should(Say("Destroying %s...", app.Name)) 84 | Eventually(sess).Should(Say(`done in `)) 85 | Eventually(sess).Should(Exit(0)) 86 | return sess 87 | } 88 | -------------------------------------------------------------------------------- /tests/cmd/auth/commands.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/model" 8 | "github.com/deis/workflow-e2e/tests/settings" 9 | 10 | . "github.com/onsi/gomega" 11 | . "github.com/onsi/gomega/gbytes" 12 | . "github.com/onsi/gomega/gexec" 13 | ) 14 | 15 | // The functions in this file implement SUCCESS CASES for commonly used `deis auth` subcommands. 16 | // This allows each of these to be re-used easily in multiple contexts. 17 | 18 | // RegisterAdmin executes `deis auth:register`, using hard-coded username, password, and email 19 | // address. When this is executed, it is executed in hopes of registering Workflow's FIRST user, 20 | // which will automatically have admin permissions. If this should fail, the function proceeds 21 | // with logging in using those same hard-coded credentials, in the hopes that the reason for the 22 | // failure is that such an account already exists, having been created by a previous execution of 23 | // the tests. 24 | func RegisterAdmin() { 25 | admin := model.Admin 26 | sess, err := cmd.Start("deis auth:register %s --username=%s --password=%s --email=%s", &admin, settings.DeisControllerURL, admin.Username, admin.Password, admin.Email) 27 | Expect(err).To(BeNil()) 28 | Eventually(sess).Should(Exit()) 29 | Expect(err).NotTo(HaveOccurred()) 30 | 31 | // We cannot entirely count on the registration having succeeded. It may have failed if a user 32 | // with the username "admin" already exists. However, if that user IS indeed an admin and their 33 | // password is also "admin" (e.g. the admin was created by a previous run of these tests), then 34 | // we can proceed... so attempt to login... 35 | Login(admin) 36 | 37 | // Now verify this user is an admin by running a privileged command. 38 | sess, err = cmd.Start("deis users:list", &admin) 39 | Expect(err).To(BeNil()) 40 | Eventually(sess).Should(Exit(0)) 41 | Expect(err).NotTo(HaveOccurred()) 42 | } 43 | 44 | // Register executes `deis auth:register --login=false` using a randomized username and returns a model.User. 45 | // The 'login' flag is set to false during registration because otherwise the newly registered user's configuration 46 | // will be automatically written to the file used to register -- which in this case is the admin user's. 47 | // Since we don't want to overwrite the admin user's configuration, we log the new user in separately. 48 | func Register() model.User { 49 | // Default registration mode is 'admin_only' as of v2.12.0, so only admin may register new users 50 | admin := model.Admin 51 | Login(admin) 52 | 53 | user := model.NewUser() 54 | sess, err := cmd.Start("deis auth:register %s --username=%s --password=%s --email=%s --login=false", &admin, settings.DeisControllerURL, user.Username, user.Password, user.Email) 55 | Expect(err).To(BeNil()) 56 | Eventually(sess).Should(Exit(0)) 57 | Expect(err).NotTo(HaveOccurred()) 58 | Eventually(sess).Should(Say(fmt.Sprintf("Registered %s\n", user.Username))) 59 | 60 | return user 61 | } 62 | 63 | // Login executes `deis auth:login` as the specified user. In the process, it creates the a 64 | // corresponding profile that contains the user's authentication token. Re-use of this profile is 65 | // for most other actions is what permits multiple test users to act in parallel without impacting 66 | // one another. 67 | func Login(user model.User) { 68 | sess, err := cmd.Start("deis auth:login %s --username=%s --password=%s", &user, settings.DeisControllerURL, user.Username, user.Password) 69 | Expect(err).To(BeNil()) 70 | Eventually(sess).Should(Exit(0)) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Eventually(sess).Should(Say(fmt.Sprintf("Logged in as %s\n", user.Username))) 73 | } 74 | 75 | // RegisterAndLogin registers a user using a randomized username and then logs in as the registered user. 76 | func RegisterAndLogin() model.User { 77 | user := Register() 78 | Login(user) 79 | return user 80 | } 81 | 82 | // Whoami executes `deis auth:whoami` as the specified user. 83 | func Whoami(user model.User) { 84 | sess, err := cmd.Start("deis auth:whoami", &user) 85 | Eventually(sess).Should(Say("You are %s", user.Username)) 86 | Eventually(sess).Should(Exit(0)) 87 | Expect(err).NotTo(HaveOccurred()) 88 | } 89 | 90 | func WhoamiAll(user model.User) { 91 | sess, err := cmd.Start("deis auth:whoami --all", &user) 92 | Eventually(sess).Should(Say("ID: 0\nUsername: %s\nEmail: %s", user.Username, user.Email)) 93 | Eventually(sess).Should(Say(`First Name: `)) 94 | Eventually(sess).Should(Say(`Last Name: `)) 95 | Eventually(sess).Should(Say(`Last Login: `)) 96 | Eventually(sess).Should(Say("Is Superuser: %t\nIs Staff: %t\nIs Active: true\n", user.IsSuperuser, user.IsSuperuser)) 97 | Eventually(sess).Should(Say(`Date Joined: `)) 98 | Eventually(sess).Should(Exit(0)) 99 | Expect(err).NotTo(HaveOccurred()) 100 | } 101 | 102 | // Regenerate executes `deis auth:regenerate` as the specified user. 103 | func Regenerate(user model.User) { 104 | sess, err := cmd.Start("deis auth:regenerate", &user) 105 | Eventually(sess).Should(Say("Token Regenerated")) 106 | Eventually(sess).Should(Exit(0)) 107 | Expect(err).NotTo(HaveOccurred()) 108 | } 109 | 110 | // Logout executes `deis auth:logout` as the specified user. 111 | func Logout(user model.User) { 112 | sess, err := cmd.Start("deis auth:logout", &user) 113 | Expect(err).To(BeNil()) 114 | Eventually(sess).Should(Exit(0)) 115 | Expect(err).NotTo(HaveOccurred()) 116 | Eventually(sess).Should(Say("Logged out\n")) 117 | } 118 | 119 | // Cancel executes `deis auth:cancel` as the specified user. 120 | func Cancel(user model.User) { 121 | sess, err := cmd.Start("deis auth:cancel --username=%s --password=%s --yes", &user, user.Username, user.Password) 122 | Expect(err).To(BeNil()) 123 | Eventually(sess).Should(Exit(0)) 124 | Expect(err).NotTo(HaveOccurred()) 125 | Eventually(sess).Should(Say("Account cancelled\n")) 126 | } 127 | 128 | // CancelAdmin deletes the admin user that was created to facilitate the tests. 129 | func CancelAdmin() { 130 | admin := model.Admin 131 | sess, err := cmd.Start("deis auth:cancel --username=%s --password=%s --yes", &admin, admin.Username, admin.Password) 132 | Expect(err).To(BeNil()) 133 | Eventually(sess).Should(Exit(0)) 134 | Expect(err).NotTo(HaveOccurred()) 135 | Eventually(sess).Should(Say("Account cancelled\n")) 136 | } 137 | -------------------------------------------------------------------------------- /tests/cmd/builds/commands.go: -------------------------------------------------------------------------------- 1 | package builds 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/model" 8 | "github.com/deis/workflow-e2e/tests/settings" 9 | 10 | . "github.com/onsi/gomega" 11 | . "github.com/onsi/gomega/gbytes" 12 | . "github.com/onsi/gomega/gexec" 13 | ) 14 | 15 | // The functions in this file implement SUCCESS CASES for commonly used `deis builds` subcommands. 16 | // This allows each of these to be re-used easily in multiple contexts. 17 | 18 | const ExampleImage = "deis/example-dockerfile-http" 19 | 20 | // Create executes `deis builds:create` as the specified user. 21 | func Create(user model.User, app model.App) { 22 | createOrPull(user, app, "builds:create") 23 | } 24 | 25 | // Pull executes the `deis pull` shortcut as the specified user. 26 | func Pull(user model.User, app model.App) { 27 | createOrPull(user, app, "pull") 28 | } 29 | 30 | func createOrPull(user model.User, app model.App, command string) { 31 | sess, err := cmd.Start("deis %s --app=%s %s", &user, command, app.Name, ExampleImage) 32 | Expect(err).NotTo(HaveOccurred()) 33 | Eventually(sess).Should(Say("Creating build...")) 34 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 35 | time.Sleep(10 * time.Second) 36 | } 37 | -------------------------------------------------------------------------------- /tests/cmd/certs/commands.go: -------------------------------------------------------------------------------- 1 | package certs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/deis/workflow-e2e/tests/cmd" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | 11 | . "github.com/onsi/gomega" 12 | . "github.com/onsi/gomega/gbytes" 13 | . "github.com/onsi/gomega/gexec" 14 | ) 15 | 16 | var ErrNoCertMatch = errors.New("\"No Certificate matches the given query.\"") 17 | 18 | // The functions in this file implement SUCCESS CASES for commonly used `deis certs` subcommands. 19 | // This allows each of these to be re-used easily in multiple contexts. 20 | 21 | // List executes `deis certs:list` as the specified user. 22 | func List(user model.User) *Session { 23 | sess, err := cmd.Start("deis certs:list", &user) 24 | Expect(err).NotTo(HaveOccurred()) 25 | Eventually(sess).Should(Exit(0)) 26 | return sess 27 | } 28 | 29 | // Add executes `deis certs:add` as the specified user to add the specified cert. 30 | func Add(user model.User, cert model.Cert) { 31 | sess, err := cmd.Start("deis certs:add %s %s %s", &user, cert.Name, cert.CertPath, cert.KeyPath) 32 | Eventually(sess).Should(Say("Adding SSL endpoint...")) 33 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 34 | Expect(err).NotTo(HaveOccurred()) 35 | Eventually(sess).Should(Exit(0)) 36 | Eventually(List(user).Wait().Out.Contents()).Should(ContainSubstring(cert.Name)) 37 | } 38 | 39 | // Remove executes `deis certs:remove` as the specified user to remove the specified cert. 40 | func Remove(user model.User, cert model.Cert) { 41 | sess, err := cmd.Start("deis certs:remove %s", &user, cert.Name) 42 | Eventually(sess).Should(Say("Removing %s...", cert.Name)) 43 | Eventually(sess).Should(Say("done")) 44 | Expect(err).NotTo(HaveOccurred()) 45 | Eventually(sess).Should(Exit(0)) 46 | Eventually(List(user).Wait().Out.Contents()).ShouldNot(ContainSubstring(cert.Name)) 47 | } 48 | 49 | // Attach executes `deis certs:attach` as the specified user to attach the specified cert to the 50 | // specified domain. 51 | func Attach(user model.User, cert model.Cert, domain string) { 52 | sess, err := cmd.Start("deis certs:attach %s %s", &user, cert.Name, domain) 53 | // Explicitly build literal substring since 'domain' may be a wildcard domain ('*.foo.com') and 54 | // we don't want Gomega interpreting this string as a regexp 55 | Eventually(sess.Wait().Out.Contents()).Should(ContainSubstring(fmt.Sprintf("Attaching certificate %s to domain %s...", cert.Name, domain))) 56 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 57 | Expect(err).NotTo(HaveOccurred()) 58 | Eventually(sess).Should(Exit(0)) 59 | } 60 | 61 | // Detatch executes `deis certs:detach` as the specified user to detach the specified cert from 62 | // the specified domain. 63 | func Detach(user model.User, cert model.Cert, domain string) { 64 | sess, err := cmd.Start("deis certs:detach %s %s", &user, cert.Name, domain) 65 | // Explicitly build literal substring since 'domain' may be a wildcard domain ('*.foo.com') and 66 | // we don't want Gomega interpreting this string as a regexp 67 | Eventually(sess.Wait().Out.Contents()).Should(ContainSubstring(fmt.Sprintf("Detaching certificate %s from domain %s...", cert.Name, domain))) 68 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Eventually(sess).Should(Exit(0)) 71 | } 72 | 73 | // Info executes `deis certs:info` as the specified user to retrieve information about the 74 | // specified cert. 75 | func Info(user model.User, cert model.Cert) *Session { 76 | sess, err := cmd.Start("deis certs:info %s", &user, cert.Name) 77 | Eventually(sess).Should(Say("=== %s Certificate", cert.Name)) 78 | Expect(err).NotTo(HaveOccurred()) 79 | Eventually(sess).Should(Exit(0)) 80 | return sess 81 | } 82 | -------------------------------------------------------------------------------- /tests/cmd/configs/commands.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/model" 6 | "github.com/deis/workflow-e2e/tests/settings" 7 | 8 | . "github.com/onsi/gomega" 9 | . "github.com/onsi/gomega/gbytes" 10 | . "github.com/onsi/gomega/gexec" 11 | ) 12 | 13 | // The functions in this file implement SUCCESS CASES for commonly used `deis config` subcommands. 14 | // This allows each of these to be re-used easily in multiple contexts. 15 | 16 | // Set executes `deis config:set` on the specified app as the specified user. 17 | func Set(user model.User, app model.App, key string, value string) *Session { 18 | sess, err := cmd.Start("deis config:set %s=%s --app=%s", &user, key, value, app.Name) 19 | Expect(err).NotTo(HaveOccurred()) 20 | sess.Wait(settings.MaxEventuallyTimeout) 21 | Eventually(sess).Should(Say("Creating config...")) 22 | Eventually(sess).Should(Exit(0)) 23 | return sess 24 | } 25 | -------------------------------------------------------------------------------- /tests/cmd/domains/commands.go: -------------------------------------------------------------------------------- 1 | package domains 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/deis/workflow-e2e/tests/cmd" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | 11 | . "github.com/onsi/gomega" 12 | . "github.com/onsi/gomega/gbytes" 13 | . "github.com/onsi/gomega/gexec" 14 | ) 15 | 16 | var ErrNoDomainMatch = errors.New("\"No Domain matches the given query.\"") 17 | 18 | // The functions in this file implement SUCCESS CASES for commonly used `deis domains` subcommands. 19 | // This allows each of these to be re-used easily in multiple contexts. 20 | 21 | // Add executes `deis domains:add` as the specified user to add the specified domain to the 22 | // specified app. 23 | func Add(user model.User, app model.App, domain string) { 24 | sess, err := cmd.Start("deis domains:add %s --app=%s", &user, domain, app.Name) 25 | // Explicitly build literal substring since 'domain' may be a wildcard domain ('*.foo.com') and 26 | // we don't want Gomega interpreting this string as a regexp 27 | Eventually(sess.Wait().Out.Contents()).Should(ContainSubstring(fmt.Sprintf("Adding %s to %s...", domain, app.Name))) 28 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 29 | Expect(err).NotTo(HaveOccurred()) 30 | Eventually(sess).Should(Exit(0)) 31 | } 32 | 33 | // Remove executes `deis domains:remove` as the specified user to remove the specified domain from 34 | // the specified app. 35 | func Remove(user model.User, app model.App, domain string) { 36 | sess, err := cmd.Start("deis domains:remove %s --app=%s", &user, domain, app.Name) 37 | // Explicitly build literal substring since 'domain' may be a wildcard domain ('*.foo.com') and 38 | // we don't want Gomega interpreting this string as a regexp 39 | Eventually(sess.Wait().Out.Contents()).Should(ContainSubstring(fmt.Sprintf("Removing %s from %s...", domain, app.Name))) 40 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 41 | Expect(err).NotTo(HaveOccurred()) 42 | Eventually(sess).Should(Exit(0)) 43 | } 44 | -------------------------------------------------------------------------------- /tests/cmd/git/commands.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/deis/workflow-e2e/tests/cmd" 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | . "github.com/onsi/gomega" 15 | . "github.com/onsi/gomega/gbytes" 16 | . "github.com/onsi/gomega/gexec" 17 | ) 18 | 19 | // The functions in this file implement SUCCESS CASES for commonly used `git` commands. 20 | // This allows each of these to be re-used easily in multiple contexts. 21 | 22 | const ( 23 | pushCommandLineString = "GIT_SSH=%s GIT_KEY=%s git push deis master" 24 | ) 25 | 26 | // Push executes a `git push deis master` from the current directory using the provided key. 27 | func Push(user model.User, keyPath string, app model.App, banner string) { 28 | sess := StartPush(user, keyPath) 29 | // sess.Wait(settings.MaxEventuallyTimeout) 30 | // output := string(sess.Out.Contents()) 31 | // Expect(output).To(MatchRegexp(`Done, %s:v\d deployed to Deis`, app.Name)) 32 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 33 | Curl(app, banner) 34 | } 35 | 36 | // Curl polls an app over HTTP until it returns the expected "Powered by" banner. 37 | func Curl(app model.App, banner string) { 38 | // curl the app's root URL and print just the HTTP response code 39 | cmdRetryTimeout := 60 40 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf( 41 | `curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 42 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusOK), cmdRetryTimeout)).Should(BeTrue()) 43 | // verify that the response contains "Powered by" as all the example apps do 44 | curlCmd = model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL "%s"`, app.URL)} 45 | Eventually(cmd.Retry(curlCmd, banner, cmdRetryTimeout)).Should(BeTrue()) 46 | } 47 | 48 | // PushWithInterrupt executes a `git push deis master` from the current 49 | // directory using the provided key, but then halts the progress via SIGINT. 50 | func PushWithInterrupt(user model.User, keyPath string) { 51 | sess := StartPush(user, keyPath) 52 | Eventually(sess.Err).Should(Say("Starting build... but first, coffee!")) 53 | 54 | sess = sess.Interrupt() 55 | 56 | newSess := StartPush(user, keyPath) 57 | Eventually(newSess.Err).ShouldNot(Say("exec request failed on channel 0")) 58 | Eventually(newSess.Err).Should(Say("fatal: remote error: Another git push is ongoing")) 59 | Eventually(newSess, settings.DefaultEventuallyTimeout).Should(Exit(128)) 60 | } 61 | 62 | // PushUntilResult executes a `git push deis master` from the current 63 | // directory using the provided key, until the command result satisfies 64 | // expectedCmdResult of type model.CmdResult, failing if 65 | // settings.DefaultEventuallyTimeout is reached first. 66 | func PushUntilResult(user model.User, keyPath string, expectedCmdResult model.CmdResult) { 67 | envVars := append(os.Environ(), fmt.Sprintf("DEIS_PROFILE=%s", user.Username)) 68 | pushCmd := model.Cmd{Env: envVars, CommandLineString: fmt.Sprintf( 69 | pushCommandLineString, settings.GitSSH, keyPath)} 70 | 71 | Eventually(cmd.RetryUntilResult(pushCmd, expectedCmdResult, 5*time.Second, 72 | settings.MaxEventuallyTimeout)).Should(BeTrue()) 73 | } 74 | 75 | // StartPush starts a `git push deis master` command and returns the command session. 76 | func StartPush(user model.User, keyPath string) *Session { 77 | sess, err := cmd.Start(pushCommandLineString, &user, settings.GitSSH, keyPath) 78 | Expect(err).NotTo(HaveOccurred()) 79 | return sess 80 | } 81 | -------------------------------------------------------------------------------- /tests/cmd/helper.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "time" 10 | 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | "github.com/onsi/ginkgo" 15 | "github.com/onsi/gomega" 16 | "github.com/onsi/gomega/gexec" 17 | ) 18 | 19 | // The functions in this file implement a generic command execution and inspection framework. 20 | 21 | // Execute executes the command generated by fmt.Sprintf(cmdLine, args...) and returns its output. 22 | func Execute(cmdLine string, args ...interface{}) (string, error) { 23 | var cmd *exec.Cmd 24 | shCommand := fmt.Sprintf(cmdLine, args...) 25 | 26 | if settings.Debug { 27 | fmt.Println(shCommand) 28 | } 29 | 30 | cmd = exec.Command("/bin/sh", "-c", shCommand) 31 | outputBytes, err := cmd.CombinedOutput() 32 | 33 | output := string(outputBytes) 34 | 35 | if settings.Debug { 36 | fmt.Println(output) 37 | } 38 | 39 | return output, err 40 | } 41 | 42 | // Start executes the provided command (often a `deis` command of some sort) as the specified user 43 | // (by selecting the corresponding profile). Optional arguments may also be supplied that will be 44 | // substituted into the provided command using fmt.Sprintf(...). 45 | func Start(cmdLine string, user *model.User, args ...interface{}) (*gexec.Session, error) { 46 | if user != nil { 47 | envVars := append(os.Environ(), fmt.Sprintf("DEIS_PROFILE=%s", user.Username)) 48 | ourCommand := model.Cmd{Env: envVars, CommandLineString: fmt.Sprintf(cmdLine, args...)} 49 | return StartCmd(ourCommand) 50 | } 51 | ourCommand := model.Cmd{Env: os.Environ(), CommandLineString: fmt.Sprintf(cmdLine, args...)} 52 | return StartCmd(ourCommand) 53 | } 54 | 55 | // StartCmd executes the provided model.Command. It is used primarily by the Start function 56 | // (above), but is also used directly in scenarios where tests must execute fine-grained control 57 | // over the environment in which the command will be executed. 58 | func StartCmd(command model.Cmd) (*gexec.Session, error) { 59 | execCmd := exec.Command("/bin/sh", "-c", command.CommandLineString) 60 | execCmd.Env = command.Env 61 | io.WriteString(ginkgo.GinkgoWriter, fmt.Sprintf("$ %s\n", command.CommandLineString)) 62 | return gexec.Start(execCmd, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) 63 | } 64 | 65 | // Retry runs the provided repeatedly, once a second up to the 66 | // supplied until the result contains the 67 | // An example use of this utility would be curl-ing a url and waiting 68 | // until the response code matches the expected response. 69 | // TODO: https://github.com/deis/workflow-e2e/issues/240 70 | func Retry(command model.Cmd, expectedResult string, timeout int) bool { 71 | var result string 72 | fmt.Fprintf(ginkgo.GinkgoWriter, "Waiting up to %d seconds for `%s` to return %s...\n", timeout, command.CommandLineString, expectedResult) 73 | for i := 0; i < timeout; i++ { 74 | sess, err := StartCmd(command) 75 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 76 | result = string(sess.Wait().Out.Contents()) 77 | if strings.Contains(result, expectedResult) { 78 | return true 79 | } 80 | time.Sleep(1 * time.Second) 81 | } 82 | fmt.Fprintf(ginkgo.GinkgoWriter, "FAIL: '%s' does not match expected result of '%s'\n", result, expectedResult) 83 | return false 84 | } 85 | 86 | // RetryUntilResult runs the provided cmd repeatedly, once every period, 87 | // up to the supplied timeout until the cmd result matches the supplied 88 | // expectedCmdResult 89 | func RetryUntilResult(command model.Cmd, expectedCmdResult model.CmdResult, period, timeout time.Duration) bool { 90 | var actualCmdResult model.CmdResult 91 | 92 | fmt.Fprintf(ginkgo.GinkgoWriter, 93 | "Waiting up to %d seconds for `%s` to return expected cmdResult %s...\n", 94 | int(timeout.Seconds()), command.CommandLineString, expectedCmdResult.String()) 95 | 96 | tck := time.NewTicker(period) 97 | tmr := time.NewTimer(timeout) 98 | defer tck.Stop() 99 | defer tmr.Stop() 100 | for { 101 | select { 102 | case <-tck.C: 103 | sess, err := StartCmd(command) 104 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 105 | sessWait := sess.Wait() 106 | actualCmdResult = model.CmdResult{ 107 | Out: sessWait.Out.Contents(), 108 | Err: sessWait.Err.Contents(), 109 | ExitCode: sessWait.ExitCode(), 110 | } 111 | if actualCmdResult.Satisfies(expectedCmdResult) { 112 | return true 113 | } 114 | case <-tmr.C: 115 | fmt.Fprintf(ginkgo.GinkgoWriter, "FAIL: Actual cmdResult '%v' does not match expected cmdResult '%v'\n", actualCmdResult, expectedCmdResult) 116 | return false 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/cmd/keys/commands.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "path" 8 | "time" 9 | 10 | "github.com/deis/workflow-e2e/tests/cmd" 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | . "github.com/onsi/gomega" 15 | . "github.com/onsi/gomega/gbytes" 16 | . "github.com/onsi/gomega/gexec" 17 | ) 18 | 19 | // The functions in this file implement SUCCESS CASES for commonly used `deis keys` subcommands. 20 | // This allows each of these to be re-used easily in multiple contexts. 21 | 22 | // Add executes `deis keys:add` as the specified user to add a new key to that user's account. 23 | func Add(user model.User) (string, string) { 24 | keyName, keyPath := createKey() 25 | sess, err := cmd.Start("deis keys:add %s.pub", &user, keyPath) 26 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Uploading %s.pub to deis... done", keyName)) 27 | Expect(err).NotTo(HaveOccurred()) 28 | Eventually(sess).Should(Exit(0)) 29 | time.Sleep(5 * time.Second) // Wait for the key to propagate before continuing 30 | return keyName, keyPath 31 | } 32 | 33 | // Remove executes `deis keys:remove` as the specified user to remove the specified key from that 34 | // user's account. 35 | func Remove(user model.User, keyName string) { 36 | sess, err := cmd.Start("deis keys:remove %s", &user, keyName) 37 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Removing %s SSH Key... done", keyName)) 38 | Eventually(sess).Should(Exit(0)) 39 | Expect(err).NotTo(HaveOccurred()) 40 | } 41 | 42 | func createKey() (string, string) { 43 | keyName := fmt.Sprintf("deiskey-%v", rand.Intn(1000)) 44 | sshHome := path.Join(settings.TestHome, ".ssh") 45 | os.MkdirAll(sshHome, 0777) 46 | keyPath := path.Join(sshHome, keyName) 47 | if _, err := os.Stat(keyPath); os.IsNotExist(err) { 48 | _, err := cmd.Execute("ssh-keygen -q -t rsa -b 4096 -C %s -f %s -N ''", keyName, keyPath) 49 | Expect(err).NotTo(HaveOccurred()) 50 | } 51 | os.Chmod(keyPath, 0600) 52 | return keyName, keyPath 53 | } 54 | -------------------------------------------------------------------------------- /tests/cmd/perms/commands.go: -------------------------------------------------------------------------------- 1 | package perms 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/model" 6 | "github.com/deis/workflow-e2e/tests/settings" 7 | 8 | . "github.com/onsi/gomega" 9 | . "github.com/onsi/gomega/gbytes" 10 | . "github.com/onsi/gomega/gexec" 11 | ) 12 | 13 | // The functions in this file implement SUCCESS CASES for commonly used `deis perms` subcommands. 14 | // This allows each of these to be re-used easily in multiple contexts. 15 | 16 | // Create executes `deis perms:create` as the specified user to grant permissions on the specified 17 | // app to a second user. 18 | func Create(user model.User, app model.App, grantUser model.User) { 19 | sess, err := cmd.Start("deis perms:create %s --app=%s", &user, grantUser.Username, app.Name) 20 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to %s collaborators... done\n", grantUser.Username, app.Name)) 21 | Expect(err).NotTo(HaveOccurred()) 22 | Eventually(sess).Should(Exit(0)) 23 | } 24 | 25 | // Delete executes `deis perms:delete` as the specified user to revoke permissions on the specified 26 | // app from a second user. 27 | func Delete(user model.User, app model.App, revokeUser model.User) { 28 | sess, err := cmd.Start("deis perms:delete %s --app=%s", &user, revokeUser.Username, app.Name) 29 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Removing %s from %s collaborators... done", revokeUser.Username, app.Name)) 30 | Expect(err).NotTo(HaveOccurred()) 31 | Eventually(sess).Should(Exit(0)) 32 | } 33 | -------------------------------------------------------------------------------- /tests/dockerfiles_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/git" 12 | "github.com/deis/workflow-e2e/tests/cmd/keys" 13 | "github.com/deis/workflow-e2e/tests/model" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/ginkgo/extensions/table" 17 | . "github.com/onsi/gomega" 18 | ) 19 | 20 | var _ = Describe("all dockerfile apps", func() { 21 | 22 | Context("with an existing user", func() { 23 | 24 | var user model.User 25 | var keyPath string 26 | 27 | BeforeEach(func() { 28 | user = auth.RegisterAndLogin() 29 | }) 30 | 31 | AfterEach(func() { 32 | auth.Cancel(user) 33 | }) 34 | 35 | Context("who has added their public key", func() { 36 | 37 | BeforeEach(func() { 38 | _, keyPath = keys.Add(user) 39 | }) 40 | 41 | DescribeTable("can deploy an example dockerfile app", 42 | func(url, buildpack, banner, proctype string) { 43 | 44 | var app model.App 45 | 46 | output, err := cmd.Execute(`git clone %s`, url) 47 | Expect(err).NotTo(HaveOccurred(), output) 48 | // infer app directory from URL 49 | splits := strings.Split(url, "/") 50 | dir := strings.TrimSuffix(splits[len(splits)-1], ".git") 51 | os.Chdir(dir) 52 | // creeate with custom buildpack if needed 53 | var args []string 54 | if buildpack != "" { 55 | args = append(args, fmt.Sprintf("--buildpack %s", buildpack)) 56 | } 57 | app = apps.Create(user, args...) 58 | defer apps.Destroy(user, app) 59 | git.Push(user, keyPath, app, banner) 60 | _ = listProcs(user, app, proctype) 61 | 62 | }, 63 | 64 | Entry("HTTP", "https://github.com/deis/example-dockerfile-http.git", "", 65 | "Powered by Deis", ""), 66 | Entry("Python", "https://github.com/deis/example-dockerfile-python.git", "", 67 | "Powered by Deis", ""), 68 | Entry("HTTP-Web", "https://github.com/deis/example-dockerfile-procfile-http.git", "", 69 | "Powered by Deis", "web"), 70 | ) 71 | 72 | }) 73 | 74 | }) 75 | 76 | }) 77 | -------------------------------------------------------------------------------- /tests/domains_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/deis/workflow-e2e/tests/cmd" 10 | "github.com/deis/workflow-e2e/tests/cmd/apps" 11 | "github.com/deis/workflow-e2e/tests/cmd/auth" 12 | "github.com/deis/workflow-e2e/tests/cmd/builds" 13 | "github.com/deis/workflow-e2e/tests/cmd/domains" 14 | "github.com/deis/workflow-e2e/tests/model" 15 | "github.com/deis/workflow-e2e/tests/settings" 16 | "github.com/deis/workflow-e2e/tests/util" 17 | 18 | . "github.com/onsi/ginkgo" 19 | . "github.com/onsi/gomega" 20 | . "github.com/onsi/gomega/gbytes" 21 | . "github.com/onsi/gomega/gexec" 22 | ) 23 | 24 | var _ = Describe("deis domains", func() { 25 | 26 | Context("with an existing user", func() { 27 | 28 | var user model.User 29 | 30 | BeforeEach(func() { 31 | user = auth.RegisterAndLogin() 32 | }) 33 | 34 | AfterEach(func() { 35 | auth.Cancel(user) 36 | }) 37 | 38 | Context("who owns an existing app", func() { 39 | 40 | var app model.App 41 | 42 | BeforeEach(func() { 43 | app = apps.Create(user, "--no-remote") 44 | }) 45 | 46 | AfterEach(func() { 47 | apps.Destroy(user, app) 48 | }) 49 | 50 | Specify("that user can list that app's domains", func() { 51 | sess, err := cmd.Start("deis domains:list --app=%s", &user, app.Name) 52 | Eventually(sess).Should(Say("=== %s Domains", app.Name)) 53 | Eventually(sess).Should(Say("%s", app.Name)) 54 | Expect(err).NotTo(HaveOccurred()) 55 | Eventually(sess).Should(Exit(0)) 56 | }) 57 | 58 | Specify("that user can add domains to that app", func() { 59 | domain := getRandDomain() 60 | sess, err := cmd.Start("deis domains:add %s --app=%s", &user, domain, app.Name) 61 | Eventually(sess).Should(Say("Adding %s to %s...", domain, app.Name)) 62 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 63 | Expect(err).NotTo(HaveOccurred()) 64 | Eventually(sess).Should(Exit(0)) 65 | }) 66 | 67 | Specify("that user cannot remove a non-existent domain from that app", func() { 68 | sess, err := cmd.Start("deis domains:remove --app=%s %s", &user, app.Name, "non.existent.domain") 69 | Eventually(sess.Err, settings.MaxEventuallyTimeout).Should(Say(util.PrependError(domains.ErrNoDomainMatch))) 70 | Expect(err).NotTo(HaveOccurred()) 71 | Eventually(sess).Should(Exit(1)) 72 | }) 73 | 74 | Context("with a domain added to it", func() { 75 | 76 | var domain string 77 | 78 | BeforeEach(func() { 79 | domain = getRandDomain() 80 | domains.Add(user, app, domain) 81 | }) 82 | 83 | Specify("that user can remove that domain from that app", func() { 84 | sess, err := cmd.Start("deis domains:remove %s --app=%s", &user, domain, app.Name) 85 | Eventually(sess).Should(Say("Removing %s from %s...", domain, app.Name)) 86 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 87 | Expect(err).NotTo(HaveOccurred()) 88 | Eventually(sess).Should(Exit(0)) 89 | }) 90 | 91 | }) 92 | 93 | }) 94 | 95 | Context("who owns an existing app that has already been deployed", func() { 96 | 97 | var app model.App 98 | 99 | BeforeEach(func() { 100 | app = apps.Create(user, "--no-remote") 101 | builds.Create(user, app) 102 | }) 103 | 104 | AfterEach(func() { 105 | apps.Destroy(user, app) 106 | }) 107 | 108 | Context("with a domain added to it", func() { 109 | 110 | cmdRetryTimeout := 60 111 | 112 | var domain string 113 | 114 | BeforeEach(func() { 115 | domain = getRandDomain() 116 | domains.Add(user, app, domain) 117 | }) 118 | 119 | AfterEach(func() { 120 | domains.Remove(user, app, domain) 121 | // App can no longer be accessed at the previously associated domain 122 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -H "Host: %s" -w "%%{http_code}\\n" "%s" -o /dev/null`, domain, app.URL)} 123 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusNotFound), cmdRetryTimeout)).Should(BeTrue()) 124 | }) 125 | 126 | Specify("that app can be accessed at its usual address", func() { 127 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 128 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusOK), cmdRetryTimeout)).Should(BeTrue()) 129 | }) 130 | 131 | Specify("that app can be accessed at the associated domain", func() { 132 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -H "Host: %s" -w "%%{http_code}\\n" "%s" -o /dev/null`, domain, app.URL)} 133 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusOK), cmdRetryTimeout)).Should(BeTrue()) 134 | }) 135 | 136 | }) 137 | 138 | }) 139 | 140 | }) 141 | 142 | }) 143 | 144 | func getRandDomain() string { 145 | return fmt.Sprintf("my-custom-%d.domain.com", rand.Intn(999999999)) 146 | } 147 | -------------------------------------------------------------------------------- /tests/files/certs/bar.com.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEmDCCA4CgAwIBAgIJAN7RJoMZT8e4MA0GCSqGSIb3DQEBBQUAMIGOMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDTAL 4 | BgNVBAoTBERlaXMxFDASBgNVBAsTC0VuZ2luZWVyaW5nMRAwDgYDVQQDEwdiYXIu 5 | Y29tMSMwIQYJKoZIhvcNAQkBFhRlbmdpbmVlcmluZ0BkZWlzLmNvbTAeFw0xNjAx 6 | MTUyMzU3NTdaFw0xNzAxMTQyMzU3NTdaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UE 7 | CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDTALBgNVBAoTBERlaXMxFDAS 8 | BgNVBAsTC0VuZ2luZWVyaW5nMRAwDgYDVQQDEwdiYXIuY29tMSMwIQYJKoZIhvcN 9 | AQkBFhRlbmdpbmVlcmluZ0BkZWlzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP 10 | ADCCAQoCggEBAKRVo89Bt3bxQV/Bq86Pr7J4wkGWVi0pmWVOMeDs1UairygVbX3N 11 | ZA0gqhtyH0owJ67HUKN8KmEM3uAZ69YeYqYNTROO6MP5rEsAOM6XPCq+ZAucnpRi 12 | iEozqnHXVybO8ZZMMDH5j6J1fpABBap5qEKUSWdjkZM9Ub8cPpiYT2ZAIxtLRh2R 13 | EKPVGI5zpibeq69Z5IyHiO8kZy1h0hkA3vbqdG9L0W28xEsfbogMKi9B0o2ux2Lv 14 | 7hh8sExwk4gDqv7gTNBEsBaROAqJ+CoNJl9SFcnc2qcmbnoH7fkCfz+llbVQ5ySu 15 | uBQbpMZW6d5YuGyjvqKVknyoqihrcpr96P8CAwEAAaOB9jCB8zAdBgNVHQ4EFgQU 16 | hcUT7hWQJ++7+J9Oh4izif/uEnswgcMGA1UdIwSBuzCBuIAUhcUT7hWQJ++7+J9O 17 | h4izif/uEnuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQG 18 | A1UEBxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMERGVpczEUMBIGA1UECxMLRW5n 19 | aW5lZXJpbmcxEDAOBgNVBAMTB2Jhci5jb20xIzAhBgkqhkiG9w0BCQEWFGVuZ2lu 20 | ZWVyaW5nQGRlaXMuY29tggkA3tEmgxlPx7gwDAYDVR0TBAUwAwEB/zANBgkqhkiG 21 | 9w0BAQUFAAOCAQEAkHlv2nay6/vsLQsjpB3t9QjcQAlbJLlWQjLjbcsuiuJJVDel 22 | ksFK8Qq/PlUaEyUXJJYZZJUPreRQFeC/A0iQ8FXAhjsJmqIeEoqjZ5Qc4WJhK9qJ 23 | CpGddTTjcKptGbdoyQTsa6bo4bNJy041zD8QCn7X4CPHIqeDtalsTZen+bTG7C/q 24 | rdShlgzI5xp3wYi5FS2EiNxgRcHGyxgRjtOGHwLibUK+UMYhXvVKaMxfWwDCA+3u 25 | mFyVWnTaVQ7ui8ybmUpsfqeaS/xSZG49gBxiTbhYrpxak3gdOm2u2FTkC/nH2s6u 26 | jKN/IBX4p/sWwffqBZ34olAoo71JpunO2weVhQ== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/bar.com.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC6TCCAdECAQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UE 3 | BxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMERGVpczEUMBIGA1UECxMLRW5naW5l 4 | ZXJpbmcxEDAOBgNVBAMTB2Jhci5jb20xIzAhBgkqhkiG9w0BCQEWFGVuZ2luZWVy 5 | aW5nQGRlaXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApFWj 6 | z0G3dvFBX8Grzo+vsnjCQZZWLSmZZU4x4OzVRqKvKBVtfc1kDSCqG3IfSjAnrsdQ 7 | o3wqYQze4Bnr1h5ipg1NE47ow/msSwA4zpc8Kr5kC5yelGKISjOqcddXJs7xlkww 8 | MfmPonV+kAEFqnmoQpRJZ2ORkz1Rvxw+mJhPZkAjG0tGHZEQo9UYjnOmJt6rr1nk 9 | jIeI7yRnLWHSGQDe9up0b0vRbbzESx9uiAwqL0HSja7HYu/uGHywTHCTiAOq/uBM 10 | 0ESwFpE4Con4Kg0mX1IVydzapyZuegft+QJ/P6WVtVDnJK64FBukxlbp3li4bKO+ 11 | opWSfKiqKGtymv3o/wIDAQABoBUwEwYJKoZIhvcNAQkCMQYTBERlaXMwDQYJKoZI 12 | hvcNAQEFBQADggEBABS6cMdmwy+7aTiWNDGRzhWx+4H8GsQ/5syDAEMYXwgS/+sB 13 | xfxSC1jrq4yW/Pk66JrQU1kTCvziMUsk4dtAibAIgsTfkZIl1ev0RBs6pXQvMp7S 14 | qhrEaod8MNtlztgWz/lwE0V29r9orvFt4yeSAxr9l7XCFu3Xc6blB+/yGpNelPQn 15 | WBZBYAKzxMNltQCHc3A1OSd0b+OEHKYSc1DvCVNVFxFHjKPUuy97QyhSZa9qk87p 16 | jtJM9peJwpi3ZiGX8Ktf/QhROZBfGlIH4/GAIy3lu8dvlZ8XssYoPYQvMvzAFhgl 17 | 1iXwH1Swt/lMBsOv00vhdKmDGp+9yH+UEw6QKHs= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/files/certs/bar.com.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEApFWjz0G3dvFBX8Grzo+vsnjCQZZWLSmZZU4x4OzVRqKvKBVt 3 | fc1kDSCqG3IfSjAnrsdQo3wqYQze4Bnr1h5ipg1NE47ow/msSwA4zpc8Kr5kC5ye 4 | lGKISjOqcddXJs7xlkwwMfmPonV+kAEFqnmoQpRJZ2ORkz1Rvxw+mJhPZkAjG0tG 5 | HZEQo9UYjnOmJt6rr1nkjIeI7yRnLWHSGQDe9up0b0vRbbzESx9uiAwqL0HSja7H 6 | Yu/uGHywTHCTiAOq/uBM0ESwFpE4Con4Kg0mX1IVydzapyZuegft+QJ/P6WVtVDn 7 | JK64FBukxlbp3li4bKO+opWSfKiqKGtymv3o/wIDAQABAoIBABnj2CPt8Y6OocMJ 8 | Sx0G7CJM/iXBHqCM3jrkn90U0uEG/lttTMu2ER40WDhsuVtBzO6vPhgTlsWldnOO 9 | AebA8L/CdrMvH6LIcgl65ng9wV/mkPJ3YVB1WY1/KEo5J+TYU51fMXSeIa/xnNfp 10 | IVBjTEv4+ruMJ0IwNfHK7F20GUY9ccev5w4X6tGgTKVCkcTFL1yNDXtvbHfGMbN9 11 | maYq+gqzY2sgYabupmpO96krSSrudipFdXIM1yZ85wtTaZVGwzzpRKKhK/LMUb4L 12 | Ko/cZ3Q4sx1tLTiJgMw4GP8dDpKtlHQat8us7s5dsPAuj7uBpGMR045gcMTM6KFc 13 | 5/11UDkCgYEA2qk8RwaC4w94zBMvDbjqGbSACKF6FLp5gwJt8yqYtVuR/my02f35 14 | SpzQdEf2QDN+nOcKvrrCNhT6z5LoV3fCSQAhPMW6tEhsIq/mI1tNVuKIfywKWl+I 15 | 1cVCyYAjt/KKGrfVyQ7YB+vc9SOr+SzqXep8NFShSwt2WBZcsd/fQKsCgYEAwGWF 16 | RWgPBfHn9SrUjL43HVRsEc8DPbpdvql4SwopLhpBn2D8dGQ6DquSWBfL3sVl6Bnx 17 | bGqIfEBmipvd80zeXemCjSFhFWscN+7WTBd/Ck79IIsextniJOY8sdfnr/n3+M2/ 18 | c+m8iwhr7Yy2ZbH3nef6mQCfCHV+c+7DHzARAP0CgYEAnvZBV/En3iI1U0bvAi7Y 19 | IW/TVHLv6XnXNKLjg9AHzHCRpkEpCQFV5iQydxaJswq8lRxx906WOfLuk1DdkBkE 20 | KUXq499rZ/zugBkYWcPaabuuN6WwsRqaw63wa8S4MtYkCGB1DwG3k6qoq54PO8qn 21 | Zzc8rF6KE6B1nHxFTxrNlpkCgYBZmm5ZBr+Ia0M2QT5AVg5hEIJMQPcndnZWZ6Lo 22 | f9Dx8bSCP68TneIUFv/PGzYNiC2PzRVNAsiR5YRcJX9W4oPlhO0SQWtviDTaL9eK 23 | FJ9L88GbuG8a+TqDKN83jHAQ2wAL1fbGSyNONRvexFvmPN4vomxpeYqXa/D6mUYy 24 | bjZdGQKBgDGMSJmyHSNiygPtM809kEOys7QZnXtO4hUTvSyOWxxfwM1FLfdRrXIq 25 | 1xvek/xuSmrkL3Sl2mg/y2h4SscTB6ltbdVpptCShaCVIb6Yo59Uy+EVBRlpXqql 26 | v18kyMq2SH8gpgGQKKtqh2C/E0vhITyE+czmzz/5/+ONZ+mpYAir 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/foo.com.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEmDCCA4CgAwIBAgIJAMCMT+W6VVrXMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDTAL 4 | BgNVBAoTBERlaXMxFDASBgNVBAsTC0VuZ2luZWVyaW5nMRAwDgYDVQQDEwdmb28u 5 | Y29tMSMwIQYJKoZIhvcNAQkBFhRlbmdpbmVlcmluZ0BkZWlzLmNvbTAeFw0xNjAx 6 | MTUyMzU1NTlaFw0xNzAxMTQyMzU1NTlaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UE 7 | CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDTALBgNVBAoTBERlaXMxFDAS 8 | BgNVBAsTC0VuZ2luZWVyaW5nMRAwDgYDVQQDEwdmb28uY29tMSMwIQYJKoZIhvcN 9 | AQkBFhRlbmdpbmVlcmluZ0BkZWlzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP 10 | ADCCAQoCggEBAMnrT/CCYw2NLp20G3iBPKfB9siMqU/tmoYzkQW99RAJxL9REpuc 11 | 6oz9tY6YbSWNdBApqauE6feENp2v9SJ2xch/8QBcNjjX6gVoWs4KAqoztSYTlefm 12 | upAYqT62j5Iiyyp863csujIUJVfkpfhH1A1FRchLJc6dVRDvnPtiaFLoKs+3SsWz 13 | M9iOim6v/FCXLy90EDFk9vZo6Rprpb/pboQ1mkmnvj1NAyilMBZN2pIYHGNCUoO0 14 | FJI8dBgSPvEtFLZ2alQ81lzdWSOmK40apnLKn1JHhBlIcoO8zdyHqEVGDUwAX7WY 15 | ErE10mCwXiwe2pU/OO6OF0VqYmpa6YTd0wcCAwEAAaOB9jCB8zAdBgNVHQ4EFgQU 16 | HaOOS3qDMj6pESgURyqFkH0vbBIwgcMGA1UdIwSBuzCBuIAUHaOOS3qDMj6pESgU 17 | RyqFkH0vbBKhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQG 18 | A1UEBxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMERGVpczEUMBIGA1UECxMLRW5n 19 | aW5lZXJpbmcxEDAOBgNVBAMTB2Zvby5jb20xIzAhBgkqhkiG9w0BCQEWFGVuZ2lu 20 | ZWVyaW5nQGRlaXMuY29tggkAwIxP5bpVWtcwDAYDVR0TBAUwAwEB/zANBgkqhkiG 21 | 9w0BAQUFAAOCAQEAsovJHN6SIw7qoVSjVgIqb5xJUHckMZANoiddAirx8hcdQ9IA 22 | rsEEgaL3DwvgWdF4RtCyNVMIRNqUpsmsqEWToaBrlZz1hWpOOkg+6igBkjm2W1E0 23 | OVpxWJL1g5QGh4hm+6m3KLg14FtRXl098ubpagKJzRMXcrX0yBWFps+2tkchbNJF 24 | OSjgvoNCGnFJRJpGyYp2Y2DaQtkevownzGicmx4poPciCPToi/Temy4BXYpFCJPb 25 | 4vk4Cu/8EuLzxg5XeuMLEg0+aytAm6oLnynVfwus8TwpOElLDGtAG+MogMgA6vJb 26 | KjXQzpBEAiZlaiIuEAN4mq6sfo0rKLsAg1S/AQ== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/foo.com.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC6TCCAdECAQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UE 3 | BxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMERGVpczEUMBIGA1UECxMLRW5naW5l 4 | ZXJpbmcxEDAOBgNVBAMTB2Zvby5jb20xIzAhBgkqhkiG9w0BCQEWFGVuZ2luZWVy 5 | aW5nQGRlaXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyetP 6 | 8IJjDY0unbQbeIE8p8H2yIypT+2ahjORBb31EAnEv1ESm5zqjP21jphtJY10ECmp 7 | q4Tp94Q2na/1InbFyH/xAFw2ONfqBWhazgoCqjO1JhOV5+a6kBipPraPkiLLKnzr 8 | dyy6MhQlV+Sl+EfUDUVFyEslzp1VEO+c+2JoUugqz7dKxbMz2I6Kbq/8UJcvL3QQ 9 | MWT29mjpGmulv+luhDWaSae+PU0DKKUwFk3akhgcY0JSg7QUkjx0GBI+8S0UtnZq 10 | VDzWXN1ZI6YrjRqmcsqfUkeEGUhyg7zN3IeoRUYNTABftZgSsTXSYLBeLB7alT84 11 | 7o4XRWpialrphN3TBwIDAQABoBUwEwYJKoZIhvcNAQkCMQYTBERlaXMwDQYJKoZI 12 | hvcNAQEFBQADggEBABQJtFSeO+pjLmz9BtsN2yw7kxYD7rt0A578mlZMbKKHhZVA 13 | x9wgZt75DGZ/8FTqlq090VsobioodQDYUwuHKdFg3rADf0A1dO4VMYkuqKFv1Yei 14 | Kw0YibeqDIzElvLZu4HqVaNRZEfQfZR7umIWwaRyOu2J1VxN7He3dpde4XJfCQbE 15 | KGZFE3RKNnJTx0D7yvTJTIWDLRJkzwMjys5tTQ8SZV1yNZgbEEa05ggqgkGRcmX+ 16 | KoQ9xpPDEDaWn8m+6Ehiku+G76gFauTg4KB8zJaj8MSYRIjRcpZXi+GqaL2Ij+oW 17 | IukHD6hQVldYdsZvRPSLKh2lU+i4UNVc8wi/T8M= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/files/certs/foo.com.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAyetP8IJjDY0unbQbeIE8p8H2yIypT+2ahjORBb31EAnEv1ES 3 | m5zqjP21jphtJY10ECmpq4Tp94Q2na/1InbFyH/xAFw2ONfqBWhazgoCqjO1JhOV 4 | 5+a6kBipPraPkiLLKnzrdyy6MhQlV+Sl+EfUDUVFyEslzp1VEO+c+2JoUugqz7dK 5 | xbMz2I6Kbq/8UJcvL3QQMWT29mjpGmulv+luhDWaSae+PU0DKKUwFk3akhgcY0JS 6 | g7QUkjx0GBI+8S0UtnZqVDzWXN1ZI6YrjRqmcsqfUkeEGUhyg7zN3IeoRUYNTABf 7 | tZgSsTXSYLBeLB7alT847o4XRWpialrphN3TBwIDAQABAoIBAF8UWxQZkaLz9Bt2 8 | j+jykik8gIR3F9L3Q2gmKAfYJulicC7WcjisbxXs8e0vgVXJgfmKZMbLU5ClxUID 9 | dR7BZui+tjFBOpcRtLTPKtMSi6axqn8/gbstPnRT6H4LYRejIp/jKs13VkX2jo8Q 10 | r8Z1rDiDghSKrkbYdxH+gqEs+YrvyKKtQOL/Nu3FNyCRZ09482MZ9D59mNui9mMr 11 | mi6w2q+BcXcOAmbp3ZKt4doMJVaAeIl5SGptt8zwvpTe37gmLhEP4rwWBqdPhg1N 12 | O5pBvGYTuUKCvzUx9Btjh1kjwK9TmcxTZdp+gG/il3tZVR7EArlyiyz1jAcO18o5 13 | Ww2CcaECgYEA/kq3dOZvMuw679PNJrZk/M6vGbPVPBuivHDD0YkOjnpw6OtU0O10 14 | aeuhKeDPXw+41U2SHET0UuvVfhjHblXWalYADfP+IqTISbATpQoC8diNiALvxPQM 15 | byjnuvjOggRDsQZ8JObCtWI5aaQgz1K10YSNH6rjYpSQLSbhLob+3nMCgYEAy0aI 16 | 8pKUuD8D5J6ZGtAdZqwuY0p6mSodMcFMRK6//Hjk55V6Gl2UzbHavcXi1IxP0qHv 17 | HAicf6D0r4nIRYP2a0VNnWnDCvGaG/zJTTX3XTPN770CdFH8ylY5GFvqk11HK7oK 18 | zFy3m+dESW4DD6DTTivk/rCIBbduiqpdHeq+4B0CgYEAwccEGBQFhuOXYeyft7Fk 19 | MXX63vY4Nw7EKx8vSXxM2GwboJK8Vl2syY5iiLwkqkcbzYfIILy3Bn1qeiW9y6mj 20 | s/KHJhrZfWLesbB4t9pyNgOUjqHWPtrOouKj+8nf7Bn9z2emsKQcmgYYxBTrX7Gi 21 | ld+RfyFFF3koiQ/IpyD+FVsCgYBNHT8KtuzQUKeLbVcrwtPEhYE7jZ+gx0c3/tqO 22 | G7UddEdyS1R8+A9hUR1obM+2TlxhzajF+8ZS7J6mkSB2rq8m1q2xD9Q8LJeIEofT 23 | UKu8odB4KD3sHsZFhBw4z3XX3cUII5XBHVNSQ5O2P5PNs/c1apV+wT143bODy0lz 24 | 9f/fSQKBgQCHaPr8K2nxy+JSauJLq1eez0+zS0Pqta8Z0qYI7yluHBJXRfwHFwxI 25 | WrKiOT4VHYjROdfp4Hgj8sZb5vYAuFE/5C/iU0/kPPP+rURYruJFEIgOUOGF4iO6 26 | uyc144xaMPEunf4/yTeqkmBYx9Z6Qyno7o3S7/P0ZKKMjf9Q8IQMdA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/wildcard.foo.com.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIECDCCAvCgAwIBAgIJAIHJ8mUaHGJhMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMREwDwYDVQQKDAhEZWlz 4 | IEluYzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEjAQBgNVBAMMCSouZm9vLmNvbTEj 5 | MCEGCSqGSIb3DQEJARYUZW5naW5lZXJpbmdAZGVpcy5jb20wHhcNMTYwMTIyMjE1 6 | NjE1WhcNMTcwMTIxMjE1NjE1WjCBiTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNB 7 | MQswCQYDVQQHDAJTRjERMA8GA1UECgwIRGVpcyBJbmMxFDASBgNVBAsMC0VuZ2lu 8 | ZWVyaW5nMRIwEAYDVQQDDAkqLmZvby5jb20xIzAhBgkqhkiG9w0BCQEWFGVuZ2lu 9 | ZWVyaW5nQGRlaXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 10 | tkhK9dvmo82/TU6wPLOqjv/EeYWjWCOpqufp4sanUzbQOnI8pHDSLrGKOivjLl2b 11 | HG+mL5mTmulgNnbm7Z3wSVsVFPuFQfqk6ATSzaAB1+8uir8Ki+pA4fhPsg0xazVk 12 | RSKXF4xld8QroRYy0K+nMl9y35Ic49xy9MolFclrwRY1JLZoaRRssmR+QaHqOdGa 13 | Cs4JcdU6pFjyNdkqvcq2+IYiSHfebGIKaWESdNHUIXGi/gYfuo6ECcIafLZAbIMN 14 | eMqT0De4+pqf2pbn9rPp4OhF7s2C9+ejlAL0TBoFZQEsnNRexTc/YpHhdSW3SJgQ 15 | mPn4aKSka6+aLxy90ZuiswIDAQABo3EwbzASBgNVHREECzAJggdmb28uY29tMAsG 16 | A1UdDwQEAwIFoDAdBgNVHQ4EFgQUEj9sR+4CBgj3XJnjvQMUO9jpkv0wHwYDVR0j 17 | BBgwFoAUEj9sR+4CBgj3XJnjvQMUO9jpkv0wDAYDVR0TBAUwAwEB/zANBgkqhkiG 18 | 9w0BAQUFAAOCAQEAQb6dbn55lpXEsBjr+KNCmJ0LPbTB64ILB7wwykdGmmO49x9r 19 | 6zq1vFPpLbgZmOQ/9tgpJ+NZ3cTzsnWo9Vt1c4LZQmvLHVSR+PHtreA3c3DqFGCu 20 | QeoWrf17WWFVQFR4WUQjYD2mDZtkAJhqa1aL0AfMxeq01PHLVWZ+iD8xLZ3MZKSH 21 | efk6U1Krs+Kxa8kPMRkltze0iLdyA8Oe+x2JT1tX26A+3PtM4wENfMmkLuWWbRR5 22 | ikAVEOmWeEsBvZqVnP+UR4s9ewIBL1RFlTeHXXn6ukMASnWwQAdDU5Aisk0z0T1b 23 | kZqEQ8Lio0ZLMCVJmHiQ4GqQbpIbKjNF3/riCQ== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /tests/files/certs/wildcard.foo.com.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5DCCAcwCAQAwgYkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE 3 | BwwCU0YxETAPBgNVBAoMCERlaXMgSW5jMRQwEgYDVQQLDAtFbmdpbmVlcmluZzES 4 | MBAGA1UEAwwJKi5mb28uY29tMSMwIQYJKoZIhvcNAQkBFhRlbmdpbmVlcmluZ0Bk 5 | ZWlzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZISvXb5qPN 6 | v01OsDyzqo7/xHmFo1gjqarn6eLGp1M20DpyPKRw0i6xijor4y5dmxxvpi+Zk5rp 7 | YDZ25u2d8ElbFRT7hUH6pOgE0s2gAdfvLoq/CovqQOH4T7INMWs1ZEUilxeMZXfE 8 | K6EWMtCvpzJfct+SHOPccvTKJRXJa8EWNSS2aGkUbLJkfkGh6jnRmgrOCXHVOqRY 9 | 8jXZKr3KtviGIkh33mxiCmlhEnTR1CFxov4GH7qOhAnCGny2QGyDDXjKk9A3uPqa 10 | n9qW5/az6eDoRe7Ngvfno5QC9EwaBWUBLJzUXsU3P2KR4XUlt0iYEJj5+GikpGuv 11 | mi8cvdGborMCAwEAAaAVMBMGCSqGSIb3DQEJAjEGDAREZWlzMA0GCSqGSIb3DQEB 12 | BQUAA4IBAQBCypl/lb1F1fwtaRVvvsWG5JIpRzzGaevatsmN54tGKAr7ixYN38C0 13 | zgfSOQSefUN5yUd7d5UWJIA1A0hvVthtMwTKNv4giGy3ow+XC+VXPBsi668Q7hfP 14 | m19fmsDbV0v8RGSFBmY/qTUyVFKwFo8nveXnVyLurOIqz9c9Z+gy+HwyNL/u95L5 15 | DD2bXqvmjMqtKqO7b7cF0eBfTDuhv62vigFbm3JNi8UeLJrFV+wketh5hthY/Ujj 16 | xQfCfaA61bUjqSHHbIpXQ/bbsOHsAHtWAH06JOgDIOEpIyzOulmYay5IaYa4+wdS 17 | srDcCeiKNACra0GXtP1s86XLb01gsjkw 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/files/certs/wildcard.foo.com.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAtkhK9dvmo82/TU6wPLOqjv/EeYWjWCOpqufp4sanUzbQOnI8 3 | pHDSLrGKOivjLl2bHG+mL5mTmulgNnbm7Z3wSVsVFPuFQfqk6ATSzaAB1+8uir8K 4 | i+pA4fhPsg0xazVkRSKXF4xld8QroRYy0K+nMl9y35Ic49xy9MolFclrwRY1JLZo 5 | aRRssmR+QaHqOdGaCs4JcdU6pFjyNdkqvcq2+IYiSHfebGIKaWESdNHUIXGi/gYf 6 | uo6ECcIafLZAbIMNeMqT0De4+pqf2pbn9rPp4OhF7s2C9+ejlAL0TBoFZQEsnNRe 7 | xTc/YpHhdSW3SJgQmPn4aKSka6+aLxy90ZuiswIDAQABAoIBAQCr/RIdYFsB+0Oh 8 | IbnPzIYFXvZ24sz4gM254BAiVOXT8kgOnXLyhTELtaCCup4kRVXxQrc++lz3MXQC 9 | b7X8RaVO0Y3WumtFkcS+1q5ALdUPdTIo35CH64NEsxIfIaubSElPog+FvIaQtpuj 10 | 5loT5WiQctbkc+ymYn5k0cakA+STzVQY0LK4Aoql7ZgVsexEVLe1eIU1ufOr3Ss5 11 | hg1/JjRJpzyCNv7yI8xr6CAfhUWtuGeGSQPYhK+f9SrypT5Fhot8kke9fCQvC3IG 12 | VbWurrO11RsuotRG5BRfln6fVK/v4ZmmAP41/vqyOwxJkcp/7q67vQkCIpYpwDAR 13 | MbHGwSKhAoGBAORa0qoC5oSm93l0wz2KAgLgK4MDsOl+q4B8290YUzYzOyBUTIH5 14 | 0wgF5waS0qOKPuGzut2z6PE44Aw+OMbxBye3Zjp1rN5c5CknDnK81QMUq2Iz+3xk 15 | PFIyxU2NEubrRzRoResFPQfT9WfdmJX1HPtMja8edvWbx8iUKy9oMGXHAoGBAMxZ 16 | l+rxQs+Edh5ONg2eiOmcANKldJw9LC5g99ezl/q39tdSkCPbhUtrSm+9m9/7JTht 17 | g8Sk9SB3ifYZue5Z1LAJ5o75yH/rsev86URn32NxNFtklaLYhiCYfUxv9P0rzjuK 18 | JNDBMqq+Mn9ebW8mkLuInornVXmjBLVU679+Wuu1AoGACGd/UVqB+Wfbu9CcTuuB 19 | X3G4qD2+iRlsXnI59U0r4tbH2ky/9Bipt6Xf9tH4hqRT5CKlQfuZGyeot0qi9E9y 20 | n/eT/5rNHfH1Q754PajNfiuIkziujMlznuLXeB81DuKh4D/mMtwifuNCKOxy7TH0 21 | Oxt6K3PHlQqCs9MgM8J15YkCgYBoov/NR9ikFfm9ruKyupj0tfMd6ab6UcCLxw9h 22 | ng6WTRjSTO6NzdxFMB0fdoGYgSsf58PvL2BtTYiRQb8ZM1pbAdbTI0ftaKzkX866 23 | Pk3+x3q9yZVtm12i1zJhr3pNIN8rUaNkWWkuUNHesmVq4t59iIlWKvpznGvOxjsp 24 | BPRdeQKBgHat/1jqhXgWZHskAYs9PTQpx/tcHOWWH1P3yxvxxa3Uo85OEnVJJLT6 25 | 2WYKbPWGzwYvJSm36n9/hK3vMuGEQb72/AXc6OW1/yGAeHbV1MOYs1zc6BM1RkyP 26 | EG/LxuEm6tZnswLehRcfR5/dZnd4ZwJzEfHGReIpbVXhxSHqAcyC 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/www.foo.com.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpDCCA4ygAwIBAgIJAP/qzWOu0pEmMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDTAL 4 | BgNVBAoTBERlaXMxFDASBgNVBAsTC0VuZ2luZWVyaW5nMRQwEgYDVQQDEwt3d3cu 5 | Zm9vLmNvbTEjMCEGCSqGSIb3DQEJARYUZW5naW5lZXJpbmdAZGVpcy5jb20wHhcN 6 | MTYwMTE1MjM1OTAyWhcNMTcwMTE0MjM1OTAyWjCBkjELMAkGA1UEBhMCVVMxCzAJ 7 | BgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKEwREZWlz 8 | MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEUMBIGA1UEAxMLd3d3LmZvby5jb20xIzAh 9 | BgkqhkiG9w0BCQEWFGVuZ2luZWVyaW5nQGRlaXMuY29tMIIBIjANBgkqhkiG9w0B 10 | AQEFAAOCAQ8AMIIBCgKCAQEAyi0V5kN1jejOtWSZMFAnexImOUZ9lWQVDFbZ05x3 11 | V2oakJB5WARl+p7yHRByyhMK2s8hb7jERGgsVL3qrrKRx+dswA3qIwTHj2WNFBfW 12 | xsmbCMpAuhu8Qv68GI5l7dA1h332m4NtWQSh9eM7T6LjBeJkc22CYkD+MnOqwt3b 13 | y7+t99OjPzlzOYi48ejA3n5mnCUYDclx1WEAhrGwo8hsNgRYUgxMdvbkFWKdet59 14 | ZoLA4OU/iBIYvaFKrFEJu9fGQOzIb495ocdUXLnHeEz9kYMvCq6mvhDJt+ntvXT+ 15 | xFDnDmPaJTd53zb9OUjXhsys8xci1NNmIEtnUoVI8CijfQIDAQABo4H6MIH3MB0G 16 | A1UdDgQWBBTIHXCCSmUjbHRm85Y++VYKb5GGlTCBxwYDVR0jBIG/MIG8gBTIHXCC 17 | SmUjbHRm85Y++VYKb5GGlaGBmKSBlTCBkjELMAkGA1UEBhMCVVMxCzAJBgNVBAgT 18 | AkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKEwREZWlzMRQwEgYD 19 | VQQLEwtFbmdpbmVlcmluZzEUMBIGA1UEAxMLd3d3LmZvby5jb20xIzAhBgkqhkiG 20 | 9w0BCQEWFGVuZ2luZWVyaW5nQGRlaXMuY29tggkA/+rNY67SkSYwDAYDVR0TBAUw 21 | AwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAnwL+sDHeGiX3vjoSrIB5nKpyIOI5bZvc 22 | BWV0BNSioe3ze2MIQAoYXKqHccsMbnSRMtO6xF+4NY/HCdYEtMh0fjyq9bmjcd4J 23 | 5UJGyNzDeWWMVo97Ue5loG2rEQXMwwLTAipPYLA9MLx2zZJ7j8NU+fH4WZPlg8AK 24 | +6yqXAwj/2wqzgzFz2lOxqtWpTf7/VgsvdgHBG7DPJe49YFZVUIByTCjkKhAyKO+ 25 | 5OAkouFhReRvPU4Q6VBsdKbpViSD7nAwaeb0Rzcb/3i7oH79l5mu3RNLkIXPAJui 26 | VayurtFbPvKsp/TLjZ/hJY/ocnwthBLVGwzm5X1lPnrFS5ycEOSQWw== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /tests/files/certs/www.foo.com.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC7TCCAdUCAQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UE 3 | BxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMERGVpczEUMBIGA1UECxMLRW5naW5l 4 | ZXJpbmcxFDASBgNVBAMTC3d3dy5mb28uY29tMSMwIQYJKoZIhvcNAQkBFhRlbmdp 5 | bmVlcmluZ0BkZWlzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 6 | AMotFeZDdY3ozrVkmTBQJ3sSJjlGfZVkFQxW2dOcd1dqGpCQeVgEZfqe8h0QcsoT 7 | CtrPIW+4xERoLFS96q6ykcfnbMAN6iMEx49ljRQX1sbJmwjKQLobvEL+vBiOZe3Q 8 | NYd99puDbVkEofXjO0+i4wXiZHNtgmJA/jJzqsLd28u/rffToz85czmIuPHowN5+ 9 | ZpwlGA3JcdVhAIaxsKPIbDYEWFIMTHb25BVinXrefWaCwODlP4gSGL2hSqxRCbvX 10 | xkDsyG+PeaHHVFy5x3hM/ZGDLwqupr4Qybfp7b10/sRQ5w5j2iU3ed82/TlI14bM 11 | rPMXItTTZiBLZ1KFSPAoo30CAwEAAaAVMBMGCSqGSIb3DQEJAjEGEwREZWlzMA0G 12 | CSqGSIb3DQEBBQUAA4IBAQDAnMAFZXdasWR32o/fakHltjDjISyWbH3rpM/kTLI5 13 | SmX08y7oJQFJe4b8WKwYFQ+sTixDveoDWzgzwJQo4kRPqFBnRFlo84hWfYMNQIdz 14 | pFSmgWJ5Cp1r4JsVm5Btvcag04QmtihgbMt5WT2T96HPdYkCjI3LxqTtXa2WLcGW 15 | QUiMB0Tk0WGn3CSLA4SXgM3U/dwrs8/KTP1sCE0Ne+zHeC7q1mIS35qNJzo4xkv4 16 | tmYRU8VYDYiYSbovULfIaozZpREo06ioVdztjXt/UnCOxE1mMl3xgPoesodiBMPD 17 | j3YMzpl72QD18EU8br36XmMqIYk/+qzLJTOS9Gfg7GzN 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/files/certs/www.foo.com.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAyi0V5kN1jejOtWSZMFAnexImOUZ9lWQVDFbZ05x3V2oakJB5 3 | WARl+p7yHRByyhMK2s8hb7jERGgsVL3qrrKRx+dswA3qIwTHj2WNFBfWxsmbCMpA 4 | uhu8Qv68GI5l7dA1h332m4NtWQSh9eM7T6LjBeJkc22CYkD+MnOqwt3by7+t99Oj 5 | PzlzOYi48ejA3n5mnCUYDclx1WEAhrGwo8hsNgRYUgxMdvbkFWKdet59ZoLA4OU/ 6 | iBIYvaFKrFEJu9fGQOzIb495ocdUXLnHeEz9kYMvCq6mvhDJt+ntvXT+xFDnDmPa 7 | JTd53zb9OUjXhsys8xci1NNmIEtnUoVI8CijfQIDAQABAoIBAF5CdPJdQ0J9Z1pk 8 | 45MF29JiXNXZSpXLCpEtMPObAH0N6AK8iQaDTnRxhJoOYCZciHQJnCI1d7QZCYoc 9 | 3XzDnnogKLDGDAJ1qQDvLL5Qev9FYXXQrirW4Ygusc2VHmqo5zwbe014EhQtt8En 10 | RzDS1ZuZuJGkXeSnPpyRFu0xeNdd2g588a1/XHosqnA4zFkaK4yVACq/+x0jcGDK 11 | OAAky95i205fGhpReTcRWPTpVWarpA5PdfA/JOSRaZpQAvFMcXLPilEdrcgiB6bF 12 | 29Tn4Mwg6usq5osy2ePW0M0eE8ZvG4+7zhuEQ5NdceFq2Fj4A1Svl44Hsrnb/Vts 13 | PxHO/jUCgYEA/6JrabRN6uNpNHpNoBg5gYRGa5y4yzhuhEgUxH1I7diHr+h9Y3Sw 14 | +/enDq6CHRpcXyOXI349wOF2ZXfMC1yFzQ2IgoMIG7SaIItAfkvqdXmlrajFSPE4 15 | mbIf/ugXqgvm+oWz6l9FIqCJNNKlwxquUMByOFOjJ71hn61lVeDIMEsCgYEAyncY 16 | rfKRbqK1USJUTRrnGA+Lp6BPKahYcgEcatYNC6Odq8uW91c3Vzd5fG5JVoUY3Ihi 17 | G0UhcIDMnM1I74PUavfQPmDAyNPEQ/120UouQNgEMBHs1mOCPezD4wVDuCImOiCE 18 | L7BYHJU1yUgkZr20K65xcAIYavIxh7Tc1bogblcCgYEApY2/eI6Po54xhQ3r9dGa 19 | dHmAzbKKrvnWAQ9Ze8MTlw2TGmY7xkxNTnEdnNGBbG2lAuxetlrMjXy2m5IQ8A60 20 | jI7GKJfJiX/WDVuBoglyRzBIDwZs9gdau5bzR7dxk+vvY7FxSkj20i0bjr0ZIxjF 21 | aYCouDfaQyNP9QRry0ku/K8CgYEAwRRwubpBDRQn+/bUFDAawGxaz4Hm3KBJsHb0 22 | xcHZ8QaYn7PpBXnsMcWampqGX/dP7Ug23zC/Ig4Ck2qGKrw6v8QSmNomH58sZXZ7 23 | cD3g/D/FRp5hkVaWZz261W441YnjkL1fsibm8GMvRwQAiuZQwvN6BMpKxPqxV2yY 24 | yU3WDcMCgYEAhEyNSTj+K5ZjZPGQdmmTUtrB9J0vT5h5RxoqaNY3w8lxZhmebN8w 25 | +6ndqzxjkdlRuSwiceqZnZZJfI7bTH9j9to+PR5ahJlr14zpUXQoCkkBqgJLfk1e 26 | VLKCH2pTUjDm/Q2CQ4AygR2hM1KmzQKDRMazynUGmMX+9yzCUNawquE= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/git_push_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "time" 7 | 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/git" 12 | "github.com/deis/workflow-e2e/tests/cmd/keys" 13 | "github.com/deis/workflow-e2e/tests/model" 14 | "github.com/deis/workflow-e2e/tests/settings" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | . "github.com/onsi/gomega/gbytes" 19 | . "github.com/onsi/gomega/gexec" 20 | ) 21 | 22 | var _ = Describe("git push deis master", func() { 23 | 24 | Context("with an existing user", func() { 25 | 26 | var user model.User 27 | var keyPath string 28 | 29 | BeforeEach(func() { 30 | user = auth.RegisterAndLogin() 31 | }) 32 | 33 | AfterEach(func() { 34 | auth.Cancel(user) 35 | }) 36 | 37 | Context("who has added their public key", func() { 38 | 39 | BeforeEach(func() { 40 | _, keyPath = keys.Add(user) 41 | }) 42 | 43 | Context("and who has a local git repo containing buildpack source code", func() { 44 | 45 | BeforeEach(func() { 46 | output, err := cmd.Execute(`git clone https://github.com/deis/example-go.git`) 47 | Expect(err).NotTo(HaveOccurred(), output) 48 | }) 49 | 50 | Context("and has run `deis apps:create` from within that repo", func() { 51 | 52 | var app model.App 53 | 54 | BeforeEach(func() { 55 | os.Chdir("example-go") 56 | app = apps.Create(user) 57 | }) 58 | 59 | AfterEach(func() { 60 | apps.Destroy(user, app) 61 | }) 62 | 63 | Specify("that user can deploy that app using a git push", func() { 64 | git.Push(user, keyPath, app, "Powered by Deis") 65 | }) 66 | 67 | Specify("that user can interrupt the deploy of the app and recover", func() { 68 | git.PushWithInterrupt(user, keyPath) 69 | 70 | git.PushUntilResult(user, keyPath, 71 | model.CmdResult{ 72 | Out: nil, 73 | Err: []byte("Everything up-to-date"), 74 | ExitCode: 0, 75 | }) 76 | }) 77 | 78 | Specify("that user can deploy that app only once concurrently", func() { 79 | sess := git.StartPush(user, keyPath) 80 | // sleep for five seconds, then push the same app 81 | time.Sleep(5000 * time.Millisecond) 82 | sess2 := git.StartPush(user, keyPath) 83 | Eventually(sess2.Err).Should(Say("fatal: remote error: Another git push is ongoing")) 84 | Eventually(sess2).Should(Exit(128)) 85 | git.PushUntilResult(user, keyPath, 86 | model.CmdResult{ 87 | Out: nil, 88 | Err: []byte("Everything up-to-date"), 89 | ExitCode: 0, 90 | }) 91 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 92 | git.Curl(app, "Powered by Deis") 93 | }) 94 | 95 | Context("with a bad buildpack", func() { 96 | 97 | BeforeEach(func() { 98 | badBuildpackURL := "https://github.com/deis/heroku-buildpack-epic-fail.git" 99 | sess, err := cmd.Start("deis config:set BUILDPACK_URL=%s", &user, badBuildpackURL) 100 | Expect(err).NotTo(HaveOccurred()) 101 | Eventually(sess).Should(Say("BUILDPACK_URL")) 102 | Eventually(sess).Should(Exit(0)) 103 | }) 104 | 105 | AfterEach(func() { 106 | sess, err := cmd.Start("deis config:unset BUILDPACK_URL", &user) 107 | Expect(err).NotTo(HaveOccurred()) 108 | Eventually(sess).ShouldNot(Say("BUILDPACK_URL")) 109 | Eventually(sess).Should(Exit(0)) 110 | }) 111 | 112 | Specify("that user can't deploy the app", func() { 113 | sess := git.StartPush(user, keyPath) 114 | Eventually(sess.Err, settings.MaxEventuallyTimeout).Should(Say("-----> Fetching custom buildpack")) 115 | Eventually(sess.Err).Should(Say("exited with code 1, stopping build")) 116 | Eventually(sess).Should(Exit(1)) 117 | }) 118 | 119 | }) 120 | 121 | Context("and who has another local git repo containing buildpack source code", func() { 122 | 123 | BeforeEach(func() { 124 | os.Chdir("..") 125 | output, err := cmd.Execute(`git clone https://github.com/deis/example-nodejs-express.git`) 126 | Expect(err).NotTo(HaveOccurred(), output) 127 | }) 128 | 129 | Context("and has run `deis apps:create` from within that repo", func() { 130 | 131 | var app2 model.App 132 | 133 | BeforeEach(func() { 134 | os.Chdir("example-nodejs-express") 135 | app2 = apps.Create(user) 136 | }) 137 | 138 | AfterEach(func() { 139 | apps.Destroy(user, app2) 140 | }) 141 | 142 | Specify("that user can deploy both apps concurrently", func() { 143 | os.Chdir(filepath.Join("..", "example-go")) 144 | sess := git.StartPush(user, keyPath) 145 | os.Chdir(filepath.Join("..", "example-nodejs-express")) 146 | sess2 := git.StartPush(user, keyPath) 147 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 148 | Eventually(sess2, settings.MaxEventuallyTimeout).Should(Exit(0)) 149 | git.Curl(app, "Powered by Deis") 150 | git.Curl(app2, "Powered by Deis") 151 | }) 152 | 153 | }) 154 | 155 | }) 156 | 157 | Specify("and can execute deis run successfully", func() { 158 | git.Push(user, keyPath, app, "Powered by Deis") 159 | sess, err := cmd.Start("deis run env -a %s", &user, app.Name) 160 | Expect(err).NotTo(HaveOccurred()) 161 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 162 | }) 163 | 164 | }) 165 | 166 | }) 167 | 168 | Context("and who has a local git repo containing dockerfile source code", func() { 169 | 170 | BeforeEach(func() { 171 | output, err := cmd.Execute(`git clone https://github.com/deis/example-dockerfile-http.git`) 172 | Expect(err).NotTo(HaveOccurred(), output) 173 | }) 174 | 175 | Context("and has run `deis apps:create` from within that repo", func() { 176 | 177 | var app model.App 178 | 179 | BeforeEach(func() { 180 | os.Chdir("example-dockerfile-http") 181 | app = apps.Create(user) 182 | }) 183 | 184 | AfterEach(func() { 185 | apps.Destroy(user, app) 186 | }) 187 | 188 | Specify("that user can deploy that app using a git push", func() { 189 | git.Push(user, keyPath, app, "Powered by Deis") 190 | }) 191 | 192 | Specify("that user can deploy that app using a git push after setting config values", func() { 193 | sess, err := cmd.Start("deis config:set -a %s PORT=80 POWERED_BY=midi-chlorians", &user, app.Name) 194 | Expect(err).NotTo(HaveOccurred()) 195 | Eventually(sess).Should(Say("Creating config")) 196 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Config", app.Name)) 197 | output := string(sess.Out.Contents()) 198 | Expect(output).To(MatchRegexp(`PORT\s+80`)) 199 | Expect(output).To(MatchRegexp(`POWERED_BY\s+midi-chlorians`)) 200 | Expect(err).NotTo(HaveOccurred()) 201 | Eventually(sess).Should(Exit(0)) 202 | 203 | git.Push(user, keyPath, app, "Powered by midi-chlorians") 204 | }) 205 | 206 | Specify("that user can deploy that app only once concurrently", func() { 207 | sess := git.StartPush(user, keyPath) 208 | // sleep for five seconds, then push the same app 209 | time.Sleep(5000 * time.Millisecond) 210 | sess2 := git.StartPush(user, keyPath) 211 | Eventually(sess2.Err).Should(Say("fatal: remote error: Another git push is ongoing")) 212 | Eventually(sess2).Should(Exit(128)) 213 | git.PushUntilResult(user, keyPath, 214 | model.CmdResult{ 215 | Out: nil, 216 | Err: []byte("Everything up-to-date"), 217 | ExitCode: 0, 218 | }) 219 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 220 | git.Curl(app, "Powered by Deis") 221 | }) 222 | 223 | Context("with a bad Dockerfile", func() { 224 | 225 | BeforeEach(func() { 226 | badCommit := `echo "BOGUS command" >> Dockerfile && EMAIL="ci@deis.com" git commit Dockerfile -m "Added a bogus command"` 227 | output, err := cmd.Execute(badCommit) 228 | Expect(err).NotTo(HaveOccurred(), output) 229 | }) 230 | 231 | AfterEach(func() { 232 | undoCommit := `git reset --hard HEAD~` 233 | output, err := cmd.Execute(undoCommit) 234 | Expect(err).NotTo(HaveOccurred(), output) 235 | }) 236 | 237 | Specify("that user can't deploy that app using a git push", func() { 238 | sess := git.StartPush(user, keyPath) 239 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(1)) 240 | Eventually(sess.Err).Should(Say("Unknown instruction: BOGUS")) 241 | Eventually(sess.Err).Should(Say("error: failed to push some refs")) 242 | }) 243 | 244 | }) 245 | 246 | Context("and who has another local git repo containing dockerfile source code", func() { 247 | 248 | BeforeEach(func() { 249 | os.Chdir("..") 250 | output, err := cmd.Execute(`git clone https://github.com/deis/example-dockerfile-procfile-http.git`) 251 | Expect(err).NotTo(HaveOccurred(), output) 252 | }) 253 | 254 | Context("and has run `deis apps:create` from within that repo", func() { 255 | 256 | var app2 model.App 257 | 258 | BeforeEach(func() { 259 | os.Chdir("example-dockerfile-procfile-http") 260 | app2 = apps.Create(user) 261 | }) 262 | 263 | AfterEach(func() { 264 | apps.Destroy(user, app2) 265 | }) 266 | 267 | Specify("that user can deploy both apps concurrently", func() { 268 | os.Chdir(filepath.Join("..", "example-dockerfile-http")) 269 | sess := git.StartPush(user, keyPath) 270 | os.Chdir(filepath.Join("..", "example-dockerfile-procfile-http")) 271 | sess2 := git.StartPush(user, keyPath) 272 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 273 | Eventually(sess2, settings.MaxEventuallyTimeout).Should(Exit(0)) 274 | git.Curl(app, "Powered by Deis") 275 | git.Curl(app2, "Powered by Deis") 276 | _ = listProcs(user, app2, "web") 277 | }) 278 | 279 | }) 280 | 281 | }) 282 | 283 | Specify("and can execute deis run successfully", func() { 284 | git.Push(user, keyPath, app, "Powered by Deis") 285 | sess, err := cmd.Start("deis run env -a %s", &user, app.Name) 286 | Expect(err).NotTo(HaveOccurred()) 287 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 288 | }) 289 | 290 | }) 291 | 292 | }) 293 | 294 | }) 295 | 296 | }) 297 | 298 | }) 299 | -------------------------------------------------------------------------------- /tests/healthcheck_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/cmd/apps" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/cmd/builds" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/ginkgo/extensions/table" 13 | . "github.com/onsi/gomega" 14 | . "github.com/onsi/gomega/gbytes" 15 | . "github.com/onsi/gomega/gexec" 16 | ) 17 | 18 | var _ = Describe("deis healthchecks", func() { 19 | 20 | Context("with an existing user", func() { 21 | 22 | var user model.User 23 | 24 | BeforeEach(func() { 25 | user = auth.RegisterAndLogin() 26 | }) 27 | 28 | AfterEach(func() { 29 | auth.Cancel(user) 30 | }) 31 | 32 | Context("who owns an existing app that has already been deployed", func() { 33 | 34 | var app model.App 35 | 36 | BeforeEach(func() { 37 | app = apps.Create(user, "--no-remote") 38 | builds.Create(user, app) 39 | }) 40 | 41 | AfterEach(func() { 42 | apps.Destroy(user, app) 43 | }) 44 | 45 | Specify("that user can list healthchecks on that app", func() { 46 | sess, err := cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 47 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Eventually(sess).Should(Exit(0)) 50 | }) 51 | 52 | Specify("that user can set an exec liveness healthcheck", func() { 53 | sess, err := cmd.Start("deis healthchecks:set -a %s liveness exec -- /bin/true", &user, app.Name) 54 | Expect(err).NotTo(HaveOccurred()) 55 | Eventually(sess).Should(Say("Applying livenessProbe healthcheck...")) 56 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 57 | Eventually(sess).Should(Say(`Exec Probe\: Command=\[/bin/true]`)) 58 | Expect(err).NotTo(HaveOccurred()) 59 | Eventually(sess).Should(Exit(0)) 60 | 61 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 62 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 63 | Eventually(sess).Should(Say(`Exec Probe\: Command=\[/bin/true]`)) 64 | Expect(err).NotTo(HaveOccurred()) 65 | Eventually(sess).Should(Exit(0)) 66 | }) 67 | 68 | // 1500 is the port of the app we are deploying deis/example-dockerfile-http 69 | Specify("that user can set a httpGet liveness healthcheck", func() { 70 | sess, err := cmd.Start("deis healthchecks:set liveness httpGet -a %s 1500", &user, app.Name) 71 | Eventually(sess).Should(Say("Applying livenessProbe healthcheck...")) 72 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 73 | Eventually(sess).Should(Say(`HTTP GET Probe\: Path="/" Port=1500 HTTPHeaders=\[]`)) 74 | Expect(err).NotTo(HaveOccurred()) 75 | Eventually(sess).Should(Exit(0)) 76 | 77 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 78 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 79 | Eventually(sess).Should(Say(`HTTP GET Probe\: Path="/" Port=1500 HTTPHeaders=\[]`)) 80 | Expect(err).NotTo(HaveOccurred()) 81 | Eventually(sess).Should(Exit(0)) 82 | }) 83 | 84 | Specify("that user can set a tcpSocket liveness healthcheck", func() { 85 | sess, err := cmd.Start("deis healthchecks:set liveness tcpSocket -a %s 1500", &user, app.Name) 86 | Eventually(sess).Should(Say("Applying livenessProbe healthcheck...")) 87 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 88 | Eventually(sess).Should(Say(`TCP Socket Probe\: Port=1500`)) 89 | Expect(err).NotTo(HaveOccurred()) 90 | Eventually(sess).Should(Exit(0)) 91 | 92 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 93 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 94 | Eventually(sess).Should(Say(`TCP Socket Probe\: Port=1500`)) 95 | Expect(err).NotTo(HaveOccurred()) 96 | Eventually(sess).Should(Exit(0)) 97 | }) 98 | 99 | Specify("that user can set an exec readiness healthcheck", func() { 100 | sess, err := cmd.Start("deis healthchecks:set readiness exec -a %s -- /bin/true", &user, app.Name) 101 | Eventually(sess).Should(Say("Applying readinessProbe healthcheck...")) 102 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 103 | Eventually(sess).Should(Say(`Exec Probe\: Command=\[/bin/true]`)) 104 | Expect(err).NotTo(HaveOccurred()) 105 | Eventually(sess).Should(Exit(0)) 106 | 107 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 108 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 109 | Eventually(sess).Should(Say(`Exec Probe\: Command=\[/bin/true]`)) 110 | Expect(err).NotTo(HaveOccurred()) 111 | Eventually(sess).Should(Exit(0)) 112 | }) 113 | 114 | Specify("that user can set a httpGet readiness healthcheck", func() { 115 | sess, err := cmd.Start("deis healthchecks:set readiness httpGet -a %s 1500", &user, app.Name) 116 | Eventually(sess).Should(Say("Applying readinessProbe healthcheck...")) 117 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 118 | Eventually(sess).Should(Say(`HTTP GET Probe\: Path="/" Port=1500 HTTPHeaders=\[]`)) 119 | Expect(err).NotTo(HaveOccurred()) 120 | Eventually(sess).Should(Exit(0)) 121 | 122 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 123 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 124 | Eventually(sess).Should(Say(`HTTP GET Probe\: Path="/" Port=1500 HTTPHeaders=\[]`)) 125 | Expect(err).NotTo(HaveOccurred()) 126 | Eventually(sess).Should(Exit(0)) 127 | }) 128 | 129 | Specify("that user can set a tcpSocket readiness healthcheck", func() { 130 | sess, err := cmd.Start("deis healthchecks:set readiness tcpSocket -a %s 1500", &user, app.Name) 131 | Eventually(sess).Should(Say("Applying readinessProbe healthcheck...")) 132 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 133 | Eventually(sess).Should(Say(`TCP Socket Probe\: Port=1500`)) 134 | Expect(err).NotTo(HaveOccurred()) 135 | Eventually(sess).Should(Exit(0)) 136 | 137 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 138 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 139 | Eventually(sess).Should(Say(`TCP Socket Probe\: Port=1500`)) 140 | Expect(err).NotTo(HaveOccurred()) 141 | Eventually(sess).Should(Exit(0)) 142 | }) 143 | 144 | Context("and already has a healthcheck set", func() { 145 | BeforeEach(func() { 146 | sess, err := cmd.Start(`deis healthchecks:set readiness exec -a %s -- /bin/true`, &user, app.Name) 147 | Eventually(sess).Should(Say("Applying readinessProbe healthcheck...")) 148 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 149 | Eventually(sess).Should(Say(`Exec Probe\: Command=\[/bin/true]`)) 150 | Expect(err).NotTo(HaveOccurred()) 151 | Eventually(sess).Should(Exit(0)) 152 | }) 153 | 154 | Specify("that user can unset that healthcheck", func() { 155 | sess, err := cmd.Start("deis healthchecks:unset -a %s readiness", &user, app.Name) 156 | Eventually(sess).Should(Say("Removing healthchecks...")) 157 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Healthchecks", app.Name)) 158 | Eventually(sess).ShouldNot(Say(`Exec Probe\: Command=\[/bin/true]`)) 159 | Expect(err).NotTo(HaveOccurred()) 160 | Eventually(sess).Should(Exit(0)) 161 | 162 | sess, err = cmd.Start("deis healthchecks:list -a %s", &user, app.Name) 163 | Eventually(sess).Should(Say("=== %s Healthchecks", app.Name)) 164 | Eventually(sess).ShouldNot(Say(`Exec Probe\: Command=\[/bin/true]`)) 165 | Expect(err).NotTo(HaveOccurred()) 166 | Eventually(sess).Should(Exit(0)) 167 | }) 168 | }) 169 | }) 170 | }) 171 | 172 | DescribeTable("any user can get command-line help for healthchecks", func(command string, expected string) { 173 | sess, err := cmd.Start(command, nil) 174 | Eventually(sess).Should(Say(expected)) 175 | Expect(err).NotTo(HaveOccurred()) 176 | Eventually(sess).Should(Exit(0)) 177 | // TODO: test that help output was more than five lines long 178 | }, 179 | Entry("helps on \"help healthchecks\"", 180 | "deis help healthchecks", "Valid commands for healthchecks:"), 181 | Entry("helps on \"healthchecks -h\"", 182 | "deis healthchecks -h", "Valid commands for healthchecks:"), 183 | Entry("helps on \"healthchecks --help\"", 184 | "deis healthchecks --help", "Valid commands for healthchecks:"), 185 | Entry("helps on \"help healthchecks:list\"", 186 | "deis help healthchecks:list", "Lists healthchecks for an application."), 187 | Entry("helps on \"healthchecks:list -h\"", 188 | "deis healthchecks:list -h", "Lists healthchecks for an application."), 189 | Entry("helps on \"healthchecks:list --help\"", 190 | "deis healthchecks:list --help", "Lists healthchecks for an application."), 191 | Entry("helps on \"help healthchecks:set\"", 192 | "deis help healthchecks:set", "Sets healthchecks for an application."), 193 | Entry("helps on \"healthchecks:set -h\"", 194 | "deis healthchecks:set -h", "Sets healthchecks for an application."), 195 | Entry("helps on \"healthchecks:set --help\"", 196 | "deis healthchecks:set --help", "Sets healthchecks for an application."), 197 | Entry("helps on \"help healthchecks:unset\"", 198 | "deis help healthchecks:unset", "Unsets healthchecks for an application."), 199 | Entry("helps on \"healthchecks:unset -h\"", 200 | "deis healthchecks:unset -h", "Unsets healthchecks for an application."), 201 | Entry("helps on \"healthchecks:unset --help\"", 202 | "deis healthchecks:unset --help", "Unsets healthchecks for an application."), 203 | ) 204 | }) 205 | -------------------------------------------------------------------------------- /tests/keys_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/cmd/auth" 8 | "github.com/deis/workflow-e2e/tests/cmd/keys" 9 | "github.com/deis/workflow-e2e/tests/model" 10 | "github.com/deis/workflow-e2e/tests/settings" 11 | 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/gomega" 14 | . "github.com/onsi/gomega/gbytes" 15 | . "github.com/onsi/gomega/gexec" 16 | ) 17 | 18 | var _ = Describe("deis keys", func() { 19 | 20 | Context("with an existing user", func() { 21 | 22 | var user model.User 23 | 24 | BeforeEach(func() { 25 | user = auth.RegisterAndLogin() 26 | }) 27 | 28 | AfterEach(func() { 29 | auth.Cancel(user) 30 | }) 31 | 32 | Context("who has at least one key", func() { 33 | 34 | var keyName string 35 | 36 | BeforeEach(func() { 37 | keyName, _ = keys.Add(user) 38 | }) 39 | 40 | Specify("that user can list their own keys", func() { 41 | sess, err := cmd.Start("deis keys:list", &user) 42 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(fmt.Sprintf("%s ssh-rsa", keyName))) 43 | Expect(err).NotTo(HaveOccurred()) 44 | Eventually(sess).Should(Exit(0)) 45 | }) 46 | 47 | }) 48 | 49 | Specify("that user can add and remove keys", func() { 50 | keyName, _ := keys.Add(user) 51 | keys.Remove(user, keyName) 52 | }) 53 | 54 | }) 55 | 56 | }) 57 | -------------------------------------------------------------------------------- /tests/labels_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/cmd/apps" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/model" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/ginkgo/extensions/table" 11 | . "github.com/onsi/gomega" 12 | . "github.com/onsi/gomega/gbytes" 13 | . "github.com/onsi/gomega/gexec" 14 | ) 15 | 16 | var _ = Describe("deis labels", func() { 17 | 18 | Context("with an existing user", func() { 19 | 20 | var user model.User 21 | 22 | BeforeEach(func() { 23 | user = auth.RegisterAndLogin() 24 | }) 25 | 26 | AfterEach(func() { 27 | auth.Cancel(user) 28 | }) 29 | 30 | Context("who owns an existing app", func() { 31 | 32 | var app model.App 33 | 34 | BeforeEach(func() { 35 | app = apps.Create(user, "--no-remote") 36 | }) 37 | 38 | AfterEach(func() { 39 | apps.Destroy(user, app) 40 | }) 41 | 42 | Specify("that user can list that app's labels", func() { 43 | sess, err := cmd.Start("deis labels:list --app=%s", &user, app.Name) 44 | Eventually(sess).Should(Say("=== %s Label", app.Name)) 45 | Expect(err).NotTo(HaveOccurred()) 46 | Eventually(sess).Should(Exit(0)) 47 | }) 48 | 49 | Specify("that user cannot set an invalid label", func() { 50 | sess, err := cmd.Start("deis labels:set --app=%s only_key", &user, app.Name) 51 | Eventually(sess).ShouldNot(Say(`done`)) 52 | Eventually(sess.Err).Should(Say("only_key is invalid")) 53 | Expect(err).NotTo(HaveOccurred()) 54 | Eventually(sess).Should(Exit(1)) 55 | }) 56 | 57 | Specify("that user cannot unset an non-exist label", func() { 58 | sess, err := cmd.Start("deis labels:unset --app=%s not_exist", &user, app.Name) 59 | Eventually(sess).ShouldNot(Say(`done`)) 60 | Eventually(sess.Err).Should(Say("not_exist does not exist")) 61 | Expect(err).NotTo(HaveOccurred()) 62 | Eventually(sess).Should(Exit(1)) 63 | }) 64 | 65 | Specify("that user can set a valid tag", func() { 66 | sess, err := cmd.Start("deis labels:set --app=%s team=bi service=frontend", &user, app.Name) 67 | Eventually(sess).Should(Say(`Applying labels on %s...`, app.Name)) 68 | Eventually(sess).Should(Say("done")) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Eventually(sess).Should(Exit(0)) 71 | 72 | sess, err = cmd.Start("deis labels:list --app=%s", &user, app.Name) 73 | Eventually(sess).Should(Say("=== %s Label", app.Name)) 74 | Eventually(sess).Should(Say(`service:\s+frontend`)) 75 | Eventually(sess).Should(Say(`team:\s+bi`)) 76 | Expect(err).NotTo(HaveOccurred()) 77 | Eventually(sess).Should(Exit(0)) 78 | }) 79 | 80 | Specify("that user can unset that label from that app", func() { 81 | sess, err := cmd.Start("deis labels:set --app=%s zoo=animal", &user, app.Name) 82 | Eventually(sess).Should(Say("done")) 83 | Expect(err).NotTo(HaveOccurred()) 84 | Eventually(sess).Should(Exit(0)) 85 | 86 | sess, err = cmd.Start("deis labels:unset --app=%s zoo", &user, app.Name) 87 | Eventually(sess).Should(Say(`Removing labels on %s...`, app.Name)) 88 | Eventually(sess).Should(Say("done")) 89 | Expect(err).NotTo(HaveOccurred()) 90 | Eventually(sess).Should(Exit(0)) 91 | 92 | sess, err = cmd.Start("deis labels:list --app=%s", &user, app.Name) 93 | Eventually(sess).Should(Say("=== %s Label", app.Name)) 94 | Eventually(sess).ShouldNot(Say("zoo", app.Name)) 95 | Expect(err).NotTo(HaveOccurred()) 96 | Eventually(sess).Should(Exit(0)) 97 | }) 98 | 99 | Context("and labels has already been added to the app", func() { 100 | 101 | Specify("that user can add more labels to the apps", func() { 102 | sess, err := cmd.Start("deis labels:set --app=%s team=frontend", &user, app.Name) 103 | Eventually(sess).Should(Say("done")) 104 | Expect(err).NotTo(HaveOccurred()) 105 | Eventually(sess).Should(Exit(0)) 106 | 107 | sess, err = cmd.Start("deis labels:set --app=%s zoo=animal", &user, app.Name) 108 | Eventually(sess).Should(Say("done")) 109 | Expect(err).NotTo(HaveOccurred()) 110 | Eventually(sess).Should(Exit(0)) 111 | 112 | sess, err = cmd.Start("deis labels:list --app=%s", &user, app.Name) 113 | Eventually(sess).Should(Say("=== %s Label", app.Name)) 114 | Eventually(sess).Should(Say(`team:\s+frontend`)) 115 | Eventually(sess).Should(Say(`zoo:\s+animal`)) 116 | Expect(err).NotTo(HaveOccurred()) 117 | Eventually(sess).Should(Exit(0)) 118 | }) 119 | 120 | }) 121 | 122 | }) 123 | 124 | }) 125 | 126 | DescribeTable("any user can get command-line help for labels", func(command string, expected string) { 127 | sess, err := cmd.Start(command, nil) 128 | Eventually(sess).Should(Say(expected)) 129 | Expect(err).NotTo(HaveOccurred()) 130 | Eventually(sess).Should(Exit(0)) 131 | }, 132 | Entry("helps on \"help labels\"", 133 | "deis help labels", "Valid commands for labels:"), 134 | Entry("helps on \"labels -h\"", 135 | "deis labels -h", "Valid commands for labels:"), 136 | Entry("helps on \"labels --help\"", 137 | "deis labels --help", "Valid commands for labels:"), 138 | Entry("helps on \"help labels:list\"", 139 | "deis help labels:list", "Prints a list of labels of the application."), 140 | Entry("helps on \"labels:list -h\"", 141 | "deis labels:list -h", "Prints a list of labels of the application."), 142 | Entry("helps on \"labels:list --help\"", 143 | "deis labels:list --help", "Prints a list of labels of the application."), 144 | Entry("helps on \"help labels:set\"", 145 | "deis help labels:set", "Sets labels for an application."), 146 | Entry("helps on \"labels:set -h\"", 147 | "deis labels:set -h", "Sets labels for an application."), 148 | Entry("helps on \"labels:set --help\"", 149 | "deis labels:set --help", "Sets labels for an application."), 150 | Entry("helps on \"help labels:unset\"", 151 | "deis help labels:unset", "Unsets labels for an application."), 152 | Entry("helps on \"labels:unset -h\"", 153 | "deis labels:unset -h", "Unsets labels for an application."), 154 | Entry("helps on \"labels:unset --help\"", 155 | "deis labels:unset --help", "Unsets labels for an application."), 156 | ) 157 | 158 | }) 159 | -------------------------------------------------------------------------------- /tests/limits_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/cmd/apps" 8 | "github.com/deis/workflow-e2e/tests/cmd/auth" 9 | "github.com/deis/workflow-e2e/tests/cmd/builds" 10 | "github.com/deis/workflow-e2e/tests/model" 11 | "github.com/deis/workflow-e2e/tests/settings" 12 | 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | . "github.com/onsi/gomega/gbytes" 16 | . "github.com/onsi/gomega/gexec" 17 | 18 | "strings" 19 | ) 20 | 21 | var _ = Describe("deis limits", func() { 22 | 23 | Context("with an existing user", func() { 24 | 25 | var user model.User 26 | 27 | BeforeEach(func() { 28 | user = auth.RegisterAndLogin() 29 | }) 30 | 31 | AfterEach(func() { 32 | auth.Cancel(user) 33 | }) 34 | 35 | Context("who owns an existing app that has already been deployed", func() { 36 | 37 | var app model.App 38 | 39 | BeforeEach(func() { 40 | app = apps.Create(user, "--no-remote") 41 | builds.Create(user, app) 42 | }) 43 | 44 | AfterEach(func() { 45 | apps.Destroy(user, app) 46 | }) 47 | 48 | Specify("that user can list that app's limits", func() { 49 | sess, err := cmd.Start("deis limits:list -a %s", &user, app.Name) 50 | Eventually(sess).Should(Say(fmt.Sprintf("=== %s Limits", app.Name))) 51 | Eventually(sess).Should(Say("--- Memory\nUnlimited")) 52 | Eventually(sess).Should(Say("--- CPU\nUnlimited")) 53 | Expect(err).NotTo(HaveOccurred()) 54 | Eventually(sess).Should(Exit(0)) 55 | }) 56 | 57 | Specify("that user can set a memory limit on that application", func() { 58 | sess, err := cmd.Start("deis limits:set cmd=64M -a %s", &user, app.Name) 59 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\ncmd 64M")) 60 | Expect(err).NotTo(HaveOccurred()) 61 | Eventually(sess).Should(Exit(0)) 62 | 63 | // Check that --memory also works 64 | // 128M 65 | sess, err = cmd.Start("deis limits:set --memory cmd=128M -a %s", &user, app.Name) 66 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\ncmd 128M")) 67 | Expect(err).NotTo(HaveOccurred()) 68 | Eventually(sess).Should(Exit(0)) 69 | 70 | // Check Kubernetes pods manifest 71 | sess, err = cmd.Start("HOME=%s kubectl get --all-namespaces pods -l app=%s --sort-by='.status.startTime' -o jsonpath={.items[*].spec.containers[0].resources}", nil, settings.ActualHome, app.Name) 72 | Eventually(sess).Should(Exit(0)) 73 | Expect(err).NotTo(HaveOccurred()) 74 | resource := string(sess.Out.Contents()) 75 | // try to get test latest pod, in case cmd still see terminated pod. 76 | // Also as per bug in https://github.com/kubernetes/kubernetes/issues/16707 77 | if strings.Contains(resource, "] map[") { 78 | resource = resource[strings.Index(resource, "] map[")+len("] "):] 79 | } 80 | Expect(resource).Should(SatisfyAny( 81 | Equal("map[requests:map[memory:128Mi] limits:map[memory:128Mi]]"), 82 | Equal("map[limits:map[memory:128Mi] requests:map[memory:128Mi]]"))) 83 | 84 | // 0/100M 85 | sess, err = cmd.Start("deis limits:set cmd=0/100M -a %s", &user, app.Name) 86 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\ncmd 0/100M")) 87 | Expect(err).NotTo(HaveOccurred()) 88 | Eventually(sess).Should(Exit(0)) 89 | 90 | // Check Kubernetes pods manifest 91 | sess, err = cmd.Start("HOME=%s kubectl get --all-namespaces pods -l app=%s --sort-by='.status.startTime' -o jsonpath={.items[*].spec.containers[0].resources}", nil, settings.ActualHome, app.Name) 92 | Eventually(sess).Should(Exit(0)) 93 | Expect(err).NotTo(HaveOccurred()) 94 | resource = string(sess.Out.Contents()) 95 | // try to get test latest pod, in case cmd still see terminated pod. 96 | if strings.Contains(resource, "] map[") { 97 | resource = resource[strings.Index(resource, "] map[")+len("] "):] 98 | } 99 | Expect(resource).Should(SatisfyAny( 100 | Equal("map[requests:map[memory:0] limits:map[memory:100Mi]]"), 101 | Equal("map[limits:map[memory:100Mi] requests:map[memory:0]]"))) 102 | 103 | // 50/100MB 104 | sess, err = cmd.Start("deis limits:set cmd=50M/100MB -a %s", &user, app.Name) 105 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\ncmd 50M/100M")) 106 | Expect(err).NotTo(HaveOccurred()) 107 | Eventually(sess).Should(Exit(0)) 108 | 109 | // Check Kubernetes pods manifest 110 | sess, err = cmd.Start("HOME=%s kubectl get --all-namespaces pods -l app=%s --sort-by='.status.startTime' -o jsonpath={.items[*].spec.containers[0].resources}", nil, settings.ActualHome, app.Name) 111 | Eventually(sess).Should(Exit(0)) 112 | Expect(err).NotTo(HaveOccurred()) 113 | resource = string(sess.Out.Contents()) 114 | // try to get test latest pod, in case cmd still see terminated pod. 115 | if strings.Contains(resource, "] map[") { 116 | resource = resource[strings.Index(resource, "] map[")+len("] "):] 117 | } 118 | Expect(resource).Should(SatisfyAny( 119 | Equal("map[requests:map[memory:50Mi] limits:map[memory:100Mi]]"), 120 | Equal("map[limits:map[memory:100Mi] requests:map[memory:50Mi]]"))) 121 | }) 122 | 123 | Specify("that user can set a CPU limit on that application", func() { 124 | sess, err := cmd.Start("deis limits:set --cpu cmd=500m -a %s", &user, app.Name) 125 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- CPU\ncmd 500m")) 126 | Expect(err).NotTo(HaveOccurred()) 127 | Eventually(sess).Should(Exit(0)) 128 | 129 | // Check Kubernetes pods manifest 130 | sess, err = cmd.Start("HOME=%s kubectl get --all-namespaces pods -l app=%s --sort-by='.status.startTime' -o jsonpath={.items[*].spec.containers[0].resources}", nil, settings.ActualHome, app.Name) 131 | Eventually(sess).Should(Exit(0)) 132 | Expect(err).NotTo(HaveOccurred()) 133 | resource := string(sess.Out.Contents()) 134 | // try to get test latest pod, in case cmd still see terminated pod. 135 | if strings.Contains(resource, "] map[") { 136 | resource = resource[strings.Index(resource, "] map[")+len("] "):] 137 | } 138 | Expect(resource).Should(SatisfyAny( 139 | Equal("map[requests:map[cpu:500m] limits:map[cpu:500m]]"), 140 | Equal("map[limits:map[cpu:500m] requests:map[cpu:500m]]"))) 141 | }) 142 | 143 | Specify("that user can unset a memory limit on that application", func() { 144 | // no memory has been set 145 | sess, err := cmd.Start("deis limits:unset cmd -a %s", &user, app.Name) 146 | Expect(err).NotTo(HaveOccurred()) 147 | Eventually(sess).Should(Exit(1)) 148 | 149 | // Check that --memory also works 150 | sess, err = cmd.Start("deis limits:set --memory cmd=64M -a %s", &user, app.Name) 151 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\ncmd 64M")) 152 | Expect(err).NotTo(HaveOccurred()) 153 | Eventually(sess).Should(Exit(0)) 154 | sess, err = cmd.Start("deis limits:unset --memory cmd -a %s", &user, app.Name) 155 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("--- Memory\nUnlimited")) 156 | Expect(err).NotTo(HaveOccurred()) 157 | Eventually(sess).Should(Exit(0)) 158 | 159 | // Check Kubernetes pods manifest 160 | sess, err = cmd.Start("HOME=%s kubectl get --all-namespaces pods -l app=%s -o jsonpath={.items[*].spec.containers[0].resources}", nil, settings.ActualHome, app.Name) 161 | Eventually(sess).Should(Exit(0)) 162 | Expect(err).NotTo(HaveOccurred()) 163 | // At least 1 pod have empty resources 164 | Expect(string(sess.Out.Contents())).Should(ContainSubstring("map[]")) 165 | }) 166 | 167 | Specify("that user can unset a CPU limit on that application", func() { 168 | // no cpu has been set 169 | sess, err := cmd.Start("deis limits:unset --cpu cmd -a %s", &user, app.Name) 170 | Expect(err).NotTo(HaveOccurred()) 171 | Eventually(sess).Should(Exit(1)) 172 | }) 173 | 174 | }) 175 | 176 | }) 177 | 178 | }) 179 | -------------------------------------------------------------------------------- /tests/maintenance_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/deis/workflow-e2e/tests/cmd" 8 | "github.com/deis/workflow-e2e/tests/cmd/apps" 9 | "github.com/deis/workflow-e2e/tests/cmd/auth" 10 | "github.com/deis/workflow-e2e/tests/cmd/builds" 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/onsi/gomega/gbytes" 17 | . "github.com/onsi/gomega/gexec" 18 | ) 19 | 20 | var _ = Describe("deis maintenance", func() { 21 | 22 | Context("with an existing user", func() { 23 | 24 | var user model.User 25 | 26 | BeforeEach(func() { 27 | user = auth.RegisterAndLogin() 28 | }) 29 | 30 | AfterEach(func() { 31 | auth.Cancel(user) 32 | }) 33 | 34 | Context("who owns an existing app that has already been deployed", func() { 35 | 36 | var app model.App 37 | 38 | BeforeEach(func() { 39 | app = apps.Create(user, "--no-remote") 40 | builds.Create(user, app) 41 | }) 42 | 43 | AfterEach(func() { 44 | apps.Destroy(user, app) 45 | }) 46 | 47 | Specify("can list that app's maintenance info", func() { 48 | sess, err := cmd.Start("deis maintenance:info --app=%s", &user, app.Name) 49 | Eventually(sess).Should(Say("Maintenance mode is off.\n")) 50 | Expect(err).NotTo(HaveOccurred()) 51 | Eventually(sess).Should(Exit(0)) 52 | }) 53 | 54 | Specify("can view app when maintenance mode is off", func() { 55 | // curl the app's root URL and print just the HTTP response code 56 | cmdRetryTimeout := 60 57 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 58 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(200), cmdRetryTimeout)).Should(BeTrue()) 59 | }) 60 | 61 | Specify("can enable/disable maintenance", func() { 62 | sess, err := cmd.Start("deis maintenance:on --app=%s", &user, app.Name) 63 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 64 | Expect(err).NotTo(HaveOccurred()) 65 | Eventually(sess).Should(Exit(0)) 66 | 67 | sess, err = cmd.Start("deis maintenance:info --app=%s", &user, app.Name) 68 | Eventually(sess).Should(Say("Maintenance mode is on.\n")) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Eventually(sess).Should(Exit(0)) 71 | 72 | // curl the app's root URL and print just the HTTP response code 73 | cmdRetryTimeout := 60 74 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 75 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(503), cmdRetryTimeout)).Should(BeTrue()) 76 | 77 | sess, err = cmd.Start("deis maintenance:off --app=%s", &user, app.Name) 78 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 79 | Expect(err).NotTo(HaveOccurred()) 80 | Eventually(sess).Should(Exit(0)) 81 | 82 | sess, err = cmd.Start("deis maintenance:info --app=%s", &user, app.Name) 83 | Eventually(sess).Should(Say("Maintenance mode is off.\n")) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Eventually(sess).Should(Exit(0)) 86 | 87 | cmdRetryTimeout = 60 88 | curlCmd = model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 89 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(200), cmdRetryTimeout)).Should(BeTrue()) 90 | }) 91 | }) 92 | }) 93 | 94 | }) 95 | -------------------------------------------------------------------------------- /tests/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "path" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/deis/workflow-e2e/tests/settings" 12 | "github.com/deis/workflow-e2e/tests/util" 13 | ) 14 | 15 | var Admin = User{ 16 | Username: "admin", 17 | Password: "admin", 18 | Email: "admintest@deis.com", 19 | IsSuperuser: false, 20 | } 21 | 22 | type User struct { 23 | Username string 24 | Password string 25 | Email string 26 | IsSuperuser bool 27 | } 28 | 29 | func NewUser() User { 30 | randSuffix := rand.Intn(999999999) 31 | return User{ 32 | Username: fmt.Sprintf("test-%d", randSuffix), 33 | Password: "asdf1234", 34 | Email: fmt.Sprintf("test-%d@deis.io", randSuffix), 35 | IsSuperuser: false, 36 | } 37 | } 38 | 39 | type App struct { 40 | Name string 41 | URL string 42 | } 43 | 44 | func NewApp() App { 45 | name := fmt.Sprintf("test-%d", rand.Intn(999999999)) 46 | app := App{ 47 | Name: name, 48 | URL: strings.Replace(settings.DeisControllerURL, "deis", name, 1), 49 | } 50 | // try adding the URL to /etc/hosts but don't cry if it's not in there because the user may 51 | // have other plans in store for DNS 52 | if err := util.AddToEtcHosts(fmt.Sprintf("%s.%s", name, settings.DeisRootHostname)); err != nil { 53 | fmt.Printf("WARNING: could not write %s to /etc/hosts (%s), continuing anyways\n", 54 | app.URL, 55 | err) 56 | } 57 | return app 58 | } 59 | 60 | type Cmd struct { 61 | Env []string 62 | CommandLineString string 63 | } 64 | 65 | type Cert struct { 66 | Name string 67 | CertPath string 68 | KeyPath string 69 | } 70 | 71 | func NewCert() Cert { 72 | certPath := path.Join(getDir(), "..", "files", "certs") 73 | return Cert{ 74 | Name: getRandCertName(), 75 | CertPath: fmt.Sprintf("%s/www.foo.com.cert", certPath), 76 | KeyPath: fmt.Sprintf("%s/www.foo.com.key", certPath), 77 | } 78 | } 79 | 80 | func getRandCertName() string { 81 | return fmt.Sprintf("%d-cert", rand.Intn(999999999)) 82 | } 83 | 84 | func getDir() string { 85 | _, filename, _, _ := runtime.Caller(1) 86 | return path.Dir(filename) 87 | } 88 | 89 | // CmdResult represents a generic command result, with expected Out, Err and 90 | // ExitCode 91 | type CmdResult struct { 92 | Out []byte 93 | Err []byte 94 | ExitCode int 95 | } 96 | 97 | // Satisfies returns whether or not the original CmdResult, ocd, meets all of 98 | // the expectations contained in the expeced CmdResult, ecd 99 | func (ocd CmdResult) Satisfies(ecd CmdResult) bool { 100 | if !bytes.Contains(ocd.Out, ecd.Out) { 101 | return false 102 | } 103 | if !bytes.Contains(ocd.Err, ecd.Err) { 104 | return false 105 | } 106 | if ocd.ExitCode != ecd.ExitCode { 107 | return false 108 | } 109 | return true 110 | } 111 | 112 | // String returns the CmdResult in printable form 113 | func (ocd CmdResult) String() string { 114 | return fmt.Sprintf("[Out: '%s', Err: '%s', ExitCode: '%d']", ocd.Out, ocd.Err, ocd.ExitCode) 115 | } 116 | -------------------------------------------------------------------------------- /tests/perms_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | deis "github.com/deis/controller-sdk-go" 5 | "github.com/deis/workflow-e2e/tests/cmd" 6 | "github.com/deis/workflow-e2e/tests/cmd/apps" 7 | "github.com/deis/workflow-e2e/tests/cmd/auth" 8 | "github.com/deis/workflow-e2e/tests/cmd/perms" 9 | "github.com/deis/workflow-e2e/tests/model" 10 | "github.com/deis/workflow-e2e/tests/settings" 11 | "github.com/deis/workflow-e2e/tests/util" 12 | 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | . "github.com/onsi/gomega/gbytes" 16 | . "github.com/onsi/gomega/gexec" 17 | ) 18 | 19 | var _ = Describe("deis perms", func() { 20 | 21 | Context("with an existing admin", func() { 22 | 23 | admin := model.Admin 24 | 25 | Specify("that admin can list admins", func() { 26 | sess, err := cmd.Start("deis perms:list --admin", &admin) 27 | Eventually(sess).Should(Say("=== Administrators")) 28 | Eventually(sess).Should(Say(admin.Username)) 29 | Expect(err).NotTo(HaveOccurred()) 30 | Eventually(sess).Should(Exit(0)) 31 | }) 32 | 33 | Context("and another existing user", func() { 34 | 35 | var otherUser model.User 36 | 37 | BeforeEach(func() { 38 | otherUser = auth.RegisterAndLogin() 39 | }) 40 | 41 | AfterEach(func() { 42 | auth.Cancel(otherUser) 43 | }) 44 | 45 | Specify("that admin can grant admin permissions to the other user", func() { 46 | sess, err := cmd.Start("deis perms:create %s --admin", &admin, otherUser.Username) 47 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to system administrators... done\n", otherUser.Username)) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Eventually(sess).Should(Exit(0)) 50 | 51 | sess, err = cmd.Start("deis perms:list --admin", &admin) 52 | Eventually(sess).Should(Say("=== Administrators")) 53 | Eventually(sess).Should(Say(otherUser.Username)) 54 | Expect(err).NotTo(HaveOccurred()) 55 | Eventually(sess).Should(Exit(0)) 56 | }) 57 | 58 | Context("who owns an existing app", func() { 59 | 60 | var app model.App 61 | 62 | BeforeEach(func() { 63 | app = apps.Create(otherUser, "--no-remote") 64 | }) 65 | 66 | AfterEach(func() { 67 | apps.Destroy(otherUser, app) 68 | }) 69 | 70 | Specify("that admin can list permissions on the app owned by the second user", func() { 71 | sess, err := cmd.Start("deis perms:list --app=%s", &admin, app.Name) 72 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 73 | Expect(err).NotTo(HaveOccurred()) 74 | Eventually(sess).Should(Exit(0)) 75 | }) 76 | 77 | Context("and a third user also exists", func() { 78 | 79 | var thirdUser model.User 80 | 81 | BeforeEach(func() { 82 | thirdUser = auth.RegisterAndLogin() 83 | }) 84 | 85 | AfterEach(func() { 86 | auth.Cancel(thirdUser) 87 | }) 88 | 89 | Specify("that admin can grant permissions on the app owned by the second user to the third user", func() { 90 | sess, err := cmd.Start("deis perms:create %s --app=%s", &admin, thirdUser.Username, app.Name) 91 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to %s collaborators... done\n", thirdUser.Username, app.Name)) 92 | Expect(err).NotTo(HaveOccurred()) 93 | Eventually(sess).Should(Exit(0)) 94 | 95 | sess, err = cmd.Start("deis perms:list --app=%s", &admin, app.Name) 96 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 97 | Eventually(sess).Should(Say("%s", thirdUser.Username)) 98 | Expect(err).NotTo(HaveOccurred()) 99 | Eventually(sess).Should(Exit(0)) 100 | }) 101 | 102 | Context("who has permissions on the second user's app", func() { 103 | 104 | BeforeEach(func() { 105 | sess, err := cmd.Start("deis perms:create %s --app=%s", &admin, thirdUser.Username, app.Name) 106 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to %s collaborators... done\n", thirdUser.Username, app.Name)) 107 | Expect(err).NotTo(HaveOccurred()) 108 | Eventually(sess).Should(Exit(0)) 109 | }) 110 | 111 | Specify("that admin can revoke the third user's permissions to an app owned by the second user", func() { 112 | sess, err := cmd.Start("deis perms:delete %s --app=%s", &admin, thirdUser.Username, app.Name) 113 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Removing %s from %s collaborators... done", thirdUser.Username, app.Name)) 114 | Expect(err).NotTo(HaveOccurred()) 115 | Eventually(sess).Should(Exit(0)) 116 | 117 | sess, err = cmd.Start("deis perms:list --app=%s", &admin, app.Name) 118 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 119 | Eventually(sess).ShouldNot(Say("%s", thirdUser.Username)) 120 | Expect(err).NotTo(HaveOccurred()) 121 | Eventually(sess).Should(Exit(0)) 122 | }) 123 | 124 | }) 125 | 126 | }) 127 | 128 | }) 129 | 130 | }) 131 | 132 | Context("and another existing admin", func() { 133 | 134 | var otherAdmin model.User 135 | 136 | BeforeEach(func() { 137 | otherAdmin = auth.RegisterAndLogin() 138 | sess, err := cmd.Start("deis perms:create %s --admin", &admin, otherAdmin.Username) 139 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to system administrators... done\n", otherAdmin.Username)) 140 | Expect(err).NotTo(HaveOccurred()) 141 | Eventually(sess).Should(Exit(0)) 142 | }) 143 | 144 | AfterEach(func() { 145 | auth.Cancel(otherAdmin) 146 | }) 147 | 148 | Specify("the first admin can delete admin permissions from the second", func() { 149 | sess, err := cmd.Start("deis perms:delete %s --admin", &admin, otherAdmin.Username) 150 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Removing %s from system administrators... done", otherAdmin.Username)) 151 | Expect(err).NotTo(HaveOccurred()) 152 | Eventually(sess).Should(Exit(0)) 153 | 154 | sess, err = cmd.Start("deis perms:list --admin", &admin) 155 | Eventually(sess).Should(Say("=== Administrators")) 156 | Expect(sess).ShouldNot(Say(otherAdmin.Username)) 157 | Expect(err).NotTo(HaveOccurred()) 158 | Eventually(sess).Should(Exit(0)) 159 | }) 160 | 161 | }) 162 | 163 | }) 164 | 165 | Context("with an existing non-admin user", func() { 166 | 167 | var user model.User 168 | 169 | BeforeEach(func() { 170 | user = auth.RegisterAndLogin() 171 | }) 172 | 173 | AfterEach(func() { 174 | auth.Cancel(user) 175 | }) 176 | 177 | Specify("that user cannot list admin permissions", func() { 178 | sess, err := cmd.Start("deis perms:list --admin", &user) 179 | Eventually(sess.Err).Should(Say(util.PrependError(deis.ErrForbidden))) 180 | Expect(err).NotTo(HaveOccurred()) 181 | Eventually(sess).Should(Exit(1)) 182 | }) 183 | 184 | Specify("that user cannot create admin permissions", func() { 185 | sess, err := cmd.Start("deis perms:create %s --admin", &user, user.Username) 186 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Adding %s to system administrators...", user.Username)) 187 | Eventually(sess.Err).Should(Say(util.PrependError(deis.ErrForbidden))) 188 | Expect(err).NotTo(HaveOccurred()) 189 | Eventually(sess).Should(Exit(1)) 190 | }) 191 | 192 | Context("and an existing admin", func() { 193 | 194 | admin := model.Admin 195 | 196 | Specify("the non-admin user cannot delete the admin's admin permissions", func() { 197 | sess, err := cmd.Start("deis perms:delete %s --admin", &user, admin.Username) 198 | Eventually(sess.Err, settings.MaxEventuallyTimeout).Should(Say(util.PrependError(deis.ErrForbidden))) 199 | Expect(err).NotTo(HaveOccurred()) 200 | Eventually(sess).Should(Exit(1)) 201 | }) 202 | 203 | }) 204 | 205 | Context("and an existing app belonging to that user", func() { 206 | 207 | var app model.App 208 | 209 | BeforeEach(func() { 210 | app = apps.Create(user, "--no-remote") 211 | }) 212 | 213 | AfterEach(func() { 214 | apps.Destroy(user, app) 215 | }) 216 | 217 | Specify("that user can list permissions for that app", func() { 218 | sess, err := cmd.Start("deis perms:list --app=%s", &user, app.Name) 219 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 220 | Expect(err).NotTo(HaveOccurred()) 221 | Eventually(sess).Should(Exit(0)) 222 | }) 223 | 224 | Context("and another existing non-admin user also exists", func() { 225 | 226 | var otherUser model.User 227 | 228 | BeforeEach(func() { 229 | otherUser = auth.RegisterAndLogin() 230 | }) 231 | 232 | AfterEach(func() { 233 | auth.Cancel(otherUser) 234 | }) 235 | 236 | Specify("that first user can grant permissions on that app to the second user", func() { 237 | perms.Create(user, app, otherUser) 238 | sess, err := cmd.Start("deis perms:list --app=%s", &user, app.Name) 239 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 240 | Eventually(sess).Should(Say("%s", otherUser.Username)) 241 | Expect(err).NotTo(HaveOccurred()) 242 | Eventually(sess).Should(Exit(0)) 243 | }) 244 | 245 | Context("who has already been granted permissions on that app", func() { 246 | 247 | BeforeEach(func() { 248 | perms.Create(user, app, otherUser) 249 | }) 250 | 251 | Specify("that first user can list permissions for that app", func() { 252 | sess, err := cmd.Start("deis perms:list --app=%s", &user, app.Name) 253 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 254 | Eventually(sess).Should(Say("%s", otherUser.Username)) 255 | Expect(err).NotTo(HaveOccurred()) 256 | Eventually(sess).Should(Exit(0)) 257 | }) 258 | 259 | Specify("that first user can revoke permissions on that app", func() { 260 | perms.Delete(user, app, otherUser) 261 | sess, err := cmd.Start("deis perms:list --app=%s", &user, app.Name) 262 | Eventually(sess).Should(Say("=== %s's Users", app.Name)) 263 | Eventually(sess).ShouldNot(Say("%s", otherUser.Username)) 264 | Expect(err).NotTo(HaveOccurred()) 265 | Eventually(sess).Should(Exit(0)) 266 | }) 267 | 268 | }) 269 | 270 | }) 271 | 272 | }) 273 | 274 | }) 275 | 276 | }) 277 | -------------------------------------------------------------------------------- /tests/ps_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "regexp" 8 | "sort" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/deis/workflow-e2e/tests/cmd" 13 | "github.com/deis/workflow-e2e/tests/cmd/apps" 14 | "github.com/deis/workflow-e2e/tests/cmd/auth" 15 | "github.com/deis/workflow-e2e/tests/cmd/builds" 16 | "github.com/deis/workflow-e2e/tests/model" 17 | "github.com/deis/workflow-e2e/tests/settings" 18 | 19 | . "github.com/onsi/ginkgo" 20 | . "github.com/onsi/ginkgo/extensions/table" 21 | . "github.com/onsi/gomega" 22 | . "github.com/onsi/gomega/gbytes" 23 | . "github.com/onsi/gomega/gexec" 24 | ) 25 | 26 | var _ = Describe("deis ps", func() { 27 | 28 | Context("with an existing user", func() { 29 | 30 | var user model.User 31 | 32 | BeforeEach(func() { 33 | user = auth.RegisterAndLogin() 34 | }) 35 | 36 | AfterEach(func() { 37 | auth.Cancel(user) 38 | }) 39 | 40 | Context("who owns an existing app that has already been deployed", func() { 41 | 42 | var app model.App 43 | 44 | BeforeEach(func() { 45 | app = apps.Create(user, "--no-remote") 46 | builds.Create(user, app) 47 | }) 48 | 49 | AfterEach(func() { 50 | apps.Destroy(user, app) 51 | }) 52 | 53 | DescribeTable("that user can scale that app up and down", 54 | func(scaleTo, respCode int) { 55 | sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name) 56 | Eventually(sess).Should(Say("Scaling processes... but first,")) 57 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`)) 58 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 59 | Expect(err).NotTo(HaveOccurred()) 60 | Eventually(sess).Should(Exit(0)) 61 | 62 | // test that there are the right number of processes listed 63 | procsListing := listProcs(user, app, "").Out.Contents() 64 | procs := scrapeProcs(app.Name, procsListing) 65 | Expect(procs).To(HaveLen(scaleTo)) 66 | 67 | // curl the app's root URL and print just the HTTP response code 68 | cmdRetryTimeout := 60 69 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 70 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue()) 71 | }, 72 | Entry("scales to 1", 1, 200), 73 | Entry("scales to 3", 3, 200), 74 | Entry("scales to 0", 0, 503), 75 | ) 76 | 77 | DescribeTable("that user can interrupt a scaling event", 78 | func(scaleTo, respCode int) { 79 | 80 | sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name) 81 | Eventually(sess).Should(Say("Scaling processes... but first,")) 82 | 83 | Expect(err).NotTo(HaveOccurred()) 84 | 85 | // Sleep for a split second to ensure scale command makes it to the server. 86 | time.Sleep(200 * time.Millisecond) 87 | 88 | // Interrupt and wait for exit. 89 | sess = sess.Interrupt().Wait() 90 | 91 | // Ensure the right number of processes listed. 92 | Eventually(func() []string { 93 | procsListing := listProcs(user, app, "").Out.Contents() 94 | return scrapeProcs(app.Name, procsListing) 95 | }, settings.MaxEventuallyTimeout).Should(HaveLen(scaleTo)) 96 | 97 | // curl the app's root URL and print just the HTTP response code 98 | cmdRetryTimeout := 60 99 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 100 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue()) 101 | }, 102 | Entry("scales to 3", 3, 200), 103 | Entry("scales to 0", 0, 503), 104 | ) 105 | 106 | // TODO: Test is broken 107 | XIt("that app remains responsive during a scaling event", func() { 108 | stopCh := make(chan struct{}) 109 | doneCh := make(chan struct{}) 110 | 111 | // start scaling the app 112 | go func() { 113 | for range stopCh { 114 | sess, err := cmd.Start("deis ps:scale web=4 -a %s", &user, app.Name) 115 | Eventually(sess).Should(Exit(0)) 116 | Expect(err).NotTo(HaveOccurred()) 117 | } 118 | close(doneCh) 119 | }() 120 | 121 | for i := 0; i < 10; i++ { 122 | // start the scale operation. waits until the last scale op has finished 123 | stopCh <- struct{}{} 124 | resp, err := http.Get(app.URL) 125 | Expect(err).To(BeNil()) 126 | Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK)) 127 | } 128 | 129 | // wait until the goroutine that was scaling the app shuts down. not strictly necessary, just good practice 130 | Eventually(doneCh).Should(BeClosed()) 131 | }) 132 | 133 | DescribeTable("that user can restart that app's processes", 134 | func(restart string, scaleTo int, respCode int) { 135 | // TODO: need some way to choose between "web" and "cmd" here! 136 | // scale the app's processes to the desired number 137 | sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name) 138 | 139 | Eventually(sess).Should(Say("Scaling processes... but first,")) 140 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`)) 141 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 142 | Expect(err).NotTo(HaveOccurred()) 143 | Eventually(sess).Should(Exit(0)) 144 | 145 | // capture the process names 146 | beforeProcs := scrapeProcs(app.Name, sess.Out.Contents()) 147 | 148 | // restart the app's process(es) 149 | var arg string 150 | switch restart { 151 | case "all": 152 | arg = "" 153 | case "by type": 154 | // TODO: need some way to choose between "web" and "cmd" here! 155 | arg = "cmd" 156 | case "by wrong type": 157 | // TODO: need some way to choose between "web" and "cmd" here! 158 | arg = "web" 159 | case "one": 160 | procsLen := len(beforeProcs) 161 | Expect(procsLen).To(BeNumerically(">", 0)) 162 | arg = beforeProcs[rand.Intn(procsLen)] 163 | } 164 | sess, err = cmd.Start("deis ps:restart %s --app=%s", &user, arg, app.Name) 165 | Eventually(sess).Should(Say("Restarting processes... but first,")) 166 | if scaleTo == 0 || restart == "by wrong type" { 167 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Could not find any processes to restart")) 168 | } else { 169 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`)) 170 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 171 | } 172 | Expect(err).NotTo(HaveOccurred()) 173 | Eventually(sess).Should(Exit(0)) 174 | 175 | // capture the process names 176 | procsListing := listProcs(user, app, "").Out.Contents() 177 | afterProcs := scrapeProcs(app.Name, procsListing) 178 | 179 | // compare the before and after sets of process names 180 | Expect(afterProcs).To(HaveLen(scaleTo)) 181 | if scaleTo > 0 && restart != "by wrong type" { 182 | Expect(beforeProcs).NotTo(Equal(afterProcs)) 183 | } 184 | 185 | // curl the app's root URL and print just the HTTP response code 186 | cmdRetryTimeout := 60 187 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 188 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue()) 189 | }, 190 | Entry("restarts one of 1", "one", 1, 200), 191 | Entry("restarts all of 1", "all", 1, 200), 192 | Entry("restarts all of 1 by type", "by type", 1, 200), 193 | Entry("restarts all of 1 by wrong type", "by wrong type", 1, 200), 194 | Entry("restarts one of 3", "one", 3, 200), 195 | Entry("restarts all of 3", "all", 3, 200), 196 | Entry("restarts all of 3 by type", "by type", 3, 200), 197 | Entry("restarts all of 3 by wrong type", "by wrong type", 3, 200), 198 | Entry("restarts all of 0", "all", 0, 503), 199 | Entry("restarts all of 0 by type", "by type", 0, 503), 200 | Entry("restarts all of 0 by wrong type", "by wrong type", 0, 503), 201 | ) 202 | 203 | }) 204 | 205 | }) 206 | 207 | }) 208 | 209 | func listProcs(user model.User, app model.App, proctype string) *Session { 210 | sess, err := cmd.Start("deis ps:list --app=%s", &user, app.Name) 211 | Eventually(sess).Should(Say("=== %s Processes", app.Name)) 212 | if proctype != "" { 213 | Eventually(sess).Should(Say("--- %s:", proctype)) 214 | } 215 | Expect(err).NotTo(HaveOccurred()) 216 | Eventually(sess).Should(Exit(0)) 217 | return sess 218 | } 219 | 220 | // scrapeProcs returns the sorted process names for an app from the given output. 221 | // It matches the current "deis ps" output for a healthy container: 222 | // earthy-vocalist-cmd-123456789-1d73e up (v2) 223 | // myapp-web-123456789-bujlq up (v16) 224 | func scrapeProcs(app string, output []byte) []string { 225 | procsRegexp := `(%s-[\w-]+) up \(v\d+\)` 226 | re := regexp.MustCompile(fmt.Sprintf(procsRegexp, app)) 227 | found := re.FindAllSubmatch(output, -1) 228 | procs := make([]string, len(found)) 229 | for i := range found { 230 | procs[i] = string(found[i][1]) 231 | } 232 | sort.Strings(procs) 233 | return procs 234 | } 235 | -------------------------------------------------------------------------------- /tests/registry_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/deis/workflow-e2e/tests/cmd" 7 | "github.com/deis/workflow-e2e/tests/cmd/apps" 8 | "github.com/deis/workflow-e2e/tests/cmd/auth" 9 | "github.com/deis/workflow-e2e/tests/model" 10 | "github.com/deis/workflow-e2e/tests/settings" 11 | 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/ginkgo/extensions/table" 14 | . "github.com/onsi/gomega" 15 | . "github.com/onsi/gomega/gbytes" 16 | . "github.com/onsi/gomega/gexec" 17 | ) 18 | 19 | var _ = Describe("deis registry", func() { 20 | 21 | Context("with an existing user", func() { 22 | 23 | var user model.User 24 | 25 | BeforeEach(func() { 26 | user = auth.RegisterAndLogin() 27 | }) 28 | 29 | AfterEach(func() { 30 | auth.Cancel(user) 31 | }) 32 | 33 | Context("who owns an existing app", func() { 34 | 35 | var app model.App 36 | 37 | BeforeEach(func() { 38 | app = apps.Create(user, "--no-remote") 39 | }) 40 | 41 | AfterEach(func() { 42 | apps.Destroy(user, app) 43 | }) 44 | 45 | Specify("that user can list that app's registry information", func() { 46 | sess, err := cmd.Start("deis registry:list --app=%s", &user, app.Name) 47 | Eventually(sess).Should(Say("=== %s Registry", app.Name)) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Eventually(sess).Should(Exit(0)) 50 | }) 51 | 52 | Specify("that user cannot unset an invalid registry information", func() { 53 | sess, err := cmd.Start("deis registry:unset --app=%s munkafolyamat", &user, app.Name) 54 | Eventually(sess).ShouldNot(Say(`munkafolyamat\s+yeah`, app.Name)) 55 | Expect(err).NotTo(HaveOccurred()) 56 | Eventually(sess).Should(Exit(1)) 57 | }) 58 | 59 | Specify("that user can set a valid registry information", func() { 60 | // Setting a port first is required for a private registry 61 | sess, err := cmd.Start("deis config:set -a %s PORT=5000", &user, app.Name) 62 | Expect(err).NotTo(HaveOccurred()) 63 | Eventually(sess).Should(Say("Creating config")) 64 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Config", app.Name)) 65 | Eventually(sess).Should(Say(`PORT\s+5000`)) 66 | Expect(err).NotTo(HaveOccurred()) 67 | Eventually(sess).Should(Exit(0)) 68 | 69 | sess, err = cmd.Start("deis registry:set --app=%s username=bob", &user, app.Name) 70 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Registry", app.Name)) 71 | Eventually(sess).Should(Say(`username\s+bob`)) 72 | Expect(err).NotTo(HaveOccurred()) 73 | Eventually(sess).Should(Exit(0)) 74 | 75 | sess, err = cmd.Start("deis registry:list --app=%s", &user, app.Name) 76 | Eventually(sess).Should(Say("=== %s Registry", app.Name)) 77 | Eventually(sess).Should(Say(`username\s+bob`)) 78 | Expect(err).NotTo(HaveOccurred()) 79 | Eventually(sess).Should(Exit(0)) 80 | }) 81 | 82 | Specify("that user can not deploy from a private registry due to lack of credentials", func() { 83 | // do an unsuccessful deploy 84 | image := "quay.io/deisci/e2e-private-registry-test" 85 | sess, err := cmd.Start("deis pull --app=%s %s", &user, app.Name, image) 86 | Expect(err).NotTo(HaveOccurred()) 87 | Eventually(sess).Should(Say("Creating build...")) 88 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(1)) 89 | time.Sleep(10 * time.Second) 90 | }) 91 | 92 | Specify("that user can deploy from a private registry using registry credentials", func() { 93 | // Setting a port first is required for a private registry 94 | sess, err := cmd.Start("deis config:set -a %s PORT=8080", &user, app.Name) 95 | Expect(err).NotTo(HaveOccurred()) 96 | Eventually(sess).Should(Say("Creating config")) 97 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Config", app.Name)) 98 | Eventually(sess).Should(Say(`PORT\s+8080`)) 99 | Expect(err).NotTo(HaveOccurred()) 100 | Eventually(sess).Should(Exit(0)) 101 | 102 | // read-only access 103 | registry_creds := "TP5BS3NHW0OZ20GER4IORTIJF90J48KKJ8NX8YC7Z22N5P7WE27BRKVMQ4QAEID8" 104 | sess, err = cmd.Start("deis registry:set --app=%s username=deisci+e2e_registry password=%s", &user, app.Name, registry_creds) 105 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Registry", app.Name)) 106 | Expect(err).NotTo(HaveOccurred()) 107 | Eventually(sess).Should(Exit(0)) 108 | 109 | // do a successful deploy 110 | image := "quay.io/deisci/e2e-private-registry-test" 111 | sess, err = cmd.Start("deis pull --app=%s %s", &user, app.Name, image) 112 | Expect(err).NotTo(HaveOccurred()) 113 | Eventually(sess).Should(Say("Creating build...")) 114 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Exit(0)) 115 | time.Sleep(10 * time.Second) 116 | }) 117 | 118 | Context("and registry information has already been added to the app", func() { 119 | 120 | BeforeEach(func() { 121 | // Setting a port first is required 122 | sess, err := cmd.Start("deis config:set -a %s PORT=5000", &user, app.Name) 123 | Expect(err).NotTo(HaveOccurred()) 124 | Eventually(sess).Should(Say("Creating config")) 125 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Config", app.Name)) 126 | Eventually(sess).Should(Say(`PORT\s+5000`)) 127 | Expect(err).NotTo(HaveOccurred()) 128 | Eventually(sess).Should(Exit(0)) 129 | 130 | sess, err = cmd.Start("deis registry:set --app=%s username=bob", &user, app.Name) 131 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Registry", app.Name)) 132 | Eventually(sess).Should(Say(`username\s+bob`)) 133 | Expect(err).NotTo(HaveOccurred()) 134 | Eventually(sess).Should(Exit(0)) 135 | }) 136 | 137 | Specify("that user can unset that registry information from that app", func() { 138 | sess, err := cmd.Start("deis registry:unset --app=%s username", &user, app.Name) 139 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Registry", app.Name)) 140 | Eventually(sess).ShouldNot(Say(`username\s+bob`)) 141 | Expect(err).NotTo(HaveOccurred()) 142 | Eventually(sess).Should(Exit(0)) 143 | 144 | sess, err = cmd.Start("deis registry:list --app=%s", &user, app.Name) 145 | Eventually(sess).Should(Say("=== %s Registry", app.Name)) 146 | Eventually(sess).ShouldNot(Say(`username\s+bob`)) 147 | Eventually(sess).ShouldNot(Say(`munkafolyamat\s+yeah`, app.Name)) 148 | Expect(err).NotTo(HaveOccurred()) 149 | Eventually(sess).Should(Exit(0)) 150 | }) 151 | 152 | }) 153 | 154 | }) 155 | 156 | }) 157 | 158 | DescribeTable("any user can get command-line help for registry", func(command string, expected string) { 159 | sess, err := cmd.Start(command, nil) 160 | Eventually(sess).Should(Say(expected)) 161 | Expect(err).NotTo(HaveOccurred()) 162 | Eventually(sess).Should(Exit(0)) 163 | // TODO: test that help output was more than five lines long 164 | }, 165 | Entry("helps on \"help registry\"", 166 | "deis help registry", "Valid commands for registry:"), 167 | Entry("helps on \"registry -h\"", 168 | "deis registry -h", "Valid commands for registry:"), 169 | Entry("helps on \"registry --help\"", 170 | "deis registry --help", "Valid commands for registry:"), 171 | Entry("helps on \"help registry:list\"", 172 | "deis help registry:list", "Lists registry information for an application."), 173 | Entry("helps on \"registry:list -h\"", 174 | "deis registry:list -h", "Lists registry information for an application."), 175 | Entry("helps on \"registry:list --help\"", 176 | "deis registry:list --help", "Lists registry information for an application."), 177 | Entry("helps on \"help registry:set\"", 178 | "deis help registry:set", "Sets registry information for an application."), 179 | Entry("helps on \"registry:set -h\"", 180 | "deis registry:set -h", "Sets registry information for an application."), 181 | Entry("helps on \"registry:set --help\"", 182 | "deis registry:set --help", "Sets registry information for an application."), 183 | Entry("helps on \"help registry:unset\"", 184 | "deis help registry:unset", "Unsets registry information for an application."), 185 | Entry("helps on \"registry:unset -h\"", 186 | "deis registry:unset -h", "Unsets registry information for an application."), 187 | Entry("helps on \"registry:unset --help\"", 188 | "deis registry:unset --help", "Unsets registry information for an application."), 189 | ) 190 | 191 | }) 192 | -------------------------------------------------------------------------------- /tests/releases_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/deis/workflow-e2e/tests/cmd" 5 | "github.com/deis/workflow-e2e/tests/cmd/apps" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/cmd/builds" 8 | "github.com/deis/workflow-e2e/tests/model" 9 | "github.com/deis/workflow-e2e/tests/settings" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | . "github.com/onsi/gomega/gbytes" 14 | . "github.com/onsi/gomega/gexec" 15 | ) 16 | 17 | var _ = Describe("deis releases", func() { 18 | 19 | Context("with an existing user", func() { 20 | 21 | var user model.User 22 | 23 | BeforeEach(func() { 24 | user = auth.RegisterAndLogin() 25 | }) 26 | 27 | AfterEach(func() { 28 | auth.Cancel(user) 29 | }) 30 | 31 | Context("who owns an existing app that has already been deployed", func() { 32 | 33 | var app model.App 34 | 35 | BeforeEach(func() { 36 | app = apps.Create(user, "--no-remote") 37 | builds.Create(user, app) 38 | }) 39 | 40 | AfterEach(func() { 41 | apps.Destroy(user, app) 42 | }) 43 | 44 | Specify("that user can list that app's releases", func() { 45 | sess, err := cmd.Start("deis releases:list -a %s", &user, app.Name) 46 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Releases", app.Name)) 47 | Eventually(sess).Should(Say(`v1\s+.*\s+%s created initial release`, user.Username)) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Eventually(sess).Should(Exit(0)) 50 | }) 51 | 52 | Specify("that user can get info on one of the app's releases", func() { 53 | sess, err := cmd.Start("deis releases:info v2 -a %s", &user, app.Name) 54 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Release v2", app.Name)) 55 | Eventually(sess).Should(Say(`config:\s+[\w-]+`)) 56 | Eventually(sess).Should(Say(`owner:\s+%s`, user.Username)) 57 | Eventually(sess).Should(Say(`summary:\s+%s \w+`, user.Username)) 58 | Expect(err).NotTo(HaveOccurred()) 59 | Eventually(sess).Should(Exit(0)) 60 | }) 61 | 62 | Context("and that app has three releases", func() { 63 | 64 | BeforeEach(func() { 65 | builds.Create(user, app) 66 | }) 67 | 68 | Specify("that user can roll the application back to the second release", func() { 69 | sess, err := cmd.Start("deis releases:rollback v2 -a %s", &user, app.Name) 70 | Eventually(sess).Should(Say(`Rolling back to`)) 71 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`...done`)) 72 | Expect(err).NotTo(HaveOccurred()) 73 | Eventually(sess).Should(Exit(0)) 74 | 75 | sess, err = cmd.Start("deis releases:info v2 -a %s", &user, app.Name) 76 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Release v2", app.Name)) 77 | Eventually(sess).Should(Say(`config:\s+[\w-]+`)) 78 | Eventually(sess).Should(Say(`owner:\s+%s`, user.Username)) 79 | Eventually(sess).Should(Say(`summary:\s+%s \w+`, user.Username)) 80 | 81 | // The updated date has to match a string like 2015-12-22T21:20:31Z: 82 | Eventually(sess).Should(Say(`updated:\s+[\w\-\:]+Z`)) 83 | Eventually(sess).Should(Say(`uuid:\s+[0-9a-f\-]+`)) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Eventually(sess).Should(Exit(0)) 86 | }) 87 | 88 | }) 89 | 90 | }) 91 | 92 | }) 93 | 94 | }) 95 | -------------------------------------------------------------------------------- /tests/routing_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/builds" 12 | "github.com/deis/workflow-e2e/tests/model" 13 | "github.com/deis/workflow-e2e/tests/settings" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/ginkgo/extensions/table" 17 | . "github.com/onsi/gomega" 18 | . "github.com/onsi/gomega/gbytes" 19 | . "github.com/onsi/gomega/gexec" 20 | ) 21 | 22 | var _ = Describe("deis routing", func() { 23 | 24 | Context("with an existing user", func() { 25 | 26 | var user model.User 27 | 28 | BeforeEach(func() { 29 | user = auth.RegisterAndLogin() 30 | }) 31 | 32 | AfterEach(func() { 33 | auth.Cancel(user) 34 | }) 35 | 36 | Context("who owns an existing app that has already been deployed", func() { 37 | 38 | var app model.App 39 | 40 | BeforeEach(func() { 41 | app = apps.Create(user, "--no-remote") 42 | builds.Create(user, app) 43 | }) 44 | 45 | AfterEach(func() { 46 | apps.Destroy(user, app) 47 | }) 48 | 49 | Specify("that user can list that app's routing info", func() { 50 | sess, err := cmd.Start("deis routing:info --app=%s", &user, app.Name) 51 | Eventually(sess).Should(Say("Routing is enabled.\n")) 52 | Expect(err).NotTo(HaveOccurred()) 53 | Eventually(sess).Should(Exit(0)) 54 | }) 55 | 56 | Specify("that user can view app when routing is enabled", func() { 57 | cmdRetryTimeout := 60 58 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 59 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusOK), cmdRetryTimeout)).Should(BeTrue()) 60 | }) 61 | 62 | Specify("that user can disable routing", func() { 63 | cmdRetryTimeout := 60 64 | sess, err := cmd.Start("deis routing:disable --app=%s", &user, app.Name) 65 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 66 | Expect(err).NotTo(HaveOccurred()) 67 | Eventually(sess).Should(Exit(0)) 68 | 69 | sess, err = cmd.Start("deis routing:info --app=%s", &user, app.Name) 70 | Eventually(sess).Should(Say("Routing is disabled.\n")) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Eventually(sess).Should(Exit(0)) 73 | 74 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 75 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(http.StatusNotFound), cmdRetryTimeout)).Should(BeTrue()) 76 | }) 77 | }) 78 | }) 79 | 80 | DescribeTable("any user can get command-line help for routing", func(command string, expected string) { 81 | sess, err := cmd.Start(command, nil) 82 | Eventually(sess).Should(Say(expected)) 83 | Expect(err).NotTo(HaveOccurred()) 84 | Eventually(sess).Should(Exit(0)) 85 | // TODO: test that help output was more than five lines long 86 | }, 87 | Entry("helps on \"help routing\"", 88 | "deis help routing", "Valid commands for routing:"), 89 | Entry("helps on \"routing -h\"", 90 | "deis routing -h", "Valid commands for routing:"), 91 | Entry("helps on \"routing --help\"", 92 | "deis routing --help", "Valid commands for routing:"), 93 | Entry("helps on \"help routing:info\"", 94 | "deis help routing:info", "Prints info about the current application's routability."), 95 | Entry("helps on \"routing:info -h\"", 96 | "deis routing:info -h", "Prints info about the current application's routability."), 97 | Entry("helps on \"routing:info --help\"", 98 | "deis routing:info --help", "Prints info about the current application's routability."), 99 | Entry("helps on \"help routing:enable\"", 100 | "deis help routing:enable", "Enables routability for an app."), 101 | Entry("helps on \"routing:enable -h\"", 102 | "deis routing:enable -h", "Enables routability for an app."), 103 | Entry("helps on \"routing:enable --help\"", 104 | "deis routing:enable --help", "Enables routability for an app."), 105 | Entry("helps on \"help routing:disable\"", 106 | "deis help routing:disable", "Disables routability for an app."), 107 | Entry("helps on \"routing:disable -h\"", 108 | "deis routing:disable -h", "Disables routability for an app."), 109 | Entry("helps on \"routing:disable --help\"", 110 | "deis routing:disable --help", "Disables routability for an app."), 111 | ) 112 | 113 | }) 114 | -------------------------------------------------------------------------------- /tests/settings/settings.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/deis/workflow-e2e/tests/util" 10 | ) 11 | 12 | const ( 13 | DeisRootHostname = "k8s.local" 14 | ) 15 | 16 | var ( 17 | ActualHome = os.Getenv("HOME") 18 | TestHome string 19 | TestRoot string 20 | DeisControllerURL string 21 | DefaultEventuallyTimeout time.Duration 22 | MaxEventuallyTimeout time.Duration 23 | GitSSH string 24 | Debug = os.Getenv("DEBUG") != "" 25 | ) 26 | 27 | func init() { 28 | DeisControllerURL = getControllerURL() 29 | defaultEventuallyTimeoutStr := os.Getenv("DEFAULT_EVENTUALLY_TIMEOUT") 30 | if defaultEventuallyTimeoutStr == "" { 31 | DefaultEventuallyTimeout = 60 * time.Second 32 | } else { 33 | DefaultEventuallyTimeout, _ = time.ParseDuration(defaultEventuallyTimeoutStr) 34 | } 35 | 36 | maxEventuallyTimeoutStr := os.Getenv("MAX_EVENTUALLY_TIMEOUT") 37 | if maxEventuallyTimeoutStr == "" { 38 | MaxEventuallyTimeout = 600 * time.Second 39 | } else { 40 | MaxEventuallyTimeout, _ = time.ParseDuration(maxEventuallyTimeoutStr) 41 | } 42 | } 43 | 44 | func getControllerURL() string { 45 | // if DEIS_CONTROLLER_URL exists in the environment, use that 46 | controllerURL := os.Getenv("DEIS_CONTROLLER_URL") 47 | if controllerURL != "" { 48 | return controllerURL 49 | } 50 | 51 | // otherwise, rely on kubernetes and some DNS magic 52 | host := "deis." + DeisRootHostname 53 | if err := util.AddToEtcHosts(host); err != nil { 54 | log.Fatalf("Could not write %s to /etc/hosts (%s)", host, err) 55 | } 56 | // also gotta write a route for the builder 57 | builderHost := "deis-builder." + DeisRootHostname 58 | if err := util.AddToEtcHosts(builderHost); err != nil { 59 | log.Fatalf("Could not write %s to /etc/hosts (%s)", builderHost, err) 60 | } 61 | 62 | port := os.Getenv("DEIS_ROUTER_SERVICE_PORT") 63 | switch port { 64 | case "443": 65 | return "https://" + host 66 | case "80", "": 67 | return "http://" + host 68 | default: 69 | return fmt.Sprintf("http://%s:%s", host, port) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/tags_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | deis "github.com/deis/controller-sdk-go" 8 | "github.com/deis/workflow-e2e/tests/cmd" 9 | "github.com/deis/workflow-e2e/tests/cmd/apps" 10 | "github.com/deis/workflow-e2e/tests/cmd/auth" 11 | "github.com/deis/workflow-e2e/tests/cmd/builds" 12 | "github.com/deis/workflow-e2e/tests/model" 13 | "github.com/deis/workflow-e2e/tests/settings" 14 | "github.com/deis/workflow-e2e/tests/util" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/ginkgo/extensions/table" 18 | . "github.com/onsi/gomega" 19 | . "github.com/onsi/gomega/gbytes" 20 | . "github.com/onsi/gomega/gexec" 21 | ) 22 | 23 | var _ = Describe("deis tags", func() { 24 | 25 | Context("with an existing user", func() { 26 | 27 | var user model.User 28 | 29 | BeforeEach(func() { 30 | user = auth.RegisterAndLogin() 31 | }) 32 | 33 | AfterEach(func() { 34 | auth.Cancel(user) 35 | }) 36 | 37 | Context("who owns an existing app that has already been deployed", func() { 38 | 39 | var app model.App 40 | 41 | BeforeEach(func() { 42 | app = apps.Create(user, "--no-remote") 43 | builds.Create(user, app) 44 | }) 45 | 46 | AfterEach(func() { 47 | apps.Destroy(user, app) 48 | }) 49 | 50 | Specify("that user can list that app's tags", func() { 51 | sess, err := cmd.Start("deis tags:list --app=%s", &user, app.Name) 52 | Eventually(sess).Should(Say("=== %s Tags", app.Name)) 53 | Expect(err).NotTo(HaveOccurred()) 54 | Eventually(sess).Should(Exit(0)) 55 | }) 56 | 57 | Specify("that user cannot set an invalid tag", func() { 58 | sess, err := cmd.Start("deis tags:set --app=%s munkafolyamat=yeah", &user, app.Name) 59 | Eventually(sess, settings.MaxEventuallyTimeout).ShouldNot(Say("=== %s Tags", app.Name)) 60 | Eventually(sess).ShouldNot(Say(`munkafolyamat\s+yeah`, app.Name)) 61 | Eventually(sess.Err).Should(Say(util.PrependError(deis.ErrTagNotFound))) 62 | Expect(err).NotTo(HaveOccurred()) 63 | Eventually(sess).Should(Exit(1)) 64 | }) 65 | 66 | Specify("that user cannot unset an invalid tag", func() { 67 | sess, err := cmd.Start("deis tags:unset --app=%s munkafolyamat", &user, app.Name) 68 | Eventually(sess).ShouldNot(Say(`munkafolyamat\s+yeah`, app.Name)) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Eventually(sess).Should(Exit(1)) 71 | }) 72 | 73 | Specify("that user can set a valid tag", func() { 74 | // Find a valid tag to set 75 | // Use original $HOME dir or else kubectl can't find its config 76 | sess, err := cmd.Start("HOME=%s kubectl get nodes -o jsonpath={.items[*].metadata..labels}", nil, settings.ActualHome) 77 | Expect(err).NotTo(HaveOccurred()) 78 | Eventually(sess).Should(Exit(0)) 79 | 80 | // grep output like "map[kubernetes.io/hostname:192.168.64.2 node:worker1]" 81 | re := regexp.MustCompile(`([\w\.\-]{0,253}/?[-_\.\w]{1,63}:[-_\.\w]{1,63})`) 82 | pairs := re.FindAllString(string(sess.Out.Contents()), -1) 83 | // Use the first key:value pair found 84 | label := strings.Split(pairs[0], ":") 85 | 86 | sess, err = cmd.Start("deis tags:set --app=%s %s=%s", &user, app.Name, label[0], label[1]) 87 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Tags", app.Name)) 88 | Eventually(sess).Should(Say(`%s\s+%s`, label[0], label[1])) 89 | Expect(err).NotTo(HaveOccurred()) 90 | Eventually(sess).Should(Exit(0)) 91 | 92 | sess, err = cmd.Start("deis tags:list --app=%s", &user, app.Name) 93 | Eventually(sess).Should(Say("=== %s Tags", app.Name)) 94 | Eventually(sess).Should(Say(`%s\s+%s`, label[0], label[1])) 95 | Expect(err).NotTo(HaveOccurred()) 96 | Eventually(sess).Should(Exit(0)) 97 | }) 98 | 99 | Context("and a tag has already been added to the app", func() { 100 | 101 | var label []string 102 | 103 | BeforeEach(func() { 104 | // Find a valid tag to set 105 | // Use original $HOME dir or else kubectl can't find its config 106 | sess, err := cmd.Start("HOME=%s kubectl get nodes -o jsonpath={.items[*].metadata..labels}", nil, settings.ActualHome) 107 | Expect(err).NotTo(HaveOccurred()) 108 | Eventually(sess).Should(Exit(0)) 109 | 110 | // grep output like "map[kubernetes.io/hostname:192.168.64.2 node:worker1]" 111 | re := regexp.MustCompile(`([\w\.\-]{0,253}/?[-_\.\w]{1,63}:[-_\.\w]{1,63})`) 112 | pairs := re.FindAllString(string(sess.Out.Contents()), -1) 113 | // Use the first key:value pair found 114 | label = strings.Split(pairs[0], ":") 115 | 116 | sess, err = cmd.Start("deis tags:set --app=%s %s=%s", &user, app.Name, label[0], label[1]) 117 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Tags", app.Name)) 118 | Eventually(sess).Should(Say(`%s\s+%s`, label[0], label[1])) 119 | Expect(err).NotTo(HaveOccurred()) 120 | Eventually(sess).Should(Exit(0)) 121 | }) 122 | 123 | Specify("that user can unset that tag from that app", func() { 124 | sess, err := cmd.Start("deis tags:unset --app=%s %s", &user, app.Name, label[0]) 125 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("=== %s Tags", app.Name)) 126 | Eventually(sess).ShouldNot(Say(`%s\s+%s`, label[0], label[1])) 127 | Expect(err).NotTo(HaveOccurred()) 128 | Eventually(sess).Should(Exit(0)) 129 | 130 | sess, err = cmd.Start("deis tags:list --app=%s", &user, app.Name) 131 | Eventually(sess).Should(Say("=== %s Tags", app.Name)) 132 | Eventually(sess).ShouldNot(Say(`%s\s+%s`, label[0], label[1])) 133 | Eventually(sess).ShouldNot(Say(`munkafolyamat\s+yeah`, app.Name)) 134 | Expect(err).NotTo(HaveOccurred()) 135 | Eventually(sess).Should(Exit(0)) 136 | }) 137 | 138 | }) 139 | 140 | }) 141 | 142 | }) 143 | 144 | DescribeTable("any user can get command-line help for tags", func(command string, expected string) { 145 | sess, err := cmd.Start(command, nil) 146 | Eventually(sess).Should(Say(expected)) 147 | Expect(err).NotTo(HaveOccurred()) 148 | Eventually(sess).Should(Exit(0)) 149 | // TODO: test that help output was more than five lines long 150 | }, 151 | Entry("helps on \"help tags\"", 152 | "deis help tags", "Valid commands for tags:"), 153 | Entry("helps on \"tags -h\"", 154 | "deis tags -h", "Valid commands for tags:"), 155 | Entry("helps on \"tags --help\"", 156 | "deis tags --help", "Valid commands for tags:"), 157 | Entry("helps on \"help tags:list\"", 158 | "deis help tags:list", "Lists tags for an application."), 159 | Entry("helps on \"tags:list -h\"", 160 | "deis tags:list -h", "Lists tags for an application."), 161 | Entry("helps on \"tags:list --help\"", 162 | "deis tags:list --help", "Lists tags for an application."), 163 | Entry("helps on \"help tags:set\"", 164 | "deis help tags:set", "Sets tags for an application."), 165 | Entry("helps on \"tags:set -h\"", 166 | "deis tags:set -h", "Sets tags for an application."), 167 | Entry("helps on \"tags:set --help\"", 168 | "deis tags:set --help", "Sets tags for an application."), 169 | Entry("helps on \"help tags:unset\"", 170 | "deis help tags:unset", "Unsets tags for an application."), 171 | Entry("helps on \"tags:unset -h\"", 172 | "deis tags:unset -h", "Unsets tags for an application."), 173 | Entry("helps on \"tags:unset --help\"", 174 | "deis tags:unset --help", "Unsets tags for an application."), 175 | ) 176 | 177 | }) 178 | -------------------------------------------------------------------------------- /tests/tests_suite_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "math/rand" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "path/filepath" 11 | "testing" 12 | "time" 13 | 14 | "github.com/deis/workflow-e2e/tests/cmd/auth" 15 | "github.com/deis/workflow-e2e/tests/settings" 16 | 17 | . "github.com/onsi/ginkgo" 18 | . "github.com/onsi/ginkgo/config" 19 | "github.com/onsi/ginkgo/reporters" 20 | . "github.com/onsi/gomega" 21 | ) 22 | 23 | func init() { 24 | rand.Seed(time.Now().UnixNano()) 25 | } 26 | 27 | func TestTests(t *testing.T) { 28 | RegisterFailHandler(Fail) 29 | 30 | enableJunit := os.Getenv("JUNIT") 31 | if enableJunit == "true" { 32 | junitReporter := reporters.NewJUnitReporter(filepath.Join(settings.ActualHome, fmt.Sprintf("junit-%d.xml", GinkgoConfig.ParallelNode))) 33 | RunSpecsWithDefaultAndCustomReporters(t, "Deis Workflow", []Reporter{junitReporter}) 34 | } else { 35 | RunSpecs(t, "Deis Workflow") 36 | } 37 | } 38 | 39 | // SynchronizedBeforeSuite will run once and only once, even when tests are parallelized. It 40 | // performs all the one-time setup required by the test suite. 41 | var _ = SynchronizedBeforeSuite(func() []byte { 42 | // Verify the "deis" executable is on the $PATH 43 | output, err := exec.LookPath("deis") 44 | Expect(err).NotTo(HaveOccurred(), output) 45 | 46 | // Create temporary home directory for use by this test run. 47 | testHome, err := ioutil.TempDir("", "deis-workflow-home") 48 | Expect(err).NotTo(HaveOccurred()) 49 | 50 | // When running parallel tests, Ginkgo seems to fork processes instead of using goroutines. 51 | // We set $HOME to our temporary home directory so it can be discovered and used by other 52 | // Ginkgo processes. 53 | os.Setenv("HOME", settings.TestHome) 54 | 55 | // Create and install a git wrapper script. This will allow us to always specify the private 56 | // key to use by means of an environment variable. This is a convenience that helps us run 57 | // tests in parallel, where each test user might have its own keys. 58 | sshHome := path.Join(testHome, ".ssh") 59 | os.MkdirAll(sshHome, 0777) 60 | settings.GitSSH = path.Join(sshHome, "git-ssh") 61 | sshFlags := "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 62 | if settings.Debug { 63 | sshFlags = sshFlags + " -v" 64 | } 65 | ioutil.WriteFile(settings.GitSSH, []byte(fmt.Sprintf("#!/bin/sh\nSSH_ORIGINAL_COMMAND=\"ssh $@\"\nexec /usr/bin/ssh %s -i \"$GIT_KEY\" \"$@\"\n", sshFlags)), 0777) 66 | 67 | // Set $HOME before we go any further. The user registration step below will need this to be 68 | // set correctly in order for the profile containing the user's auth token to be written to 69 | // the correct directory. 70 | os.Setenv("HOME", testHome) 71 | 72 | // Set the defaultEventuallyTimeout before we go any further. The next step carries out a user 73 | // registration and we'll want this timeout set before then. 74 | SetDefaultEventuallyTimeout(settings.DefaultEventuallyTimeout) 75 | 76 | // ATTEMPT to register the admin user. Since the FIRST user to regiser in a new cluster is 77 | // automatically the admin, it's vitally important that this happen now. If the admin user 78 | // already exists, this step will attempt to login as that user. 79 | auth.RegisterAdmin() 80 | 81 | // Return the value of testHome as bytes. Ginkgo will pass these to the function below, which 82 | // will be executed on every node (like BeforeSuite would if we were using it.) 83 | return []byte(testHome) 84 | }, func(data []byte) { 85 | settings.TestHome = string(data) 86 | 87 | // Set $HOME for the benefit of all commands we will fork to execute. 88 | os.Setenv("HOME", settings.TestHome) 89 | 90 | // Derive settings.GitSSH from settings.TestHome. 91 | sshHome := path.Join(settings.TestHome, ".ssh") 92 | settings.GitSSH = path.Join(sshHome, "git-ssh") 93 | 94 | // Set the defaultEventuallyTimeout for ALL Ginko nodes. 95 | SetDefaultEventuallyTimeout(settings.DefaultEventuallyTimeout) 96 | }) 97 | 98 | var _ = BeforeEach(func() { 99 | // Make a directory within the home directory for each test. This is to avoid collisions when 100 | // tests do things like clone git repos. 101 | var err error 102 | settings.TestRoot, err = ioutil.TempDir("", "deis-workflow-test") 103 | Expect(err).NotTo(HaveOccurred()) 104 | // Everything we do, we do from within that directory... 105 | os.Chdir(settings.TestRoot) 106 | // But note that all test users and tests still share a common $HOME! 107 | }) 108 | 109 | var _ = SynchronizedAfterSuite(func() {}, func() { 110 | auth.CancelAdmin() 111 | os.RemoveAll(settings.TestHome) 112 | }) 113 | -------------------------------------------------------------------------------- /tests/tls_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/deis/workflow-e2e/tests/cmd" 8 | "github.com/deis/workflow-e2e/tests/cmd/apps" 9 | "github.com/deis/workflow-e2e/tests/cmd/auth" 10 | "github.com/deis/workflow-e2e/tests/cmd/builds" 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/onsi/gomega/gbytes" 17 | . "github.com/onsi/gomega/gexec" 18 | ) 19 | 20 | var _ = Describe("deis tls", func() { 21 | 22 | Context("with an existing user", func() { 23 | 24 | var user model.User 25 | 26 | BeforeEach(func() { 27 | user = auth.RegisterAndLogin() 28 | }) 29 | 30 | AfterEach(func() { 31 | auth.Cancel(user) 32 | }) 33 | 34 | Context("who owns an existing app that has already been deployed", func() { 35 | 36 | var app model.App 37 | 38 | BeforeEach(func() { 39 | app = apps.Create(user, "--no-remote") 40 | builds.Create(user, app) 41 | }) 42 | 43 | AfterEach(func() { 44 | apps.Destroy(user, app) 45 | }) 46 | 47 | Specify("can enable/disable tls", func() { 48 | sess, err := cmd.Start("deis tls:enable --app=%s", &user, app.Name) 49 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 50 | Expect(err).NotTo(HaveOccurred()) 51 | Eventually(sess).Should(Exit(0)) 52 | 53 | // curl the app's root URL and ensure we get a 301 redirect 54 | cmdRetryTimeout := 60 55 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 56 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(301), cmdRetryTimeout)).Should(BeTrue()) 57 | 58 | sess, err = cmd.Start("deis tls:disable --app=%s", &user, app.Name) 59 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 60 | Expect(err).NotTo(HaveOccurred()) 61 | Eventually(sess).Should(Exit(0)) 62 | 63 | cmdRetryTimeout = 60 64 | curlCmd = model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 65 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(200), cmdRetryTimeout)).Should(BeTrue()) 66 | }) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/users_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | deis "github.com/deis/controller-sdk-go" 5 | "github.com/deis/workflow-e2e/tests/cmd" 6 | "github.com/deis/workflow-e2e/tests/cmd/auth" 7 | "github.com/deis/workflow-e2e/tests/model" 8 | "github.com/deis/workflow-e2e/tests/settings" 9 | "github.com/deis/workflow-e2e/tests/util" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | . "github.com/onsi/gomega/gbytes" 14 | . "github.com/onsi/gomega/gexec" 15 | ) 16 | 17 | var _ = Describe("deis users", func() { 18 | 19 | Context("with an existing admin", func() { 20 | 21 | admin := model.Admin 22 | 23 | Specify("that admin can list all users", func() { 24 | sess, err := cmd.Start("deis users:list", &admin) 25 | Eventually(sess).Should(Say("=== Users")) 26 | output := string(sess.Out.Contents()) 27 | Expect(output).To(ContainSubstring(admin.Username)) 28 | Expect(err).NotTo(HaveOccurred()) 29 | Eventually(sess).Should(Exit(0)) 30 | }) 31 | 32 | }) 33 | 34 | Context("with an existing non-admin user", func() { 35 | 36 | var user model.User 37 | 38 | BeforeEach(func() { 39 | user = auth.RegisterAndLogin() 40 | }) 41 | 42 | AfterEach(func() { 43 | auth.Cancel(user) 44 | }) 45 | 46 | Specify("that user cannot list all users", func() { 47 | sess, err := cmd.Start("deis users:list", &user) 48 | Eventually(sess.Err, settings.MaxEventuallyTimeout).Should(Say(util.PrependError(deis.ErrForbidden))) 49 | Expect(err).NotTo(HaveOccurred()) 50 | Eventually(sess).Should(Exit(1)) 51 | }) 52 | }) 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /tests/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var errNoRouterHost = errors.New(`Set the router host and port for tests, such as: 10 | 11 | $ DEIS_ROUTER_SERVICE_HOST=192.0.2.10 DEIS_ROUTER_SERVICE_PORT=31182 make test-integration`) 12 | 13 | // PrependError adds 'Error: ' to an expected error, like the CLI does to error messages. 14 | func PrependError(expected error) string { 15 | return "Error: " + expected.Error() 16 | } 17 | 18 | // AddToEtcHosts aliases the router IP address to the hostname via /etc/hosts 19 | func AddToEtcHosts(hostname string) error { 20 | addr := os.Getenv("DEIS_ROUTER_SERVICE_HOST") 21 | if addr == "" { 22 | return errNoRouterHost 23 | } 24 | 25 | text := fmt.Sprintf("%s\t%s\n", addr, hostname) 26 | f, err := os.OpenFile("/etc/hosts", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | defer f.Close() 32 | 33 | _, err = f.WriteString(text) 34 | return err 35 | } 36 | -------------------------------------------------------------------------------- /tests/whitelist_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/deis/workflow-e2e/tests/cmd" 8 | "github.com/deis/workflow-e2e/tests/cmd/apps" 9 | "github.com/deis/workflow-e2e/tests/cmd/auth" 10 | "github.com/deis/workflow-e2e/tests/cmd/builds" 11 | "github.com/deis/workflow-e2e/tests/model" 12 | "github.com/deis/workflow-e2e/tests/settings" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/onsi/gomega/gbytes" 17 | . "github.com/onsi/gomega/gexec" 18 | ) 19 | 20 | var _ = Describe("deis whitelist", func() { 21 | 22 | Context("with an existing user", func() { 23 | 24 | var user model.User 25 | 26 | BeforeEach(func() { 27 | user = auth.RegisterAndLogin() 28 | }) 29 | 30 | AfterEach(func() { 31 | auth.Cancel(user) 32 | }) 33 | 34 | Context("who owns an existing app that has already been deployed", func() { 35 | 36 | var app model.App 37 | 38 | BeforeEach(func() { 39 | app = apps.Create(user, "--no-remote") 40 | builds.Create(user, app) 41 | }) 42 | 43 | AfterEach(func() { 44 | apps.Destroy(user, app) 45 | }) 46 | 47 | Specify("can list that app's whitelist list", func() { 48 | sess, err := cmd.Start("deis whitelist:list --app=%s", &user, app.Name) 49 | Expect(err).NotTo(HaveOccurred()) 50 | Eventually(sess).Should(Exit(0)) 51 | }) 52 | 53 | Specify("can view app when no addresses whitelist", func() { 54 | // curl the app's root URL and print just the HTTP response code 55 | cmdRetryTimeout := 60 56 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 57 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(200), cmdRetryTimeout)).Should(BeTrue()) 58 | }) 59 | 60 | Specify("can add/remove addresses from the whitelist", func() { 61 | sess, err := cmd.Start("deis whitelist:add 1.2.3.4 --app=%s", &user, app.Name) 62 | Expect(err).NotTo(HaveOccurred()) 63 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 64 | Eventually(sess).Should(Exit(0)) 65 | 66 | // curl the app's root URL and print just the HTTP response code 67 | cmdRetryTimeout := 60 68 | curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 69 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(403), cmdRetryTimeout)).Should(BeTrue()) 70 | 71 | sess, err = cmd.Start("deis whitelist:add 0.0.0.0/0 --app=%s", &user, app.Name) 72 | Expect(err).NotTo(HaveOccurred()) 73 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 74 | Eventually(sess).Should(Exit(0)) 75 | 76 | cmdRetryTimeout = 60 77 | curlCmd = model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 78 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(200), cmdRetryTimeout)).Should(BeTrue()) 79 | 80 | sess, err = cmd.Start("deis whitelist:remove 0.0.0.0/0 --app=%s", &user, app.Name) 81 | Expect(err).NotTo(HaveOccurred()) 82 | Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("done")) 83 | Eventually(sess).Should(Exit(0)) 84 | 85 | // curl the app's root URL and print just the HTTP response code 86 | cmdRetryTimeout = 60 87 | curlCmd = model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)} 88 | Eventually(cmd.Retry(curlCmd, strconv.Itoa(403), cmdRetryTimeout)).Should(BeTrue()) 89 | }) 90 | }) 91 | }) 92 | 93 | }) 94 | --------------------------------------------------------------------------------