diff --git a/.dockerignore b/.dockerignore index 46c83b0467..07c13d382d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,3 @@ awx/ui/node_modules +awx/ui_next/node_modules +Dockerfile diff --git a/INSTALL.md b/INSTALL.md index 1a63cc9b89..590ddb4d98 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,410 +1,120 @@ +Table of Contents +================= + + * [Installing AWX](#installing-awx) + * [The AWX Operator](#the-awx-operator) + * [Quickstart with minikube](#quickstart-with-minikube) + * [Starting minikube](#starting-minikube) + * [Deploying the AWX Operator](#deploying-the-awx-operator) + * [Verifying the Operator Deployment](#verifying-the-operator-deployment) + * [Deploy AWX](#deploy-awx) + * [Accessing AWX](#accessing-awx) + * [Installing the AWX CLI](#installing-the-awx-cli) + * [Building the CLI Documentation](#building-the-cli-documentation) + + # Installing AWX -This document provides a guide for installing AWX. +:warning: NOTE | +--- | +If you're installing an older release of AWX (prior to 18.0), these instructions have changed. Take a look at your version specific instructions, e.g., for AWX 17.0.1, see: [https://github.com/ansible/awx/blob/17.0.1/INSTALL.md](https://github.com/ansible/awx/blob/17.0.1/INSTALL.md) +If you're attempting to migrate an older Docker-based AWX installation, see: [Migrating Data from Local Docker](https://github.com/ansible/awx/blob/devel/tools/docker-compose/docs/data_migration.md) | -## Table of contents +## The AWX Operator -- [Installing AWX](#installing-awx) - * [Getting started](#getting-started) - + [Clone the repo](#clone-the-repo) - + [AWX branding](#awx-branding) - + [Prerequisites](#prerequisites) - + [System Requirements](#system-requirements) - + [Choose a deployment platform](#choose-a-deployment-platform) - + [Official vs Building Images](#official-vs-building-images) - * [OpenShift](#openshift) - + [Prerequisites](#prerequisites-1) - + [Pre-install steps](#pre-install-steps) - - [Deploying to Minishift](#deploying-to-minishift) - - [PostgreSQL](#postgresql) - + [Run the installer](#run-the-installer) - + [Post-install](#post-install) - + [Accessing AWX](#accessing-awx) - * [Kubernetes](#kubernetes) - + [Prerequisites](#prerequisites-2) - + [Pre-install steps](#pre-install-steps-1) - + [Configuring Helm](#configuring-helm) - + [Run the installer](#run-the-installer-1) - + [Post-install](#post-install-1) - + [Accessing AWX](#accessing-awx-1) - + [SSL Termination](#ssl-termination) -- [Installing the AWX CLI](#installing-the-awx-cli) - * [Building the CLI Documentation](#building-the-cli-documentation) +Starting in version 18.0, the [AWX Operator](https://github.com/ansible/awx-operator) is the preferred way to install AWX. +### Quickstart with minikube -## Getting started +If you don't have an existing OpenShift or Kubernetes cluster, minikube is a fast and easy way to get up and running. -### Clone the repo +To install minikube, follow the steps in their [documentation](https://minikube.sigs.k8s.io/docs/start/). -If you have not already done so, you will need to clone, or create a local copy, of the [AWX repo](https://github.com/ansible/awx). We generally recommend that you view the releases page: +#### Starting minikube -https://github.com/ansible/awx/releases - -...and clone the latest stable release, e.g., - -`git clone -b x.y.z https://github.com/ansible/awx.git` - -Please note that deploying from `HEAD` (or the latest commit) is **not** stable, and that if you want to do this, you should proceed at your own risk (also, see the section #official-vs-building-images for building your own image). - -For more on how to clone the repo, view [git clone help](https://git-scm.com/docs/git-clone). - -Once you have a local copy, run the commands in the following sections from the root of the project tree. - -### AWX branding - -You can optionally install the AWX branding assets from the [awx-logos repo](https://github.com/ansible/awx-logos). Prior to installing, please review and agree to the [trademark guidelines](https://github.com/ansible/awx-logos/blob/master/TRADEMARKS.md). - -To install the assets, clone the `awx-logos` repo so that it is next to your `awx` clone. As you progress through the installation steps, you'll be setting variables in the [inventory](./installer/inventory) file. To include the assets in the build, set `awx_official=true`. - -### Prerequisites - -Before you can run a deployment, you'll need the following installed in your local environment: - -- [Ansible](http://docs.ansible.com/ansible/latest/intro_installation.html) Requires Version 2.8+ -- [Docker](https://docs.docker.com/engine/installation/) - + A recent version -- [docker](https://pypi.org/project/docker/) Python module - + This is incompatible with `docker-py`. If you have previously installed `docker-py`, please uninstall it. - + We use this module instead of `docker-py` because it is what the `docker-compose` Python module requires. -- [community.general.docker_image collection](https://docs.ansible.com/ansible/latest/collections/community/general/docker_image_module.html) - + This is only required if you are using Ansible >= 2.10 -- [GNU Make](https://www.gnu.org/software/make/) -- [Git](https://git-scm.com/) Requires Version 1.8.4+ -- Python 3.6+ - -### System Requirements - -The system that runs the AWX service will need to satisfy the following requirements - -- At least 4GB of memory -- At least 2 cpu cores -- At least 20GB of space -- Running Docker, Openshift, or Kubernetes -- If you choose to use an external PostgreSQL database, please note that the minimum version is 10+. - -### Choose a deployment platform - -We currently support running AWX as a containerized application using Docker images deployed to either an OpenShift cluster or a Kubernetes cluster. The remainder of this document will walk you through the process of building the images, and deploying them to either platform. - -The [installer](./installer) directory contains an [inventory](./installer/inventory) file, and a playbook, [install.yml](./installer/install.yml). You'll begin by setting variables in the inventory file according to the platform you wish to use, and then you'll start the image build and deployment process by running the playbook. - -In the sections below, you'll find deployment details and instructions for each platform: -- [OpenShift](#openshift) -- [Kubernetes](#kubernetes) - -### Official vs Building Images - -When installing AWX you have the option of building your own image or using the image provided on DockerHub (see [awx](https://hub.docker.com/r/ansible/awx/)) - -This is controlled by the following variables in the `inventory` file +Once you have installed minikube, run the following command to start it. You may wish to customize these options. ``` -dockerhub_base=ansible -dockerhub_version=latest +$ minikube start --cpus=4 --memory=8g --addons=ingress ``` -If these variables are present then all deployments will use these hosted images. If the variables are not present then the images will be built during the install. +#### Deploying the AWX Operator -*dockerhub_base* - -> The base location on DockerHub where the images are hosted (by default this pulls a container image named `ansible/awx:tag`) - -*dockerhub_version* - -> Multiple versions are provided. `latest` always pulls the most recent. You may also select version numbers at different granularities: 1, 1.0, 1.0.1, 1.0.0.123 - -To build your own container use the `build.yml` playbook: +For a comprehensive overview of features, see [README.md](https://github.com/ansible/awx-operator/blob/devel/README.md) in the awx-operator repo. The following steps are the bare minimum to get AWX up and running. ``` -ansible-playbook tools/ansible/build.yml -e awx_version=test-build +$ minikube kubectl -- apply -f https://raw.githubusercontent.com/ansible/awx-operator/devel/deploy/awx-operator.yaml ``` -The resulting image will automatically be pushed to a registry if `docker_registry` is defined. +##### Verifying the Operator Deployment - - -## OpenShift - -### Prerequisites - -To complete a deployment to OpenShift, you will need access to an OpenShift cluster. For demo and testing purposes, you can use [Minishift](https://github.com/minishift/minishift) to create a single node cluster running inside a virtual machine. - -When using OpenShift for deploying AWX make sure you have correct privileges to add the security context 'privileged', otherwise the installation will fail. The privileged context is needed because of the use of [the bubblewrap tool](https://github.com/containers/bubblewrap) to add an additional layer of security when using containers. - -You will also need to have the `oc` command in your PATH. The `install.yml` playbook will call out to `oc` when logging into, and creating objects on the cluster. - -The default resource requests per-deployment requires: - -> Memory: 6GB -> CPU: 3 cores - -This can be tuned by overriding the variables found in [/installer/roles/kubernetes/defaults/main.yml](/installer/roles/kubernetes/defaults/main.yml). Special care should be taken when doing this as undersized instances will experience crashes and resource exhaustion. - -For more detail on how resource requests are formed see: [https://docs.openshift.com/container-platform/latest/dev_guide/compute_resources.html#dev-compute-resources](https://docs.openshift.com/container-platform/latest/dev_guide/compute_resources.html#dev-compute-resources) - -### Pre-install steps - -Before starting the install, review the [inventory](./installer/inventory) file, and uncomment and provide values for the following variables found in the `[all:vars]` section: - -*openshift_host* - -> IP address or hostname of the OpenShift cluster. If you're using Minishift, this will be the value returned by `minishift ip`. - - -*openshift_skip_tls_verify* - -> Boolean. Set to True if using self-signed certs. - -*openshift_project* - -> Name of the OpenShift project that will be created, and used as the namespace for the AWX app. Defaults to *awx*. - -*openshift_user* - -> Username of the OpenShift user that will create the project, and deploy the application. Defaults to *developer*. - -*openshift_pg_emptydir* - -> Boolean. Set to True to use an emptyDir volume when deploying the PostgreSQL pod. Note: This should only be used for demo and testing purposes. - -*docker_registry* - -> IP address and port, or URL, for accessing a registry that the OpenShift cluster can access. Defaults to *172.30.1.1:5000*, the internal registry delivered with Minishift. This is not needed if you are using official hosted images. - -*docker_registry_repository* - -> Namespace to use when pushing and pulling images to and from the registry. Generally this will match the project name. It defaults to *awx*. This is not needed if you are using official hosted images. - -*docker_registry_username* - -> Username of the user that will push images to the registry. Will generally match the *openshift_user* value. Defaults to *developer*. This is not needed if you are using official hosted images. - -#### Deploying to Minishift - -Install Minishift by following the [installation guide](https://docs.openshift.org/latest/minishift/getting-started/installing.html). - -The recommended minimum resources for your Minishift VM: - -```bash -$ minishift start --cpus=4 --memory=8GB -``` - -The Minishift VM contains a Docker daemon, which you can use to build the AWX images. This is generally the approach you should take, and we recommend doing so. To use this instance, run the following command to setup your environment: - -```bash -# Set DOCKER environment variable to point to the Minishift VM -$ eval $(minishift docker-env) -``` - -**Note** - -> If you choose to not use the Docker instance running inside the VM, and build the images externally, you will have to enable the OpenShift cluster to access the images. This involves pushing the images to an external Docker registry, and granting the cluster access to it, or exposing the internal registry, and pushing the images into it. - -#### PostgreSQL - -By default, AWX will deploy a PostgreSQL pod inside of your cluster. You will need to create a [Persistent Volume Claim](https://docs.openshift.org/latest/dev_guide/persistent_volumes.html) which is named `postgresql` by default, and can be overridden by setting the `openshift_pg_pvc_name` variable. For testing and demo purposes, you may set `openshift_pg_emptydir=yes`. - -If you wish to use an external database, in the inventory file, set the value of `pg_hostname`, and update `pg_username`, `pg_password`, `pg_admin_password`, `pg_database`, and `pg_port` with the connection information. When setting `pg_hostname` the installer will assume you have configured the database in that location and will not launch the postgresql pod. - -### Run the installer - -To start the install, you will pass two *extra* variables on the command line. The first is *openshift_password*, which is the password for the *openshift_user*, and the second is *docker_registry_password*, which is the password associated with *docker_registry_username*. - -If you're using the OpenShift internal registry, then you'll pass an access token for the *docker_registry_password* value, rather than a password. The `oc whoami -t` command will generate the required token, as long as you're logged into the cluster via `oc cluster login`. - -Run the following command (docker_registry_password is optional if using official images): - -```bash -# Start the install -$ ansible-playbook -i inventory install.yml -e openshift_password=developer -e docker_registry_password=$(oc whoami -t) -``` - -### Post-install - -After the playbook run completes, check the status of the deployment by running `oc get pods`: - -```bash -# View the running pods -$ oc get pods - -NAME READY STATUS RESTARTS AGE -awx-3886581826-5mv0l 4/4 Running 0 8s -postgresql-1-l85fh 1/1 Running 0 20m +After a few seconds, the operator should be up and running. Verify it by running the following command: ``` - -In the above example, the name of the AWX pod is `awx-3886581826-5mv0l`. Before accessing the AWX web interface, setup tasks and database migrations need to complete. These tasks are running in the `awx_task` container inside the AWX pod. To monitor their status, tail the container's STDOUT by running the following command, replacing the AWX pod name with the pod name from your environment: - -```bash -# Follow the awx_task log output -$ oc logs -f awx-3886581826-5mv0l -c awx-celery +$ minikube kubectl get pods +NAME READY STATUS RESTARTS AGE +awx-operator-7c78bfbfd-xb6th 1/1 Running 0 11s ``` -You will see the following indicating that database migrations are running: +#### Deploy AWX -```bash -Using /etc/ansible/ansible.cfg as config file -127.0.0.1 | SUCCESS => { - "changed": false, - "db": "awx" -} -Operations to perform: - Synchronize unmigrated apps: solo, api, staticfiles, messages, channels, django_extensions, ui, rest_framework, polymorphic - Apply all migrations: sso, taggit, sessions, sites, kombu_transport_django, social_auth, contenttypes, auth, conf, main -Synchronizing apps without migrations: - Creating tables... - Running deferred SQL... - Installing custom SQL... -Running migrations: - Rendering model states... DONE - Applying contenttypes.0001_initial... OK - Applying contenttypes.0002_remove_content_type_name... OK - Applying auth.0001_initial... OK - Applying auth.0002_alter_permission_name_max_length... OK - Applying auth.0003_alter_user_email_max_length... OK - Applying auth.0004_alter_user_username_opts... OK - Applying auth.0005_alter_user_last_login_null... OK - Applying auth.0006_require_contenttypes_0002... OK - Applying taggit.0001_initial... OK - Applying taggit.0002_auto_20150616_2121... OK - ... +Once the Operator is running, you can now deploy AWX by creating a simple YAML file: + +``` +$ cat myawx.yml +--- +apiVersion: awx.ansible.com/v1beta1 +kind: AWX +metadata: + name: awx +spec: + tower_ingress_type: Ingress ``` -When you see output similar to the following, you'll know that database migrations have completed, and you can access the web interface: +And then creating the AWX object in the Kubernetes API: -```bash -Python 2.7.5 (default, Nov 6 2016, 00:28:07) -[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2 -Type "help", "copyright", "credits" or "license" for more information. -(InteractiveConsole) - ->>> ->>> Default organization added. -Demo Credential, Inventory, and Job Template added. -Successfully registered instance awx-3886581826-5mv0l -(changed: True) -Creating instance group tower -Added instance awx-3886581826-5mv0l to tower +``` +$ minikube kubectl apply -- -f myawx.yml +awx.awx.ansible.com/awx created ``` -Once database migrations complete, the web interface will be accessible. +After creating the AWX object in the Kubernetes API, the operator will begin running its reconciliation loop. -### Accessing AWX +To see what's going on, you can tail the logs of the operator pod (note that your pod name will be different): -The AWX web interface is running in the AWX pod, behind the `awx-web-svc` service. To view the service, and its port value, run the following command: - -```bash -# View available services -$ oc get services - -NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE -awx-web-svc 172.30.111.74 8052:30083/TCP 37m -postgresql 172.30.102.9 5432/TCP 38m +``` +$ minikube kubectl logs -- -f awx-operator-7c78bfbfd-xb6th ``` -The deployment process creates a route, `awx-web-svc`, to expose the service. How the ingres is actually created will vary depending on your environment, and how the cluster is configured. You can view the route, and the external IP address and hostname assigned to it, by running the following command: +After a few seconds, you will see the database and application pods show up. On a fresh system, it may take a few minutes for the container images to download. -```bash -# View available routes -$ oc get routes - -NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD -awx-web-svc awx-web-svc-awx.192.168.64.2.nip.io awx-web-svc http edge/Allow None +``` +$ minikube kubectl get pods +NAME READY STATUS RESTARTS AGE +awx-5ffbfd489c-bvtvf 3/3 Running 0 2m54s +awx-operator-7c78bfbfd-xb6th 1/1 Running 0 6m42s +awx-postgres-0 1/1 Running 0 2m58s ``` -The above example is taken from a Minishift instance. From a web browser, use `https` to access the `HOST/PORT` value from your environment. Using the above example, the URL to access the server would be [https://awx-web-svc-awx.192.168.64.2.nip.io](https://awx-web-svc-awx.192.168.64.2.nip.io). +##### Accessing AWX -Once you access the AWX server, you will be prompted with a login dialog. The default administrator username is `admin`, and the password is `password`. +To access the AWX UI, you'll need to grab the service url from minikube: -## Kubernetes - -### Prerequisites - -A Kubernetes deployment will require you to have access to a Kubernetes cluster as well as the following tools: - -- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) -- [helm](https://helm.sh/docs/intro/quickstart/) - -The installation program will reference `kubectl` directly. `helm` is only necessary if you are letting the installer configure PostgreSQL for you. - -The default resource requests per-pod requires: - -> Memory: 6GB -> CPU: 3 cores - -This can be tuned by overriding the variables found in [/installer/roles/kubernetes/defaults/main.yml](/installer/roles/kubernetes/defaults/main.yml). Special care should be taken when doing this as undersized instances will experience crashes and resource exhaustion. - -For more detail on how resource requests are formed see: [https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/) - -### Pre-install steps - -Before starting the install process, review the [inventory](./installer/inventory) file, and uncomment and provide values for the following variables found in the `[all:vars]` section uncommenting when necessary. Make sure the openshift and standalone docker sections are commented out: - -*kubernetes_context* - -> Prior to running the installer, make sure you've configured the context for the cluster you'll be installing to. This is how the installer knows which cluster to connect to and what authentication to use - -*kubernetes_namespace* - -> Name of the Kubernetes namespace where the AWX resources will be installed. This will be created if it doesn't exist - -*docker_registry_* - -> These settings should be used if building your own base images. You'll need access to an external registry and are responsible for making sure your kube cluster can talk to it and use it. If these are undefined and the dockerhub_ configuration settings are uncommented then the images will be pulled from dockerhub instead - -### Configuring Helm - -If you want the AWX installer to manage creating the database pod (rather than installing and configuring postgres on your own). Then you will need to have a working `helm` installation, you can find details here: [https://helm.sh/docs/intro/quickstart/](https://helm.sh/docs/intro/quickstart/). - -You do not need to create a [Persistent Volume Claim](https://docs.openshift.org/latest/dev_guide/persistent_volumes.html) as Helm does it for you. However, an existing one may be used by setting the `pg_persistence_existingclaim` variable. - -Newer Kubernetes clusters with RBAC enabled will need to make sure a service account is created, make sure to follow the instructions here [https://helm.sh/docs/topics/rbac/](https://helm.sh/docs/topics/rbac/) - -### Run the installer - -After making changes to the `inventory` file use `ansible-playbook` to begin the install - -```bash -$ ansible-playbook -i inventory install.yml +``` +$ minikube service awx-service --url +http://192.168.59.2:31868 ``` -### Post-install +On fresh installs, you will see the "AWX is currently upgrading." page until database migrations finish. -After the playbook run completes, check the status of the deployment by running `kubectl get pods --namespace awx` (replace awx with the namespace you used): +Once you are redirected to the login screen, you can now log in by obtaining the generated admin password (note: do not copy the trailing `%`): -```bash -# View the running pods, it may take a few minutes for everything to be marked in the Running state -$ kubectl get pods --namespace awx -NAME READY STATUS RESTARTS AGE -awx-2558692395-2r8ss 4/4 Running 0 29s -awx-postgresql-355348841-kltkn 1/1 Running 0 1m +``` +$ minikube kubectl -- get secret awx-admin-password -o jsonpath='{.data.password}' | base64 --decode +b6ChwVmqEiAsil2KSpH4xGaZPeZvWnWj% ``` -### Accessing AWX - -The AWX web interface is running in the AWX pod behind the `awx-web-svc` service: - -```bash -# View available services -$ kubectl get svc --namespace awx -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -awx-postgresql ClusterIP 10.7.250.208 5432/TCP 2m -awx-web-svc NodePort 10.7.241.35 80:30177/TCP 1m -``` - -The deployment process creates an `Ingress` named `awx-web-svc` also. Some kubernetes cloud providers will automatically handle routing configuration when an Ingress is created others may require that you more explicitly configure it. You can see what kubernetes knows about things with: - -```bash - kubectl get ing --namespace awx -NAME HOSTS ADDRESS PORTS AGE -awx-web-svc * 35.227.x.y 80 3m -``` - -If your provider is able to allocate an IP Address from the Ingress controller then you can navigate to the address and access the AWX interface. For some providers it can take a few minutes to allocate and make this accessible. For other providers it may require you to manually intervene. - -### SSL Termination - -Unlike Openshift's `Route` the Kubernetes `Ingress` doesn't yet handle SSL termination. As such the default configuration will only expose AWX through HTTP on port 80. You are responsible for configuring SSL support until support is added (either to Kubernetes or AWX itself). +Now you can log in at the URL above with the username "admin" and the password above. Happy Automating! # Installing the AWX CLI diff --git a/Makefile b/Makefile index b3d14d4ba9..aaf1dc6d8f 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,6 @@ COMPOSE_TAG ?= $(GIT_BRANCH) COMPOSE_HOST ?= $(shell hostname) VENV_BASE ?= /var/lib/awx/venv/ -COLLECTION_BASE ?= /var/lib/awx/vendor/awx_ansible_collections SCL_PREFIX ?= CELERY_SCHEDULE_FILE ?= /var/lib/awx/beat.db @@ -62,11 +61,11 @@ WHEEL_FILE ?= $(WHEEL_NAME)-py2-none-any.whl I18N_FLAG_FILE = .i18n_built .PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \ - develop refresh adduser migrate dbchange runserver \ + develop refresh adduser migrate dbchange \ receiver test test_unit test_coverage coverage_html \ - dev_build release_build release_clean sdist \ - ui-docker-machine ui-docker ui-release ui-devel \ - ui-test ui-deps ui-test-ci VERSION docker-compose-sources + dev_build release_build sdist \ + ui-release ui-devel \ + VERSION docker-compose-sources clean-tmp: rm -rf tmp/ @@ -115,31 +114,7 @@ guard-%: exit 1; \ fi -virtualenv: virtualenv_ansible virtualenv_awx - -# virtualenv_* targets do not use --system-site-packages to prevent bugs installing packages -# but Ansible venvs are expected to have this, so that must be done after venv creation -virtualenv_ansible: - if [ "$(VENV_BASE)" ]; then \ - if [ ! -d "$(VENV_BASE)" ]; then \ - mkdir $(VENV_BASE); \ - fi; \ - if [ ! -d "$(VENV_BASE)/ansible" ]; then \ - virtualenv -p python $(VENV_BASE)/ansible && \ - $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP); \ - fi; \ - fi - -virtualenv_ansible_py3: - if [ "$(VENV_BASE)" ]; then \ - if [ ! -d "$(VENV_BASE)" ]; then \ - mkdir $(VENV_BASE); \ - fi; \ - if [ ! -d "$(VENV_BASE)/ansible" ]; then \ - virtualenv -p $(PYTHON) $(VENV_BASE)/ansible; \ - $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP); \ - fi; \ - fi +virtualenv: virtualenv_awx # flit is needed for offline install of certain packages, specifically ptyprocess # it is needed for setup, but not always recognized as a setup dependency @@ -155,32 +130,6 @@ virtualenv_awx: fi; \ fi -# --ignore-install flag is not used because *.txt files should specify exact versions -requirements_ansible: virtualenv_ansible - if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) -r /dev/stdin ; \ - else \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ - fi - $(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt - # Same effect as using --system-site-packages flag on venv creation - rm $(shell ls -d $(VENV_BASE)/ansible/lib/python* | head -n 1)/no-global-site-packages.txt - -requirements_ansible_py3: virtualenv_ansible_py3 - if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) -r /dev/stdin ; \ - else \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ - fi - $(VENV_BASE)/ansible/bin/pip3 uninstall --yes -r requirements/requirements_ansible_uninstall.txt - # Same effect as using --system-site-packages flag on venv creation - rm $(shell ls -d $(VENV_BASE)/ansible/lib/python* | head -n 1)/no-global-site-packages.txt - -requirements_ansible_dev: - if [ "$(VENV_BASE)" ]; then \ - $(VENV_BASE)/ansible/bin/pip install pytest mock; \ - fi - # Install third-party requirements needed for AWX's environment. # this does not use system site packages intentionally requirements_awx: virtualenv_awx @@ -194,17 +143,9 @@ requirements_awx: virtualenv_awx requirements_awx_dev: $(VENV_BASE)/awx/bin/pip install -r requirements/requirements_dev.txt -requirements_collections: - mkdir -p $(COLLECTION_BASE) - n=0; \ - until [ "$$n" -ge 5 ]; do \ - ansible-galaxy collection install -r requirements/collections_requirements.yml -p $(COLLECTION_BASE) && break; \ - n=$$((n+1)); \ - done +requirements: requirements_awx -requirements: requirements_ansible requirements_awx requirements_collections - -requirements_dev: requirements_awx requirements_ansible_py3 requirements_awx_dev requirements_ansible_dev +requirements_dev: requirements_awx requirements_awx_dev requirements_test: requirements @@ -383,7 +324,8 @@ test_collection: rm -f $(shell ls -d $(VENV_BASE)/awx/lib/python* | head -n 1)/no-global-site-packages.txt if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ - fi; \ + fi && \ + pip install ansible && \ py.test $(COLLECTION_TEST_DIRS) -v # The python path needs to be modified so that the tests can find Ansible within the container # First we will use anything expility set as PYTHONPATH @@ -457,7 +399,6 @@ clean-ui: rm -rf awx/ui_next/build rm -rf awx/ui_next/src/locales/_build rm -rf $(UI_BUILD_FLAG_FILE) - git checkout awx/ui_next/src/locales awx/ui_next/node_modules: $(NPM_BIN) --prefix awx/ui_next --loglevel warn --ignore-scripts install @@ -533,30 +474,29 @@ awx/projects: @mkdir -p $@ COMPOSE_UP_OPTS ?= +CLUSTER_NODE_COUNT ?= 1 docker-compose-sources: ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/sources.yml \ -e awx_image=$(DEV_DOCKER_TAG_BASE)/awx_devel \ - -e awx_image_tag=$(COMPOSE_TAG) + -e awx_image_tag=$(COMPOSE_TAG) \ + -e cluster_node_count=$(CLUSTER_NODE_COUNT) docker-compose: docker-auth awx/projects docker-compose-sources - docker-compose -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_UP_OPTS) up --no-recreate awx - -docker-compose-cluster: docker-auth awx/projects - docker-compose -f tools/docker-compose-cluster.yml up + docker-compose -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_UP_OPTS) up docker-compose-credential-plugins: docker-auth awx/projects docker-compose-sources echo -e "\033[0;31mTo generate a CyberArk Conjur API key: docker exec -it tools_conjur_1 conjurctl account create quick-start\033[0m" docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/docker-credential-plugins-override.yml up --no-recreate awx docker-compose-test: docker-auth awx/projects docker-compose-sources - docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx /bin/bash + docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx_1 /bin/bash docker-compose-runtest: awx/projects docker-compose-sources - docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx /start_tests.sh + docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx_1 /start_tests.sh docker-compose-build-swagger: awx/projects docker-compose-sources - docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports --no-deps awx /start_tests.sh swagger + docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports --no-deps awx_1 /start_tests.sh swagger detect-schema-change: genschema curl https://s3.amazonaws.com/awx-public-ci-files/schema.json -o reference-schema.json diff --git a/README.md b/README.md index 45bec0d81c..da811aa676 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Gated by Zuul](https://zuul-ci.org/gated.svg)](https://ansible.softwarefactory-project.io/zuul/status) +[![Gated by Zuul](https://zuul-ci.org/gated.svg)](https://ansible.softwarefactory-project.io/zuul/status) [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-Ansible-yellow.svg)](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) [![Apache v2 License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](https://github.com/ansible/awx/blob/devel/LICENSE.md) [![AWX Mailing List](https://img.shields.io/badge/mailing%20list-AWX-orange.svg)](https://groups.google.com/g/awx-project) +[![IRC Chat](https://img.shields.io/badge/IRC-%23ansible--awx-blueviolet.svg)](https://webchat.freenode.net/#ansible-awx) AWX @@ -38,8 +39,3 @@ We welcome your feedback and ideas. Here's how to reach us with feedback and que - Join the `#ansible-awx` channel on irc.freenode.net - Join the [mailing list](https://groups.google.com/forum/#!forum/awx-project) - -License -------- - -[Apache v2](./LICENSE.md) diff --git a/awx/api/metadata.py b/awx/api/metadata.py index 0b60f9a1ef..dedeeba8fb 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -24,7 +24,7 @@ from rest_framework.request import clone_request from awx.api.fields import ChoiceNullField from awx.main.fields import JSONField, ImplicitRoleField from awx.main.models import NotificationTemplate -from awx.main.scheduler.kubernetes import PodManager +from awx.main.tasks import AWXReceptorJob class Metadata(metadata.SimpleMetadata): @@ -209,7 +209,7 @@ class Metadata(metadata.SimpleMetadata): continue if field == "pod_spec_override": - meta['default'] = PodManager().pod_definition + meta['default'] = AWXReceptorJob().pod_definition # Add type choices if available from the serializer. if field == 'type' and hasattr(serializer, 'get_type_choices'): diff --git a/awx/api/serializers.py b/awx/api/serializers.py index d34c0d924a..8246ad8f1d 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -50,7 +50,7 @@ from awx.main.constants import ( ) from awx.main.models import ( ActivityStream, AdHocCommand, AdHocCommandEvent, Credential, CredentialInputSource, - CredentialType, CustomInventoryScript, Group, Host, Instance, + CredentialType, CustomInventoryScript, ExecutionEnvironment, Group, Host, Instance, InstanceGroup, Inventory, InventorySource, InventoryUpdate, InventoryUpdateEvent, Job, JobEvent, JobHostSummary, JobLaunchConfig, JobNotificationMixin, JobTemplate, Label, Notification, NotificationTemplate, @@ -107,6 +107,8 @@ SUMMARIZABLE_FK_FIELDS = { 'insights_credential_id',), 'host': DEFAULT_SUMMARY_FIELDS, 'group': DEFAULT_SUMMARY_FIELDS, + 'default_environment': DEFAULT_SUMMARY_FIELDS + ('image',), + 'execution_environment': DEFAULT_SUMMARY_FIELDS + ('image',), 'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'), 'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'), 'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',), @@ -129,7 +131,7 @@ SUMMARIZABLE_FK_FIELDS = { 'source_script': DEFAULT_SUMMARY_FIELDS, 'role': ('id', 'role_field'), 'notification_template': DEFAULT_SUMMARY_FIELDS, - 'instance_group': ('id', 'name', 'controller_id', 'is_containerized'), + 'instance_group': ('id', 'name', 'controller_id', 'is_container_group'), 'insights_credential': DEFAULT_SUMMARY_FIELDS, 'source_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'credential_type_id'), 'target_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'credential_type_id'), @@ -647,7 +649,7 @@ class UnifiedJobTemplateSerializer(BaseSerializer): class Meta: model = UnifiedJobTemplate fields = ('*', 'last_job_run', 'last_job_failed', - 'next_job_run', 'status') + 'next_job_run', 'status', 'execution_environment') def get_related(self, obj): res = super(UnifiedJobTemplateSerializer, self).get_related(obj) @@ -657,6 +659,9 @@ class UnifiedJobTemplateSerializer(BaseSerializer): res['last_job'] = obj.last_job.get_absolute_url(request=self.context.get('request')) if obj.next_schedule: res['next_schedule'] = obj.next_schedule.get_absolute_url(request=self.context.get('request')) + if obj.execution_environment_id: + res['execution_environment'] = self.reverse('api:execution_environment_detail', + kwargs={'pk': obj.execution_environment_id}) return res def get_types(self): @@ -711,6 +716,7 @@ class UnifiedJobSerializer(BaseSerializer): class Meta: model = UnifiedJob fields = ('*', 'unified_job_template', 'launch_type', 'status', + 'execution_environment', 'failed', 'started', 'finished', 'canceled_on', 'elapsed', 'job_args', 'job_cwd', 'job_env', 'job_explanation', 'execution_node', 'controller_node', @@ -748,6 +754,9 @@ class UnifiedJobSerializer(BaseSerializer): res['stdout'] = self.reverse('api:ad_hoc_command_stdout', kwargs={'pk': obj.pk}) if obj.workflow_job_id: res['source_workflow_job'] = self.reverse('api:workflow_job_detail', kwargs={'pk': obj.workflow_job_id}) + if obj.execution_environment_id: + res['execution_environment'] = self.reverse('api:execution_environment_detail', + kwargs={'pk': obj.execution_environment_id}) return res def get_summary_fields(self, obj): @@ -1243,11 +1252,13 @@ class OrganizationSerializer(BaseSerializer): class Meta: model = Organization - fields = ('*', 'max_hosts', 'custom_virtualenv',) + fields = ('*', 'max_hosts', 'custom_virtualenv', 'default_environment',) + read_only_fields = ('*', 'custom_virtualenv',) def get_related(self, obj): res = super(OrganizationSerializer, self).get_related(obj) - res.update(dict( + res.update( + execution_environments = self.reverse('api:organization_execution_environments_list', kwargs={'pk': obj.pk}), projects = self.reverse('api:organization_projects_list', kwargs={'pk': obj.pk}), inventories = self.reverse('api:organization_inventories_list', kwargs={'pk': obj.pk}), job_templates = self.reverse('api:organization_job_templates_list', kwargs={'pk': obj.pk}), @@ -1267,7 +1278,10 @@ class OrganizationSerializer(BaseSerializer): access_list = self.reverse('api:organization_access_list', kwargs={'pk': obj.pk}), instance_groups = self.reverse('api:organization_instance_groups_list', kwargs={'pk': obj.pk}), galaxy_credentials = self.reverse('api:organization_galaxy_credentials_list', kwargs={'pk': obj.pk}), - )) + ) + if obj.default_environment: + res['default_environment'] = self.reverse('api:execution_environment_detail', + kwargs={'pk': obj.default_environment_id}) return res def get_summary_fields(self, obj): @@ -1347,6 +1361,29 @@ class ProjectOptionsSerializer(BaseSerializer): return super(ProjectOptionsSerializer, self).validate(attrs) +class ExecutionEnvironmentSerializer(BaseSerializer): + show_capabilities = ['edit', 'delete', 'copy'] + managed_by_tower = serializers.ReadOnlyField() + + class Meta: + model = ExecutionEnvironment + fields = ('*', 'organization', 'image', 'managed_by_tower', 'credential', 'pull') + + def get_related(self, obj): + res = super(ExecutionEnvironmentSerializer, self).get_related(obj) + res.update( + activity_stream=self.reverse('api:execution_environment_activity_stream_list', kwargs={'pk': obj.pk}), + unified_job_templates=self.reverse('api:execution_environment_job_template_list', kwargs={'pk': obj.pk}), + copy=self.reverse('api:execution_environment_copy', kwargs={'pk': obj.pk}), + ) + if obj.organization: + res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk}) + if obj.credential: + res['credential'] = self.reverse('api:credential_detail', + kwargs={'pk': obj.credential.pk}) + return res + + class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True) @@ -1360,9 +1397,10 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): class Meta: model = Project - fields = ('*', 'organization', 'scm_update_on_launch', - 'scm_update_cache_timeout', 'allow_override', 'custom_virtualenv',) + \ + fields = ('*', '-execution_environment', 'organization', 'scm_update_on_launch', + 'scm_update_cache_timeout', 'allow_override', 'custom_virtualenv', 'default_environment') + \ ('last_update_failed', 'last_updated') # Backwards compatibility + read_only_fields = ('*', 'custom_virtualenv',) def get_related(self, obj): res = super(ProjectSerializer, self).get_related(obj) @@ -1386,6 +1424,9 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): if obj.organization: res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk}) + if obj.default_environment: + res['default_environment'] = self.reverse('api:execution_environment_detail', + kwargs={'pk': obj.default_environment_id}) # Backwards compatibility. if obj.current_update: res['current_update'] = self.reverse('api:project_update_detail', @@ -1939,6 +1980,7 @@ class InventorySourceOptionsSerializer(BaseSerializer): fields = ('*', 'source', 'source_path', 'source_script', 'source_vars', 'credential', 'enabled_var', 'enabled_value', 'host_filter', 'overwrite', 'overwrite_vars', 'custom_virtualenv', 'timeout', 'verbosity') + read_only_fields = ('*', 'custom_virtualenv',) def get_related(self, obj): res = super(InventorySourceOptionsSerializer, self).get_related(obj) @@ -2924,6 +2966,7 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO 'become_enabled', 'diff_mode', 'allow_simultaneous', 'custom_virtualenv', 'job_slice_count', 'webhook_service', 'webhook_credential', ) + read_only_fields = ('*', 'custom_virtualenv',) def get_related(self, obj): res = super(JobTemplateSerializer, self).get_related(obj) @@ -4731,7 +4774,7 @@ class InstanceGroupSerializer(BaseSerializer): 'Isolated groups have a designated controller group.'), read_only=True ) - is_containerized = serializers.BooleanField( + is_container_group = serializers.BooleanField( help_text=_('Indicates whether instances in this group are containerized.' 'Containerized groups have a designated Openshift or Kubernetes cluster.'), read_only=True @@ -4761,7 +4804,7 @@ class InstanceGroupSerializer(BaseSerializer): fields = ("id", "type", "url", "related", "name", "created", "modified", "capacity", "committed_capacity", "consumed_capacity", "percent_capacity_remaining", "jobs_running", "jobs_total", - "instances", "controller", "is_controller", "is_isolated", "is_containerized", "credential", + "instances", "controller", "is_controller", "is_isolated", "is_container_group", "credential", "policy_instance_percentage", "policy_instance_minimum", "policy_instance_list", "pod_spec_override", "summary_fields") @@ -4786,17 +4829,17 @@ class InstanceGroupSerializer(BaseSerializer): raise serializers.ValidationError(_('Isolated instances may not be added or removed from instances groups via the API.')) if self.instance and self.instance.controller_id is not None: raise serializers.ValidationError(_('Isolated instance group membership may not be managed via the API.')) - if value and self.instance and self.instance.is_containerized: + if value and self.instance and self.instance.is_container_group: raise serializers.ValidationError(_('Containerized instances may not be managed via the API')) return value def validate_policy_instance_percentage(self, value): - if value and self.instance and self.instance.is_containerized: + if value and self.instance and self.instance.is_container_group: raise serializers.ValidationError(_('Containerized instances may not be managed via the API')) return value def validate_policy_instance_minimum(self, value): - if value and self.instance and self.instance.is_containerized: + if value and self.instance and self.instance.is_container_group: raise serializers.ValidationError(_('Containerized instances may not be managed via the API')) return value diff --git a/awx/api/urls/execution_environments.py b/awx/api/urls/execution_environments.py new file mode 100644 index 0000000000..99b9cb3ddc --- /dev/null +++ b/awx/api/urls/execution_environments.py @@ -0,0 +1,20 @@ +from django.conf.urls import url + +from awx.api.views import ( + ExecutionEnvironmentList, + ExecutionEnvironmentDetail, + ExecutionEnvironmentJobTemplateList, + ExecutionEnvironmentCopy, + ExecutionEnvironmentActivityStreamList, +) + + +urls = [ + url(r'^$', ExecutionEnvironmentList.as_view(), name='execution_environment_list'), + url(r'^(?P[0-9]+)/$', ExecutionEnvironmentDetail.as_view(), name='execution_environment_detail'), + url(r'^(?P[0-9]+)/unified_job_templates/$', ExecutionEnvironmentJobTemplateList.as_view(), name='execution_environment_job_template_list'), + url(r'^(?P[0-9]+)/copy/$', ExecutionEnvironmentCopy.as_view(), name='execution_environment_copy'), + url(r'^(?P[0-9]+)/activity_stream/$', ExecutionEnvironmentActivityStreamList.as_view(), name='execution_environment_activity_stream_list'), +] + +__all__ = ['urls'] diff --git a/awx/api/urls/organization.py b/awx/api/urls/organization.py index 12b2807905..9d8fecf4bc 100644 --- a/awx/api/urls/organization.py +++ b/awx/api/urls/organization.py @@ -9,6 +9,7 @@ from awx.api.views import ( OrganizationUsersList, OrganizationAdminsList, OrganizationInventoriesList, + OrganizationExecutionEnvironmentsList, OrganizationProjectsList, OrganizationJobTemplatesList, OrganizationWorkflowJobTemplatesList, @@ -34,6 +35,7 @@ urls = [ url(r'^(?P[0-9]+)/users/$', OrganizationUsersList.as_view(), name='organization_users_list'), url(r'^(?P[0-9]+)/admins/$', OrganizationAdminsList.as_view(), name='organization_admins_list'), url(r'^(?P[0-9]+)/inventories/$', OrganizationInventoriesList.as_view(), name='organization_inventories_list'), + url(r'^(?P[0-9]+)/execution_environments/$', OrganizationExecutionEnvironmentsList.as_view(), name='organization_execution_environments_list'), url(r'^(?P[0-9]+)/projects/$', OrganizationProjectsList.as_view(), name='organization_projects_list'), url(r'^(?P[0-9]+)/job_templates/$', OrganizationJobTemplatesList.as_view(), name='organization_job_templates_list'), url(r'^(?P[0-9]+)/workflow_job_templates/$', OrganizationWorkflowJobTemplatesList.as_view(), name='organization_workflow_job_templates_list'), diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index 636e68e4bd..2beeb47a47 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -42,6 +42,7 @@ from .user import urls as user_urls from .project import urls as project_urls from .project_update import urls as project_update_urls from .inventory import urls as inventory_urls +from .execution_environments import urls as execution_environment_urls from .team import urls as team_urls from .host import urls as host_urls from .group import urls as group_urls @@ -106,6 +107,7 @@ v2_urls = [ url(r'^schedules/', include(schedule_urls)), url(r'^organizations/', include(organization_urls)), url(r'^users/', include(user_urls)), + url(r'^execution_environments/', include(execution_environment_urls)), url(r'^projects/', include(project_urls)), url(r'^project_updates/', include(project_update_urls)), url(r'^teams/', include(team_urls)), diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 43e845af0c..2d29519de8 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -112,6 +112,7 @@ from awx.api.views.organization import ( # noqa OrganizationInventoriesList, OrganizationUsersList, OrganizationAdminsList, + OrganizationExecutionEnvironmentsList, OrganizationProjectsList, OrganizationJobTemplatesList, OrganizationWorkflowJobTemplatesList, @@ -396,7 +397,7 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP permission_classes = (InstanceGroupTowerPermission,) def update_raw_data(self, data): - if self.get_object().is_containerized: + if self.get_object().is_container_group: data.pop('policy_instance_percentage', None) data.pop('policy_instance_minimum', None) data.pop('policy_instance_list', None) @@ -685,6 +686,52 @@ class TeamAccessList(ResourceAccessList): parent_model = models.Team +class ExecutionEnvironmentList(ListCreateAPIView): + + always_allow_superuser = False + model = models.ExecutionEnvironment + serializer_class = serializers.ExecutionEnvironmentSerializer + swagger_topic = "Execution Environments" + + +class ExecutionEnvironmentDetail(RetrieveUpdateDestroyAPIView): + + always_allow_superuser = False + model = models.ExecutionEnvironment + serializer_class = serializers.ExecutionEnvironmentSerializer + swagger_topic = "Execution Environments" + + +class ExecutionEnvironmentJobTemplateList(SubListAPIView): + + model = models.UnifiedJobTemplate + serializer_class = serializers.UnifiedJobTemplateSerializer + parent_model = models.ExecutionEnvironment + relationship = 'unifiedjobtemplates' + + +class ExecutionEnvironmentCopy(CopyAPIView): + + model = models.ExecutionEnvironment + copy_return_serializer_class = serializers.ExecutionEnvironmentSerializer + + +class ExecutionEnvironmentActivityStreamList(SubListAPIView): + + model = models.ActivityStream + serializer_class = serializers.ActivityStreamSerializer + parent_model = models.ExecutionEnvironment + relationship = 'activitystream_set' + search_fields = ('changes',) + + def get_queryset(self): + parent = self.get_parent_object() + self.check_parent_access(parent) + + qs = self.request.user.get_queryset(self.model) + return qs.filter(execution_environment=parent) + + class ProjectList(ListCreateAPIView): model = models.Project diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index d03dfcc86f..b33259a8ad 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -15,6 +15,7 @@ from awx.main.models import ( Inventory, Host, Project, + ExecutionEnvironment, JobTemplate, WorkflowJobTemplate, Organization, @@ -45,6 +46,7 @@ from awx.api.serializers import ( RoleSerializer, NotificationTemplateSerializer, InstanceGroupSerializer, + ExecutionEnvironmentSerializer, ProjectSerializer, JobTemplateSerializer, WorkflowJobTemplateSerializer, CredentialSerializer ) @@ -141,6 +143,16 @@ class OrganizationProjectsList(SubListCreateAPIView): parent_key = 'organization' +class OrganizationExecutionEnvironmentsList(SubListCreateAttachDetachAPIView): + + model = ExecutionEnvironment + serializer_class = ExecutionEnvironmentSerializer + parent_model = Organization + relationship = 'executionenvironments' + parent_key = 'organization' + swagger_topic = "Execution Environments" + + class OrganizationJobTemplatesList(SubListCreateAPIView): model = JobTemplate diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 0f5e7e6cdd..d6fc20d105 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -100,6 +100,7 @@ class ApiVersionRootView(APIView): data['dashboard'] = reverse('api:dashboard_view', request=request) data['organizations'] = reverse('api:organization_list', request=request) data['users'] = reverse('api:user_list', request=request) + data['execution_environments'] = reverse('api:execution_environment_list', request=request) data['projects'] = reverse('api:project_list', request=request) data['project_updates'] = reverse('api:project_update_list', request=request) data['teams'] = reverse('api:team_list', request=request) diff --git a/awx/conf/fields.py b/awx/conf/fields.py index 7c9a94969d..e28a44aa32 100644 --- a/awx/conf/fields.py +++ b/awx/conf/fields.py @@ -14,6 +14,7 @@ from rest_framework.fields import ( # noqa BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField ) +from rest_framework.serializers import PrimaryKeyRelatedField # noqa logger = logging.getLogger('awx.conf.fields') diff --git a/awx/main/access.py b/awx/main/access.py index 89a6c0607d..d2a2aa9f3b 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -29,9 +29,9 @@ from awx.main.utils import ( ) from awx.main.models import ( ActivityStream, AdHocCommand, AdHocCommandEvent, Credential, CredentialType, - CredentialInputSource, CustomInventoryScript, Group, Host, Instance, InstanceGroup, - Inventory, InventorySource, InventoryUpdate, InventoryUpdateEvent, Job, JobEvent, - JobHostSummary, JobLaunchConfig, JobTemplate, Label, Notification, + CredentialInputSource, CustomInventoryScript, ExecutionEnvironment, Group, Host, Instance, + InstanceGroup, Inventory, InventorySource, InventoryUpdate, InventoryUpdateEvent, Job, + JobEvent, JobHostSummary, JobLaunchConfig, JobTemplate, Label, Notification, NotificationTemplate, Organization, Project, ProjectUpdate, ProjectUpdateEvent, Role, Schedule, SystemJob, SystemJobEvent, SystemJobTemplate, Team, UnifiedJob, UnifiedJobTemplate, WorkflowJob, @@ -1308,6 +1308,54 @@ class TeamAccess(BaseAccess): *args, **kwargs) +class ExecutionEnvironmentAccess(BaseAccess): + """ + I can see an execution environment when: + - I'm a superuser + - I'm a member of the same organization + - it is a global ExecutionEnvironment + I can create/change an execution environment when: + - I'm a superuser + - I'm an admin for the organization(s) + """ + + model = ExecutionEnvironment + select_related = ('organization',) + prefetch_related = ('organization__admin_role', 'organization__execution_environment_admin_role') + + def filtered_queryset(self): + return ExecutionEnvironment.objects.filter( + Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role')) | + Q(organization__isnull=True) + ).distinct() + + @check_superuser + def can_add(self, data): + if not data: # So the browseable API will work + return Organization.accessible_objects(self.user, 'execution_environment_admin_role').exists() + return self.check_related('organization', Organization, data, mandatory=True, + role_field='execution_environment_admin_role') + + def can_change(self, obj, data): + if obj.managed_by_tower: + raise PermissionDenied + if self.user.is_superuser: + return True + if obj and obj.organization_id is None: + raise PermissionDenied + if self.user not in obj.organization.execution_environment_admin_role: + raise PermissionDenied + if data and 'organization' in data: + new_org = get_object_from_data('organization', Organization, data, obj=obj) + if not new_org or self.user not in new_org.execution_environment_admin_role: + return False + return self.check_related('organization', Organization, data, obj=obj, mandatory=True, + role_field='execution_environment_admin_role') + + def can_delete(self, obj): + return self.can_change(obj, None) + + class ProjectAccess(NotificationAttachMixin, BaseAccess): ''' I can see projects when: diff --git a/awx/main/analytics/collectors.py b/awx/main/analytics/collectors.py index b0ac43cc65..89bc28ea56 100644 --- a/awx/main/analytics/collectors.py +++ b/awx/main/analytics/collectors.py @@ -311,7 +311,7 @@ def events_table(since, full_path, until, **kwargs): return _copy_table(table='events', query=events_query, path=full_path) -@register('unified_jobs_table', '1.1', format='csv', description=_('Data on jobs run'), expensive=True) +@register('unified_jobs_table', '1.2', format='csv', description=_('Data on jobs run'), expensive=True) def unified_jobs_table(since, full_path, until, **kwargs): unified_job_query = '''COPY (SELECT main_unifiedjob.id, main_unifiedjob.polymorphic_ctype_id, @@ -334,7 +334,8 @@ def unified_jobs_table(since, full_path, until, **kwargs): main_unifiedjob.finished, main_unifiedjob.elapsed, main_unifiedjob.job_explanation, - main_unifiedjob.instance_group_id + main_unifiedjob.instance_group_id, + main_unifiedjob.installed_collections FROM main_unifiedjob JOIN django_content_type ON main_unifiedjob.polymorphic_ctype_id = django_content_type.id LEFT JOIN main_job ON main_unifiedjob.id = main_job.unifiedjob_ptr_id diff --git a/awx/main/conf.py b/awx/main/conf.py index 6bf86db214..f46371e22b 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -10,6 +10,7 @@ from rest_framework.fields import FloatField # Tower from awx.conf import fields, register, register_validate +from awx.main.models import ExecutionEnvironment logger = logging.getLogger('awx.main.conf') @@ -176,6 +177,18 @@ register( read_only=True, ) +register( + 'DEFAULT_EXECUTION_ENVIRONMENT', + field_class=fields.PrimaryKeyRelatedField, + allow_null=True, + default=None, + queryset=ExecutionEnvironment.objects.all(), + label=_('Global default execution environment'), + help_text=_('.'), + category=_('System'), + category_slug='system', +) + register( 'CUSTOM_VENV_PATHS', field_class=fields.StringListPathField, diff --git a/awx/main/isolated/manager.py b/awx/main/isolated/manager.py index de4783e277..abcd41c5c1 100644 --- a/awx/main/isolated/manager.py +++ b/awx/main/isolated/manager.py @@ -6,7 +6,6 @@ import stat import tempfile import time import logging -import yaml import datetime from django.conf import settings @@ -32,7 +31,7 @@ def set_pythonpath(venv_libdir, env): class IsolatedManager(object): - def __init__(self, event_handler, canceled_callback=None, check_callback=None, pod_manager=None): + def __init__(self, event_handler, canceled_callback=None, check_callback=None): """ :param event_handler: a callable used to persist event data from isolated nodes :param canceled_callback: a callable - which returns `True` or `False` @@ -45,28 +44,12 @@ class IsolatedManager(object): self.started_at = None self.captured_command_artifact = False self.instance = None - self.pod_manager = pod_manager def build_inventory(self, hosts): - if self.instance and self.instance.is_containerized: - inventory = {'all': {'hosts': {}}} - fd, path = tempfile.mkstemp( - prefix='.kubeconfig', dir=self.private_data_dir - ) - with open(path, 'wb') as temp: - temp.write(yaml.dump(self.pod_manager.kube_config).encode()) - temp.flush() - os.chmod(temp.name, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - for host in hosts: - inventory['all']['hosts'][host] = { - "ansible_connection": "kubectl", - "ansible_kubectl_config": path, - } - else: - inventory = '\n'.join([ - '{} ansible_ssh_user={}'.format(host, settings.AWX_ISOLATED_USERNAME) - for host in hosts - ]) + inventory = '\n'.join([ + '{} ansible_ssh_user={}'.format(host, settings.AWX_ISOLATED_USERNAME) + for host in hosts + ]) return inventory diff --git a/awx/main/management/commands/create_preload_data.py b/awx/main/management/commands/create_preload_data.py index 9b1d131735..05ed18b96c 100644 --- a/awx/main/management/commands/create_preload_data.py +++ b/awx/main/management/commands/create_preload_data.py @@ -2,22 +2,22 @@ # All Rights Reserved from django.core.management.base import BaseCommand +from django.conf import settings from crum import impersonate -from awx.main.models import User, Organization, Project, Inventory, CredentialType, Credential, Host, JobTemplate +from awx.main.models import ( + User, Organization, Project, Inventory, CredentialType, + Credential, Host, JobTemplate, ExecutionEnvironment +) from awx.main.signals import disable_computed_fields class Command(BaseCommand): """Create preloaded data, intended for new installs """ - help = 'Creates a preload tower data iff there is none.' + help = 'Creates a preload tower data if there is none.' def handle(self, *args, **kwargs): - # Sanity check: Is there already an organization in the system? - if Organization.objects.count(): - print('An organization is already in the system, exiting.') - print('(changed: False)') - return + changed = False # Create a default organization as the first superuser found. try: @@ -26,44 +26,62 @@ class Command(BaseCommand): superuser = None with impersonate(superuser): with disable_computed_fields(): - o = Organization.objects.create(name='Default') - p = Project(name='Demo Project', - scm_type='git', - scm_url='https://github.com/ansible/ansible-tower-samples', - scm_update_on_launch=True, - scm_update_cache_timeout=0, - organization=o) - p.save(skip_update=True) - ssh_type = CredentialType.objects.filter(namespace='ssh').first() - c = Credential.objects.create(credential_type=ssh_type, - name='Demo Credential', - inputs={ - 'username': superuser.username - }, - created_by=superuser) - c.admin_role.members.add(superuser) - public_galaxy_credential = Credential( - name='Ansible Galaxy', - managed_by_tower=True, - credential_type=CredentialType.objects.get(kind='galaxy'), - inputs = { - 'url': 'https://galaxy.ansible.com/' - } - ) - public_galaxy_credential.save() - o.galaxy_credentials.add(public_galaxy_credential) - i = Inventory.objects.create(name='Demo Inventory', - organization=o, - created_by=superuser) - Host.objects.create(name='localhost', - inventory=i, - variables="ansible_connection: local\nansible_python_interpreter: '{{ ansible_playbook_python }}'", - created_by=superuser) - jt = JobTemplate.objects.create(name='Demo Job Template', - playbook='hello_world.yml', - project=p, - inventory=i) - jt.credentials.add(c) - print('Default organization added.') - print('Demo Credential, Inventory, and Job Template added.') - print('(changed: True)') + if not Organization.objects.exists(): + o = Organization.objects.create(name='Default') + + p = Project(name='Demo Project', + scm_type='git', + scm_url='https://github.com/ansible/ansible-tower-samples', + scm_update_on_launch=True, + scm_update_cache_timeout=0, + organization=o) + p.save(skip_update=True) + + ssh_type = CredentialType.objects.filter(namespace='ssh').first() + c = Credential.objects.create(credential_type=ssh_type, + name='Demo Credential', + inputs={ + 'username': superuser.username + }, + created_by=superuser) + + c.admin_role.members.add(superuser) + + public_galaxy_credential = Credential(name='Ansible Galaxy', + managed_by_tower=True, + credential_type=CredentialType.objects.get(kind='galaxy'), + inputs={'url': 'https://galaxy.ansible.com/'}) + public_galaxy_credential.save() + o.galaxy_credentials.add(public_galaxy_credential) + + i = Inventory.objects.create(name='Demo Inventory', + organization=o, + created_by=superuser) + + Host.objects.create(name='localhost', + inventory=i, + variables="ansible_connection: local\nansible_python_interpreter: '{{ ansible_playbook_python }}'", + created_by=superuser) + + jt = JobTemplate.objects.create(name='Demo Job Template', + playbook='hello_world.yml', + project=p, + inventory=i) + jt.credentials.add(c) + + print('Default organization added.') + print('Demo Credential, Inventory, and Job Template added.') + changed = True + + default_ee = settings.AWX_EXECUTION_ENVIRONMENT_DEFAULT_IMAGE + ee, created = ExecutionEnvironment.objects.get_or_create(name='Default EE', defaults={'image': default_ee, + 'managed_by_tower': True}) + + if created: + changed = True + print('Default Execution Environment registered.') + + if changed: + print('(changed: True)') + else: + print('(changed: False)') diff --git a/awx/main/managers.py b/awx/main/managers.py index ae93a552a0..1af57a9423 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -237,7 +237,7 @@ class InstanceGroupManager(models.Manager): elif t.status == 'running': # Subtract capacity from all groups that contain the instance if t.execution_node not in instance_ig_mapping: - if not t.is_containerized: + if not t.is_container_group_task: logger.warning('Detected %s running inside lost instance, ' 'may still be waiting for reaper.', t.log_format) if t.instance_group: diff --git a/awx/main/migrations/0124_execution_environments.py b/awx/main/migrations/0124_execution_environments.py new file mode 100644 index 0000000000..18aad9a174 --- /dev/null +++ b/awx/main/migrations/0124_execution_environments.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.11 on 2020-07-08 18:42 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.db.models.expressions +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('taggit', '0003_taggeditem_add_unique_index'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('main', '0123_drop_hg_support'), + ] + + operations = [ + migrations.CreateModel( + name='ExecutionEnvironment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=None, editable=False)), + ('modified', models.DateTimeField(default=None, editable=False)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(help_text='The registry location where the container is stored.', max_length=1024, verbose_name='image location')), + ('managed_by_tower', models.BooleanField(default=False, editable=False)), + ('created_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="{'class': 'executionenvironment', 'model_name': 'executionenvironment', 'app_label': 'main'}(class)s_created+", to=settings.AUTH_USER_MODEL)), + ('credential', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='executionenvironments', to='main.Credential')), + ('modified_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="{'class': 'executionenvironment', 'model_name': 'executionenvironment', 'app_label': 'main'}(class)s_modified+", to=settings.AUTH_USER_MODEL)), + ('organization', models.ForeignKey(blank=True, default=None, help_text='The organization used to determine access to this execution environment.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executionenvironments', to='main.Organization')), + ('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')), + ], + options={ + 'ordering': (django.db.models.expressions.OrderBy(django.db.models.expressions.F('organization_id'), nulls_first=True), 'image'), + 'unique_together': {('organization', 'image')}, + }, + ), + migrations.AddField( + model_name='activitystream', + name='execution_environment', + field=models.ManyToManyField(blank=True, to='main.ExecutionEnvironment'), + ), + migrations.AddField( + model_name='organization', + name='default_environment', + field=models.ForeignKey(blank=True, default=None, help_text='The default execution environment for jobs run by this organization.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='main.ExecutionEnvironment'), + ), + migrations.AddField( + model_name='unifiedjob', + name='execution_environment', + field=models.ForeignKey(blank=True, default=None, help_text='The container image to be used for execution.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unifiedjobs', to='main.ExecutionEnvironment'), + ), + migrations.AddField( + model_name='unifiedjobtemplate', + name='execution_environment', + field=models.ForeignKey(blank=True, default=None, help_text='The container image to be used for execution.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unifiedjobtemplates', to='main.ExecutionEnvironment'), + ), + ] diff --git a/awx/main/migrations/0125_more_ee_modeling_changes.py b/awx/main/migrations/0125_more_ee_modeling_changes.py new file mode 100644 index 0000000000..be999cbb79 --- /dev/null +++ b/awx/main/migrations/0125_more_ee_modeling_changes.py @@ -0,0 +1,46 @@ +# Generated by Django 2.2.16 on 2020-11-19 16:20 +import uuid + +import awx.main.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0124_execution_environments'), + ] + + operations = [ + migrations.AlterModelOptions( + name='executionenvironment', + options={'ordering': ('-created',)}, + ), + migrations.AddField( + model_name='executionenvironment', + name='name', + field=models.CharField(default=uuid.uuid4, max_length=512, unique=True), + preserve_default=False, + ), + migrations.AddField( + model_name='organization', + name='execution_environment_admin_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role'), + preserve_default='True', + ), + migrations.AddField( + model_name='project', + name='default_environment', + field=models.ForeignKey(blank=True, default=None, help_text='The default execution environment for jobs run using this project.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='main.ExecutionEnvironment'), + ), + migrations.AlterField( + model_name='credentialtype', + name='kind', + field=models.CharField(choices=[('ssh', 'Machine'), ('vault', 'Vault'), ('net', 'Network'), ('scm', 'Source Control'), ('cloud', 'Cloud'), ('registry', 'Container Registry'), ('token', 'Personal Access Token'), ('insights', 'Insights'), ('external', 'External'), ('kubernetes', 'Kubernetes'), ('galaxy', 'Galaxy/Automation Hub')], max_length=32), + ), + migrations.AlterUniqueTogether( + name='executionenvironment', + unique_together=set(), + ), + ] diff --git a/awx/main/migrations/0126_executionenvironment_container_options.py b/awx/main/migrations/0126_executionenvironment_container_options.py new file mode 100644 index 0000000000..d26fcb9298 --- /dev/null +++ b/awx/main/migrations/0126_executionenvironment_container_options.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2021-01-27 22:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0125_more_ee_modeling_changes'), + ] + + operations = [ + migrations.AddField( + model_name='executionenvironment', + name='pull', + field=models.CharField(choices=[('always', 'Always pull container before running.'), ('missing', 'No pull option has been selected.'), ('never', 'Never pull container before running.')], blank=True, default='', help_text='Pull image before running?', max_length=16), + ), + ] diff --git a/awx/main/migrations/0127_reset_pod_spec_override.py b/awx/main/migrations/0127_reset_pod_spec_override.py new file mode 100644 index 0000000000..c3ebe0b504 --- /dev/null +++ b/awx/main/migrations/0127_reset_pod_spec_override.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2021-02-15 22:02 + +from django.db import migrations + +def reset_pod_specs(apps, schema_editor): + InstanceGroup = apps.get_model('main', 'InstanceGroup') + InstanceGroup.objects.update(pod_spec_override="") + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0126_executionenvironment_container_options'), + ] + + operations = [ + migrations.RunPython(reset_pod_specs) + ] diff --git a/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py b/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py new file mode 100644 index 0000000000..f03a4e0ba2 --- /dev/null +++ b/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.16 on 2021-02-18 22:57 + +import awx.main.fields +from django.db import migrations +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0127_reset_pod_spec_override'), + ] + + operations = [ + migrations.AlterField( + model_name='organization', + name='read_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['member_role', 'auditor_role', 'execute_role', 'project_admin_role', 'inventory_admin_role', 'workflow_admin_role', 'notification_admin_role', 'credential_admin_role', 'job_template_admin_role', 'approval_role', 'execution_environment_admin_role'], related_name='+', to='main.Role'), + ), + ] diff --git a/awx/main/migrations/0129_unifiedjob_installed_collections.py b/awx/main/migrations/0129_unifiedjob_installed_collections.py new file mode 100644 index 0000000000..897708a631 --- /dev/null +++ b/awx/main/migrations/0129_unifiedjob_installed_collections.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.16 on 2021-02-16 20:27 + +import awx.main.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0128_organiaztion_read_roles_ee_admin'), + ] + + operations = [ + migrations.AddField( + model_name='unifiedjob', + name='installed_collections', + field=awx.main.fields.JSONBField(blank=True, default=dict, editable=False, help_text='The Collections names and versions installed in the execution environment.'), + ), + ] diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 87fa5d791f..52cabf3774 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -35,6 +35,7 @@ from awx.main.models.events import ( # noqa ) from awx.main.models.ad_hoc_commands import AdHocCommand # noqa from awx.main.models.schedules import Schedule # noqa +from awx.main.models.execution_environments import ExecutionEnvironment # noqa from awx.main.models.activity_stream import ActivityStream # noqa from awx.main.models.ha import ( # noqa Instance, InstanceGroup, TowerScheduleState, @@ -45,7 +46,7 @@ from awx.main.models.rbac import ( # noqa ROLE_SINGLETON_SYSTEM_AUDITOR, ) from awx.main.models.mixins import ( # noqa - CustomVirtualEnvMixin, ResourceMixin, SurveyJobMixin, + CustomVirtualEnvMixin, ExecutionEnvironmentMixin, ResourceMixin, SurveyJobMixin, SurveyJobTemplateMixin, TaskManagerInventoryUpdateMixin, TaskManagerJobMixin, TaskManagerProjectUpdateMixin, TaskManagerUnifiedJobMixin, @@ -221,6 +222,7 @@ activity_stream_registrar.connect(CredentialType) activity_stream_registrar.connect(Team) activity_stream_registrar.connect(Project) #activity_stream_registrar.connect(ProjectUpdate) +activity_stream_registrar.connect(ExecutionEnvironment) activity_stream_registrar.connect(JobTemplate) activity_stream_registrar.connect(Job) activity_stream_registrar.connect(AdHocCommand) diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index 85666e49d2..1c344692d6 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -61,6 +61,7 @@ class ActivityStream(models.Model): team = models.ManyToManyField("Team", blank=True) project = models.ManyToManyField("Project", blank=True) project_update = models.ManyToManyField("ProjectUpdate", blank=True) + execution_environment = models.ManyToManyField("ExecutionEnvironment", blank=True) job_template = models.ManyToManyField("JobTemplate", blank=True) job = models.ManyToManyField("Job", blank=True) workflow_job_template_node = models.ManyToManyField("WorkflowJobTemplateNode", blank=True) @@ -74,6 +75,7 @@ class ActivityStream(models.Model): ad_hoc_command = models.ManyToManyField("AdHocCommand", blank=True) schedule = models.ManyToManyField("Schedule", blank=True) custom_inventory_script = models.ManyToManyField("CustomInventoryScript", blank=True) + execution_environment = models.ManyToManyField("ExecutionEnvironment", blank=True) notification_template = models.ManyToManyField("NotificationTemplate", blank=True) notification = models.ManyToManyField("Notification", blank=True) label = models.ManyToManyField("Label", blank=True) diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py index 9787f01423..f327e2a7e6 100644 --- a/awx/main/models/ad_hoc_commands.py +++ b/awx/main/models/ad_hoc_commands.py @@ -151,8 +151,8 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin): return True @property - def is_containerized(self): - return bool(self.instance_group and self.instance_group.is_containerized) + def is_container_group_task(self): + return bool(self.instance_group and self.instance_group.is_container_group) @property def can_run_containerized(self): @@ -198,8 +198,8 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin): def copy(self): data = {} for field in ('job_type', 'inventory_id', 'limit', 'credential_id', - 'module_name', 'module_args', 'forks', 'verbosity', - 'extra_vars', 'become_enabled', 'diff_mode'): + 'execution_environment_id', 'module_name', 'module_args', + 'forks', 'verbosity', 'extra_vars', 'become_enabled', 'diff_mode'): data[field] = getattr(self, field) return AdHocCommand.objects.create(**data) @@ -209,6 +209,9 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin): self.name = Truncator(u': '.join(filter(None, (self.module_name, self.module_args)))).chars(512) if 'name' not in update_fields: update_fields.append('name') + if not self.execution_environment_id: + self.execution_environment = self.resolve_execution_environment() + update_fields.append('execution_environment') super(AdHocCommand, self).save(*args, **kwargs) @property diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index e8a2884083..7cdd9898d3 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -331,6 +331,7 @@ class CredentialType(CommonModelNameNotUnique): ('net', _('Network')), ('scm', _('Source Control')), ('cloud', _('Cloud')), + ('registry', _('Container Registry')), ('token', _('Personal Access Token')), ('insights', _('Insights')), ('external', _('External')), @@ -528,15 +529,20 @@ class CredentialType(CommonModelNameNotUnique): with open(path, 'w') as f: f.write(data) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) + # FIXME: develop some better means of referencing paths inside containers + container_path = os.path.join( + '/runner', + os.path.basename(path) + ) # determine if filename indicates single file or many if file_label.find('.') == -1: - tower_namespace.filename = path + tower_namespace.filename = container_path else: if not hasattr(tower_namespace, 'filename'): tower_namespace.filename = TowerNamespace() file_label = file_label.split('.')[1] - setattr(tower_namespace.filename, file_label, path) + setattr(tower_namespace.filename, file_label, container_path) injector_field = self._meta.get_field('injectors') for env_var, tmpl in self.injectors.get('env', {}).items(): @@ -564,7 +570,12 @@ class CredentialType(CommonModelNameNotUnique): if extra_vars: path = build_extra_vars_file(extra_vars, private_data_dir) - args.extend(['-e', '@%s' % path]) + # FIXME: develop some better means of referencing paths inside containers + container_path = os.path.join( + '/runner', + os.path.basename(path) + ) + args.extend(['-e', '@%s' % container_path]) class ManagedCredentialType(SimpleNamespace): @@ -1123,7 +1134,6 @@ ManagedCredentialType( }, ) - ManagedCredentialType( namespace='kubernetes_bearer_token', kind='kubernetes', @@ -1155,6 +1165,37 @@ ManagedCredentialType( } ) +ManagedCredentialType( + namespace='registry', + kind='registry', + name=ugettext_noop('Container Registry'), + inputs={ + 'fields': [{ + 'id': 'host', + 'label': ugettext_noop('Authentication URL'), + 'type': 'string', + 'help_text': ugettext_noop('Authentication endpoint for the container registry.'), + }, { + 'id': 'username', + 'label': ugettext_noop('Username'), + 'type': 'string', + }, { + 'id': 'password', + 'label': ugettext_noop('Password'), + 'type': 'string', + 'secret': True, + }, { + 'id': 'token', + 'label': ugettext_noop('Access Token'), + 'type': 'string', + 'secret': True, + 'help_text': ugettext_noop('A token to use to authenticate with. ' + 'This should not be set if username/password are being used.'), + }], + 'required': ['host'], + } +) + ManagedCredentialType( namespace='galaxy_api_token', diff --git a/awx/main/models/credential/injectors.py b/awx/main/models/credential/injectors.py index 90615f2d66..75a08482cc 100644 --- a/awx/main/models/credential/injectors.py +++ b/awx/main/models/credential/injectors.py @@ -35,8 +35,8 @@ def gce(cred, env, private_data_dir): json.dump(json_cred, f, indent=2) f.close() os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) - env['GCE_CREDENTIALS_FILE_PATH'] = path - env['GCP_SERVICE_ACCOUNT_FILE'] = path + env['GCE_CREDENTIALS_FILE_PATH'] = os.path.join('/runner', os.path.basename(path)) + env['GCP_SERVICE_ACCOUNT_FILE'] = os.path.join('/runner', os.path.basename(path)) # Handle env variables for new module types. # This includes gcp_compute inventory plugin and @@ -105,7 +105,8 @@ def openstack(cred, env, private_data_dir): yaml.safe_dump(openstack_data, f, default_flow_style=False, allow_unicode=True) f.close() os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) - env['OS_CLIENT_CONFIG_FILE'] = path + # TODO: constant for container base path + env['OS_CLIENT_CONFIG_FILE'] = os.path.join('/runner', os.path.basename(path)) def kubernetes_bearer_token(cred, env, private_data_dir): diff --git a/awx/main/models/execution_environments.py b/awx/main/models/execution_environments.py new file mode 100644 index 0000000000..eabd0cce7c --- /dev/null +++ b/awx/main/models/execution_environments.py @@ -0,0 +1,53 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from awx.api.versioning import reverse +from awx.main.models.base import CommonModel + + +__all__ = ['ExecutionEnvironment'] + + +class ExecutionEnvironment(CommonModel): + class Meta: + ordering = ('-created',) + + PULL_CHOICES = [ + ('always', _("Always pull container before running.")), + ('missing', _("No pull option has been selected.")), + ('never', _("Never pull container before running.")) + ] + + organization = models.ForeignKey( + 'Organization', + null=True, + default=None, + blank=True, + on_delete=models.CASCADE, + related_name='%(class)ss', + help_text=_('The organization used to determine access to this execution environment.'), + ) + image = models.CharField( + max_length=1024, + verbose_name=_('image location'), + help_text=_("The registry location where the container is stored."), + ) + managed_by_tower = models.BooleanField(default=False, editable=False) + credential = models.ForeignKey( + 'Credential', + related_name='%(class)ss', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) + pull = models.CharField( + max_length=16, + choices=PULL_CHOICES, + blank=True, + default='', + help_text=_('Pull image before running?'), + ) + + def get_absolute_url(self, request=None): + return reverse('api:execution_environment_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 5071786653..94d4b8d462 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -147,6 +147,13 @@ class Instance(HasPolicyEditsMixin, BaseModel): return self.rampart_groups.filter(controller__isnull=False).exists() def refresh_capacity(self): + if settings.IS_K8S: + self.capacity = self.cpu = self.memory = self.cpu_capacity = self.mem_capacity = 0 # noqa + self.version = awx_application_version + self.save(update_fields=['capacity', 'version', 'modified', 'cpu', + 'memory', 'cpu_capacity', 'mem_capacity']) + return + cpu = get_cpu_capacity() mem = get_mem_capacity() if self.enabled: @@ -247,7 +254,10 @@ class InstanceGroup(HasPolicyEditsMixin, BaseModel, RelatedJobsMixin): return bool(self.controller) @property - def is_containerized(self): + def is_container_group(self): + if settings.IS_K8S: + return True + return bool(self.credential and self.credential.kubernetes) ''' @@ -306,9 +316,9 @@ def schedule_policy_task(): @receiver(post_save, sender=InstanceGroup) def on_instance_group_saved(sender, instance, created=False, raw=False, **kwargs): if created or instance.has_policy_changes(): - if not instance.is_containerized: + if not instance.is_container_group: schedule_policy_task() - elif created or instance.is_containerized: + elif created or instance.is_container_group: instance.set_default_policy_fields() @@ -320,7 +330,7 @@ def on_instance_saved(sender, instance, created=False, raw=False, **kwargs): @receiver(post_delete, sender=InstanceGroup) def on_instance_group_deleted(sender, instance, using, **kwargs): - if not instance.is_containerized: + if not instance.is_container_group: schedule_policy_task() diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 5305e6e532..9000fe41c4 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1373,6 +1373,7 @@ class PluginFileInjector(object): collection = None collection_migration = '2.9' # Starting with this version, we use collections + # TODO: delete this method and update unit tests @classmethod def get_proper_name(cls): if cls.plugin_name is None: @@ -1397,13 +1398,12 @@ class PluginFileInjector(object): def inventory_as_dict(self, inventory_update, private_data_dir): source_vars = dict(inventory_update.source_vars_dict) # make a copy - proper_name = self.get_proper_name() ''' None conveys that we should use the user-provided plugin. Note that a plugin value of '' should still be overridden. ''' - if proper_name is not None: - source_vars['plugin'] = proper_name + if self.plugin_name is not None: + source_vars['plugin'] = self.plugin_name return source_vars def build_env(self, inventory_update, env, private_data_dir, private_data_files): @@ -1441,7 +1441,6 @@ class PluginFileInjector(object): def get_plugin_env(self, inventory_update, private_data_dir, private_data_files): env = self._get_shared_env(inventory_update, private_data_dir, private_data_files) - env['ANSIBLE_COLLECTIONS_PATHS'] = settings.AWX_ANSIBLE_COLLECTIONS_PATHS return env def build_private_data(self, inventory_update, private_data_dir): @@ -1544,7 +1543,7 @@ class openstack(PluginFileInjector): env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files) credential = inventory_update.get_cloud_credential() cred_data = private_data_files['credentials'] - env['OS_CLIENT_CONFIG_FILE'] = cred_data[credential] + env['OS_CLIENT_CONFIG_FILE'] = os.path.join('/runner', os.path.basename(cred_data[credential])) return env @@ -1574,6 +1573,12 @@ class satellite6(PluginFileInjector): ret['FOREMAN_PASSWORD'] = credential.get_input('password', default='') return ret + def inventory_as_dict(self, inventory_update, private_data_dir): + ret = super(satellite6, self).inventory_as_dict(inventory_update, private_data_dir) + # this inventory plugin requires the fully qualified inventory plugin name + ret['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}' + return ret + class tower(PluginFileInjector): plugin_name = 'tower' diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 638954e53c..70cdfa363a 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -284,7 +284,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour def _get_unified_job_field_names(cls): return set(f.name for f in JobOptions._meta.fields) | set( ['name', 'description', 'organization', 'survey_passwords', 'labels', 'credentials', - 'job_slice_number', 'job_slice_count'] + 'job_slice_number', 'job_slice_count', 'execution_environment'] ) @property @@ -768,11 +768,11 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana @property def can_run_containerized(self): - return any([ig for ig in self.preferred_instance_groups if ig.is_containerized]) + return any([ig for ig in self.preferred_instance_groups if ig.is_container_group]) @property - def is_containerized(self): - return bool(self.instance_group and self.instance_group.is_containerized) + def is_container_group_task(self): + return bool(self.instance_group and self.instance_group.is_container_group) @property def preferred_instance_groups(self): @@ -1286,6 +1286,8 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin): @property def task_impact(self): + if settings.IS_K8S: + return 0 return 5 @property diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index ce6d3717a7..549c93607d 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -34,7 +34,7 @@ logger = logging.getLogger('awx.main.models.mixins') __all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin', 'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin', - 'TaskManagerInventoryUpdateMixin', 'CustomVirtualEnvMixin'] + 'TaskManagerInventoryUpdateMixin', 'ExecutionEnvironmentMixin', 'CustomVirtualEnvMixin'] class ResourceMixin(models.Model): @@ -441,6 +441,44 @@ class TaskManagerInventoryUpdateMixin(TaskManagerUpdateOnLaunchMixin): abstract = True +class ExecutionEnvironmentMixin(models.Model): + class Meta: + abstract = True + + execution_environment = models.ForeignKey( + 'ExecutionEnvironment', + null=True, + blank=True, + default=None, + on_delete=models.SET_NULL, + related_name='%(class)ss', + help_text=_('The container image to be used for execution.'), + ) + + def get_execution_environment_default(self): + from awx.main.models.execution_environments import ExecutionEnvironment + + if settings.DEFAULT_EXECUTION_ENVIRONMENT is not None: + return settings.DEFAULT_EXECUTION_ENVIRONMENT + return ExecutionEnvironment.objects.filter(organization=None, managed_by_tower=True).first() + + def resolve_execution_environment(self): + """ + Return the execution environment that should be used when creating a new job. + """ + if self.execution_environment is not None: + return self.execution_environment + if getattr(self, 'project_id', None) and self.project.default_environment is not None: + return self.project.default_environment + if getattr(self, 'organization', None) and self.organization.default_environment is not None: + return self.organization.default_environment + if getattr(self, 'inventory', None) and self.inventory.organization is not None: + if self.inventory.organization.default_environment is not None: + return self.inventory.organization.default_environment + + return self.get_execution_environment_default() + + class CustomVirtualEnvMixin(models.Model): class Meta: abstract = True diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index bf2e07d255..f0ecfea5c7 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -61,6 +61,15 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi blank=True, related_name='%(class)s_notification_templates_for_approvals' ) + default_environment = models.ForeignKey( + 'ExecutionEnvironment', + null=True, + blank=True, + default=None, + on_delete=models.SET_NULL, + related_name='+', + help_text=_('The default execution environment for jobs run by this organization.'), + ) admin_role = ImplicitRoleField( parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, @@ -86,6 +95,9 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi job_template_admin_role = ImplicitRoleField( parent_role='admin_role', ) + execution_environment_admin_role = ImplicitRoleField( + parent_role='admin_role', + ) auditor_role = ImplicitRoleField( parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ) @@ -97,7 +109,8 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi 'execute_role', 'project_admin_role', 'inventory_admin_role', 'workflow_admin_role', 'notification_admin_role', 'credential_admin_role', - 'job_template_admin_role', 'approval_role',], + 'job_template_admin_role', 'approval_role', + 'execution_environment_admin_role',], ) approval_role = ImplicitRoleField( parent_role='admin_role', diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 65fb8304ce..c9bf87f408 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -187,6 +187,14 @@ class ProjectOptions(models.Model): pass return cred + def resolve_execution_environment(self): + """ + Project updates, themselves, will use the default execution environment. + Jobs using the project can use the default_environment, but the project updates + are not flexible enough to allow customizing the image they use. + """ + return self.get_execution_environment_default() + def get_project_path(self, check_if_exists=True): local_path = os.path.basename(self.local_path) if local_path and not local_path.startswith('.'): @@ -259,6 +267,15 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEn app_label = 'main' ordering = ('id',) + default_environment = models.ForeignKey( + 'ExecutionEnvironment', + null=True, + blank=True, + default=None, + on_delete=models.SET_NULL, + related_name='+', + help_text=_('The default execution environment for jobs run using this project.'), + ) scm_update_on_launch = models.BooleanField( default=False, help_text=_('Update the project when a job is launched that uses the project.'), @@ -554,6 +571,8 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage @property def task_impact(self): + if settings.IS_K8S: + return 0 return 0 if self.job_type == 'run' else 1 @property diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 67d21e873d..fe8d622ac6 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -40,6 +40,7 @@ role_names = { 'inventory_admin_role': _('Inventory Admin'), 'credential_admin_role': _('Credential Admin'), 'job_template_admin_role': _('Job Template Admin'), + 'execution_environment_admin_role': _('Execution Environment Admin'), 'workflow_admin_role': _('Workflow Admin'), 'notification_admin_role': _('Notification Admin'), 'auditor_role': _('Auditor'), @@ -60,6 +61,7 @@ role_descriptions = { 'inventory_admin_role': _('Can manage all inventories of the %s'), 'credential_admin_role': _('Can manage all credentials of the %s'), 'job_template_admin_role': _('Can manage all job templates of the %s'), + 'execution_environment_admin_role': _('Can manage all execution environments of the %s'), 'workflow_admin_role': _('Can manage all workflows of the %s'), 'notification_admin_role': _('Can manage all notifications of the %s'), 'auditor_role': _('Can view all aspects of the %s'), diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 064585c6c1..45d7739ee3 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -39,7 +39,7 @@ from awx.main.models.base import ( from awx.main.dispatch import get_local_queuename from awx.main.dispatch.control import Control as ControlDispatcher from awx.main.registrar import activity_stream_registrar -from awx.main.models.mixins import ResourceMixin, TaskManagerUnifiedJobMixin +from awx.main.models.mixins import ResourceMixin, TaskManagerUnifiedJobMixin, ExecutionEnvironmentMixin from awx.main.utils import ( camelcase_to_underscore, get_model_for_type, encrypt_dict, decrypt_field, _inventory_updates, @@ -50,7 +50,7 @@ from awx.main.utils import ( from awx.main.constants import ACTIVE_STATES, CAN_CANCEL from awx.main.redact import UriCleaner, REPLACE_STR from awx.main.consumers import emit_channel_notification -from awx.main.fields import JSONField, AskForField, OrderedManyToManyField +from awx.main.fields import JSONField, JSONBField, AskForField, OrderedManyToManyField __all__ = ['UnifiedJobTemplate', 'UnifiedJob', 'StdoutMaxBytesExceeded'] @@ -59,7 +59,7 @@ logger_job_lifecycle = logging.getLogger('awx.analytics.job_lifecycle') # NOTE: ACTIVE_STATES moved to constants because it is used by parent modules -class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, NotificationFieldsModel): +class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, ExecutionEnvironmentMixin, NotificationFieldsModel): ''' Concrete base class for unified job templates. ''' @@ -376,6 +376,8 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio for fd, val in eager_fields.items(): setattr(unified_job, fd, val) + unified_job.execution_environment = self.resolve_execution_environment() + # NOTE: slice workflow jobs _get_parent_field_name method # is not correct until this is set if not parent_field_name: @@ -527,7 +529,7 @@ class StdoutMaxBytesExceeded(Exception): class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, - UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin): + UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin, ExecutionEnvironmentMixin): ''' Concrete base class for unified job run by the task engine. ''' @@ -720,6 +722,12 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique 'Credential', related_name='%(class)ss', ) + installed_collections = JSONBField( + blank=True, + default=dict, + editable=False, + help_text=_("The Collections names and versions installed in the execution environment."), + ) def get_absolute_url(self, request=None): RealClass = self.get_real_instance_class() @@ -1488,7 +1496,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique return bool(self.controller_node) @property - def is_containerized(self): + def is_container_group_task(self): return False def log_lifecycle(self, state, blocked_by=None): diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index f06f93834a..8d8df5eee2 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -70,7 +70,7 @@ class TaskManager(): ''' Init AFTER we know this instance of the task manager will run because the lock is acquired. ''' - instances = Instance.objects.filter(~Q(hostname=None), capacity__gt=0, enabled=True) + instances = Instance.objects.filter(~Q(hostname=None), enabled=True) self.real_instances = {i.hostname: i for i in instances} instances_partial = [SimpleNamespace(obj=instance, @@ -86,7 +86,7 @@ class TaskManager(): capacity_total=rampart_group.capacity, consumed_capacity=0, instances=[]) - for instance in rampart_group.instances.filter(capacity__gt=0, enabled=True).order_by('hostname'): + for instance in rampart_group.instances.filter(enabled=True).order_by('hostname'): if instance.hostname in instances_by_hostname: self.graph[rampart_group.name]['instances'].append(instances_by_hostname[instance.hostname]) @@ -283,12 +283,12 @@ class TaskManager(): task.controller_node = controller_node logger.debug('Submitting isolated {} to queue {} controlled by {}.'.format( task.log_format, task.execution_node, controller_node)) - elif rampart_group.is_containerized: + elif rampart_group.is_container_group: # find one real, non-containerized instance with capacity to # act as the controller for k8s API interaction match = None for group in InstanceGroup.objects.all(): - if group.is_containerized or group.controller_id: + if group.is_container_group or group.controller_id: continue match = group.fit_task_to_most_remaining_capacity_instance(task, group.instances.all()) if match: @@ -521,14 +521,17 @@ class TaskManager(): self.start_task(task, None, task.get_jobs_fail_chain(), None) continue for rampart_group in preferred_instance_groups: - if task.can_run_containerized and rampart_group.is_containerized: + if task.can_run_containerized and rampart_group.is_container_group: self.graph[rampart_group.name]['graph'].add_job(task) self.start_task(task, rampart_group, task.get_jobs_fail_chain(), None) found_acceptable_queue = True break remaining_capacity = self.get_remaining_capacity(rampart_group.name) - if not rampart_group.is_containerized and self.get_remaining_capacity(rampart_group.name) <= 0: + if ( + task.task_impact > 0 and # project updates have a cost of zero + not rampart_group.is_container_group and + self.get_remaining_capacity(rampart_group.name) <= 0): logger.debug("Skipping group {}, remaining_capacity {} <= 0".format( rampart_group.name, remaining_capacity)) continue @@ -536,8 +539,8 @@ class TaskManager(): execution_instance = InstanceGroup.fit_task_to_most_remaining_capacity_instance(task, self.graph[rampart_group.name]['instances']) or \ InstanceGroup.find_largest_idle_instance(self.graph[rampart_group.name]['instances']) - if execution_instance or rampart_group.is_containerized: - if not rampart_group.is_containerized: + if execution_instance or rampart_group.is_container_group: + if not rampart_group.is_container_group: execution_instance.remaining_capacity = max(0, execution_instance.remaining_capacity - task.task_impact) execution_instance.jobs_running += 1 logger.debug("Starting {} in group {} instance {} (remaining_capacity={})".format( @@ -594,7 +597,7 @@ class TaskManager(): ).exclude( execution_node__in=Instance.objects.values_list('hostname', flat=True) ): - if j.execution_node and not j.is_containerized: + if j.execution_node and not j.is_container_group_task: logger.error(f'{j.execution_node} is not a registered instance; reaping {j.log_format}') reap_job(j, 'failed') diff --git a/awx/main/signals.py b/awx/main/signals.py index 0a29fa9d6c..ac7a3d2301 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -368,6 +368,7 @@ def model_serializer_mapping(): models.Credential: serializers.CredentialSerializer, models.Team: serializers.TeamSerializer, models.Project: serializers.ProjectSerializer, + models.ExecutionEnvironment: serializers.ExecutionEnvironmentSerializer, models.JobTemplate: serializers.JobTemplateWithSpecSerializer, models.Job: serializers.JobSerializer, models.AdHocCommand: serializers.AdHocCommandSerializer, diff --git a/awx/main/tasks.py b/awx/main/tasks.py index b6ab905837..0f02f3a507 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -23,6 +23,10 @@ import fcntl from pathlib import Path from uuid import uuid4 import urllib.parse as urlparse +import socket +import threading +import concurrent.futures +from base64 import b64encode # Django from django.conf import settings @@ -36,9 +40,6 @@ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist from django_guid.middleware import GuidMiddleware -# Kubernetes -from kubernetes.client.rest import ApiException - # Django-CRUM from crum import impersonate @@ -49,6 +50,9 @@ from gitdb.exc import BadName as BadGitName # Runner import ansible_runner +# Receptor +from receptorctl.socket_interface import ReceptorControl + # AWX from awx import __version__ as awx_application_version from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV @@ -72,9 +76,10 @@ from awx.main.dispatch import get_local_queuename, reaper from awx.main.utils import (update_scm_url, ignore_inventory_computed_fields, ignore_inventory_group_removal, extract_ansible_vars, schedule_task_manager, - get_awx_version) + get_awx_version, + deepmerge, + parse_yaml_or_json) from awx.main.utils.ansible import read_ansible_config -from awx.main.utils.common import get_custom_venv_choices from awx.main.utils.external_logging import reconfigure_rsyslog from awx.main.utils.safe_yaml import safe_dump, sanitize_jinja from awx.main.utils.reload import stop_local_services @@ -257,7 +262,7 @@ def apply_cluster_membership_policies(): # On a differential basis, apply instances to non-isolated groups with transaction.atomic(): for g in actual_groups: - if g.obj.is_containerized: + if g.obj.is_container_group: logger.debug('Skipping containerized group {} for policy calculation'.format(g.obj.name)) continue instances_to_add = set(g.instances) - set(g.prior_instances) @@ -502,7 +507,7 @@ def cluster_node_heartbeat(): def awx_k8s_reaper(): from awx.main.scheduler.kubernetes import PodManager # prevent circular import for group in InstanceGroup.objects.filter(credential__isnull=False).iterator(): - if group.is_containerized: + if group.is_container_group: logger.debug("Checking for orphaned k8s pods for {}.".format(group)) for job in UnifiedJob.objects.filter( pk__in=list(PodManager.list_active_jobs(group)) @@ -887,6 +892,34 @@ class BaseTask(object): ''' return os.path.abspath(os.path.join(os.path.dirname(__file__), *args)) + def build_execution_environment_params(self, instance): + if settings.IS_K8S: + return {} + + if instance.execution_environment_id is None: + from awx.main.signals import disable_activity_stream + + with disable_activity_stream(): + self.instance = instance = self.update_model( + instance.pk, execution_environment=instance.resolve_execution_environment()) + + image = instance.execution_environment.image + params = { + "container_image": image, + "process_isolation": True, + "container_options": ['--user=root'], + } + + pull = instance.execution_environment.pull + if pull: + params['container_options'].append(f'--pull={pull}') + + if settings.AWX_PROOT_SHOW_PATHS: + params['container_volume_mounts'] = [] + for this_path in settings.AWX_PROOT_SHOW_PATHS: + params['container_volume_mounts'].append(f'{this_path}:{this_path}:Z') + return params + def build_private_data(self, instance, private_data_dir): ''' Return SSH private key data (only if stored in DB as ssh_key_data). @@ -981,46 +1014,6 @@ class BaseTask(object): Build ansible yaml file filled with extra vars to be passed via -e@file.yml ''' - def build_params_process_isolation(self, instance, private_data_dir, cwd): - ''' - Build ansible runner .run() parameters for process isolation. - ''' - process_isolation_params = dict() - if self.should_use_proot(instance): - local_paths = [private_data_dir] - if cwd != private_data_dir and Path(private_data_dir) not in Path(cwd).parents: - local_paths.append(cwd) - show_paths = self.proot_show_paths + local_paths + \ - settings.AWX_PROOT_SHOW_PATHS - - pi_path = settings.AWX_PROOT_BASE_PATH - if not self.instance.is_isolated() and not self.instance.is_containerized: - pi_path = tempfile.mkdtemp( - prefix='ansible_runner_pi_', - dir=settings.AWX_PROOT_BASE_PATH - ) - os.chmod(pi_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - self.cleanup_paths.append(pi_path) - - process_isolation_params = { - 'process_isolation': True, - 'process_isolation_path': pi_path, - 'process_isolation_show_paths': show_paths, - 'process_isolation_hide_paths': [ - settings.AWX_PROOT_BASE_PATH, - '/etc/tower', - '/etc/ssh', - '/var/lib/awx', - '/var/log', - settings.PROJECTS_ROOT, - settings.JOBOUTPUT_ROOT, - ] + getattr(settings, 'AWX_PROOT_HIDE_PATHS', None) or [], - 'process_isolation_ro_paths': [settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH], - } - if getattr(instance, 'ansible_virtualenv_path', settings.ANSIBLE_VENV_PATH) != settings.ANSIBLE_VENV_PATH: - process_isolation_params['process_isolation_ro_paths'].append(instance.ansible_virtualenv_path) - return process_isolation_params - def build_params_resource_profiling(self, instance, private_data_dir): resource_profiling_params = {} if self.should_use_resource_profiling(instance): @@ -1031,6 +1024,8 @@ class BaseTask(object): results_dir = os.path.join(private_data_dir, 'artifacts/playbook_profiling') if not os.path.isdir(results_dir): os.makedirs(results_dir, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) + # FIXME: develop some better means of referencing paths inside containers + container_results_dir = os.path.join('/runner', 'artifacts/playbook_profiling') logger.debug('Collected the following resource profiling intervals: cpu: {} mem: {} pid: {}' .format(cpu_poll_interval, mem_poll_interval, pid_poll_interval)) @@ -1040,7 +1035,7 @@ class BaseTask(object): 'resource_profiling_cpu_poll_interval': cpu_poll_interval, 'resource_profiling_memory_poll_interval': mem_poll_interval, 'resource_profiling_pid_poll_interval': pid_poll_interval, - 'resource_profiling_results_dir': results_dir}) + 'resource_profiling_results_dir': container_results_dir}) return resource_profiling_params @@ -1063,30 +1058,18 @@ class BaseTask(object): os.chmod(path, stat.S_IRUSR) return path - def add_ansible_venv(self, venv_path, env, isolated=False): - env['VIRTUAL_ENV'] = venv_path - env['PATH'] = os.path.join(venv_path, "bin") + ":" + env['PATH'] - venv_libdir = os.path.join(venv_path, "lib") - - if not isolated and ( - not os.path.exists(venv_libdir) or - os.path.join(venv_path, '') not in get_custom_venv_choices() - ): - raise InvalidVirtualenvError(_( - 'Invalid virtual environment selected: {}'.format(venv_path) - )) - - isolated_manager.set_pythonpath(venv_libdir, env) - def add_awx_venv(self, env): env['VIRTUAL_ENV'] = settings.AWX_VENV_PATH - env['PATH'] = os.path.join(settings.AWX_VENV_PATH, "bin") + ":" + env['PATH'] + if 'PATH' in env: + env['PATH'] = os.path.join(settings.AWX_VENV_PATH, "bin") + ":" + env['PATH'] + else: + env['PATH'] = os.path.join(settings.AWX_VENV_PATH, "bin") def build_env(self, instance, private_data_dir, isolated, private_data_files=None): ''' Build environment dictionary for ansible-playbook. ''' - env = dict(os.environ.items()) + env = {} # Add ANSIBLE_* settings to the subprocess environment. for attr in dir(settings): if attr == attr.upper() and attr.startswith('ANSIBLE_'): @@ -1094,14 +1077,9 @@ class BaseTask(object): # Also set environment variables configured in AWX_TASK_ENV setting. for key, value in settings.AWX_TASK_ENV.items(): env[key] = str(value) - # Set environment variables needed for inventory and job event - # callbacks to work. - # Update PYTHONPATH to use local site-packages. - # NOTE: - # Derived class should call add_ansible_venv() or add_awx_venv() - if self.should_use_proot(instance): - env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH + env['AWX_PRIVATE_DATA_DIR'] = private_data_dir + return env def should_use_resource_profiling(self, job): @@ -1129,12 +1107,13 @@ class BaseTask(object): for hostname, hv in script_data.get('_meta', {}).get('hostvars', {}).items() } json_data = json.dumps(script_data) - handle, path = tempfile.mkstemp(dir=private_data_dir) - f = os.fdopen(handle, 'w') - f.write('#! /usr/bin/env python\n# -*- coding: utf-8 -*-\nprint(%r)\n' % json_data) - f.close() - os.chmod(path, stat.S_IRUSR | stat.S_IXUSR | stat.S_IWUSR) - return path + path = os.path.join(private_data_dir, 'inventory') + os.makedirs(path, mode=0o700) + fn = os.path.join(path, 'hosts') + with open(fn, 'w') as f: + os.chmod(fn, stat.S_IRUSR | stat.S_IXUSR | stat.S_IWUSR) + f.write('#! /usr/bin/env python3\n# -*- coding: utf-8 -*-\nprint(%r)\n' % json_data) + return fn def build_args(self, instance, private_data_dir, passwords): raise NotImplementedError @@ -1205,17 +1184,17 @@ class BaseTask(object): instance.log_lifecycle("finalize_run") job_profiling_dir = os.path.join(private_data_dir, 'artifacts/playbook_profiling') awx_profiling_dir = '/var/log/tower/playbook_profiling/' + collections_info = os.path.join(private_data_dir, 'artifacts/', 'collections.json') + if not os.path.exists(awx_profiling_dir): os.mkdir(awx_profiling_dir) if os.path.isdir(job_profiling_dir): shutil.copytree(job_profiling_dir, os.path.join(awx_profiling_dir, str(instance.pk))) - - if instance.is_containerized: - from awx.main.scheduler.kubernetes import PodManager # prevent circular import - pm = PodManager(instance) - logger.debug(f"Deleting pod {pm.pod_name}") - pm.delete() - + if os.path.exists(collections_info): + with open(collections_info) as ee_json_info: + ee_collections_info = json.loads(ee_json_info.read()) + instance.installed_collections = ee_collections_info + instance.save(update_fields=['installed_collections']) def event_handler(self, event_data): # @@ -1355,16 +1334,6 @@ class BaseTask(object): Run the job/task and capture its output. ''' self.instance = self.model.objects.get(pk=pk) - containerized = self.instance.is_containerized - pod_manager = None - if containerized: - # Here we are trying to launch a pod before transitioning the job into a running - # state. For some scenarios (like waiting for resources to become available) we do this - # rather than marking the job as error or failed. This is not always desirable. Cases - # such as invalid authentication should surface as an error. - pod_manager = self.deploy_container_group_pod(self.instance) - if not pod_manager: - return # self.instance because of the update_model pattern and when it's used in callback handlers self.instance = self.update_model(pk, status='running', @@ -1423,12 +1392,8 @@ class BaseTask(object): passwords = self.build_passwords(self.instance, kwargs) self.build_extra_vars_file(self.instance, private_data_dir) args = self.build_args(self.instance, private_data_dir, passwords) - cwd = self.build_cwd(self.instance, private_data_dir) resource_profiling_params = self.build_params_resource_profiling(self.instance, private_data_dir) - process_isolation_params = self.build_params_process_isolation(self.instance, - private_data_dir, - cwd) env = self.build_env(self.instance, private_data_dir, isolated, private_data_files=private_data_files) self.safe_env = build_safe_env(env) @@ -1451,27 +1416,17 @@ class BaseTask(object): params = { 'ident': self.instance.id, 'private_data_dir': private_data_dir, - 'project_dir': cwd, 'playbook': self.build_playbook_path_relative_to_cwd(self.instance, private_data_dir), 'inventory': self.build_inventory(self.instance, private_data_dir), 'passwords': expect_passwords, 'envvars': env, - 'event_handler': self.event_handler, - 'cancel_callback': self.cancel_callback, - 'finished_callback': self.finished_callback, - 'status_handler': self.status_handler, 'settings': { 'job_timeout': self.get_instance_timeout(self.instance), 'suppress_ansible_output': True, - **process_isolation_params, **resource_profiling_params, }, } - if containerized: - # We don't want HOME passed through to container groups. - params['envvars'].pop('HOME') - if isinstance(self.instance, AdHocCommand): params['module'] = self.build_module_name(self.instance) params['module_args'] = self.build_module_args(self.instance) @@ -1483,6 +1438,9 @@ class BaseTask(object): # Disable Ansible fact cache. params['fact_cache_type'] = '' + if self.instance.is_container_group_task or settings.IS_K8S: + params['envvars'].pop('HOME', None) + ''' Delete parameters if the values are None or empty array ''' @@ -1491,37 +1449,24 @@ class BaseTask(object): del params[v] self.dispatcher = CallbackQueueDispatcher() - if self.instance.is_isolated() or containerized: - module_args = None - if 'module_args' in params: - # if it's adhoc, copy the module args - module_args = ansible_runner.utils.args2cmdline( - params.get('module_args'), - ) - shutil.move( - params.pop('inventory'), - os.path.join(private_data_dir, 'inventory') - ) - ansible_runner.utils.dump_artifacts(params) - isolated_manager_instance = isolated_manager.IsolatedManager( - self.event_handler, - canceled_callback=lambda: self.update_model(self.instance.pk).cancel_flag, - check_callback=self.check_handler, - pod_manager=pod_manager - ) - status, rc = isolated_manager_instance.run(self.instance, - private_data_dir, - params.get('playbook'), - params.get('module'), - module_args, - ident=str(self.instance.pk)) - self.finished_callback(None) - else: - res = ansible_runner.interface.run(**params) - status = res.status - rc = res.rc self.instance.log_lifecycle("running_playbook") + if isinstance(self.instance, SystemJob): + cwd = self.build_cwd(self.instance, private_data_dir) + res = ansible_runner.interface.run(project_dir=cwd, + event_handler=self.event_handler, + finished_callback=self.finished_callback, + status_handler=self.status_handler, + **params) + else: + receptor_job = AWXReceptorJob(self, params) + res = receptor_job.run() + + if not res: + return + + status = res.status + rc = res.rc if status == 'timeout': self.instance.job_explanation = "Job terminated due to timeout" @@ -1569,37 +1514,6 @@ class BaseTask(object): raise AwxTaskError.TaskError(self.instance, rc) - def deploy_container_group_pod(self, task): - from awx.main.scheduler.kubernetes import PodManager # Avoid circular import - pod_manager = PodManager(self.instance) - try: - log_name = task.log_format - logger.debug(f"Launching pod for {log_name}.") - pod_manager.deploy() - except (ApiException, Exception) as exc: - if isinstance(exc, ApiException) and exc.status == 403: - try: - if 'exceeded quota' in json.loads(exc.body)['message']: - # If the k8s cluster does not have capacity, we move the - # job back into pending and wait until the next run of - # the task manager. This does not exactly play well with - # our current instance group precendence logic, since it - # will just sit here forever if kubernetes returns this - # error. - logger.warn(exc.body) - logger.warn(f"Could not launch pod for {log_name}. Exceeded quota.") - self.update_model(task.pk, status='pending') - return - except Exception: - logger.exception(f"Unable to handle response from Kubernetes API for {log_name}.") - - logger.exception(f"Error when launching pod for {log_name}") - self.update_model(task.pk, status='error', result_traceback=traceback.format_exc()) - return - - self.update_model(task.pk, execution_node=pod_manager.pod_name) - return pod_manager - @@ -1690,7 +1604,6 @@ class RunJob(BaseTask): private_data_files=private_data_files) if private_data_files is None: private_data_files = {} - self.add_ansible_venv(job.ansible_virtualenv_path, env, isolated=isolated) # Set environment variables needed for inventory and job event # callbacks to work. env['JOB_ID'] = str(job.pk) @@ -1709,13 +1622,17 @@ class RunJob(BaseTask): cp_dir = os.path.join(private_data_dir, 'cp') if not os.path.exists(cp_dir): os.mkdir(cp_dir, 0o700) - env['ANSIBLE_SSH_CONTROL_PATH_DIR'] = cp_dir + # FIXME: more elegant way to manage this path in container + env['ANSIBLE_SSH_CONTROL_PATH_DIR'] = '/runner/cp' # Set environment variables for cloud credentials. cred_files = private_data_files.get('credentials', {}) for cloud_cred in job.cloud_credentials: if cloud_cred and cloud_cred.credential_type.namespace == 'openstack': - env['OS_CLIENT_CONFIG_FILE'] = cred_files.get(cloud_cred, '') + env['OS_CLIENT_CONFIG_FILE'] = os.path.join( + '/runner', + os.path.basename(cred_files.get(cloud_cred, '')) + ) for network_cred in job.network_credentials: env['ANSIBLE_NET_USERNAME'] = network_cred.get_input('username', default='') @@ -1746,7 +1663,8 @@ class RunJob(BaseTask): for path in config_values[config_setting].split(':'): if path not in paths: paths = [config_values[config_setting]] + paths - paths = [os.path.join(private_data_dir, folder)] + paths + # FIXME: again, figure out more elegant way for inside container + paths = [os.path.join('/runner', folder)] + paths env[env_key] = os.pathsep.join(paths) return env @@ -1875,10 +1793,26 @@ class RunJob(BaseTask): ''' Return whether this task should use proot. ''' - if job.is_containerized: + if job.is_container_group_task: return False return getattr(settings, 'AWX_PROOT_ENABLED', False) + def build_execution_environment_params(self, instance): + if settings.IS_K8S: + return {} + + params = super(RunJob, self).build_execution_environment_params(instance) + # If this has an insights agent and it is not already mounted then show it + insights_dir = os.path.dirname(settings.INSIGHTS_SYSTEM_ID_FILE) + if instance.use_fact_cache and os.path.exists(insights_dir): + logger.info('not parent of others') + params.setdefault('container_volume_mounts', []) + params['container_volume_mounts'].extend([ + f"{insights_dir}:{insights_dir}:Z", + ]) + + return params + def pre_run_hook(self, job, private_data_dir): super(RunJob, self).pre_run_hook(job, private_data_dir) if job.inventory is None: @@ -1989,10 +1923,10 @@ class RunJob(BaseTask): return if job.use_fact_cache: job.finish_job_fact_cache( - os.path.join(private_data_dir, 'artifacts', str(job.id), 'fact_cache'), + os.path.join(private_data_dir, 'artifacts', 'fact_cache'), fact_modification_times, ) - if isolated_manager_instance and not job.is_containerized: + if isolated_manager_instance and not job.is_container_group_task: isolated_manager_instance.cleanup() try: @@ -2068,7 +2002,6 @@ class RunProjectUpdate(BaseTask): env = super(RunProjectUpdate, self).build_env(project_update, private_data_dir, isolated=isolated, private_data_files=private_data_files) - self.add_ansible_venv(settings.ANSIBLE_VENV_PATH, env) env['ANSIBLE_RETRY_FILES_ENABLED'] = str(False) env['ANSIBLE_ASK_PASS'] = str(False) env['ANSIBLE_BECOME_ASK_PASS'] = str(False) @@ -2202,6 +2135,14 @@ class RunProjectUpdate(BaseTask): elif project_update.project.allow_override: # If branch is override-able, do extra fetch for all branches extra_vars['scm_refspec'] = 'refs/heads/*:refs/remotes/origin/*' + + if project_update.scm_type == 'archive': + # for raw archive, prevent error moving files between volumes + extra_vars['ansible_remote_tmp'] = os.path.join( + project_update.get_project_path(check_if_exists=False), + '.ansible_awx', 'tmp' + ) + self._write_extra_vars_file(private_data_dir, extra_vars) def build_cwd(self, project_update, private_data_dir): @@ -2330,10 +2271,14 @@ class RunProjectUpdate(BaseTask): # re-create root project folder if a natural disaster has destroyed it if not os.path.exists(settings.PROJECTS_ROOT): os.mkdir(settings.PROJECTS_ROOT) + project_path = instance.project.get_project_path(check_if_exists=False) + if not os.path.exists(project_path): + os.makedirs(project_path) # used as container mount + self.acquire_lock(instance) + self.original_branch = None if instance.scm_type == 'git' and instance.branch_override: - project_path = instance.project.get_project_path(check_if_exists=False) if os.path.exists(project_path): git_repo = git.Repo(project_path) if git_repo.head.is_detached: @@ -2349,7 +2294,7 @@ class RunProjectUpdate(BaseTask): # the project update playbook is not in a git repo, but uses a vendoring directory # to be consistent with the ansible-runner model, - # that is moved into the runner projecct folder here + # that is moved into the runner project folder here awx_playbooks = self.get_path_to('..', 'playbooks') copy_tree(awx_playbooks, os.path.join(private_data_dir, 'project')) @@ -2484,6 +2429,20 @@ class RunProjectUpdate(BaseTask): ''' return getattr(settings, 'AWX_PROOT_ENABLED', False) + def build_execution_environment_params(self, instance): + if settings.IS_K8S: + return {} + + params = super(RunProjectUpdate, self).build_execution_environment_params(instance) + project_path = instance.get_project_path(check_if_exists=False) + cache_path = instance.get_cache_path() + params.setdefault('container_volume_mounts', []) + params['container_volume_mounts'].extend([ + f"{project_path}:{project_path}:Z", + f"{cache_path}:{cache_path}:Z", + ]) + return params + @task(queue=get_local_queuename) class RunInventoryUpdate(BaseTask): @@ -2492,18 +2451,6 @@ class RunInventoryUpdate(BaseTask): event_model = InventoryUpdateEvent event_data_key = 'inventory_update_id' - # TODO: remove once inv updates run in containers - def should_use_proot(self, inventory_update): - ''' - Return whether this task should use proot. - ''' - return getattr(settings, 'AWX_PROOT_ENABLED', False) - - # TODO: remove once inv updates run in containers - @property - def proot_show_paths(self): - return [settings.AWX_ANSIBLE_COLLECTIONS_PATHS] - def build_private_data(self, inventory_update, private_data_dir): """ Return private data needed for inventory update. @@ -2530,17 +2477,13 @@ class RunInventoryUpdate(BaseTask): are accomplished by the inventory source injectors (in this method) or custom credential type injectors (in main run method). """ - env = super(RunInventoryUpdate, self).build_env(inventory_update, - private_data_dir, - isolated, - private_data_files=private_data_files) + env = super(RunInventoryUpdate, self).build_env( + inventory_update, private_data_dir, isolated, + private_data_files=private_data_files) + if private_data_files is None: private_data_files = {} - # TODO: remove once containers replace custom venvs - self.add_ansible_venv(inventory_update.ansible_virtualenv_path, env, isolated=isolated) - - # Legacy environment variables, were used as signal to awx-manage command - # now they are provided in case some scripts may be relying on them + # Pass inventory source ID to inventory script. env['INVENTORY_SOURCE_ID'] = str(inventory_update.inventory_source_id) env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) env.update(STANDARD_INVENTORY_UPDATE_ENV) @@ -2578,7 +2521,8 @@ class RunInventoryUpdate(BaseTask): for path in config_values[config_setting].split(':'): if path not in paths: paths = [config_values[config_setting]] + paths - paths = [os.path.join(private_data_dir, folder)] + paths + # FIXME: containers + paths = [os.path.join('/runner', folder)] + paths env[env_key] = os.pathsep.join(paths) return env @@ -2606,17 +2550,20 @@ class RunInventoryUpdate(BaseTask): args = ['ansible-inventory', '--list', '--export'] # Add arguments for the source inventory file/script/thing - source_location = self.pseudo_build_inventory(inventory_update, private_data_dir) + rel_path = self.pseudo_build_inventory(inventory_update, private_data_dir) + container_location = os.path.join('/runner', rel_path) # TODO: make container paths elegant + source_location = os.path.join(private_data_dir, rel_path) + args.append('-i') - args.append(source_location) + args.append(container_location) args.append('--output') - args.append(os.path.join(private_data_dir, 'artifacts', 'output.json')) + args.append(os.path.join('/runner', 'artifacts', 'output.json')) if os.path.isdir(source_location): - playbook_dir = source_location + playbook_dir = container_location else: - playbook_dir = os.path.dirname(source_location) + playbook_dir = os.path.dirname(container_location) args.extend(['--playbook-dir', playbook_dir]) if inventory_update.verbosity: @@ -2647,8 +2594,10 @@ class RunInventoryUpdate(BaseTask): with open(inventory_path, 'w') as f: f.write(content) os.chmod(inventory_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + + rel_path = injector.filename elif src == 'scm': - inventory_path = os.path.join(private_data_dir, 'project', inventory_update.source_path) + rel_path = os.path.join('project', inventory_update.source_path) elif src == 'custom': handle, inventory_path = tempfile.mkstemp(dir=private_data_dir) f = os.fdopen(handle, 'w') @@ -2657,7 +2606,9 @@ class RunInventoryUpdate(BaseTask): f.write(inventory_update.source_script.script) f.close() os.chmod(inventory_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - return inventory_path + + rel_path = os.path.split(inventory_path)[-1] + return rel_path def build_cwd(self, inventory_update, private_data_dir): ''' @@ -2666,9 +2617,10 @@ class RunInventoryUpdate(BaseTask): - SCM, where source needs to live in the project folder ''' src = inventory_update.source + container_dir = '/runner' # TODO: make container paths elegant if src == 'scm' and inventory_update.source_project_update: - return os.path.join(private_data_dir, 'project') - return private_data_dir + return os.path.join(container_dir, 'project') + return container_dir def build_playbook_path_relative_to_cwd(self, inventory_update, private_data_dir): return None @@ -2853,7 +2805,6 @@ class RunAdHocCommand(BaseTask): env = super(RunAdHocCommand, self).build_env(ad_hoc_command, private_data_dir, isolated=isolated, private_data_files=private_data_files) - self.add_ansible_venv(settings.ANSIBLE_VENV_PATH, env) # Set environment variables needed for inventory and ad hoc event # callbacks to work. env['AD_HOC_COMMAND_ID'] = str(ad_hoc_command.pk) @@ -2867,7 +2818,8 @@ class RunAdHocCommand(BaseTask): cp_dir = os.path.join(private_data_dir, 'cp') if not os.path.exists(cp_dir): os.mkdir(cp_dir, 0o700) - env['ANSIBLE_SSH_CONTROL_PATH'] = cp_dir + # FIXME: more elegant way to manage this path in container + env['ANSIBLE_SSH_CONTROL_PATH'] = '/runner/cp' return env @@ -2974,7 +2926,7 @@ class RunAdHocCommand(BaseTask): ''' Return whether this task should use proot. ''' - if ad_hoc_command.is_containerized: + if ad_hoc_command.is_container_group_task: return False return getattr(settings, 'AWX_PROOT_ENABLED', False) @@ -2991,6 +2943,9 @@ class RunSystemJob(BaseTask): event_model = SystemJobEvent event_data_key = 'system_job_id' + def build_execution_environment_params(self, system_job): + return {} + def build_args(self, system_job, private_data_dir, passwords): args = ['awx-manage', system_job.job_type] try: @@ -3022,10 +2977,13 @@ class RunSystemJob(BaseTask): return path def build_env(self, instance, private_data_dir, isolated=False, private_data_files=None): - env = super(RunSystemJob, self).build_env(instance, private_data_dir, - isolated=isolated, - private_data_files=private_data_files) - self.add_awx_venv(env) + base_env = super(RunSystemJob, self).build_env( + instance, private_data_dir, isolated=isolated, + private_data_files=private_data_files) + # TODO: this is able to run by turning off isolation + # the goal is to run it a container instead + env = dict(os.environ.items()) + env.update(base_env) return env def build_cwd(self, instance, private_data_dir): @@ -3103,3 +3061,235 @@ def deep_copy_model_obj( permission_check_func(creater, copy_mapping.values()) if isinstance(new_obj, Inventory): update_inventory_computed_fields.delay(new_obj.id) + + +class AWXReceptorJob: + def __init__(self, task=None, runner_params=None): + self.task = task + self.runner_params = runner_params + self.unit_id = None + + if self.task and not self.task.instance.is_container_group_task: + execution_environment_params = self.task.build_execution_environment_params(self.task.instance) + self.runner_params['settings'].update(execution_environment_params) + + def run(self): + # We establish a connection to the Receptor socket + receptor_ctl = ReceptorControl('/var/run/receptor/receptor.sock') + + try: + return self._run_internal(receptor_ctl) + finally: + # Make sure to always release the work unit if we established it + if self.unit_id is not None: + receptor_ctl.simple_command(f"work release {self.unit_id}") + + def _run_internal(self, receptor_ctl): + # Create a socketpair. Where the left side will be used for writing our payload + # (private data dir, kwargs). The right side will be passed to Receptor for + # reading. + sockin, sockout = socket.socketpair() + + threading.Thread(target=self.transmit, args=[sockin]).start() + + # submit our work, passing + # in the right side of our socketpair for reading. + result = receptor_ctl.submit_work(worktype=self.work_type, + payload=sockout.makefile('rb'), + params=self.receptor_params) + self.unit_id = result['unitid'] + + sockin.close() + sockout.close() + + resultsock, resultfile = receptor_ctl.get_work_results(self.unit_id, + return_socket=True, + return_sockfile=True) + # Both "processor" and "cancel_watcher" are spawned in separate threads. + # We wait for the first one to return. If cancel_watcher returns first, + # we yank the socket out from underneath the processor, which will cause it + # to exit. A reference to the processor_future is passed into the cancel_watcher_future, + # Which exits if the job has finished normally. The context manager ensures we do not + # leave any threads laying around. + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: + processor_future = executor.submit(self.processor, resultfile) + cancel_watcher_future = executor.submit(self.cancel_watcher, processor_future) + futures = [processor_future, cancel_watcher_future] + first_future = concurrent.futures.wait(futures, + return_when=concurrent.futures.FIRST_COMPLETED) + + res = list(first_future.done)[0].result() + if res.status == 'canceled': + receptor_ctl.simple_command(f"work cancel {self.unit_id}") + resultsock.shutdown(socket.SHUT_RDWR) + resultfile.close() + elif res.status == 'error': + # TODO: There should be a more efficient way of getting this information + receptor_work_list = receptor_ctl.simple_command("work list") + detail = receptor_work_list[self.unit_id]['Detail'] + if 'exceeded quota' in detail: + logger.warn(detail) + log_name = self.task.instance.log_format + logger.warn(f"Could not launch pod for {log_name}. Exceeded quota.") + self.task.update_model(self.task.instance.pk, status='pending') + return + + raise RuntimeError(detail) + + return res + + # Spawned in a thread so Receptor can start reading before we finish writing, we + # write our payload to the left side of our socketpair. + def transmit(self, _socket): + if not settings.IS_K8S and self.work_type == 'local': + self.runner_params['only_transmit_kwargs'] = True + + ansible_runner.interface.run(streamer='transmit', + _output=_socket.makefile('wb'), + **self.runner_params) + + # Socket must be shutdown here, or the reader will hang forever. + _socket.shutdown(socket.SHUT_WR) + + def processor(self, resultfile): + return ansible_runner.interface.run(streamer='process', + quiet=True, + _input=resultfile, + event_handler=self.task.event_handler, + finished_callback=self.task.finished_callback, + status_handler=self.task.status_handler, + **self.runner_params) + + @property + def receptor_params(self): + if self.task.instance.is_container_group_task: + spec_yaml = yaml.dump(self.pod_definition, explicit_start=True) + + receptor_params = { + "secret_kube_pod": spec_yaml, + } + + if self.credential: + kubeconfig_yaml = yaml.dump(self.kube_config, explicit_start=True) + receptor_params["secret_kube_config"] = kubeconfig_yaml + else: + private_data_dir = self.runner_params['private_data_dir'] + receptor_params = { + "params": f"--private-data-dir={private_data_dir}" + } + + return receptor_params + + + + @property + def work_type(self): + if self.task.instance.is_container_group_task: + if self.credential: + work_type = 'kubernetes-runtime-auth' + else: + work_type = 'kubernetes-incluster-auth' + else: + work_type = 'local' + + return work_type + + def cancel_watcher(self, processor_future): + while True: + if processor_future.done(): + return processor_future.result() + + if self.task.cancel_callback(): + result = namedtuple('result', ['status', 'rc']) + return result('canceled', 1) + time.sleep(1) + + @property + def pod_definition(self): + default_pod_spec = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "namespace": settings.AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE + }, + "spec": { + "containers": [{ + "image": settings.AWX_CONTAINER_GROUP_DEFAULT_IMAGE, + "name": 'worker', + "args": ['ansible-runner', 'worker'] + }] + } + } + + pod_spec_override = {} + if self.task and self.task.instance.instance_group.pod_spec_override: + pod_spec_override = parse_yaml_or_json( + self.task.instance.instance_group.pod_spec_override) + pod_spec = {**default_pod_spec, **pod_spec_override} + + if self.task: + pod_spec['metadata'] = deepmerge( + pod_spec.get('metadata', {}), + dict(name=self.pod_name, + labels={ + 'ansible-awx': settings.INSTALL_UUID, + 'ansible-awx-job-id': str(self.task.instance.id) + })) + + return pod_spec + + @property + def pod_name(self): + return f"awx-job-{self.task.instance.id}" + + @property + def credential(self): + return self.task.instance.instance_group.credential + + @property + def namespace(self): + return self.pod_definition['metadata']['namespace'] + + @property + def kube_config(self): + host_input = self.credential.get_input('host') + config = { + "apiVersion": "v1", + "kind": "Config", + "preferences": {}, + "clusters": [ + { + "name": host_input, + "cluster": { + "server": host_input + } + } + ], + "users": [ + { + "name": host_input, + "user": { + "token": self.credential.get_input('bearer_token') + } + } + ], + "contexts": [ + { + "name": host_input, + "context": { + "cluster": host_input, + "user": host_input, + "namespace": self.namespace + } + } + ], + "current-context": host_input + } + + if self.credential.get_input('verify_ssl') and 'ssl_ca_cert' in self.credential.inputs: + config["clusters"][0]["cluster"]["certificate-authority-data"] = b64encode( + self.credential.get_input('ssl_ca_cert').encode() # encode to bytes + ).decode() # decode the base64 data into a str + else: + config["clusters"][0]["cluster"]["insecure-skip-tls-verify"] = True + return config diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 43c7d51960..61c1054912 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -255,7 +255,7 @@ def test_instance_group_update_fields(patch, instance, instance_group, admin, co # policy_instance_ variables can only be updated in instance groups that are NOT containerized # instance group (not containerized) ig_url = reverse("api:instance_group_detail", kwargs={'pk': instance_group.pk}) - assert not instance_group.is_containerized + assert not instance_group.is_container_group assert not containerized_instance_group.is_isolated resp = patch(ig_url, {'policy_instance_percentage':15}, admin, expect=200) assert 15 == resp.data['policy_instance_percentage'] @@ -266,7 +266,7 @@ def test_instance_group_update_fields(patch, instance, instance_group, admin, co # containerized instance group cg_url = reverse("api:instance_group_detail", kwargs={'pk': containerized_instance_group.pk}) - assert containerized_instance_group.is_containerized + assert containerized_instance_group.is_container_group assert not containerized_instance_group.is_isolated resp = patch(cg_url, {'policy_instance_percentage':15}, admin, expect=400) assert ["Containerized instances may not be managed via the API"] == resp.data['policy_instance_percentage'] @@ -291,4 +291,3 @@ def test_containerized_group_default_fields(instance_group, kube_credential): assert ig.policy_instance_list == [] assert ig.policy_instance_minimum == 0 assert ig.policy_instance_percentage == 0 - \ No newline at end of file diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 1883f6e6ec..2eeefa7eed 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -1,6 +1,3 @@ -import os - -from backports.tempfile import TemporaryDirectory import pytest # AWX @@ -10,7 +7,6 @@ from awx.main.models import Job, JobTemplate, CredentialType, WorkflowJobTemplat from awx.main.migrations import _save_password_keys as save_password_keys # Django -from django.conf import settings from django.apps import apps # DRF @@ -302,61 +298,6 @@ def test_save_survey_passwords_on_migration(job_template_with_survey_passwords): assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'} -@pytest.mark.django_db -@pytest.mark.parametrize('access', ["superuser", "admin", "peon"]) -def test_job_template_custom_virtualenv(get, patch, organization_factory, job_template_factory, alice, access): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - user = alice - if access == "superuser": - user = objs.superusers.admin - elif access == "admin": - jt.admin_role.members.add(alice) - else: - jt.read_role.members.add(alice) - - with TemporaryDirectory(dir=settings.BASE_VENV_PATH) as temp_dir: - os.makedirs(os.path.join(temp_dir, 'bin', 'activate')) - url = reverse('api:job_template_detail', kwargs={'pk': jt.id}) - - if access == "peon": - patch(url, {'custom_virtualenv': temp_dir}, user=user, expect=403) - assert 'custom_virtualenv' not in get(url, user=user) - assert JobTemplate.objects.get(pk=jt.id).custom_virtualenv is None - else: - patch(url, {'custom_virtualenv': temp_dir}, user=user, expect=200) - assert get(url, user=user).data['custom_virtualenv'] == os.path.join(temp_dir, '') - - -@pytest.mark.django_db -def test_job_template_invalid_custom_virtualenv(get, patch, organization_factory, - job_template_factory): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_detail', kwargs={'pk': jt.id}) - resp = patch(url, {'custom_virtualenv': '/foo/bar'}, user=objs.superusers.admin, expect=400) - assert resp.data['custom_virtualenv'] == [ - '/foo/bar is not a valid virtualenv in {}'.format(settings.BASE_VENV_PATH) - ] - - -@pytest.mark.django_db -@pytest.mark.parametrize('value', ["", None]) -def test_job_template_unset_custom_virtualenv(get, patch, organization_factory, - job_template_factory, value): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_detail', kwargs={'pk': jt.id}) - resp = patch(url, {'custom_virtualenv': value}, user=objs.superusers.admin, expect=200) - assert resp.data['custom_virtualenv'] is None - - @pytest.mark.django_db def test_jt_organization_follows_project(post, patch, admin_user): org1 = Organization.objects.create(name='foo1') diff --git a/awx/main/tests/functional/api/test_organizations.py b/awx/main/tests/functional/api/test_organizations.py index 6c45c0c681..93994d3842 100644 --- a/awx/main/tests/functional/api/test_organizations.py +++ b/awx/main/tests/functional/api/test_organizations.py @@ -1,11 +1,6 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -# Python -import os - -from backports.tempfile import TemporaryDirectory -from django.conf import settings import pytest # AWX @@ -242,32 +237,6 @@ def test_delete_organization_xfail2(delete, organization): delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=None, expect=401) -@pytest.mark.django_db -def test_organization_custom_virtualenv(get, patch, organization, admin): - with TemporaryDirectory(dir=settings.BASE_VENV_PATH) as temp_dir: - os.makedirs(os.path.join(temp_dir, 'bin', 'activate')) - url = reverse('api:organization_detail', kwargs={'pk': organization.id}) - patch(url, {'custom_virtualenv': temp_dir}, user=admin, expect=200) - assert get(url, user=admin).data['custom_virtualenv'] == os.path.join(temp_dir, '') - - -@pytest.mark.django_db -def test_organization_invalid_custom_virtualenv(get, patch, organization, admin): - url = reverse('api:organization_detail', kwargs={'pk': organization.id}) - resp = patch(url, {'custom_virtualenv': '/foo/bar'}, user=admin, expect=400) - assert resp.data['custom_virtualenv'] == [ - '/foo/bar is not a valid virtualenv in {}'.format(settings.BASE_VENV_PATH) - ] - - -@pytest.mark.django_db -@pytest.mark.parametrize('value', ["", None]) -def test_organization_unset_custom_virtualenv(get, patch, organization, admin, value): - url = reverse('api:organization_detail', kwargs={'pk': organization.id}) - resp = patch(url, {'custom_virtualenv': value}, user=admin, expect=200) - assert resp.data['custom_virtualenv'] is None - - @pytest.mark.django_db def test_organization_delete(delete, admin, organization, organization_jobs_successful): url = reverse('api:organization_detail', kwargs={'pk': organization.id}) diff --git a/awx/main/tests/functional/api/test_project.py b/awx/main/tests/functional/api/test_project.py index a31eb0804a..71d685ce7b 100644 --- a/awx/main/tests/functional/api/test_project.py +++ b/awx/main/tests/functional/api/test_project.py @@ -1,7 +1,3 @@ -import os - -from backports.tempfile import TemporaryDirectory -from django.conf import settings import pytest from awx.api.versioning import reverse @@ -21,32 +17,6 @@ class TestInsightsCredential: expect=400) -@pytest.mark.django_db -def test_project_custom_virtualenv(get, patch, project, admin): - with TemporaryDirectory(dir=settings.BASE_VENV_PATH) as temp_dir: - os.makedirs(os.path.join(temp_dir, 'bin', 'activate')) - url = reverse('api:project_detail', kwargs={'pk': project.id}) - patch(url, {'custom_virtualenv': temp_dir}, user=admin, expect=200) - assert get(url, user=admin).data['custom_virtualenv'] == os.path.join(temp_dir, '') - - -@pytest.mark.django_db -def test_project_invalid_custom_virtualenv(get, patch, project, admin): - url = reverse('api:project_detail', kwargs={'pk': project.id}) - resp = patch(url, {'custom_virtualenv': '/foo/bar'}, user=admin, expect=400) - assert resp.data['custom_virtualenv'] == [ - '/foo/bar is not a valid virtualenv in {}'.format(settings.BASE_VENV_PATH) - ] - - -@pytest.mark.django_db -@pytest.mark.parametrize('value', ["", None]) -def test_project_unset_custom_virtualenv(get, patch, project, admin, value): - url = reverse('api:project_detail', kwargs={'pk': project.id}) - resp = patch(url, {'custom_virtualenv': value}, user=admin, expect=200) - assert resp.data['custom_virtualenv'] is None - - @pytest.mark.django_db def test_no_changing_overwrite_behavior_if_used(post, patch, organization, admin_user): r1 = post( diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 7111950003..4cbd5a40d3 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -52,6 +52,7 @@ from awx.main.models.events import ( from awx.main.models.workflow import WorkflowJobTemplate from awx.main.models.ad_hoc_commands import AdHocCommand from awx.main.models.oauth import OAuth2Application as Application +from awx.main.models.execution_environments import ExecutionEnvironment __SWAGGER_REQUESTS__ = {} @@ -850,3 +851,8 @@ def slice_job_factory(slice_jt_factory): node.save() return slice_job return r + + +@pytest.fixture +def execution_environment(organization): + return ExecutionEnvironment.objects.create(name="test-ee", description="test-ee", organization=organization) diff --git a/awx/main/tests/functional/task_management/test_container_groups.py b/awx/main/tests/functional/task_management/test_container_groups.py index 47d982a725..84dcaf12d7 100644 --- a/awx/main/tests/functional/task_management/test_container_groups.py +++ b/awx/main/tests/functional/task_management/test_container_groups.py @@ -29,8 +29,8 @@ def containerized_job(default_instance_group, kube_credential, job_template_fact @pytest.mark.django_db def test_containerized_job(containerized_job): - assert containerized_job.is_containerized - assert containerized_job.instance_group.is_containerized + assert containerized_job.is_container_group_task + assert containerized_job.instance_group.is_container_group assert containerized_job.instance_group.credential.kubernetes diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py index 27f67b96f4..4f87c249be 100644 --- a/awx/main/tests/functional/test_credential.py +++ b/awx/main/tests/functional/test_credential.py @@ -90,6 +90,7 @@ def test_default_cred_types(): 'kubernetes_bearer_token', 'net', 'openstack', + 'registry', 'rhv', 'satellite6', 'scm', diff --git a/awx/main/tests/functional/test_execution_environments.py b/awx/main/tests/functional/test_execution_environments.py new file mode 100644 index 0000000000..5f1e430fe8 --- /dev/null +++ b/awx/main/tests/functional/test_execution_environments.py @@ -0,0 +1,19 @@ +import pytest + +from awx.main.models import (ExecutionEnvironment) + + +@pytest.mark.django_db +def test_execution_environment_creation(execution_environment, organization): + execution_env = ExecutionEnvironment.objects.create( + name='Hello Environment', + image='', + organization=organization, + managed_by_tower=False, + credential=None, + pull='missing' + ) + assert type(execution_env) is type(execution_environment) + assert execution_env.organization == organization + assert execution_env.name == 'Hello Environment' + assert execution_env.pull == 'missing' diff --git a/awx/main/tests/functional/test_inventory_source_injectors.py b/awx/main/tests/functional/test_inventory_source_injectors.py index fc28c92294..f9edfdcd22 100644 --- a/awx/main/tests/functional/test_inventory_source_injectors.py +++ b/awx/main/tests/functional/test_inventory_source_injectors.py @@ -6,7 +6,7 @@ import re from collections import namedtuple from awx.main.tasks import RunInventoryUpdate -from awx.main.models import InventorySource, Credential, CredentialType, UnifiedJob +from awx.main.models import InventorySource, Credential, CredentialType, UnifiedJob, ExecutionEnvironment from awx.main.constants import CLOUD_PROVIDERS, STANDARD_INVENTORY_UPDATE_ENV from awx.main.tests import data @@ -110,7 +110,8 @@ def read_content(private_data_dir, raw_env, inventory_update): continue # Ansible runner abs_file_path = os.path.join(private_data_dir, filename) file_aliases[abs_file_path] = filename - if abs_file_path in inverse_env: + runner_path = os.path.join('/runner', os.path.basename(abs_file_path)) + if runner_path in inverse_env: referenced_paths.add(abs_file_path) alias = 'file_reference' for i in range(10): @@ -121,7 +122,7 @@ def read_content(private_data_dir, raw_env, inventory_update): raise RuntimeError('Test not able to cope with >10 references by env vars. ' 'Something probably went very wrong.') file_aliases[abs_file_path] = alias - for env_key in inverse_env[abs_file_path]: + for env_key in inverse_env[runner_path]: env[env_key] = '{{{{ {} }}}}'.format(alias) try: with open(abs_file_path, 'r') as f: @@ -182,6 +183,8 @@ def create_reference_data(source_dir, env, content): @pytest.mark.django_db @pytest.mark.parametrize('this_kind', CLOUD_PROVIDERS) def test_inventory_update_injected_content(this_kind, inventory, fake_credential_factory): + ExecutionEnvironment.objects.create(name='test EE', managed_by_tower=True) + injector = InventorySource.injectors[this_kind] if injector.plugin_name is None: pytest.skip('Use of inventory plugin is not enabled for this source') @@ -197,12 +200,14 @@ def test_inventory_update_injected_content(this_kind, inventory, fake_credential inventory_update = inventory_source.create_unified_job() task = RunInventoryUpdate() - def substitute_run(envvars=None, **_kw): + def substitute_run(awx_receptor_job): """This method will replace run_pexpect instead of running, it will read the private data directory contents It will make assertions that the contents are correct If MAKE_INVENTORY_REFERENCE_FILES is set, it will produce reference files """ + envvars = awx_receptor_job.runner_params['envvars'] + private_data_dir = envvars.pop('AWX_PRIVATE_DATA_DIR') assert envvars.pop('ANSIBLE_INVENTORY_ENABLED') == 'auto' set_files = bool(os.getenv("MAKE_INVENTORY_REFERENCE_FILES", 'false').lower()[0] not in ['f', '0']) @@ -214,9 +219,6 @@ def test_inventory_update_injected_content(this_kind, inventory, fake_credential f"'{inventory_filename}' file not found in inventory update runtime files {content.keys()}" env.pop('ANSIBLE_COLLECTIONS_PATHS', None) # collection paths not relevant to this test - env.pop('PYTHONPATH') - env.pop('VIRTUAL_ENV') - env.pop('PROOT_TMP_DIR') base_dir = os.path.join(DATA, 'plugins') if not os.path.exists(base_dir): os.mkdir(base_dir) @@ -256,6 +258,6 @@ def test_inventory_update_injected_content(this_kind, inventory, fake_credential # Also do not send websocket status updates with mock.patch.object(UnifiedJob, 'websocket_emit_status', mock.Mock()): # The point of this test is that we replace run with assertions - with mock.patch('awx.main.tasks.ansible_runner.interface.run', substitute_run): + with mock.patch('awx.main.tasks.AWXReceptorJob.run', substitute_run): # so this sets up everything for a run and then yields control over to substitute_run task.run(inventory_update.pk) diff --git a/awx/main/tests/functional/test_licenses.py b/awx/main/tests/functional/test_licenses.py index 757349ee13..46700d38a8 100644 --- a/awx/main/tests/functional/test_licenses.py +++ b/awx/main/tests/functional/test_licenses.py @@ -49,7 +49,7 @@ def test_python_and_js_licenses(): def read_api_requirements(path): ret = {} - for req_file in ['requirements.txt', 'requirements_ansible.txt', 'requirements_git.txt', 'requirements_ansible_git.txt']: + for req_file in ['requirements.txt', 'requirements_git.txt']: fname = '%s/%s' % (path, req_file) for reqt in parse_requirements(fname, session=''): diff --git a/awx/main/tests/unit/api/serializers/test_job_serializers.py b/awx/main/tests/unit/api/serializers/test_job_serializers.py index e7b0ee7792..53cc07676d 100644 --- a/awx/main/tests/unit/api/serializers/test_job_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_serializers.py @@ -40,7 +40,7 @@ def project_update(mocker): @pytest.fixture def job(mocker, job_template, project_update): return mocker.MagicMock(pk=5, job_template=job_template, project_update=project_update, - workflow_job_id=None) + workflow_job_id=None, execution_environment_id=None) @pytest.fixture diff --git a/awx/main/tests/unit/test_capacity.py b/awx/main/tests/unit/test_capacity.py index 16fe81053c..1da05ec1f3 100644 --- a/awx/main/tests/unit/test_capacity.py +++ b/awx/main/tests/unit/test_capacity.py @@ -11,7 +11,7 @@ class FakeObject(object): class Job(FakeObject): task_impact = 43 - is_containerized = False + is_container_group_task = False def log_format(self): return 'job 382 (fake)' diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 053745cc64..3acdd7ead9 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -6,7 +6,6 @@ import os import shutil import tempfile -from backports.tempfile import TemporaryDirectory import fcntl from unittest import mock import pytest @@ -19,6 +18,7 @@ from awx.main.models import ( AdHocCommand, Credential, CredentialType, + ExecutionEnvironment, Inventory, InventorySource, InventoryUpdate, @@ -347,11 +347,12 @@ def pytest_generate_tests(metafunc): ) -def parse_extra_vars(args): +def parse_extra_vars(args, private_data_dir): extra_vars = {} for chunk in args: - if chunk.startswith('@/tmp/'): - with open(chunk.strip('@'), 'r') as f: + if chunk.startswith('@/runner/'): + local_path = os.path.join(private_data_dir, os.path.basename(chunk.strip('@'))) + with open(local_path, 'r') as f: extra_vars.update(yaml.load(f, Loader=SafeLoader)) return extra_vars @@ -546,44 +547,6 @@ class TestGenericRun(): job_cwd='/foobar', job_env={'switch': 'blade', 'foot': 'ball', 'secret_key': 'redacted_value'}) - def test_uses_process_isolation(self, settings): - job = Job(project=Project(), inventory=Inventory()) - task = tasks.RunJob() - task.should_use_proot = lambda instance: True - task.instance = job - - private_data_dir = '/foo' - cwd = '/bar' - - settings.AWX_PROOT_HIDE_PATHS = ['/AWX_PROOT_HIDE_PATHS1', '/AWX_PROOT_HIDE_PATHS2'] - settings.ANSIBLE_VENV_PATH = '/ANSIBLE_VENV_PATH' - settings.AWX_VENV_PATH = '/AWX_VENV_PATH' - - process_isolation_params = task.build_params_process_isolation(job, private_data_dir, cwd) - assert True is process_isolation_params['process_isolation'] - assert process_isolation_params['process_isolation_path'].startswith(settings.AWX_PROOT_BASE_PATH), \ - "Directory where a temp directory will be created for the remapping to take place" - assert private_data_dir in process_isolation_params['process_isolation_show_paths'], \ - "The per-job private data dir should be in the list of directories the user can see." - assert cwd in process_isolation_params['process_isolation_show_paths'], \ - "The current working directory should be in the list of directories the user can see." - - for p in [settings.AWX_PROOT_BASE_PATH, - '/etc/tower', - '/etc/ssh', - '/var/lib/awx', - '/var/log', - settings.PROJECTS_ROOT, - settings.JOBOUTPUT_ROOT, - '/AWX_PROOT_HIDE_PATHS1', - '/AWX_PROOT_HIDE_PATHS2']: - assert p in process_isolation_params['process_isolation_hide_paths'] - assert 9 == len(process_isolation_params['process_isolation_hide_paths']) - assert '/ANSIBLE_VENV_PATH' in process_isolation_params['process_isolation_ro_paths'] - assert '/AWX_VENV_PATH' in process_isolation_params['process_isolation_ro_paths'] - assert 2 == len(process_isolation_params['process_isolation_ro_paths']) - - @mock.patch('os.makedirs') def test_build_params_resource_profiling(self, os_makedirs): job = Job(project=Project(), inventory=Inventory()) @@ -597,7 +560,7 @@ class TestGenericRun(): assert resource_profiling_params['resource_profiling_cpu_poll_interval'] == '0.25' assert resource_profiling_params['resource_profiling_memory_poll_interval'] == '0.25' assert resource_profiling_params['resource_profiling_pid_poll_interval'] == '0.25' - assert resource_profiling_params['resource_profiling_results_dir'] == '/fake_private_data_dir/artifacts/playbook_profiling' + assert resource_profiling_params['resource_profiling_results_dir'] == '/runner/artifacts/playbook_profiling' @pytest.mark.parametrize("scenario, profiling_enabled", [ @@ -656,34 +619,13 @@ class TestGenericRun(): env = task.build_env(job, private_data_dir) assert env['FOO'] == 'BAR' - def test_valid_custom_virtualenv(self, patch_Job, private_data_dir): - job = Job(project=Project(), inventory=Inventory()) - - with TemporaryDirectory(dir=settings.BASE_VENV_PATH) as tempdir: - job.project.custom_virtualenv = tempdir - os.makedirs(os.path.join(tempdir, 'lib')) - os.makedirs(os.path.join(tempdir, 'bin', 'activate')) - - task = tasks.RunJob() - env = task.build_env(job, private_data_dir) - - assert env['PATH'].startswith(os.path.join(tempdir, 'bin')) - assert env['VIRTUAL_ENV'] == tempdir - - def test_invalid_custom_virtualenv(self, patch_Job, private_data_dir): - job = Job(project=Project(), inventory=Inventory()) - job.project.custom_virtualenv = '/var/lib/awx/venv/missing' - task = tasks.RunJob() - - with pytest.raises(tasks.InvalidVirtualenvError) as e: - task.build_env(job, private_data_dir) - - assert 'Invalid virtual environment selected: /var/lib/awx/venv/missing' == str(e.value) - +@pytest.mark.django_db class TestAdhocRun(TestJobExecution): def test_options_jinja_usage(self, adhoc_job, adhoc_update_model_wrapper): + ExecutionEnvironment.objects.create(name='test EE', managed_by_tower=True) + adhoc_job.module_args = '{{ ansible_ssh_pass }}' adhoc_job.websocket_emit_status = mock.Mock() adhoc_job.send_notification_templates = mock.Mock() @@ -1203,7 +1145,9 @@ class TestJobCredentials(TestJobExecution): credential.credential_type.inject_credential( credential, env, safe_env, [], private_data_dir ) - json_data = json.load(open(env['GCE_CREDENTIALS_FILE_PATH'], 'rb')) + runner_path = env['GCE_CREDENTIALS_FILE_PATH'] + local_path = os.path.join(private_data_dir, os.path.basename(runner_path)) + json_data = json.load(open(local_path, 'rb')) assert json_data['type'] == 'service_account' assert json_data['private_key'] == self.EXAMPLE_PRIVATE_KEY assert json_data['client_email'] == 'bob' @@ -1306,7 +1250,11 @@ class TestJobCredentials(TestJobExecution): credential, env, {}, [], private_data_dir ) - shade_config = open(env['OS_CLIENT_CONFIG_FILE'], 'r').read() + # convert container path to host machine path + config_loc = os.path.join( + private_data_dir, os.path.basename(env['OS_CLIENT_CONFIG_FILE']) + ) + shade_config = open(config_loc, 'r').read() assert shade_config == '\n'.join([ 'clouds:', ' devstack:', @@ -1344,7 +1292,7 @@ class TestJobCredentials(TestJobExecution): ) config = configparser.ConfigParser() - config.read(env['OVIRT_INI_PATH']) + config.read(os.path.join(private_data_dir, os.path.basename(env['OVIRT_INI_PATH']))) assert config.get('ovirt', 'ovirt_url') == 'some-ovirt-host.example.org' assert config.get('ovirt', 'ovirt_username') == 'bob' assert config.get('ovirt', 'ovirt_password') == 'some-pass' @@ -1577,7 +1525,7 @@ class TestJobCredentials(TestJobExecution): credential.credential_type.inject_credential( credential, {}, {}, args, private_data_dir ) - extra_vars = parse_extra_vars(args) + extra_vars = parse_extra_vars(args, private_data_dir) assert extra_vars["api_token"] == "ABC123" assert hasattr(extra_vars["api_token"], '__UNSAFE__') @@ -1612,7 +1560,7 @@ class TestJobCredentials(TestJobExecution): credential.credential_type.inject_credential( credential, {}, {}, args, private_data_dir ) - extra_vars = parse_extra_vars(args) + extra_vars = parse_extra_vars(args, private_data_dir) assert extra_vars["turbo_button"] == "True" return ['successful', 0] @@ -1647,7 +1595,7 @@ class TestJobCredentials(TestJobExecution): credential.credential_type.inject_credential( credential, {}, {}, args, private_data_dir ) - extra_vars = parse_extra_vars(args) + extra_vars = parse_extra_vars(args, private_data_dir) assert extra_vars["turbo_button"] == "FAST!" @@ -1687,7 +1635,7 @@ class TestJobCredentials(TestJobExecution): credential, {}, {}, args, private_data_dir ) - extra_vars = parse_extra_vars(args) + extra_vars = parse_extra_vars(args, private_data_dir) assert extra_vars["password"] == "SUPER-SECRET-123" def test_custom_environment_injectors_with_file(self, private_data_dir): @@ -1722,7 +1670,8 @@ class TestJobCredentials(TestJobExecution): credential, env, {}, [], private_data_dir ) - assert open(env['MY_CLOUD_INI_FILE'], 'r').read() == '[mycloud]\nABC123' + path = os.path.join(private_data_dir, os.path.basename(env['MY_CLOUD_INI_FILE'])) + assert open(path, 'r').read() == '[mycloud]\nABC123' def test_custom_environment_injectors_with_unicode_content(self, private_data_dir): value = 'Iñtërnâtiônàlizætiøn' @@ -1746,7 +1695,8 @@ class TestJobCredentials(TestJobExecution): credential, env, {}, [], private_data_dir ) - assert open(env['MY_CLOUD_INI_FILE'], 'r').read() == value + path = os.path.join(private_data_dir, os.path.basename(env['MY_CLOUD_INI_FILE'])) + assert open(path, 'r').read() == value def test_custom_environment_injectors_with_files(self, private_data_dir): some_cloud = CredentialType( @@ -1786,8 +1736,10 @@ class TestJobCredentials(TestJobExecution): credential, env, {}, [], private_data_dir ) - assert open(env['MY_CERT_INI_FILE'], 'r').read() == '[mycert]\nCERT123' - assert open(env['MY_KEY_INI_FILE'], 'r').read() == '[mykey]\nKEY123' + cert_path = os.path.join(private_data_dir, os.path.basename(env['MY_CERT_INI_FILE'])) + key_path = os.path.join(private_data_dir, os.path.basename(env['MY_KEY_INI_FILE'])) + assert open(cert_path, 'r').read() == '[mycert]\nCERT123' + assert open(key_path, 'r').read() == '[mykey]\nKEY123' def test_multi_cloud(self, private_data_dir): gce = CredentialType.defaults['gce']() @@ -1826,7 +1778,8 @@ class TestJobCredentials(TestJobExecution): assert env['AZURE_AD_USER'] == 'bob' assert env['AZURE_PASSWORD'] == 'secret' - json_data = json.load(open(env['GCE_CREDENTIALS_FILE_PATH'], 'rb')) + path = os.path.join(private_data_dir, os.path.basename(env['GCE_CREDENTIALS_FILE_PATH'])) + json_data = json.load(open(path, 'rb')) assert json_data['type'] == 'service_account' assert json_data['private_key'] == self.EXAMPLE_PRIVATE_KEY assert json_data['client_email'] == 'bob' @@ -1971,29 +1924,6 @@ class TestProjectUpdateCredentials(TestJobExecution): ] } - def test_process_isolation_exposes_projects_root(self, private_data_dir, project_update): - task = tasks.RunProjectUpdate() - task.revision_path = 'foobar' - task.instance = project_update - ssh = CredentialType.defaults['ssh']() - project_update.scm_type = 'git' - project_update.credential = Credential( - pk=1, - credential_type=ssh, - ) - process_isolation = task.build_params_process_isolation(job, private_data_dir, 'cwd') - - assert process_isolation['process_isolation'] is True - assert settings.PROJECTS_ROOT in process_isolation['process_isolation_show_paths'] - - task._write_extra_vars_file = mock.Mock() - - with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): - task.build_extra_vars_file(project_update, private_data_dir) - - call_args, _ = task._write_extra_vars_file.call_args_list[0] - _, extra_vars = call_args - def test_username_and_password_auth(self, project_update, scm_type): task = tasks.RunProjectUpdate() ssh = CredentialType.defaults['ssh']() @@ -2107,7 +2037,8 @@ class TestInventoryUpdateCredentials(TestJobExecution): assert '-i' in ' '.join(args) script = args[args.index('-i') + 1] - with open(script, 'r') as f: + host_script = script.replace('/runner', private_data_dir) + with open(host_script, 'r') as f: assert f.read() == inventory_update.source_script.script assert env['FOO'] == 'BAR' if with_credential: @@ -2307,7 +2238,8 @@ class TestInventoryUpdateCredentials(TestJobExecution): private_data_files = task.build_private_data_files(inventory_update, private_data_dir) env = task.build_env(inventory_update, private_data_dir, False, private_data_files) - shade_config = open(env['OS_CLIENT_CONFIG_FILE'], 'r').read() + path = os.path.join(private_data_dir, os.path.basename(env['OS_CLIENT_CONFIG_FILE'])) + shade_config = open(path, 'r').read() assert '\n'.join([ 'clouds:', ' devstack:', diff --git a/awx/main/tests/unit/utils/test_common.py b/awx/main/tests/unit/utils/test_common.py index 8c07020c53..b86798ec51 100644 --- a/awx/main/tests/unit/utils/test_common.py +++ b/awx/main/tests/unit/utils/test_common.py @@ -9,9 +9,6 @@ import json import yaml from unittest import mock -from backports.tempfile import TemporaryDirectory -from django.conf import settings - from rest_framework.exceptions import ParseError from awx.main.utils import common @@ -194,24 +191,3 @@ def test_extract_ansible_vars(): redacted, var_list = common.extract_ansible_vars(json.dumps(my_dict)) assert var_list == set(['ansible_connetion_setting']) assert redacted == {"foobar": "baz"} - - -def test_get_custom_venv_choices(): - bundled_venv = os.path.join(settings.BASE_VENV_PATH, 'ansible', '') - assert sorted(common.get_custom_venv_choices()) == [bundled_venv] - - with TemporaryDirectory(dir=settings.BASE_VENV_PATH, prefix='tmp') as temp_dir: - os.makedirs(os.path.join(temp_dir, 'bin', 'activate')) - - custom_venv_dir = os.path.join(temp_dir, 'custom') - custom_venv_1 = os.path.join(custom_venv_dir, 'venv-1') - custom_venv_awx = os.path.join(custom_venv_dir, 'custom', 'awx') - - os.makedirs(os.path.join(custom_venv_1, 'bin', 'activate')) - os.makedirs(os.path.join(custom_venv_awx, 'bin', 'activate')) - - assert sorted(common.get_custom_venv_choices([custom_venv_dir])) == [ - bundled_venv, - os.path.join(temp_dir, ''), - os.path.join(custom_venv_1, '') - ] diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 283a028f3f..ad90d5e4ec 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -55,7 +55,8 @@ __all__ = [ 'model_instance_diff', 'parse_yaml_or_json', 'RequireDebugTrueOrTest', 'has_model_field_prefetched', 'set_environ', 'IllegalArgumentError', 'get_custom_venv_choices', 'get_external_account', 'task_manager_bulk_reschedule', - 'schedule_task_manager', 'classproperty', 'create_temporary_fifo', 'truncate_stdout' + 'schedule_task_manager', 'classproperty', 'create_temporary_fifo', 'truncate_stdout', + 'deepmerge' ] @@ -1079,3 +1080,21 @@ def truncate_stdout(stdout, size): set_count += 1 return stdout + u'\u001b[0m' * (set_count - reset_count) + + +def deepmerge(a, b): + """ + Merge dict structures and return the result. + + >>> a = {'first': {'all_rows': {'pass': 'dog', 'number': '1'}}} + >>> b = {'first': {'all_rows': {'fail': 'cat', 'number': '5'}}} + >>> import pprint; pprint.pprint(deepmerge(a, b)) + {'first': {'all_rows': {'fail': 'cat', 'number': '5', 'pass': 'dog'}}} + """ + if isinstance(a, dict) and isinstance(b, dict): + return dict([(k, deepmerge(a.get(k), b.get(k))) + for k in set(a.keys()).union(b.keys())]) + elif b is None: + return a + else: + return b diff --git a/awx/main/utils/external_logging.py b/awx/main/utils/external_logging.py index 7213f6f749..3c281719ff 100644 --- a/awx/main/utils/external_logging.py +++ b/awx/main/utils/external_logging.py @@ -32,7 +32,7 @@ def construct_rsyslog_conf_template(settings=settings): '$IncludeConfig /var/lib/awx/rsyslog/conf.d/*.conf', f'main_queue(queue.spoolDirectory="{spool_directory}" queue.maxdiskspace="{max_disk_space}g" queue.type="Disk" queue.filename="awx-external-logger-backlog")', # noqa 'module(load="imuxsock" SysSock.Use="off")', - 'input(type="imuxsock" Socket="' + settings.LOGGING['handlers']['external_logger']['address'] + '" unlink="on")', + 'input(type="imuxsock" Socket="' + settings.LOGGING['handlers']['external_logger']['address'] + '" unlink="on" RateLimit.Burst="0")', 'template(name="awx" type="string" string="%rawmsg-after-pri%")', ]) diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index f59ba17478..9ce1afabbe 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -3,7 +3,8 @@ # Python import logging -import os.path +import sys +import traceback # Django from django.conf import settings @@ -21,27 +22,31 @@ class RSysLogHandler(logging.handlers.SysLogHandler): super(RSysLogHandler, self)._connect_unixsocket(address) self.socket.setblocking(False) + def handleError(self, record): + # for any number of reasons, rsyslogd has gone to lunch; + # this usually means that it's just been restarted (due to + # a configuration change) unfortunately, we can't log that + # because...rsyslogd is down (and would just put us back down this + # code path) + # as a fallback, it makes the most sense to just write the + # messages to sys.stderr (which will end up in supervisord logs, + # and in containerized installs, cascaded down to pod logs) + # because the alternative is blocking the + # socket.send() in the Python process, which we definitely don't + # want to do) + msg = f'{record.asctime} ERROR rsyslogd was unresponsive: ' + exc = traceback.format_exc() + try: + msg += exc.splitlines()[-1] + except Exception: + msg += exc + msg = '\n'.join([msg, record.msg, '']) + sys.stderr.write(msg) + def emit(self, msg): if not settings.LOG_AGGREGATOR_ENABLED: return - if not os.path.exists(settings.LOGGING['handlers']['external_logger']['address']): - return - try: - return super(RSysLogHandler, self).emit(msg) - except ConnectionRefusedError: - # rsyslogd has gone to lunch; this generally means that it's just - # been restarted (due to a configuration change) - # unfortunately, we can't log that because...rsyslogd is down (and - # would just us back ddown this code path) - pass - except BlockingIOError: - # for , rsyslogd is no longer reading from the domain socket, and - # we're unable to write any more to it without blocking (we've seen this behavior - # from time to time when logging is totally misconfigured; - # in this scenario, it also makes more sense to just drop the messages, - # because the alternative is blocking the socket.send() in the - # Python process, which we definitely don't want to do) - pass + return super(RSysLogHandler, self).emit(msg) class SpecialInventoryHandler(logging.Handler): diff --git a/awx/playbooks/project_update.yml b/awx/playbooks/project_update.yml index a7b7007d56..e00bed4249 100644 --- a/awx/playbooks/project_update.yml +++ b/awx/playbooks/project_update.yml @@ -24,9 +24,7 @@ tasks: - name: delete project directory before update - file: - path: "{{project_path|quote}}" - state: absent + command: "rm -rf {{project_path}}/*" # volume mounted, cannot delete folder itself tags: - delete @@ -57,6 +55,8 @@ force: "{{scm_clean}}" username: "{{scm_username|default(omit)}}" password: "{{scm_password|default(omit)}}" + # must be in_place because folder pre-existing, because it is mounted + in_place: true environment: LC_ALL: 'en_US.UTF-8' register: svn_result @@ -206,6 +206,9 @@ ANSIBLE_FORCE_COLOR: false ANSIBLE_COLLECTIONS_PATHS: "{{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_collections" GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no" + # Put the local tmp directory in same volume as collection destination + # otherwise, files cannot be moved accross volumes and will cause error + ANSIBLE_LOCAL_TEMP: "{{projects_root}}/.__awx_cache/{{local_path}}/stage/tmp" when: - "ansible_version.full is version_compare('2.9', '>=')" diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index e0c1db197b..ba8b8c69cf 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -59,11 +59,23 @@ DATABASES = { } } +# Whether or not the deployment is a K8S-based deployment +# In K8S-based deployments, instances have zero capacity - all playbook +# automation is intended to flow through defined Container Groups that +# interface with some (or some set of) K8S api (which may or may not include +# the K8S cluster where awx itself is running) +IS_K8S = False + +# TODO: remove this setting in favor of a default execution environment +AWX_EXECUTION_ENVIRONMENT_DEFAULT_IMAGE = 'quay.io/ansible/awx-ee' + AWX_CONTAINER_GROUP_K8S_API_TIMEOUT = 10 AWX_CONTAINER_GROUP_POD_LAUNCH_RETRIES = 100 AWX_CONTAINER_GROUP_POD_LAUNCH_RETRY_DELAY = 5 -AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE = 'default' -AWX_CONTAINER_GROUP_DEFAULT_IMAGE = 'ansible/ansible-runner' +AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE = os.getenv('MY_POD_NAMESPACE', 'default') + +# TODO: remove this setting in favor of a default execution environment +AWX_CONTAINER_GROUP_DEFAULT_IMAGE = AWX_EXECUTION_ENVIRONMENT_DEFAULT_IMAGE # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ @@ -173,6 +185,7 @@ REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] PROXY_IP_ALLOWED_LIST = [] CUSTOM_VENV_PATHS = [] +DEFAULT_EXECUTION_ENVIRONMENT = None # Note: This setting may be overridden by database settings. STDOUT_MAX_BYTES_DISPLAY = 1048576 @@ -679,7 +692,7 @@ AD_HOC_COMMANDS = [ 'win_user', ] -INV_ENV_VARIABLE_BLOCKED = ("HOME", "USER", "_", "TERM") +INV_ENV_VARIABLE_BLOCKED = ("HOME", "USER", "_", "TERM", "PATH") # ---------------- # -- Amazon EC2 -- @@ -783,6 +796,8 @@ TOWER_URL_BASE = "https://towerhost" INSIGHTS_URL_BASE = "https://example.org" INSIGHTS_AGENT_MIME = 'application/example' +# See https://github.com/ansible/awx-facts-playbooks +INSIGHTS_SYSTEM_ID_FILE='/etc/redhat-access-insights/machine-id' TOWER_SETTINGS_MANIFEST = {} diff --git a/awx/settings/development.py b/awx/settings/development.py index 6181d16ec6..d181ca10fc 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -177,15 +177,6 @@ CELERYBEAT_SCHEDULE.update({ # noqa CLUSTER_HOST_ID = socket.gethostname() - -if 'Docker Desktop' in os.getenv('OS', ''): - os.environ['SDB_NOTIFY_HOST'] = 'docker.for.mac.host.internal' -else: - try: - os.environ['SDB_NOTIFY_HOST'] = os.popen('ip route').read().split(' ')[2] - except Exception: - pass - AWX_CALLBACK_PROFILE = True if 'sqlite3' not in DATABASES['default']['ENGINE']: # noqa diff --git a/awx/ui_next/Dockerfile b/awx/ui_next/Dockerfile index a710a820cd..1d35f9221d 100644 --- a/awx/ui_next/Dockerfile +++ b/awx/ui_next/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /ui_next ADD public public ADD package.json package.json ADD package-lock.json package-lock.json +ADD .linguirc .linguirc COPY ${NPMRC_FILE} .npmrc RUN npm install ADD src src diff --git a/awx/ui_next/SEARCH.md b/awx/ui_next/SEARCH.md index 111dfb2f56..131e4fe277 100644 --- a/awx/ui_next/SEARCH.md +++ b/awx/ui_next/SEARCH.md @@ -86,7 +86,7 @@ Instances of orgs list include: **Instance Groups list** - Name - search is ?name=ig - - ? is_containerized boolean choice (doesn't work right now in API but will soon) - search is ?is_containerized=true + - ? is_container_group boolean choice (doesn't work right now in API but will soon) - search is ?is_container_group=true - ? credential name - search is ?credentials__name=kubey Instance of instance groups list include: @@ -136,7 +136,7 @@ Instance of team lists include: **Credentials list** - Name - - ? Type (dropdown on right with different types) + - ? Type (dropdown on right with different types) - ? Created by (username) - ? Modified by (username) @@ -273,7 +273,7 @@ For the UI url params, we want to only encode those params that aren't defaults, #### mergeParams vs. replaceParams -**mergeParams** is used to suppport putting values with the same key +**mergeParams** is used to suppport putting values with the same key From a UX perspective, we wanted to be able to support searching on the same key multiple times (i.e. searching for things like `?foo=bar&foo=baz`). We do this by creating an array of all values. i.e.: @@ -361,7 +361,7 @@ Smart search will be able to craft the tag through various states. Note that th "instance_groups__search" ], ``` - + PHASE 3: keys, give by object key names for data.actions.GET - type is given for each key which we could use to help craft the value diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index 0d470a17ca..24371abfc7 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -55,6 +55,11 @@ "react-scripts": "^3.4.4" }, "scripts": { + "prelint": "lingui compile", + "prestart": "lingui compile", + "prestart-instrumented": "lingui compile", + "pretest": "lingui compile", + "pretest-watch": "lingui compile", "start": "PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start", "start-instrumented": "DEBUG=instrument-cra PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts -r @cypress/instrument-cra start", "build": "INLINE_RUNTIME_CHUNK=false react-scripts build", diff --git a/awx/ui_next/src/api/index.js b/awx/ui_next/src/api/index.js index 3160ebd907..d048237b74 100644 --- a/awx/ui_next/src/api/index.js +++ b/awx/ui_next/src/api/index.js @@ -7,6 +7,7 @@ import CredentialInputSources from './models/CredentialInputSources'; import CredentialTypes from './models/CredentialTypes'; import Credentials from './models/Credentials'; import Dashboard from './models/Dashboard'; +import ExecutionEnvironments from './models/ExecutionEnvironments'; import Groups from './models/Groups'; import Hosts from './models/Hosts'; import InstanceGroups from './models/InstanceGroups'; @@ -50,6 +51,7 @@ const CredentialInputSourcesAPI = new CredentialInputSources(); const CredentialTypesAPI = new CredentialTypes(); const CredentialsAPI = new Credentials(); const DashboardAPI = new Dashboard(); +const ExecutionEnvironmentsAPI = new ExecutionEnvironments(); const GroupsAPI = new Groups(); const HostsAPI = new Hosts(); const InstanceGroupsAPI = new InstanceGroups(); @@ -94,6 +96,7 @@ export { CredentialTypesAPI, CredentialsAPI, DashboardAPI, + ExecutionEnvironmentsAPI, GroupsAPI, HostsAPI, InstanceGroupsAPI, diff --git a/awx/ui_next/src/api/models/ExecutionEnvironments.js b/awx/ui_next/src/api/models/ExecutionEnvironments.js new file mode 100644 index 0000000000..2df933d53a --- /dev/null +++ b/awx/ui_next/src/api/models/ExecutionEnvironments.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class ExecutionEnvironments extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/execution_environments/'; + } +} + +export default ExecutionEnvironments; diff --git a/awx/ui_next/src/api/models/Organizations.js b/awx/ui_next/src/api/models/Organizations.js index ce236067b4..fd980fece8 100644 --- a/awx/ui_next/src/api/models/Organizations.js +++ b/awx/ui_next/src/api/models/Organizations.js @@ -30,6 +30,18 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) { }); } + readExecutionEnvironments(id, params) { + return this.http.get(`${this.baseUrl}${id}/execution_environments/`, { + params, + }); + } + + readExecutionEnvironmentsOptions(id, params) { + return this.http.options(`${this.baseUrl}${id}/execution_environments/`, { + params, + }); + } + createUser(id, data) { return this.http.post(`${this.baseUrl}${id}/users/`, data); } diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/CredentialsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/CredentialsStep.jsx index 1b736c6ad0..a3659d43f7 100644 --- a/awx/ui_next/src/components/LaunchPrompt/steps/CredentialsStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/steps/CredentialsStep.jsx @@ -167,9 +167,10 @@ function CredentialsStep({ i18n }) { const hasSameVaultID = val => val?.inputs?.vault_id !== undefined && val?.inputs?.vault_id === item?.inputs?.vault_id; - const hasSameKind = val => val.kind === item.kind; + const hasSameCredentialType = val => + val.credential_type === item.credential_type; const newItems = field.value.filter(i => - isVault ? !hasSameVaultID(i) : !hasSameKind(i) + isVault ? !hasSameVaultID(i) : !hasSameCredentialType(i) ); newItems.push(item); helpers.setValue(newItems); diff --git a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx new file mode 100644 index 0000000000..4647d5809e --- /dev/null +++ b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx @@ -0,0 +1,195 @@ +import React, { useCallback, useEffect } from 'react'; +import { string, func, bool, oneOfType, number } from 'prop-types'; +import { useLocation } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { FormGroup, Tooltip } from '@patternfly/react-core'; + +import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api'; +import { ExecutionEnvironment } from '../../types'; +import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs'; +import Popover from '../Popover'; +import OptionsList from '../OptionsList'; +import useRequest from '../../util/useRequest'; + +import Lookup from './Lookup'; +import LookupErrorMessage from './shared/LookupErrorMessage'; + +const QS_CONFIG = getQSConfig('execution_environments', { + page: 1, + page_size: 5, + order_by: 'name', +}); + +function ExecutionEnvironmentLookup({ + globallyAvailable, + i18n, + isDefaultEnvironment, + isDisabled, + onBlur, + onChange, + organizationId, + popoverContent, + projectId, + tooltip, + value, +}) { + const location = useLocation(); + + const { + request: fetchProject, + error: fetchProjectError, + isLoading: fetchProjectLoading, + result: project, + } = useRequest( + useCallback(async () => { + if (!projectId) { + return {}; + } + const { data } = await ProjectsAPI.readDetail(projectId); + return data; + }, [projectId]), + { + project: null, + } + ); + + useEffect(() => { + fetchProject(); + }, [fetchProject]); + + const { + result: { + executionEnvironments, + count, + relatedSearchableKeys, + searchableKeys, + }, + request: fetchExecutionEnvironments, + error, + isLoading, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); + const globallyAvailableParams = globallyAvailable + ? { or__organization__isnull: 'True' } + : {}; + const organizationIdParams = + organizationId || project?.organization + ? { or__organization__id: organizationId } + : {}; + const [{ data }, actionsResponse] = await Promise.all([ + ExecutionEnvironmentsAPI.read( + mergeParams(params, { + ...globallyAvailableParams, + ...organizationIdParams, + }) + ), + ExecutionEnvironmentsAPI.readOptions(), + ]); + return { + executionEnvironments: data.results, + count: data.count, + relatedSearchableKeys: ( + actionsResponse?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + actionsResponse.data.actions?.GET || {} + ).filter(key => actionsResponse.data.actions?.GET[key].filterable), + }; + }, [location, globallyAvailable, organizationId, project]), + { + executionEnvironments: [], + count: 0, + relatedSearchableKeys: [], + searchableKeys: [], + } + ); + + useEffect(() => { + fetchExecutionEnvironments(); + }, [fetchExecutionEnvironments]); + + const renderLookup = () => ( + <> + ( + dispatch({ type: 'SELECT_ITEM', item })} + deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} + /> + )} + /> + + ); + + return ( + } + > + {isDisabled ? ( + {renderLookup()} + ) : ( + renderLookup() + )} + + + + ); +} + +ExecutionEnvironmentLookup.propTypes = { + value: ExecutionEnvironment, + popoverContent: string, + onChange: func.isRequired, + isDefaultEnvironment: bool, + projectId: oneOfType([number, string]), + organizationId: oneOfType([number, string]), +}; + +ExecutionEnvironmentLookup.defaultProps = { + popoverContent: '', + isDefaultEnvironment: false, + value: null, + projectId: null, + organizationId: null, +}; + +export default withI18n()(ExecutionEnvironmentLookup); diff --git a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx new file mode 100644 index 0000000000..7093854366 --- /dev/null +++ b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; +import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup'; +import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api'; + +jest.mock('../../api'); + +const mockedExecutionEnvironments = { + count: 1, + results: [ + { + id: 2, + name: 'Foo', + image: 'quay.io/ansible/awx-ee', + pull: 'missing', + }, + ], +}; + +const executionEnvironment = { + id: 42, + name: 'Bar', + image: 'quay.io/ansible/bar', + pull: 'missing', +}; + +describe('ExecutionEnvironmentLookup', () => { + let wrapper; + + beforeEach(() => { + ExecutionEnvironmentsAPI.read.mockResolvedValue( + mockedExecutionEnvironments + ); + ProjectsAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Fuz', + }, + ], + }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('should render successfully', async () => { + ExecutionEnvironmentsAPI.readOptions.mockReturnValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); + await act(async () => { + wrapper = mountWithContexts( + {}} + /> + ); + }); + wrapper.update(); + expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2); + expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1); + expect( + wrapper.find('FormGroup[label="Default Execution Environment"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="Execution Environment"]').length + ).toBe(0); + }); + + test('should fetch execution environments', async () => { + await act(async () => { + wrapper = mountWithContexts( + {}} + /> + ); + }); + expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2); + expect( + wrapper.find('FormGroup[label="Default Execution Environment"]').length + ).toBe(0); + expect( + wrapper.find('FormGroup[label="Execution Environment"]').length + ).toBe(1); + }); +}); diff --git a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx index 3fb443426e..8252c9035c 100644 --- a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx +++ b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx @@ -30,6 +30,7 @@ function OrganizationLookup({ history, autoPopulate, isDisabled, + helperText, }) { const autoPopulateLookup = useAutoPopulateLookup(onChange); @@ -79,6 +80,7 @@ function OrganizationLookup({ isRequired={required} validated={isValid ? 'default' : 'error'} label={i18n._(t`Organization`)} + helperText={helperText} > { - const { name, username } = props; - if (!name && !username) { +const requiredField = props => { + const { name, username, image } = props; + if (!name && !username && !image) { return new Error( - `One of 'name' or 'username' is required by ItemToDelete component.` + `One of 'name', 'username' or 'image' is required by ItemToDelete component.` ); } if (name) { @@ -47,13 +47,24 @@ const requireNameOrUsername = props => { 'ItemToDelete' ); } + if (image) { + checkPropTypes( + { + image: string, + }, + { image: props.image }, + 'prop', + 'ItemToDelete' + ); + } return null; }; const ItemToDelete = shape({ id: number.isRequired, - name: requireNameOrUsername, - username: requireNameOrUsername, + name: requiredField, + username: requiredField, + image: requiredField, summary_fields: shape({ user_capabilities: shape({ delete: bool.isRequired, @@ -171,7 +182,7 @@ function ToolbarDeleteButton({
{i18n._(t`This action will delete the following:`)}
{itemsToDelete.map(item => ( - {item.name || item.username} + {item.name || item.username || item.image}
))} diff --git a/awx/ui_next/src/locales/en/messages.js b/awx/ui_next/src/locales/en/messages.js deleted file mode 100644 index 61cb8198b8..0000000000 --- a/awx/ui_next/src/locales/en/messages.js +++ /dev/null @@ -1 +0,0 @@ -/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}},messages:{"404":"404","> add":"> add","> edit":"> edit","AWX Logo":"AWX Logo","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Add Roles":"Add Roles","Add Team Roles":"Add Team Roles","Add User Roles":"Add User Roles","Administration":"Administration","Admins":"Admins","Ansible Environment":"Ansible Environment","Ansible Version":"Ansible Version","Applications":"Applications","Apply roles":"Apply roles","Are you sure you want to delete:":"Are you sure you want to delete:","Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team.":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("1"),"? Doing so affects all members of the team."]},"Are you sure you want to remove {0} access from {username}?":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("username"),"?"]},"Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Cancel":"Cancel","Cannot find organization with ID":"Cannot find organization with ID","Cannot find resource.":"Cannot find resource.","Cannot find route {0}.":function(a){return["Cannot find route ",a("0"),"."]},"Close":"Close","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Copyright 2019 Red Hat, Inc.":"Copyright 2019 Red Hat, Inc.","Create New Organization":"Create New Organization","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Current page":"Current page","Dashboard":"Dashboard","Delete":"Delete","Delete {0}":function(a){return["Delete ",a("0")]},"Delete {itemName}":function(a){return["Delete ",a("itemName")]},"Description":"Description","Details":"Details","Edit":"Edit","Edit Details":"Edit Details","Expand":"Expand","Failure":"Failure","First":"First","Go to first page":"Go to first page","Go to last page":"Go to last page","Go to next page":"Go to next page","Go to previous page":"Go to previous page","Help":"Help","If you {0} want to remove access for this particular user, please remove them from the team.":function(a){return["If you ",a("0")," want to remove access for this particular user, please remove them from the team."]},"Info":"Info","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Items Per Page":"Items Per Page","Items per page":"Items per page","Items {itemMin} \u2013 {itemMax} of {count}":function(a){return["Items ",a("itemMin")," \u2013 ",a("itemMax")," of ",a("count")]},"Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","Last Modified":"Last Modified","Last Name":"Last Name","License":"License","Loading...":"Loading...","Logout":"Logout","Management Jobs":"Management Jobs","Members":"Members","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","No {0} Found":function(a){return["No ",a("0")," Found"]},"Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page":"Page","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Pagination":"Pagination","Password":"Password","Per Page":"Per Page","Please add {0} to populate this list":function(a){return["Please add ",a("0")," to populate this list"]},"Please add {0} {itemName} to populate this list":function(a){return["Please add ",a("0")," ",a("itemName")," to populate this list"]},"Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Remove {0} Access":function(a){return["Remove ",a("0")," Access"]},"Resources":"Resources","Save":"Save","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select":"Select","Select Input":"Select Input","Select Users Or Teams":"Select Users Or Teams","Select a row to delete":"Select a row to delete","Select all":"Select all","Select items from list":"Select items from list","Select the Instance Groups for this Organization to run on.":"Select the Instance Groups for this Organization to run on.","Select {header}":function(a){return["Select ",a("header")]},"Selected":"Selected","Settings":"Settings","Sort":"Sort","Successful":"Successful","System":"System","System Settings":"System Settings","Team":"Team","Team Roles":"Team Roles","Teams":"Teams","Templates":"Templates","This field must not be blank":"This field must not be blank","This field must not exceed {max} characters":function(a){return["This field must not exceed ",a("max")," characters"]},"Toggle notification failure":"Toggle notification failure","Toggle notification success":"Toggle notification success","Use Default {label}":function(a){return["Use Default ",a("label")]},"User":"User","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","User Roles":"User Roles","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible {brandName}! Please Sign In.":function(a){return["Welcome to Ansible ",a("brandName"),"! Please Sign In."]},"You do not have permission to delete the following {0}: {itemsUnableToDelete}":function(a){return["You do not have permission to delete the following ",a("0"),": ",a("itemsUnableToDelete")]},"You have been logged out.":"You have been logged out.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"cancel delete":"cancel delete","confirm delete":"confirm delete","confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","items":"items","of {pageCount}":function(a){return["of ",a("pageCount")]},"pages":"pages","per page":"per page","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{0} List":function(a){return[a("0")," List"]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}}; \ No newline at end of file diff --git a/awx/ui_next/src/locales/ja/messages.js b/awx/ui_next/src/locales/ja/messages.js deleted file mode 100644 index b3b6c304be..0000000000 --- a/awx/ui_next/src/locales/ja/messages.js +++ /dev/null @@ -1 +0,0 @@ -/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){if(ord)return"other";return"other"}},messages:{"404":"404","> add":"> add","> edit":"> edit","AWX Logo":"AWX Logo","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Add Roles":"Add Roles","Add Team Roles":"Add Team Roles","Add User Roles":"Add User Roles","Administration":"Administration","Admins":"Admins","Ansible Environment":"Ansible Environment","Ansible Version":"Ansible Version","Applications":"Applications","Apply roles":"Apply roles","Are you sure you want to delete:":"Are you sure you want to delete:","Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team.":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("1"),"? Doing so affects all members of the team."]},"Are you sure you want to remove {0} access from {username}?":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("username"),"?"]},"Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Cancel":"Cancel","Cannot find organization with ID":"Cannot find organization with ID","Cannot find resource.":"Cannot find resource.","Cannot find route {0}.":function(a){return["Cannot find route ",a("0"),"."]},"Close":"Close","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Copyright 2019 Red Hat, Inc.":"Copyright 2019 Red Hat, Inc.","Create New Organization":"Create New Organization","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Current page":"Current page","Dashboard":"Dashboard","Delete":"Delete","Delete {0}":function(a){return["Delete ",a("0")]},"Delete {itemName}":function(a){return["Delete ",a("itemName")]},"Description":"Description","Details":"Details","Edit":"Edit","Edit Details":"Edit Details","Expand":"Expand","Failure":"Failure","First":"First","Go to first page":"Go to first page","Go to last page":"Go to last page","Go to next page":"Go to next page","Go to previous page":"Go to previous page","Help":"Help","If you {0} want to remove access for this particular user, please remove them from the team.":function(a){return["If you ",a("0")," want to remove access for this particular user, please remove them from the team."]},"Info":"Info","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Items Per Page":"Items Per Page","Items per page":"Items per page","Items {itemMin} \u2013 {itemMax} of {count}":function(a){return["Items ",a("itemMin")," \u2013 ",a("itemMax")," of ",a("count")]},"Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","Last Modified":"Last Modified","Last Name":"Last Name","License":"License","Loading...":"Loading...","Logout":"Logout","Management Jobs":"Management Jobs","Members":"Members","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","No {0} Found":function(a){return["No ",a("0")," Found"]},"Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page":"Page","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Pagination":"Pagination","Password":"Password","Per Page":"Per Page","Please add {0} to populate this list":function(a){return["Please add ",a("0")," to populate this list"]},"Please add {0} {itemName} to populate this list":function(a){return["Please add ",a("0")," ",a("itemName")," to populate this list"]},"Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Remove {0} Access":function(a){return["Remove ",a("0")," Access"]},"Resources":"Resources","Save":"Save","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select":"Select","Select Input":"Select Input","Select Users Or Teams":"Select Users Or Teams","Select a row to delete":"Select a row to delete","Select all":"Select all","Select items from list":"Select items from list","Select the Instance Groups for this Organization to run on.":"Select the Instance Groups for this Organization to run on.","Select {header}":function(a){return["Select ",a("header")]},"Selected":"Selected","Settings":"Settings","Sort":"Sort","Successful":"Successful","System":"System","System Settings":"System Settings","Team":"Team","Team Roles":"Team Roles","Teams":"Teams","Templates":"Templates","This field must not be blank":"This field must not be blank","This field must not exceed {max} characters":function(a){return["This field must not exceed ",a("max")," characters"]},"Toggle notification failure":"Toggle notification failure","Toggle notification success":"Toggle notification success","Use Default {label}":function(a){return["Use Default ",a("label")]},"User":"User","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","User Roles":"User Roles","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible {brandName}! Please Sign In.":function(a){return["Welcome to Ansible ",a("brandName"),"! Please Sign In."]},"You do not have permission to delete the following {0}: {itemsUnableToDelete}":function(a){return["You do not have permission to delete the following ",a("0"),": ",a("itemsUnableToDelete")]},"You have been logged out.":"You have been logged out.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"cancel delete":"cancel delete","confirm delete":"confirm delete","confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","items":"items","of {pageCount}":function(a){return["of ",a("pageCount")]},"pages":"pages","per page":"per page","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{0} List":function(a){return[a("0")," List"]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}}; \ No newline at end of file diff --git a/awx/ui_next/src/routeConfig.js b/awx/ui_next/src/routeConfig.js index a343a7d1e0..507bc4e6d7 100644 --- a/awx/ui_next/src/routeConfig.js +++ b/awx/ui_next/src/routeConfig.js @@ -2,13 +2,13 @@ import { t } from '@lingui/macro'; import ActivityStream from './screens/ActivityStream'; import Applications from './screens/Application'; -import Credentials from './screens/Credential'; import CredentialTypes from './screens/CredentialType'; +import Credentials from './screens/Credential'; import Dashboard from './screens/Dashboard'; +import ExecutionEnvironments from './screens/ExecutionEnvironment'; import Hosts from './screens/Host'; import InstanceGroups from './screens/InstanceGroup'; import Inventory from './screens/Inventory'; -import { Jobs } from './screens/Job'; import ManagementJobs from './screens/ManagementJob'; import NotificationTemplates from './screens/NotificationTemplate'; import Organizations from './screens/Organization'; @@ -19,6 +19,7 @@ import Teams from './screens/Team'; import Templates from './screens/Template'; import Users from './screens/User'; import WorkflowApprovals from './screens/WorkflowApproval'; +import { Jobs } from './screens/Job'; // Ideally, this should just be a regular object that we export, but we // need the i18n. When lingui3 arrives, we will be able to import i18n @@ -138,6 +139,11 @@ function getRouteConfig(i18n) { path: '/applications', screen: Applications, }, + { + title: i18n._(t`Execution Environments`), + path: '/execution_environments', + screen: ExecutionEnvironments, + }, ], }, { diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx new file mode 100644 index 0000000000..55a3228e13 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx @@ -0,0 +1,126 @@ +import React, { useEffect, useCallback } from 'react'; +import { + Link, + Redirect, + Route, + Switch, + useLocation, + useParams, +} from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Card, PageSection } from '@patternfly/react-core'; +import { CaretLeftIcon } from '@patternfly/react-icons'; + +import useRequest from '../../util/useRequest'; +import { ExecutionEnvironmentsAPI } from '../../api'; +import RoutedTabs from '../../components/RoutedTabs'; +import ContentError from '../../components/ContentError'; +import ContentLoading from '../../components/ContentLoading'; + +import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails'; +import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit'; + +function ExecutionEnvironment({ i18n, setBreadcrumb }) { + const { id } = useParams(); + const { pathname } = useLocation(); + + const { + isLoading, + error: contentError, + request: fetchExecutionEnvironments, + result: executionEnvironment, + } = useRequest( + useCallback(async () => { + const { data } = await ExecutionEnvironmentsAPI.readDetail(id); + return data; + }, [id]), + null + ); + + useEffect(() => { + fetchExecutionEnvironments(); + }, [fetchExecutionEnvironments, pathname]); + + useEffect(() => { + if (executionEnvironment) { + setBreadcrumb(executionEnvironment); + } + }, [executionEnvironment, setBreadcrumb]); + + const tabsArray = [ + { + name: ( + <> + + {i18n._(t`Back to execution environments`)} + + ), + link: '/execution_environments', + id: 99, + }, + { + name: i18n._(t`Details`), + link: `/execution_environments/${id}/details`, + id: 0, + }, + ]; + + if (!isLoading && contentError) { + return ( + + + + {contentError.response?.status === 404 && ( + + {i18n._(t`Execution environment not found.`)}{' '} + + {i18n._(t`View all execution environments`)} + + + )} + + + + ); + } + + let cardHeader = ; + if (pathname.endsWith('edit')) { + cardHeader = null; + } + + return ( + + + {cardHeader} + {isLoading && } + {!isLoading && executionEnvironment && ( + + + {executionEnvironment && ( + <> + + + + + + + + )} + + )} + + + ); +} + +export default withI18n()(ExecutionEnvironment); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.jsx new file mode 100644 index 0000000000..2c94b46602 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.jsx @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import { Card, PageSection } from '@patternfly/react-core'; +import { useHistory } from 'react-router-dom'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import { Config } from '../../../contexts/Config'; +import { CardBody } from '../../../components/Card'; +import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm'; + +function ExecutionEnvironmentAdd() { + const history = useHistory(); + const [submitError, setSubmitError] = useState(null); + + const handleSubmit = async values => { + try { + const { data: response } = await ExecutionEnvironmentsAPI.create({ + ...values, + credential: values.credential?.id, + organization: values.organization?.id, + }); + history.push(`/execution_environments/${response.id}/details`); + } catch (error) { + setSubmitError(error); + } + }; + + const handleCancel = () => { + history.push(`/execution_environments`); + }; + return ( + + + + + {({ me }) => ( + + )} + + + + + ); +} + +export default ExecutionEnvironmentAdd; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.jsx new file mode 100644 index 0000000000..92f18c7d33 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; + +import { + mountWithContexts, + waitForElement, +} from '../../../../testUtils/enzymeHelpers'; +import { ExecutionEnvironmentsAPI } from '../../../api'; +import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd'; + +jest.mock('../../../api'); + +const mockMe = { + is_superuser: true, + is_system_auditor: false, +}; + +const executionEnvironmentData = { + name: 'Test EE', + credential: 4, + description: 'A simple EE', + image: 'https://registry.com/image/container', + pull: 'one', +}; + +const mockOptions = { + data: { + actions: { + POST: { + pull: { + choices: [ + ['one', 'One'], + ['two', 'Two'], + ['three', 'Three'], + ], + }, + }, + }, + }, +}; + +ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions); +ExecutionEnvironmentsAPI.create.mockResolvedValue({ + data: { + id: 42, + }, +}); + +describe('', () => { + let wrapper; + let history; + + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/execution_environments'], + }); + await act(async () => { + wrapper = mountWithContexts(, { + context: { router: { history } }, + }); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('handleSubmit should call the api and redirect to details page', async () => { + await act(async () => { + wrapper.find('ExecutionEnvironmentForm').prop('onSubmit')({ + executionEnvironmentData, + }); + }); + wrapper.update(); + expect(ExecutionEnvironmentsAPI.create).toHaveBeenCalledWith({ + executionEnvironmentData, + }); + expect(history.location.pathname).toBe( + '/execution_environments/42/details' + ); + }); + + test('handleCancel should return the user back to the execution environments list', async () => { + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + + wrapper.find('Button[aria-label="Cancel"]').simulate('click'); + expect(history.location.pathname).toEqual('/execution_environments'); + }); + + test('failed form submission should show an error message', async () => { + const error = { + response: { + data: { detail: 'An error occurred' }, + }, + }; + ExecutionEnvironmentsAPI.create.mockImplementationOnce(() => + Promise.reject(error) + ); + await act(async () => { + wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')( + executionEnvironmentData + ); + }); + wrapper.update(); + expect(wrapper.find('FormSubmitError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js new file mode 100644 index 0000000000..69765fcf3b --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironmentAdd'; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.jsx new file mode 100644 index 0000000000..68b9f9879d --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.jsx @@ -0,0 +1,138 @@ +import React, { useCallback } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Link, useHistory } from 'react-router-dom'; +import { Button, Label } from '@patternfly/react-core'; + +import AlertModal from '../../../components/AlertModal'; +import { CardBody, CardActionsRow } from '../../../components/Card'; +import DeleteButton from '../../../components/DeleteButton'; +import { + Detail, + DetailList, + UserDateDetail, +} from '../../../components/DetailList'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; +import { toTitleCase } from '../../../util/strings'; +import { ExecutionEnvironmentsAPI } from '../../../api'; + +function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) { + const history = useHistory(); + const { + id, + name, + image, + description, + pull, + organization, + summary_fields, + } = executionEnvironment; + + const { + request: deleteExecutionEnvironment, + isLoading, + error: deleteError, + } = useRequest( + useCallback(async () => { + await ExecutionEnvironmentsAPI.destroy(id); + history.push(`/execution_environments`); + }, [id, history]) + ); + + const { error, dismissError } = useDismissableError(deleteError); + + return ( + + + + + + + {summary_fields.organization.name} + + ) : ( + i18n._(t`Globally Available`) + ) + } + dataCy="execution-environment-detail-organization" + /> + + {executionEnvironment.summary_fields.credential && ( + + {executionEnvironment.summary_fields.credential.name} + + } + dataCy="execution-environment-credential" + /> + )} + + + + + + + {i18n._(t`Delete`)} + + + + {error && ( + + )} + + ); +} + +export default withI18n()(ExecutionEnvironmentDetails); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.jsx new file mode 100644 index 0000000000..d258ebe65b --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.jsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; + +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; +import { ExecutionEnvironmentsAPI } from '../../../api'; + +import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails'; + +jest.mock('../../../api'); + +const executionEnvironment = { + id: 17, + type: 'execution_environment', + url: '/api/v2/execution_environments/17/', + related: { + created_by: '/api/v2/users/1/', + modified_by: '/api/v2/users/1/', + activity_stream: '/api/v2/execution_environments/17/activity_stream/', + unified_job_templates: + '/api/v2/execution_environments/17/unified_job_templates/', + credential: '/api/v2/credentials/4/', + }, + summary_fields: { + credential: { + id: 4, + name: 'Container Registry', + }, + created_by: { + id: 1, + username: 'admin', + first_name: '', + last_name: '', + }, + modified_by: { + id: 1, + username: 'admin', + first_name: '', + last_name: '', + }, + }, + name: 'Default EE', + created: '2020-09-17T20:14:15.408782Z', + modified: '2020-09-17T20:14:15.408802Z', + description: 'Foo', + organization: null, + image: 'https://localhost:90/12345/ma', + managed_by_tower: false, + credential: 4, +}; + +describe('', () => { + let wrapper; + test('should render details properly', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + wrapper.update(); + + expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual( + executionEnvironment.image + ); + expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual( + 'Foo' + ); + expect(wrapper.find('Detail[label="Organization"]').prop('value')).toEqual( + 'Globally Available' + ); + expect( + wrapper.find('Detail[label="Credential"]').prop('value').props.children + ).toEqual(executionEnvironment.summary_fields.credential.name); + const dates = wrapper.find('UserDateDetail'); + expect(dates).toHaveLength(2); + expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created); + expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified); + }); + + test('should render organization detail', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + wrapper.update(); + + expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual( + executionEnvironment.image + ); + expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual( + 'Foo' + ); + expect(wrapper.find(`Detail[label="Organization"] dd`).text()).toBe('Bar'); + expect( + wrapper.find('Detail[label="Credential"]').prop('value').props.children + ).toEqual(executionEnvironment.summary_fields.credential.name); + const dates = wrapper.find('UserDateDetail'); + expect(dates).toHaveLength(2); + expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created); + expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified); + }); + + test('expected api call is made for delete', async () => { + const history = createMemoryHistory({ + initialEntries: ['/execution_environments/42/details'], + }); + await act(async () => { + wrapper = mountWithContexts( + , + { + context: { router: { history } }, + } + ); + }); + await act(async () => { + wrapper.find('DeleteButton').invoke('onConfirm')(); + }); + expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(1); + expect(history.location.pathname).toBe('/execution_environments'); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js new file mode 100644 index 0000000000..36121ea0d9 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironmentDetails'; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.jsx new file mode 100644 index 0000000000..ea4943b2da --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.jsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; + +import { CardBody } from '../../../components/Card'; +import { ExecutionEnvironmentsAPI } from '../../../api'; +import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm'; +import { Config } from '../../../contexts/Config'; + +function ExecutionEnvironmentEdit({ executionEnvironment }) { + const history = useHistory(); + const [submitError, setSubmitError] = useState(null); + const detailsUrl = `/execution_environments/${executionEnvironment.id}/details`; + + const handleSubmit = async values => { + try { + await ExecutionEnvironmentsAPI.update(executionEnvironment.id, { + ...values, + credential: values.credential ? values.credential.id : null, + organization: values.organization ? values.organization.id : null, + }); + history.push(detailsUrl); + } catch (error) { + setSubmitError(error); + } + }; + + const handleCancel = () => { + history.push(detailsUrl); + }; + return ( + + + {({ me }) => ( + + )} + + + ); +} + +export default ExecutionEnvironmentEdit; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.jsx new file mode 100644 index 0000000000..374a0c5dba --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.jsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; + +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; +import { ExecutionEnvironmentsAPI } from '../../../api'; + +import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit'; + +jest.mock('../../../api'); + +const mockMe = { + is_superuser: true, + is_system_auditor: false, +}; + +const executionEnvironmentData = { + id: 42, + credential: { id: 4 }, + description: 'A simple EE', + image: 'https://registry.com/image/container', + pull: 'one', + name: 'Test EE', +}; + +const updateExecutionEnvironmentData = { + image: 'https://registry.com/image/container2', + description: 'Updated new description', +}; + +const mockOptions = { + data: { + actions: { + POST: { + pull: { + choices: [ + ['one', 'One'], + ['two', 'Two'], + ['three', 'Three'], + ], + }, + }, + }, + }, +}; + +ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions); + +describe('', () => { + let wrapper; + let history; + + beforeAll(async () => { + history = createMemoryHistory(); + await act(async () => { + wrapper = mountWithContexts( + , + { + context: { router: { history } }, + } + ); + }); + }); + + afterAll(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('handleSubmit should call the api and redirect to details page', async () => { + await act(async () => { + wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')( + updateExecutionEnvironmentData + ); + wrapper.update(); + expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, { + ...updateExecutionEnvironmentData, + credential: null, + organization: null, + }); + }); + + expect(history.location.pathname).toEqual( + '/execution_environments/42/details' + ); + }); + + test('should navigate to execution environments details when cancel is clicked', async () => { + await act(async () => { + wrapper.find('button[aria-label="Cancel"]').prop('onClick')(); + }); + expect(history.location.pathname).toEqual( + '/execution_environments/42/details' + ); + }); + + test('should navigate to execution environments detail after successful submission', async () => { + await act(async () => { + wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')({ + updateExecutionEnvironmentData, + }); + }); + wrapper.update(); + expect(wrapper.find('FormSubmitError').length).toBe(0); + expect(history.location.pathname).toEqual( + '/execution_environments/42/details' + ); + }); + + test('failed form submission should show an error message', async () => { + const error = { + response: { + data: { detail: 'An error occurred' }, + }, + }; + ExecutionEnvironmentsAPI.update.mockImplementationOnce(() => + Promise.reject(error) + ); + await act(async () => { + wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')( + updateExecutionEnvironmentData + ); + }); + wrapper.update(); + expect(wrapper.find('FormSubmitError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js new file mode 100644 index 0000000000..6ab135ca05 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironmentEdit'; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnviromentList.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnviromentList.test.jsx new file mode 100644 index 0000000000..1490ff49e3 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnviromentList.test.jsx @@ -0,0 +1,188 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { + mountWithContexts, + waitForElement, +} from '../../../../testUtils/enzymeHelpers'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import ExecutionEnvironmentList from './ExecutionEnvironmentList'; + +jest.mock('../../../api/models/ExecutionEnvironments'); + +const executionEnvironments = { + data: { + results: [ + { + name: 'Foo', + id: 1, + image: 'https://registry.com/r/image/manifest', + organization: null, + credential: null, + url: '/api/v2/execution_environments/1/', + summary_fields: { user_capabilities: { edit: true, delete: true } }, + }, + { + name: 'Bar', + id: 2, + image: 'https://registry.com/r/image2/manifest', + organization: null, + credential: null, + url: '/api/v2/execution_environments/2/', + summary_fields: { user_capabilities: { edit: false, delete: true } }, + }, + ], + count: 2, + }, +}; + +const options = { data: { actions: { POST: true } } }; + +describe('', () => { + beforeEach(() => { + ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments); + ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + let wrapper; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentList', + el => el.length > 0 + ); + }); + + test('should have data fetched and render 2 rows', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentList', + el => el.length > 0 + ); + + expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(2); + expect(ExecutionEnvironmentsAPI.read).toBeCalled(); + expect(ExecutionEnvironmentsAPI.readOptions).toBeCalled(); + }); + + test('should delete items successfully', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentList', + el => el.length > 0 + ); + + await act(async () => { + wrapper + .find('ExecutionEnvironmentListItem') + .at(0) + .invoke('onSelect')(); + }); + wrapper.update(); + await act(async () => { + wrapper + .find('ExecutionEnvironmentListItem') + .at(1) + .invoke('onSelect')(); + }); + wrapper.update(); + await act(async () => { + wrapper.find('ToolbarDeleteButton').invoke('onDelete')(); + }); + + expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(2); + }); + + test('should render deletion error modal', async () => { + ExecutionEnvironmentsAPI.destroy.mockRejectedValue( + new Error({ + response: { + config: { + method: 'DELETE', + url: '/api/v2/execution_environments', + }, + data: 'An error occurred', + }, + }) + ); + await act(async () => { + wrapper = mountWithContexts(); + }); + waitForElement(wrapper, 'ExecutionEnvironmentList', el => el.length > 0); + + wrapper + .find('ExecutionEnvironmentListItem') + .at(0) + .find('input') + .simulate('change', 'a'); + wrapper.update(); + + expect( + wrapper + .find('ExecutionEnvironmentListItem') + .at(0) + .find('input') + .prop('checked') + ).toBe(true); + + await act(async () => + wrapper.find('Button[aria-label="Delete"]').prop('onClick')() + ); + wrapper.update(); + + await act(async () => + wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')() + ); + wrapper.update(); + expect(wrapper.find('ErrorDetail').length).toBe(1); + }); + + test('should thrown content error', async () => { + ExecutionEnvironmentsAPI.read.mockRejectedValue( + new Error({ + response: { + config: { + method: 'GET', + url: '/api/v2/execution_environments', + }, + data: 'An error occurred', + }, + }) + ); + await act(async () => { + wrapper = mountWithContexts(); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentList', + el => el.length > 0 + ); + expect(wrapper.find('ContentError').length).toBe(1); + }); + + test('should not render add button', async () => { + ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments); + ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({ + data: { actions: { POST: false } }, + }); + await act(async () => { + wrapper = mountWithContexts(); + }); + waitForElement(wrapper, 'ExecutionEnvironmentList', el => el.length > 0); + expect(wrapper.find('ToolbarAddButton').length).toBe(0); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx new file mode 100644 index 0000000000..312b18f2cf --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx @@ -0,0 +1,221 @@ +import React, { useEffect, useCallback } from 'react'; +import { useLocation, useRouteMatch } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Card, PageSection } from '@patternfly/react-core'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import { getQSConfig, parseQueryString } from '../../../util/qs'; +import useRequest, { useDeleteItems } from '../../../util/useRequest'; +import useSelected from '../../../util/useSelected'; +import { + ToolbarDeleteButton, + ToolbarAddButton, +} from '../../../components/PaginatedDataList'; +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; +import ErrorDetail from '../../../components/ErrorDetail'; +import AlertModal from '../../../components/AlertModal'; +import DatalistToolbar from '../../../components/DataListToolbar'; + +import ExecutionEnvironmentsListItem from './ExecutionEnvironmentListItem'; + +const QS_CONFIG = getQSConfig('execution_environments', { + page: 1, + page_size: 20, + order_by: 'name', +}); + +function ExecutionEnvironmentList({ i18n }) { + const location = useLocation(); + const match = useRouteMatch(); + + const { + error: contentError, + isLoading, + request: fetchExecutionEnvironments, + result: { + executionEnvironments, + executionEnvironmentsCount, + actions, + relatedSearchableKeys, + searchableKeys, + }, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); + + const [response, responseActions] = await Promise.all([ + ExecutionEnvironmentsAPI.read(params), + ExecutionEnvironmentsAPI.readOptions(), + ]); + + return { + executionEnvironments: response.data.results, + executionEnvironmentsCount: response.data.count, + actions: responseActions.data.actions, + relatedSearchableKeys: ( + responseActions?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + responseActions.data.actions?.GET || {} + ).filter(key => responseActions.data.actions?.GET[key].filterable), + }; + }, [location]), + { + executionEnvironments: [], + executionEnvironmentsCount: 0, + actions: {}, + relatedSearchableKeys: [], + searchableKeys: [], + } + ); + + useEffect(() => { + fetchExecutionEnvironments(); + }, [fetchExecutionEnvironments]); + + const { selected, isAllSelected, handleSelect, setSelected } = useSelected( + executionEnvironments + ); + + const { + isLoading: deleteLoading, + deletionError, + deleteItems: deleteExecutionEnvironments, + clearDeletionError, + } = useDeleteItems( + useCallback(async () => { + await Promise.all( + selected.map(({ id }) => ExecutionEnvironmentsAPI.destroy(id)) + ); + }, [selected]), + { + qsConfig: QS_CONFIG, + allItemsSelected: isAllSelected, + fetchItems: fetchExecutionEnvironments, + } + ); + + const handleDelete = async () => { + await deleteExecutionEnvironments(); + setSelected([]); + }; + + const canAdd = actions && actions.POST; + + return ( + <> + + + + {i18n._(t`Name`)} + {i18n._(t`Image`)} + {i18n._(t`Organization`)} + {i18n._(t`Actions`)} + + } + renderToolbar={props => ( + + setSelected(isSelected ? [...executionEnvironments] : []) + } + qsConfig={QS_CONFIG} + additionalControls={[ + ...(canAdd + ? [ + , + ] + : []), + , + ]} + /> + )} + renderRow={(executionEnvironment, index) => ( + handleSelect(executionEnvironment)} + isSelected={selected.some( + row => row.id === executionEnvironment.id + )} + /> + )} + emptyStateControls={ + canAdd && ( + + ) + } + /> + + + + {i18n._(t`Failed to delete one or more execution environments`)} + + + + ); +} + +export default withI18n()(ExecutionEnvironmentList); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.jsx new file mode 100644 index 0000000000..bb814a1921 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { string, bool, func } from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Link } from 'react-router-dom'; +import { Button } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; +import { PencilAltIcon } from '@patternfly/react-icons'; + +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; +import { ExecutionEnvironment } from '../../../types'; + +function ExecutionEnvironmentListItem({ + executionEnvironment, + detailUrl, + isSelected, + onSelect, + i18n, + rowIndex, +}) { + const labelId = `check-action-${executionEnvironment.id}`; + + return ( + + + + + {executionEnvironment.name} + + + + {executionEnvironment.image} + + + {executionEnvironment.organization ? ( + + {executionEnvironment?.summary_fields?.organization?.name} + + ) : ( + i18n._(t`Globally Available`) + )} + + + + + + + + ); +} + +ExecutionEnvironmentListItem.prototype = { + executionEnvironment: ExecutionEnvironment.isRequired, + detailUrl: string.isRequired, + isSelected: bool.isRequired, + onSelect: func.isRequired, +}; + +export default withI18n()(ExecutionEnvironmentListItem); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.jsx new file mode 100644 index 0000000000..0e7c037aed --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; + +import ExecutionEnvironmentListItem from './ExecutionEnvironmentListItem'; + +describe('', () => { + let wrapper; + const executionEnvironment = { + name: 'Foo', + id: 1, + image: 'https://registry.com/r/image/manifest', + organization: null, + credential: null, + summary_fields: { user_capabilities: { edit: true } }, + }; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + {}} + /> + +
+ ); + }); + expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(1); + }); + + test('should render the proper data', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + {}} + /> + +
+ ); + }); + expect( + wrapper + .find('Td') + .at(1) + .text() + ).toBe(executionEnvironment.name); + expect( + wrapper + .find('Td') + .at(2) + .text() + ).toBe(executionEnvironment.image); + + expect( + wrapper + .find('Td') + .at(3) + .text() + ).toBe('Globally Available'); + + expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js new file mode 100644 index 0000000000..a8aa4263d7 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironmentList'; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.jsx new file mode 100644 index 0000000000..802e78a679 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.jsx @@ -0,0 +1,56 @@ +import React, { useState, useCallback } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Route, Switch } from 'react-router-dom'; + +import ExecutionEnvironment from './ExecutionEnvironment'; +import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd'; +import ExecutionEnvironmentList from './ExecutionEnvironmentList'; +import ScreenHeader from '../../components/ScreenHeader/ScreenHeader'; + +function ExecutionEnvironments({ i18n }) { + const [breadcrumbConfig, setBreadcrumbConfig] = useState({ + '/execution_environments': i18n._(t`Execution environments`), + '/execution_environments/add': i18n._(t`Create Execution environments`), + }); + + const buildBreadcrumbConfig = useCallback( + executionEnvironments => { + if (!executionEnvironments) { + return; + } + setBreadcrumbConfig({ + '/execution_environments': i18n._(t`Execution environments`), + '/execution_environments/add': i18n._(t`Create Execution environments`), + [`/execution_environments/${executionEnvironments.id}`]: `${executionEnvironments.name}`, + [`/execution_environments/${executionEnvironments.id}/edit`]: i18n._( + t`Edit details` + ), + [`/execution_environments/${executionEnvironments.id}/details`]: i18n._( + t`Details` + ), + }); + }, + [i18n] + ); + return ( + <> + + + + + + + + + + + + + + ); +} +export default withI18n()(ExecutionEnvironments); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.jsx new file mode 100644 index 0000000000..5ceb36ac93 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; + +import ExecutionEnvironments from './ExecutionEnvironments'; + +describe('', () => { + let pageWrapper; + let pageSections; + + beforeEach(() => { + pageWrapper = mountWithContexts(); + pageSections = pageWrapper.find('PageSection'); + }); + + afterEach(() => { + pageWrapper.unmount(); + }); + + test('initially renders without crashing', () => { + expect(pageWrapper.length).toBe(1); + expect(pageSections.length).toBe(1); + expect(pageSections.first().props().variant).toBe('light'); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/index.js new file mode 100644 index 0000000000..f66a2b3cf3 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironments'; diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.jsx new file mode 100644 index 0000000000..a8e0e33a0f --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.jsx @@ -0,0 +1,211 @@ +import React, { useCallback, useEffect } from 'react'; +import { func, shape } from 'prop-types'; +import { Formik, useField, useFormikContext } from 'formik'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Form, FormGroup } from '@patternfly/react-core'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import CredentialLookup from '../../../components/Lookup/CredentialLookup'; +import FormActionGroup from '../../../components/FormActionGroup'; +import FormField, { FormSubmitError } from '../../../components/FormField'; +import AnsibleSelect from '../../../components/AnsibleSelect'; +import { FormColumnLayout } from '../../../components/FormLayout'; +import { OrganizationLookup } from '../../../components/Lookup'; +import ContentError from '../../../components/ContentError'; +import ContentLoading from '../../../components/ContentLoading'; +import { required } from '../../../util/validators'; +import useRequest from '../../../util/useRequest'; + +function ExecutionEnvironmentFormFields({ + i18n, + me, + options, + executionEnvironment, +}) { + const [credentialField] = useField('credential'); + const [organizationField, organizationMeta, organizationHelpers] = useField({ + name: 'organization', + validate: + !me?.is_superuser && + required(i18n._(t`Select a value for this field`), i18n), + }); + + const { setFieldValue } = useFormikContext(); + + const onCredentialChange = useCallback( + value => { + setFieldValue('credential', value); + }, + [setFieldValue] + ); + + const onOrganizationChange = useCallback( + value => { + setFieldValue('organization', value); + }, + [setFieldValue] + ); + + const [ + containerOptionsField, + containerOptionsMeta, + containerOptionsHelpers, + ] = useField({ + name: 'pull', + }); + + const containerPullChoices = options?.actions?.POST?.pull?.choices.map( + ([value, label]) => ({ value, label, key: value }) + ); + + return ( + <> + + + + { + containerOptionsHelpers.setValue(value); + }} + /> + + + organizationHelpers.setTouched()} + onChange={onOrganizationChange} + value={organizationField.value} + required={!me.is_superuser} + helperText={ + me?.is_superuser + ? i18n._( + t`Leave this field blank to make the execution environment globally available.` + ) + : null + } + autoPopulate={!me?.is_superuser ? !executionEnvironment?.id : null} + /> + + + + ); +} + +function ExecutionEnvironmentForm({ + executionEnvironment = {}, + onSubmit, + onCancel, + submitError, + me, + ...rest +}) { + const { + isLoading, + error, + request: fetchOptions, + result: options, + } = useRequest( + useCallback(async () => { + const res = await ExecutionEnvironmentsAPI.readOptions(); + const { data } = res; + return data; + }, []), + null + ); + + useEffect(() => { + fetchOptions(); + }, [fetchOptions]); + + if (isLoading || !options) { + return ; + } + + if (error) { + return ; + } + + const initialValues = { + name: executionEnvironment.name || '', + image: executionEnvironment.image || '', + pull: executionEnvironment?.pull || '', + description: executionEnvironment.description || '', + credential: executionEnvironment.summary_fields?.credential || null, + organization: executionEnvironment.summary_fields?.organization || null, + }; + return ( + onSubmit(values)}> + {formik => ( +
+ + + {submitError && } + + +
+ )} +
+ ); +} + +ExecutionEnvironmentForm.propTypes = { + executionEnvironment: shape({}), + onCancel: func.isRequired, + onSubmit: func.isRequired, + submitError: shape({}), +}; + +ExecutionEnvironmentForm.defaultProps = { + executionEnvironment: {}, + submitError: null, +}; + +export default withI18n()(ExecutionEnvironmentForm); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.jsx new file mode 100644 index 0000000000..cddef9ffce --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.jsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { + mountWithContexts, + waitForElement, +} from '../../../../testUtils/enzymeHelpers'; +import { ExecutionEnvironmentsAPI } from '../../../api'; + +import ExecutionEnvironmentForm from './ExecutionEnvironmentForm'; + +jest.mock('../../../api'); + +const mockMe = { + is_superuser: true, + is_super_auditor: false, +}; + +const executionEnvironment = { + id: 16, + name: 'Test EE', + type: 'execution_environment', + pull: 'one', + url: '/api/v2/execution_environments/16/', + related: { + created_by: '/api/v2/users/1/', + modified_by: '/api/v2/users/1/', + activity_stream: '/api/v2/execution_environments/16/activity_stream/', + unified_job_templates: + '/api/v2/execution_environments/16/unified_job_templates/', + credential: '/api/v2/credentials/4/', + }, + summary_fields: { + credential: { + id: 4, + name: 'Container Registry', + }, + }, + created: '2020-09-17T16:06:57.346128Z', + modified: '2020-09-17T16:06:57.346147Z', + description: 'A simple EE', + organization: null, + image: 'https://registry.com/image/container', + managed_by_tower: false, + credential: 4, +}; + +const mockOptions = { + data: { + actions: { + POST: { + pull: { + choices: [ + ['one', 'One'], + ['two', 'Two'], + ['three', 'Three'], + ], + }, + }, + }, + }, +}; + +describe('', () => { + let wrapper; + let onCancel; + let onSubmit; + + beforeEach(async () => { + onCancel = jest.fn(); + onSubmit = jest.fn(); + ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('Initially renders successfully', () => { + expect(wrapper.length).toBe(1); + }); + + test('should display form fields properly', () => { + expect(wrapper.find('FormGroup[label="Image name"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1); + expect(wrapper.find('CredentialLookup').length).toBe(1); + }); + + test('should call onSubmit when form submitted', async () => { + expect(onSubmit).not.toHaveBeenCalled(); + await act(async () => { + wrapper.find('button[aria-label="Save"]').simulate('click'); + }); + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + test('should update form values', async () => { + await act(async () => { + wrapper.find('input#execution-environment-image').simulate('change', { + target: { + value: 'Updated EE Name', + name: 'name', + }, + }); + wrapper.find('input#execution-environment-image').simulate('change', { + target: { + value: 'https://registry.com/image/container2', + name: 'image', + }, + }); + wrapper + .find('input#execution-environment-description') + .simulate('change', { + target: { value: 'New description', name: 'description' }, + }); + wrapper.find('CredentialLookup').invoke('onBlur')(); + wrapper.find('CredentialLookup').invoke('onChange')({ + id: 99, + name: 'credential', + }); + + wrapper.find('OrganizationLookup').invoke('onBlur')(); + wrapper.find('OrganizationLookup').invoke('onChange')({ + id: 3, + name: 'organization', + }); + }); + + wrapper.update(); + expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({ + id: 3, + name: 'organization', + }); + expect( + wrapper.find('input#execution-environment-image').prop('value') + ).toEqual('https://registry.com/image/container2'); + expect( + wrapper.find('input#execution-environment-description').prop('value') + ).toEqual('New description'); + expect(wrapper.find('CredentialLookup').prop('value')).toEqual({ + id: 99, + name: 'credential', + }); + }); + + test('should call handleCancel when Cancel button is clicked', async () => { + expect(onCancel).not.toHaveBeenCalled(); + wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); + expect(onCancel).toBeCalled(); + }); +}); diff --git a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.jsx b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.jsx index aa979ef730..68ca23f80a 100644 --- a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.jsx @@ -32,7 +32,7 @@ const instanceGroup = { controller: null, is_controller: false, is_isolated: false, - is_containerized: true, + is_container_group: true, credential: 71, policy_instance_percentage: 0, policy_instance_minimum: 0, diff --git a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.jsx b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.jsx index 3bf66a9d4d..9f4454c0a8 100644 --- a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.jsx @@ -37,7 +37,7 @@ function ContainerGroupEdit({ instanceGroup }) { try { await InstanceGroupsAPI.update(instanceGroup.id, { name: values.name, - credential: values.credential.id, + credential: values.credential ? values.credential.id : null, pod_spec_override: values.override ? values.pod_spec_override : null, }); history.push(detailsIUrl); diff --git a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.jsx b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.jsx index 937aa15adb..860c6363c5 100644 --- a/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.jsx @@ -31,7 +31,7 @@ const instanceGroup = { controller: null, is_controller: false, is_isolated: false, - is_containerized: true, + is_container_group: true, credential: 71, policy_instance_percentage: 0, policy_instance_minimum: 0, diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.jsx index 4b2d879398..a4ae1e74fc 100644 --- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.jsx @@ -29,7 +29,7 @@ const instanceGroupData = { controller: null, is_controller: false, is_isolated: false, - is_containerized: false, + is_container_group: false, credential: null, policy_instance_percentage: 46, policy_instance_minimum: 12, diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.jsx index 748e92702d..05ad3277dc 100644 --- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.jsx @@ -78,7 +78,7 @@ function InstanceGroupDetails({ instanceGroup, i18n }) { { - return item.is_containerized + return item.is_container_group ? `${match.url}/container_group/${item.id}/details` : `${match.url}/${item.id}/details`; }; diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.jsx index 8bfcf05325..4c47269074 100644 --- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.jsx @@ -32,7 +32,7 @@ function InstanceGroupListItem({ const labelId = `check-action-${instanceGroup.id}`; const isContainerGroup = item => { - return item.is_containerized; + return item.is_container_group; }; function usedCapacity(item) { diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.jsx index 0f22a4b6d7..579aa36dcc 100644 --- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.jsx @@ -17,7 +17,7 @@ describe('', () => { policy_instance_minimum: 10, policy_instance_percentage: 50, percent_capacity_remaining: 60, - is_containerized: false, + is_container_group: false, summary_fields: { user_capabilities: { edit: true, @@ -34,7 +34,7 @@ describe('', () => { policy_instance_minimum: 0, policy_instance_percentage: 0, percent_capacity_remaining: 0, - is_containerized: true, + is_container_group: true, summary_fields: { user_capabilities: { edit: false, diff --git a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx index fda18b73bc..e41ab0a1b0 100644 --- a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.jsx @@ -25,7 +25,6 @@ function ContainerGroupFormFields({ i18n, instanceGroup }) { const { setFieldValue } = useFormikContext(); const [credentialField, credentialMeta, credentialHelpers] = useField({ name: 'credential', - validate: required(i18n._(t`Select a value for this field`), i18n), }); const [overrideField] = useField('override'); @@ -55,9 +54,8 @@ function ContainerGroupFormFields({ i18n, instanceGroup }) { onBlur={() => credentialHelpers.setTouched()} onChange={onCredentialChange} value={credentialField.value} - required tooltip={i18n._( - t`Credential to authenticate with Kubernetes or OpenShift. Must be of type "Kubernetes/OpenShift API Bearer Token”.` + t`Credential to authenticate with Kubernetes or OpenShift. Must be of type "Kubernetes/OpenShift API Bearer Token". If left blank, the underlying Pod's service account will be used.` )} autoPopulate={!instanceGroup?.id} /> diff --git a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.test.jsx b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.test.jsx index 62709df53e..3e48389195 100644 --- a/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/shared/ContainerGroupForm.test.jsx @@ -27,7 +27,7 @@ const instanceGroup = { controller: null, is_controller: false, is_isolated: false, - is_containerized: false, + is_container_group: false, credential: 3, policy_instance_percentage: 46, policy_instance_minimum: 12, diff --git a/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.test.jsx b/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.test.jsx index 233ce7f849..0dad4fe6d1 100644 --- a/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.test.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.test.jsx @@ -27,7 +27,7 @@ const instanceGroup = { controller: null, is_controller: false, is_isolated: false, - is_containerized: false, + is_container_group: false, credential: null, policy_instance_percentage: 46, policy_instance_minimum: 12, diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.jsx index 0db70f2dbf..67ea90f4ea 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.jsx @@ -1,14 +1,14 @@ import React, { useCallback, useEffect } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { Card } from '@patternfly/react-core'; import { InventorySourcesAPI } from '../../../api'; import useRequest from '../../../util/useRequest'; import { CardBody } from '../../../components/Card'; import InventorySourceForm from '../shared/InventorySourceForm'; -function InventorySourceAdd() { +function InventorySourceAdd({ inventory }) { const history = useHistory(); - const { id } = useParams(); + const { id, organization } = inventory; const { error, request, result } = useRequest( useCallback(async values => { @@ -31,6 +31,7 @@ function InventorySourceAdd() { source_path, source_project, source_script, + execution_environment, ...remainingForm } = form; @@ -46,6 +47,7 @@ function InventorySourceAdd() { credential: credential?.id || null, inventory: id, source_script: source_script?.id || null, + execution_environment: execution_environment?.id || null, ...sourcePath, ...sourceProject, ...remainingForm, @@ -63,6 +65,7 @@ function InventorySourceAdd() { onCancel={handleCancel} onSubmit={handleSubmit} submitError={error} + organizationId={organization} /> diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.jsx index afc4d69d0e..c186d4dcb7 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.jsx @@ -35,6 +35,12 @@ describe('', () => { verbosity: 1, }; + const mockInventory = { + id: 111, + name: 'Foo', + organization: 2, + }; + InventorySourcesAPI.readOptions.mockResolvedValue({ data: { actions: { @@ -72,9 +78,12 @@ describe('', () => { custom_virtualenvs: ['venv/foo', 'venv/bar'], }; await act(async () => { - wrapper = mountWithContexts(, { - context: { config }, - }); + wrapper = mountWithContexts( + , + { + context: { config }, + } + ); }); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1); @@ -88,9 +97,12 @@ describe('', () => { test('should navigate to inventory sources list when cancel is clicked', async () => { const history = createMemoryHistory({}); await act(async () => { - wrapper = mountWithContexts(, { - context: { router: { history } }, - }); + wrapper = mountWithContexts( + , + { + context: { router: { history } }, + } + ); }); await act(async () => { wrapper.find('InventorySourceForm').invoke('onCancel')(); @@ -103,7 +115,9 @@ describe('', () => { test('should post to the api when submit is clicked', async () => { InventorySourcesAPI.create.mockResolvedValueOnce({ data: {} }); await act(async () => { - wrapper = mountWithContexts(); + wrapper = mountWithContexts( + + ); }); await act(async () => { wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData); @@ -114,6 +128,7 @@ describe('', () => { credential: 222, source_project: 999, source_script: null, + execution_environment: null, }); }); @@ -123,9 +138,12 @@ describe('', () => { data: { id: 123, inventory: 111 }, }); await act(async () => { - wrapper = mountWithContexts(, { - context: { router: { history } }, - }); + wrapper = mountWithContexts( + , + { + context: { router: { history } }, + } + ); }); await act(async () => { wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData); @@ -143,7 +161,9 @@ describe('', () => { }; InventorySourcesAPI.create.mockImplementation(() => Promise.reject(error)); await act(async () => { - wrapper = mountWithContexts(); + wrapper = mountWithContexts( + + ); }); expect(wrapper.find('FormSubmitError').length).toBe(0); await act(async () => { diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx index f3fcdebfca..4383998994 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx @@ -50,6 +50,7 @@ function InventorySourceDetail({ inventorySource, i18n }) { organization, source_project, user_capabilities, + execution_environment, }, } = inventorySource; const [deletionError, setDeletionError] = useState(false); @@ -214,6 +215,18 @@ function InventorySourceDetail({ inventorySource, i18n }) { } /> )} + {execution_environment?.name && ( + + {execution_environment.name} + + } + /> + )} {source === 'scm' ? ( diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.jsx index 87ec0288c1..cc8ad163b2 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.jsx @@ -18,7 +18,7 @@ jest.mock('react-router-dom', () => ({ }), })); -describe('', () => { +describe('', () => { let wrapper; let history; const mockInvSrc = { @@ -37,6 +37,11 @@ describe('', () => { update_on_project_update: false, verbosity: 1, }; + const mockInventory = { + id: 1, + name: 'Foo', + organization: 1, + }; InventorySourcesAPI.readOptions.mockResolvedValue({ data: { actions: { @@ -89,9 +94,12 @@ describe('', () => { beforeAll(async () => { history = createMemoryHistory(); await act(async () => { - wrapper = mountWithContexts(, { - context: { router: { history } }, - }); + wrapper = mountWithContexts( + , + { + context: { router: { history } }, + } + ); }); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); }); @@ -133,7 +141,9 @@ describe('', () => { }; InventorySourcesAPI.replace.mockImplementation(() => Promise.reject(error)); await act(async () => { - wrapper = mountWithContexts(); + wrapper = mountWithContexts( + + ); }); expect(wrapper.find('FormSubmitError').length).toBe(0); await act(async () => { diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySources.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySources.jsx index 2e8f1a3785..e55125187d 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySources.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySources.jsx @@ -9,7 +9,7 @@ function InventorySources({ inventory, setBreadcrumb }) { return ( - + diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx index 3bc824fca8..05bbd4e370 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx @@ -31,6 +31,7 @@ import { VMwareSubForm, VirtualizationSubForm, } from './InventorySourceSubForms'; +import { ExecutionEnvironmentLookup } from '../../../components/Lookup'; const buildSourceChoiceOptions = options => { const sourceChoices = options.actions.GET.source.choices.map( @@ -39,7 +40,12 @@ const buildSourceChoiceOptions = options => { return sourceChoices.filter(({ key }) => key !== 'file'); }; -const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => { +const InventorySourceFormFields = ({ + source, + sourceOptions, + organizationId, + i18n, +}) => { const { values, initialValues, @@ -51,6 +57,13 @@ const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => { name: 'source', validate: required(i18n._(t`Set a value for this field`), i18n), }); + const [ + executionEnvironmentField, + executionEnvironmentMeta, + executionEnvironmentHelpers, + ] = useField({ + name: 'execution_environment', + }); const { custom_virtualenvs } = useContext(ConfigContext); const [venvField] = useField('custom_virtualenv'); const defaultVenv = { @@ -111,6 +124,17 @@ const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => { name="description" type="text" /> + executionEnvironmentHelpers.setTouched()} + value={executionEnvironmentField.value} + onChange={value => executionEnvironmentHelpers.setValue(value)} + globallyAvailable + organizationId={organizationId} + /> { const initialValues = { credential: source?.summary_fields?.credential || null, @@ -264,6 +289,8 @@ const InventorySourceForm = ({ enabled_var: source?.enabled_var || '', enabled_value: source?.enabled_value || '', host_filter: source?.host_filter || '', + execution_environment: + source?.summary_fields?.execution_environment || null, }; const { @@ -306,6 +333,7 @@ const InventorySourceForm = ({ i18n={i18n} source={source} sourceOptions={sourceOptions} + organizationId={organizationId} /> {submitError && } ', () => { expect( wrapper.find('FormGroup[label="Ansible Environment"]') ).toHaveLength(1); + expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1); }); test('should display subform when source dropdown has a value', async () => { diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx index 35b48f17fb..ad0be4c9e6 100644 --- a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx +++ b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx @@ -252,13 +252,13 @@ function JobDetail({ job, i18n }) { - {instanceGroup && !instanceGroup?.is_containerized && ( + {instanceGroup && !instanceGroup?.is_container_group && ( )} - {instanceGroup && instanceGroup?.is_containerized && ( + {instanceGroup && instanceGroup?.is_container_group && ( )} + {organization && ( + + + + )} {!organizationLoading && !rolesLoading && ( diff --git a/awx/ui_next/src/screens/Organization/Organization.test.jsx b/awx/ui_next/src/screens/Organization/Organization.test.jsx index 10982505d5..487ebff36b 100644 --- a/awx/ui_next/src/screens/Organization/Organization.test.jsx +++ b/awx/ui_next/src/screens/Organization/Organization.test.jsx @@ -68,7 +68,7 @@ describe('', () => { const tabs = await waitForElement( wrapper, '.pf-c-tabs__item', - el => el.length === 5 + el => el.length === 6 ); expect(tabs.last().text()).toEqual('Notifications'); wrapper.unmount(); @@ -92,7 +92,7 @@ describe('', () => { const tabs = await waitForElement( wrapper, '.pf-c-tabs__item', - el => el.length === 4 + el => el.length === 5 ); tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications')); wrapper.unmount(); diff --git a/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.jsx b/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.jsx index d9c14765ac..adbe04820d 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.jsx @@ -13,7 +13,10 @@ function OrganizationAdd() { const handleSubmit = async (values, groupsToAssociate) => { try { - const { data: response } = await OrganizationsAPI.create(values); + const { data: response } = await OrganizationsAPI.create({ + ...values, + default_environment: values.default_environment?.id, + }); await Promise.all( groupsToAssociate .map(id => OrganizationsAPI.associateInstanceGroup(response.id, id)) diff --git a/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.jsx index 8fa4e2cbc2..d99634ea09 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.jsx @@ -17,13 +17,18 @@ describe('', () => { description: 'new description', custom_virtualenv: 'Buzz', galaxy_credentials: [], + default_environment: { id: 1, name: 'Foo' }, }; OrganizationsAPI.create.mockResolvedValueOnce({ data: {} }); await act(async () => { const wrapper = mountWithContexts(); wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, []); }); - expect(OrganizationsAPI.create).toHaveBeenCalledWith(updatedOrgData); + expect(OrganizationsAPI.create).toHaveBeenCalledWith({ + ...updatedOrgData, + default_environment: 1, + }); + expect(OrganizationsAPI.create).toHaveBeenCalledTimes(1); }); test('should navigate to organizations list when cancel is clicked', async () => { diff --git a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx index 6b55780333..e3b544c091 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx @@ -94,6 +94,18 @@ function OrganizationDetail({ i18n, organization }) { label={i18n._(t`Ansible Environment`)} value={custom_virtualenv} /> + {summary_fields?.default_environment?.name && ( + + {summary_fields.default_environment.name} + + } + /> + )} ', () => { const mockOrganization = { + id: 12, name: 'Foo', description: 'Bar', custom_virtualenv: 'Fizz', @@ -24,7 +25,14 @@ describe('', () => { edit: true, delete: true, }, + default_environment: { + id: 1, + name: 'Default EE', + description: '', + image: 'quay.io/ansible/awx-ee', + }, }, + default_environment: 1, }; const mockInstanceGroups = { data: { @@ -43,7 +51,7 @@ describe('', () => { jest.clearAllMocks(); }); - test('initially renders succesfully', async () => { + test('initially renders successfully', async () => { await act(async () => { mountWithContexts(); }); @@ -86,6 +94,7 @@ describe('', () => { { label: 'Created', value: '7/7/2015, 5:21:26 PM' }, { label: 'Last Modified', value: '8/11/2019, 7:47:37 PM' }, { label: 'Max Hosts', value: '0' }, + { label: 'Default Execution Environment', value: 'Default EE' }, ]; for (let i = 0; i < testParams.length; i++) { const { label, value } = testParams[i]; diff --git a/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.jsx b/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.jsx index 849a273ef5..3297d2fd6f 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.jsx @@ -28,7 +28,10 @@ function OrganizationEdit({ organization }) { const addedCredentialIds = addedCredentials.map(({ id }) => id); const removedCredentialIds = removedCredentials.map(({ id }) => id); - await OrganizationsAPI.update(organization.id, values); + await OrganizationsAPI.update(organization.id, { + ...values, + default_environment: values.default_environment?.id || null, + }); await Promise.all( groupsToAssociate .map(id => diff --git a/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.jsx index ea62e38c9e..5556ee05d5 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.jsx @@ -19,6 +19,13 @@ describe('', () => { related: { instance_groups: '/api/v2/organizations/1/instance_groups', }, + default_environment: 1, + summary_fields: { + default_environment: { + id: 1, + name: 'Baz', + }, + }, }; test('onSubmit should call api update', async () => { @@ -31,6 +38,7 @@ describe('', () => { name: 'new name', description: 'new description', custom_virtualenv: 'Buzz', + default_environment: null, }; wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []); diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx new file mode 100644 index 0000000000..9f2c4ae817 --- /dev/null +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx @@ -0,0 +1,135 @@ +import React, { useEffect, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Card } from '@patternfly/react-core'; + +import { OrganizationsAPI } from '../../../api'; +import { getQSConfig, parseQueryString } from '../../../util/qs'; +import useRequest from '../../../util/useRequest'; +import PaginatedDataList from '../../../components/PaginatedDataList'; +import DatalistToolbar from '../../../components/DataListToolbar'; + +import OrganizationExecEnvListItem from './OrganizationExecEnvListItem'; + +const QS_CONFIG = getQSConfig('organizations', { + page: 1, + page_size: 20, + order_by: 'name', +}); + +function OrganizationExecEnvList({ i18n, organization }) { + const { id } = organization; + const location = useLocation(); + + const { + error: contentError, + isLoading, + request: fetchExecutionEnvironments, + result: { + executionEnvironments, + executionEnvironmentsCount, + relatedSearchableKeys, + searchableKeys, + }, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); + + const [response, responseActions] = await Promise.all([ + OrganizationsAPI.readExecutionEnvironments(id, params), + OrganizationsAPI.readExecutionEnvironmentsOptions(id, params), + ]); + + return { + executionEnvironments: response.data.results, + executionEnvironmentsCount: response.data.count, + actions: responseActions.data.actions, + relatedSearchableKeys: ( + responseActions?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + responseActions.data.actions?.GET || {} + ).filter(key => responseActions.data.actions?.GET[key].filterable), + }; + }, [location, id]), + { + executionEnvironments: [], + executionEnvironmentsCount: 0, + actions: {}, + relatedSearchableKeys: [], + searchableKeys: [], + } + ); + + useEffect(() => { + fetchExecutionEnvironments(); + }, [fetchExecutionEnvironments]); + + return ( + <> + + ( + + )} + renderItem={executionEnvironment => ( + + )} + /> + + + ); +} + +export default withI18n()(OrganizationExecEnvList); diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.jsx new file mode 100644 index 0000000000..07e8a53ea5 --- /dev/null +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { + mountWithContexts, + waitForElement, +} from '../../../../testUtils/enzymeHelpers'; + +import { OrganizationsAPI } from '../../../api'; +import OrganizationExecEnvList from './OrganizationExecEnvList'; + +jest.mock('../../../api/'); + +const executionEnvironments = { + data: { + count: 3, + results: [ + { + id: 1, + type: 'execution_environment', + url: '/api/v2/execution_environments/1/', + related: { + organization: '/api/v2/organizations/1/', + }, + organization: 1, + image: 'https://localhost.com/image/disk', + managed_by_tower: false, + credential: null, + }, + { + id: 2, + type: 'execution_environment', + url: '/api/v2/execution_environments/2/', + related: { + organization: '/api/v2/organizations/1/', + }, + organization: 1, + image: 'test/image123', + managed_by_tower: false, + credential: null, + }, + { + id: 3, + type: 'execution_environment', + url: '/api/v2/execution_environments/3/', + related: { + organization: '/api/v2/organizations/1/', + }, + organization: 1, + image: 'test/test', + managed_by_tower: false, + credential: null, + }, + ], + }, +}; + +const mockOrganization = { + id: 1, + type: 'organization', + name: 'Default', +}; + +const options = { data: { actions: { POST: {}, GET: {} } } }; + +describe('', () => { + let wrapper; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement( + wrapper, + 'OrganizationExecEnvList', + el => el.length > 0 + ); + }); + + test('should have data fetched and render 3 rows', async () => { + OrganizationsAPI.readExecutionEnvironments.mockResolvedValue( + executionEnvironments + ); + + OrganizationsAPI.readExecutionEnvironmentsOptions.mockResolvedValue( + options + ); + + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement( + wrapper, + 'OrganizationExecEnvList', + el => el.length > 0 + ); + + expect(wrapper.find('OrganizationExecEnvListItem').length).toBe(3); + expect(OrganizationsAPI.readExecutionEnvironments).toBeCalled(); + expect(OrganizationsAPI.readExecutionEnvironmentsOptions).toBeCalled(); + }); + + test('should not render add button', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + waitForElement(wrapper, 'OrganizationExecEnvList', el => el.length > 0); + expect(wrapper.find('ToolbarAddButton').length).toBe(0); + }); +}); diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.jsx b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.jsx new file mode 100644 index 0000000000..0d2715d7a6 --- /dev/null +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.jsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { string } from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Link } from 'react-router-dom'; +import { + DataListItem, + DataListItemRow, + DataListItemCells, +} from '@patternfly/react-core'; + +import DataListCell from '../../../components/DataListCell'; +import { ExecutionEnvironment } from '../../../types'; + +function OrganizationExecEnvListItem({ + executionEnvironment, + detailUrl, + i18n, +}) { + const labelId = `check-action-${executionEnvironment.id}`; + + return ( + + + + + {executionEnvironment.name} + + , + + {executionEnvironment.image} + , + ]} + /> + + + ); +} + +OrganizationExecEnvListItem.prototype = { + executionEnvironment: ExecutionEnvironment.isRequired, + detailUrl: string.isRequired, +}; + +export default withI18n()(OrganizationExecEnvListItem); diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.jsx new file mode 100644 index 0000000000..29181f4ec3 --- /dev/null +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; + +import OrganizationExecEnvListItem from './OrganizationExecEnvListItem'; + +describe('', () => { + let wrapper; + const executionEnvironment = { + id: 1, + image: 'https://registry.com/r/image/manifest', + name: 'foo', + organization: 1, + credential: null, + pull: 'always', + }; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('OrganizationExecEnvListItem').length).toBe(1); + }); + + test('should render the proper data', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect( + wrapper + .find('DataListCell[aria-label="Execution environment image"]') + .text() + ).toBe(executionEnvironment.image); + }); +}); diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/index.js b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/index.js new file mode 100644 index 0000000000..668a3beb61 --- /dev/null +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/index.js @@ -0,0 +1 @@ +export { default } from './OrganizationExecEnvList'; diff --git a/awx/ui_next/src/screens/Organization/Organizations.jsx b/awx/ui_next/src/screens/Organization/Organizations.jsx index 6c7b17dc69..fcf1b8398b 100644 --- a/awx/ui_next/src/screens/Organization/Organizations.jsx +++ b/awx/ui_next/src/screens/Organization/Organizations.jsx @@ -34,6 +34,9 @@ function Organizations({ i18n }) { [`/organizations/${organization.id}/notifications`]: i18n._( t`Notifications` ), + [`/organizations/${organization.id}/execution_environments`]: i18n._( + t`Execution Environments` + ), }; setBreadcrumbConfig(breadcrumb); }, diff --git a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx index 094e6ac5b6..eb46f8c5cc 100644 --- a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx +++ b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx @@ -12,16 +12,21 @@ import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import FormField, { FormSubmitError } from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup'; -import { InstanceGroupsLookup } from '../../../components/Lookup'; +import { + InstanceGroupsLookup, + ExecutionEnvironmentLookup, +} from '../../../components/Lookup'; import { getAddedAndRemoved } from '../../../util/lists'; import { required, minMaxValue } from '../../../util/validators'; import { FormColumnLayout } from '../../../components/FormLayout'; import CredentialLookup from '../../../components/Lookup/CredentialLookup'; function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) { + const { license_info = {}, me = {} } = useConfig(); + const { custom_virtualenvs } = useContext(ConfigContext); + const { setFieldValue } = useFormikContext(); const [venvField] = useField('custom_virtualenv'); - const { license_info = {}, me = {} } = useConfig(); const [ galaxyCredentialsField, @@ -29,12 +34,19 @@ function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) { galaxyCredentialsHelpers, ] = useField('galaxy_credentials'); + const [ + executionEnvironmentField, + executionEnvironmentMeta, + executionEnvironmentHelpers, + ] = useField({ + name: 'default_environment', + }); + const defaultVenv = { label: i18n._(t`Use Default Ansible Environment`), value: '/var/lib/awx/venv/ansible/', key: 'default', }; - const { custom_virtualenvs } = useContext(ConfigContext); const handleCredentialUpdate = useCallback( value => { @@ -100,6 +112,20 @@ function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) { t`Select the Instance Groups for this Organization to run on.` )} /> + executionEnvironmentHelpers.setTouched()} + value={executionEnvironmentField.value} + onChange={value => executionEnvironmentHelpers.setValue(value)} + popoverContent={i18n._( + t`Select the default execution environment for this organization.` + )} + globallyAvailable + isDefaultEnvironment + /> @@ -221,6 +249,7 @@ OrganizationForm.defaultProps = { description: '', max_hosts: '0', custom_virtualenv: '', + default_environment: '', }, submitError: null, }; diff --git a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.test.jsx b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.test.jsx index 67cf0a60d6..7dfbca620c 100644 --- a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.test.jsx +++ b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.test.jsx @@ -4,7 +4,7 @@ import { mountWithContexts, waitForElement, } from '../../../../testUtils/enzymeHelpers'; -import { OrganizationsAPI } from '../../../api'; +import { OrganizationsAPI, ExecutionEnvironmentsAPI } from '../../../api'; import OrganizationForm from './OrganizationForm'; @@ -32,6 +32,8 @@ describe('', () => { { name: 'Two', id: 2 }, ]; + const mockExecutionEnvironment = [{ name: 'EE' }]; + afterEach(() => { jest.clearAllMocks(); }); @@ -132,6 +134,11 @@ describe('', () => { results: mockInstanceGroups, }, }); + ExecutionEnvironmentsAPI.read.mockReturnValue({ + data: { + results: mockExecutionEnvironment, + }, + }); let wrapper; const onSubmit = jest.fn(); await act(async () => { @@ -155,10 +162,15 @@ describe('', () => { wrapper.find('input#org-max_hosts').simulate('change', { target: { value: 134, name: 'max_hosts' }, }); + wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({ + id: 1, + name: 'Test EE', + }); }); await act(async () => { wrapper.find('button[aria-label="Save"]').simulate('click'); }); + wrapper.update(); expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit.mock.calls[0][0]).toEqual({ name: 'new foo', @@ -166,6 +178,7 @@ describe('', () => { galaxy_credentials: [], custom_virtualenv: 'Fizz', max_hosts: 134, + default_environment: { id: 1, name: 'Test EE' }, }); }); @@ -209,12 +222,16 @@ describe('', () => { results: mockInstanceGroups, }, }); + ExecutionEnvironmentsAPI.read.mockReturnValue({ + data: { results: mockExecutionEnvironment }, + }); const mockDataForm = { name: 'Foo', description: 'Bar', galaxy_credentials: [], max_hosts: 1, custom_virtualenv: 'Fizz', + default_environment: '', }; const onSubmit = jest.fn(); OrganizationsAPI.update.mockResolvedValue(1, mockDataForm); @@ -320,6 +337,7 @@ describe('', () => { galaxy_credentials: [], max_hosts: 0, custom_virtualenv: 'Fizz', + default_environment: '', }, [], [] diff --git a/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.jsx b/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.jsx index d0190830e6..eaaa4274f3 100644 --- a/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.jsx +++ b/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.jsx @@ -27,6 +27,7 @@ function ProjectAdd() { } = await ProjectsAPI.create({ ...values, organization: values.organization.id, + default_environment: values.default_environment?.id, }); history.push(`/projects/${id}/details`); } catch (error) { diff --git a/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.test.jsx b/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.test.jsx index 8bc136b889..76bfd49256 100644 --- a/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectAdd/ProjectAdd.test.jsx @@ -20,11 +20,12 @@ describe('', () => { scm_clean: true, credential: 100, local_path: '', - organization: 2, + organization: { id: 2, name: 'Bar' }, scm_update_on_launch: true, scm_update_cache_timeout: 3, allow_override: false, custom_virtualenv: '/var/lib/awx/venv/custom-env', + default_environment: { id: 1, name: 'Foo' }, }; const projectOptionsResolve = { @@ -102,6 +103,11 @@ describe('', () => { await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); wrapper.find('ProjectForm').invoke('handleSubmit')(projectData); expect(ProjectsAPI.create).toHaveBeenCalledTimes(1); + expect(ProjectsAPI.create).toHaveBeenCalledWith({ + ...projectData, + organization: 2, + default_environment: 1, + }); }); test('handleSubmit should throw an error', async () => { diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 4c92c9695e..984aaab6fb 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -124,10 +124,23 @@ function ProjectDetail({ project, i18n }) { label={i18n._(t`Cache Timeout`)} value={`${scm_update_cache_timeout} ${i18n._(t`Seconds`)}`} /> + + {summary_fields?.default_environment?.name && ( + + {summary_fields.default_environment.name} + + } + /> + )} {({ project_base_dir }) => ( + ', () => { id: 10, name: 'Foo', }, + default_environment: { + id: 12, + name: 'Bar', + image: 'quay.io/ansible/awx-ee', + }, credential: { id: 1000, name: 'qux', @@ -67,9 +72,10 @@ describe('', () => { scm_update_cache_timeout: 5, allow_override: true, custom_virtualenv: '/custom-venv', + default_environment: 1, }; - test('initially renders succesfully', () => { + test('initially renders successfully', () => { mountWithContexts(); }); @@ -95,6 +101,10 @@ describe('', () => { `${mockProject.scm_update_cache_timeout} Seconds` ); assertDetail('Ansible Environment', mockProject.custom_virtualenv); + assertDetail( + 'Execution Environment', + mockProject.summary_fields.default_environment.name + ); const dateDetails = wrapper.find('UserDateDetail'); expect(dateDetails).toHaveLength(2); expect(dateDetails.at(0).prop('label')).toEqual('Created'); diff --git a/awx/ui_next/src/screens/Project/ProjectEdit/ProjectEdit.jsx b/awx/ui_next/src/screens/Project/ProjectEdit/ProjectEdit.jsx index 6642e03503..3682a01cdc 100644 --- a/awx/ui_next/src/screens/Project/ProjectEdit/ProjectEdit.jsx +++ b/awx/ui_next/src/screens/Project/ProjectEdit/ProjectEdit.jsx @@ -26,6 +26,7 @@ function ProjectEdit({ project }) { } = await ProjectsAPI.update(project.id, { ...values, organization: values.organization.id, + default_environment: values.default_environment?.id || null, }); history.push(`/projects/${id}/details`); } catch (error) { diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx index 8c52218c1e..b2b5b80486 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx @@ -12,6 +12,7 @@ import ContentLoading from '../../../components/ContentLoading'; import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup'; import FormField, { FormSubmitError } from '../../../components/FormField'; import OrganizationLookup from '../../../components/Lookup/OrganizationLookup'; +import ExecutionEnvironmentLookup from '../../../components/Lookup/ExecutionEnvironmentLookup'; import { CredentialTypesAPI, ProjectsAPI } from '../../../api'; import { required } from '../../../util/validators'; import { @@ -101,6 +102,14 @@ function ProjectFormFields({ validate: required(i18n._(t`Select a value for this field`), i18n), }); + const [ + executionEnvironmentField, + executionEnvironmentMeta, + executionEnvironmentHelpers, + ] = useField({ + name: 'default_environment', + }); + /* Save current scm subform field values to state */ const saveSubFormState = form => { const currentScmFormFields = { ...scmFormFields }; @@ -178,6 +187,25 @@ function ProjectFormFields({ required autoPopulate={!project?.id} /> + executionEnvironmentHelpers.setTouched()} + value={executionEnvironmentField.value} + onChange={value => executionEnvironmentHelpers.setValue(value)} + popoverContent={i18n._( + t`Select the default execution environment for this project.` + )} + tooltip={i18n._( + t`Select an organization before editing the default execution environment.` + )} + globallyAvailable + isDisabled={!organizationField.value} + organizationId={organizationField.value?.id} + isDefaultEnvironment + /> diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx index 562987ba4b..bbb5e47b84 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx @@ -27,7 +27,10 @@ function JobTemplateAdd() { try { const { data: { id, type }, - } = await JobTemplatesAPI.create(remainingValues); + } = await JobTemplatesAPI.create({ + ...remainingValues, + execution_environment: values.execution_environment?.id, + }); await Promise.all([ submitLabels(id, labels, values.project.summary_fields.organization.id), submitInstanceGroups(id, instanceGroups), diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx index 7b8675cc65..b93b56ef53 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx @@ -58,6 +58,7 @@ const jobTemplateData = { timeout: 0, use_fact_cache: false, verbosity: '0', + execution_environment: { id: 1, name: 'Foo', image: 'localhost.com' }, }; describe('', () => { @@ -77,6 +78,12 @@ describe('', () => { beforeEach(() => { LabelsAPI.read.mockResolvedValue({ data: { results: [] } }); + ProjectsAPI.readDetail.mockReturnValue({ + name: 'foo', + id: 1, + allow_override: true, + organization: 1, + }); }); afterEach(() => { @@ -126,12 +133,13 @@ describe('', () => { ...jobTemplateData, }, }); + let wrapper; await act(async () => { wrapper = mountWithContexts(); }); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); - act(() => { + await act(async () => { wrapper.find('input#template-name').simulate('change', { target: { value: 'Bar', name: 'name' }, }); @@ -144,6 +152,10 @@ describe('', () => { name: 'project', summary_fields: { organization: { id: 1, name: 'Org Foo' } }, }); + wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({ + id: 1, + name: 'Foo', + }); wrapper.update(); wrapper.find('Select#template-playbook').prop('onToggle')(); wrapper.update(); @@ -170,6 +182,7 @@ describe('', () => { inventory: 2, webhook_credential: undefined, webhook_service: '', + execution_environment: 1, }); }); @@ -190,7 +203,7 @@ describe('', () => { }); }); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); - act(() => { + await act(async () => { wrapper.find('input#template-name').simulate('change', { target: { value: 'Foo', name: 'name' }, }); diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx index 3feed6bb7d..4a2b47605f 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx @@ -206,6 +206,18 @@ function JobTemplateDetail({ i18n, template }) { ) : ( )} + {summary_fields?.execution_environment && ( + + {summary_fields.execution_environment.name} + + } + /> + )} ', () => { el => el.length === 0 ); }); + test('webhook fields should render properly', () => { expect(wrapper.find('Detail[label="Webhook Service"]').length).toBe(1); expect(wrapper.find('Detail[label="Webhook Service"]').prop('value')).toBe( @@ -154,4 +155,13 @@ describe('', () => { expect(wrapper.find('Detail[label="Webhook Key"]').length).toBe(1); expect(wrapper.find('Detail[label="Webhook Credential"]').length).toBe(1); }); + + test('execution environment field should render properly', () => { + expect(wrapper.find('Detail[label="Execution Environment"]').length).toBe( + 1 + ); + expect( + wrapper.find(`Detail[label="Execution Environment"] dd`).text() + ).toBe('Default EE'); + }); }); diff --git a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.jsx index 213900d40d..6f109604b5 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.jsx @@ -57,7 +57,10 @@ function JobTemplateEdit({ template }) { remainingValues.project = values.project.id; remainingValues.webhook_credential = webhook_credential?.id || null; try { - await JobTemplatesAPI.update(template.id, remainingValues); + await JobTemplatesAPI.update(template.id, { + ...remainingValues, + execution_environment: values.execution_environment?.id, + }); await Promise.all([ submitLabels(labels, template?.organization), submitInstanceGroups(instanceGroups, initialInstanceGroups), diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx index d9edd77030..3656c73c02 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx @@ -28,7 +28,10 @@ function WorkflowJobTemplateAdd() { try { const { data: { id }, - } = await WorkflowJobTemplatesAPI.create(templatePayload); + } = await WorkflowJobTemplatesAPI.create({ + ...templatePayload, + execution_environment: values.execution_environment?.id, + }); await Promise.all(await submitLabels(id, labels, organizationId)); history.push(`/templates/workflow_job_template/${id}/visualizer`); } catch (err) { diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx index c6ac734bfa..2cfd5e0093 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx @@ -6,6 +6,7 @@ import { WorkflowJobTemplatesAPI, OrganizationsAPI, LabelsAPI, + ExecutionEnvironmentsAPI, } from '../../../api'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; @@ -15,6 +16,7 @@ jest.mock('../../../api/models/WorkflowJobTemplates'); jest.mock('../../../api/models/Organizations'); jest.mock('../../../api/models/Labels'); jest.mock('../../../api/models/Inventories'); +jest.mock('../../../api/models/ExecutionEnvironments'); describe('', () => { let wrapper; @@ -34,6 +36,10 @@ describe('', () => { }, }); + ExecutionEnvironmentsAPI.read.mockResolvedValue({ + data: { results: [{ id: 1, name: 'Foo', image: 'localhost.com' }] }, + }); + await act(async () => { history = createMemoryHistory({ initialEntries: ['/templates/workflow_job_template/add'], @@ -82,6 +88,11 @@ describe('', () => { .find('LabelSelect') .find('SelectToggle') .simulate('click'); + + wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({ + id: 1, + name: 'Foo', + }); }); wrapper.update(); @@ -113,6 +124,7 @@ describe('', () => { webhook_credential: undefined, webhook_service: '', webhook_url: '', + execution_environment: 1, }); expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(1); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx index 0ce65ca092..4f8237e8c2 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx @@ -125,6 +125,18 @@ function WorkflowJobTemplateDetail({ template, i18n }) { } /> )} + {summary_fields?.execution_environment && ( + + {summary_fields.execution_environment.name} + + } + /> + )} {summary_fields.inventory && ( ', () => { created_by: { id: 1, username: 'Athena' }, modified_by: { id: 1, username: 'Apollo' }, organization: { id: 1, name: 'Org' }, + execution_environment: { + id: 4, + name: 'Demo EE', + description: '', + image: 'quay.io/ansible/awx-ee', + }, inventory: { kind: 'Foo', id: 1, name: 'Bar' }, labels: { results: [ @@ -40,6 +46,7 @@ describe('', () => { }, webhook_service: 'Github', webhook_key: 'Foo webhook key', + execution_environment: 4, }; beforeEach(async () => { @@ -150,6 +157,10 @@ describe('', () => { }; renderedValues.map(value => assertValue(value)); + + expect( + wrapper.find(`Detail[label="Execution Environment"] dd`).text() + ).toBe('Demo EE'); }); test('link out resource have the correct url', () => { diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx index be61a0ee43..cb963e634f 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx @@ -20,7 +20,7 @@ function WorkflowJobTemplateEdit({ template }) { ...templatePayload } = values; templatePayload.inventory = inventory?.id || null; - templatePayload.organization = organization?.id; + templatePayload.organization = organization?.id || null; templatePayload.webhook_credential = webhook_credential?.id || null; const formOrgId = @@ -29,7 +29,10 @@ function WorkflowJobTemplateEdit({ template }) { await Promise.all( await submitLabels(labels, formOrgId, template.organization) ); - await WorkflowJobTemplatesAPI.update(template.id, templatePayload); + await WorkflowJobTemplatesAPI.update(template.id, { + ...templatePayload, + execution_environment: values.execution_environment?.id, + }); history.push(`/templates/workflow_job_template/${template.id}/details`); } catch (err) { setFormSubmitError(err); diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index f504224507..b1ba6aac3d 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -37,6 +37,7 @@ import { InstanceGroupsLookup, ProjectLookup, MultiCredentialsLookup, + ExecutionEnvironmentLookup, } from '../../../components/Lookup'; import Popover from '../../../components/Popover'; import { JobTemplatesAPI } from '../../../api'; @@ -101,10 +102,16 @@ function JobTemplateForm({ 'webhook_credential' ); + const [ + executionEnvironmentField, + executionEnvironmentMeta, + executionEnvironmentHelpers, + ] = useField({ name: 'execution_environment' }); + const { request: loadRelatedInstanceGroups, error: instanceGroupError, - contentLoading: instanceGroupLoading, + isLoading: instanceGroupLoading, } = useRequest( useCallback(async () => { if (!template?.id) { @@ -258,6 +265,7 @@ function JobTemplateForm({ isOverrideDisabled={isOverrideDisabledLookup} /> + projectHelpers.setTouched()} @@ -270,6 +278,26 @@ function JobTemplateForm({ autoPopulate={!template?.id} isOverrideDisabled={isOverrideDisabledLookup} /> + + executionEnvironmentHelpers.setTouched()} + value={executionEnvironmentField.value} + onChange={value => executionEnvironmentHelpers.setValue(value)} + popoverContent={i18n._( + t`Select the execution environment for this job template.` + )} + tooltip={i18n._( + t`Select a project before editing the execution environment.` + )} + globallyAvailable + isDisabled={!projectField.value} + projectId={projectField.value?.id} + /> + {projectField.value?.allow_override && ( { diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx index fd15c92e28..5ee34bbe12 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx @@ -22,7 +22,10 @@ import { SubFormLayout, } from '../../../components/FormLayout'; import OrganizationLookup from '../../../components/Lookup/OrganizationLookup'; -import { InventoryLookup } from '../../../components/Lookup'; +import { + InventoryLookup, + ExecutionEnvironmentLookup, +} from '../../../components/Lookup'; import { VariablesField } from '../../../components/CodeMirrorInput'; import FormActionGroup from '../../../components/FormActionGroup'; import ContentError from '../../../components/ContentError'; @@ -63,6 +66,14 @@ function WorkflowJobTemplateForm({ 'webhook_credential' ); + const [ + executionEnvironmentField, + executionEnvironmentMeta, + executionEnvironmentHelpers, + ] = useField({ + name: 'execution_environment', + }); + useEffect(() => { if (enableWebhooks) { webhookServiceHelpers.setValue(webhookServiceMeta.initialValue); @@ -178,6 +189,20 @@ function WorkflowJobTemplateForm({ }} /> + executionEnvironmentHelpers.setTouched()} + value={executionEnvironmentField.value} + onChange={value => executionEnvironmentHelpers.setValue(value)} + tooltip={i18n._( + t`Select the default execution environment for this organization to run on.` + )} + globallyAvailable + organizationId={organizationField.value?.id} + /> { diff --git a/awx/ui_next/src/screens/Template/shared/data.job_template.json b/awx/ui_next/src/screens/Template/shared/data.job_template.json index 804c3b72a2..fa516d46db 100644 --- a/awx/ui_next/src/screens/Template/shared/data.job_template.json +++ b/awx/ui_next/src/screens/Template/shared/data.job_template.json @@ -133,6 +133,12 @@ "id": "1", "name": "Webhook Credential" + }, + "execution_environment": { + "id": 1, + "name": "Default EE", + "description": "", + "image": "quay.io/ansible/awx-ee" } }, "created": "2019-09-30T16:18:34.564820Z", @@ -177,5 +183,6 @@ "job_slice_count": 1, "webhook_credential": 1, "webhook_key": "asertdyuhjkhgfd234567kjgfds", - "webhook_service": "github" + "webhook_service": "github", + "execution_environment": 1 } diff --git a/awx/ui_next/src/types.js b/awx/ui_next/src/types.js index 760ecf4ed2..744360eaea 100644 --- a/awx/ui_next/src/types.js +++ b/awx/ui_next/src/types.js @@ -407,3 +407,14 @@ export const WorkflowApproval = shape({ approval_expiration: string, timed_out: bool, }); + +export const ExecutionEnvironment = shape({ + id: number.isRequired, + organization: number, + credential: number, + image: string.isRequired, + url: string, + summary_fields: shape({}), + description: string, + pull: string, +}); diff --git a/awx_collection/galaxy.yml b/awx_collection/galaxy.yml index 15ae9cdcbb..0db8abb865 100644 --- a/awx_collection/galaxy.yml +++ b/awx_collection/galaxy.yml @@ -29,7 +29,7 @@ tags: - awx - ansible - automation -version: 0.0.1-devel +version: 17.0.2-devel build_ignore: - tools - setup.cfg diff --git a/awx_collection/plugins/modules/tower_ad_hoc_command.py b/awx_collection/plugins/modules/tower_ad_hoc_command.py index 00f16d9f13..4ee416ccdf 100644 --- a/awx_collection/plugins/modules/tower_ad_hoc_command.py +++ b/awx_collection/plugins/modules/tower_ad_hoc_command.py @@ -28,6 +28,11 @@ options: - Job_type to use for the ad hoc command. type: str choices: [ 'run', 'check' ] + execution_environment: + description: + - Execution Environment to use for the ad hoc command. + required: False + type: str inventory: description: - Inventory to use for the ad hoc command. @@ -127,6 +132,7 @@ def main(): wait=dict(default=False, type='bool'), interval=dict(default=1.0, type='float'), timeout=dict(default=None, type='int'), + execution_environment=dict(), ) # Create a module for ourselves diff --git a/awx_collection/plugins/modules/tower_execution_environment.py b/awx_collection/plugins/modules/tower_execution_environment.py new file mode 100644 index 0000000000..b728810323 --- /dev/null +++ b/awx_collection/plugins/modules/tower_execution_environment.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2020, Shane McDonald +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: tower_execution_environment +author: "Shane McDonald (@shanemcd)" +short_description: create, update, or destroy Execution Environments in Ansible Tower. +description: + - Create, update, or destroy Execution Environments in Ansible Tower. See + U(https://www.ansible.com/tower) for an overview. +options: + name: + description: + - Name to use for the execution environment. + required: True + type: str + image: + description: + - The fully qualified url of the container image. + required: True + type: str + description: + description: + - Description to use for the execution environment. + type: str + organization: + description: + - The organization the execution environment belongs to. + type: str + credential: + description: + - Name of the credential to use for the execution environment. + type: str + state: + description: + - Desired state of the resource. + choices: ["present", "absent"] + default: "present" + type: str + pull: + description: + - determine image pull behavior + choices: ["always", "missing", "never"] + default: 'missing' + type: str +extends_documentation_fragment: awx.awx.auth +''' + + +EXAMPLES = ''' +- name: Add EE to Tower + tower_execution_environment: + name: "My EE" + image: quay.io/ansible/awx-ee +''' + + +from ..module_utils.tower_api import TowerAPIModule +import json + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + name=dict(required=True), + image=dict(required=True), + description=dict(default=''), + organization=dict(), + credential=dict(default=''), + state=dict(choices=['present', 'absent'], default='present'), + pull=dict(choices=['always', 'missing', 'never'], default='missing') + ) + + # Create a module for ourselves + module = TowerAPIModule(argument_spec=argument_spec) + + # Extract our parameters + name = module.params.get('name') + image = module.params.get('image') + description = module.params.get('description') + state = module.params.get('state') + pull = module.params.get('pull') + + existing_item = module.get_one('execution_environments', name_or_id=name) + + if state == 'absent': + module.delete_if_needed(existing_item) + + new_fields = { + 'name': name, + 'image': image, + } + if description: + new_fields['description'] = description + + if pull: + new_fields['pull'] = pull + + # Attempt to look up the related items the user specified (these will fail the module if not found) + organization = module.params.get('organization') + if organization: + new_fields['organization'] = module.resolve_name_to_id('organizations', organization) + + credential = module.params.get('credential') + if credential: + new_fields['credential'] = module.resolve_name_to_id('credentials', credential) + + module.create_or_update_if_needed( + existing_item, new_fields, + endpoint='execution_environments', + item_type='execution_environment' + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/plugins/modules/tower_export.py b/awx_collection/plugins/modules/tower_export.py index ac7ff5a499..e3b2fa37d6 100644 --- a/awx_collection/plugins/modules/tower_export.py +++ b/awx_collection/plugins/modules/tower_export.py @@ -47,6 +47,10 @@ options: description: - credential name to export type: str + execution_environments: + description: + - execution environment name to export + type: str notification_templates: description: - notification template name to export diff --git a/awx_collection/plugins/modules/tower_inventory_source.py b/awx_collection/plugins/modules/tower_inventory_source.py index 5945d411d8..0bde4d7af3 100644 --- a/awx_collection/plugins/modules/tower_inventory_source.py +++ b/awx_collection/plugins/modules/tower_inventory_source.py @@ -73,6 +73,10 @@ options: description: - Credential to use for the source. type: str + execution_environment: + description: + - Execution Environment to use for the source. + type: str overwrite: description: - Delete child groups and hosts not found in source. @@ -81,10 +85,6 @@ options: description: - Override vars in child groups and hosts with those from external source. type: bool - custom_virtualenv: - description: - - Local absolute file path containing a custom Python virtualenv to use. - type: str timeout: description: The amount of time (in seconds) to run before the task is canceled. type: int @@ -173,10 +173,10 @@ def main(): enabled_value=dict(), host_filter=dict(), credential=dict(), + execution_environment=dict(), organization=dict(), overwrite=dict(type='bool'), overwrite_vars=dict(type='bool'), - custom_virtualenv=dict(), timeout=dict(type='int'), verbosity=dict(type='int', choices=[0, 1, 2]), update_on_launch=dict(type='bool'), @@ -199,6 +199,7 @@ def main(): organization = module.params.get('organization') source_script = module.params.get('source_script') credential = module.params.get('credential') + ee = module.params.get('execution_environment') source_project = module.params.get('source_project') state = module.params.get('state') @@ -250,6 +251,8 @@ def main(): # Attempt to look up the related items the user specified (these will fail the module if not found) if credential is not None: inventory_source_fields['credential'] = module.resolve_name_to_id('credentials', credential) + if ee is not None: + inventory_source_fields['execution_environment'] = module.resolve_name_to_id('execution_environments', ee) if source_project is not None: inventory_source_fields['source_project'] = module.resolve_name_to_id('projects', source_project) if source_script is not None: @@ -257,7 +260,7 @@ def main(): OPTIONAL_VARS = ( 'description', 'source', 'source_path', 'source_vars', - 'overwrite', 'overwrite_vars', 'custom_virtualenv', + 'overwrite', 'overwrite_vars', 'timeout', 'verbosity', 'update_on_launch', 'update_cache_timeout', 'update_on_project_update', 'enabled_var', 'enabled_value', 'host_filter', ) diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 2ea032f9bc..d38ef0f5a9 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -83,6 +83,10 @@ options: - Name of the vault credential to use for the job template. - Deprecated, use 'credentials'. type: str + execution_environment: + description: + - Execution Environment to use for the JT. + type: str forks: description: - The number of parallel or simultaneous processes to use while executing the playbook. @@ -231,10 +235,6 @@ options: description: - Maximum time in seconds to wait for a job to finish (server-side). type: int - custom_virtualenv: - description: - - Local absolute file path containing a custom Python virtualenv to use. - type: str job_slice_count: description: - The number of jobs to slice into at runtime. Will cause the Job Template to launch a workflow if value is greater than 1. @@ -308,7 +308,6 @@ EXAMPLES = ''' tower_config_file: "~/tower_cli.cfg" survey_enabled: yes survey_spec: "{{ lookup('file', 'my_survey.json') }}" - custom_virtualenv: "/var/lib/awx/venv/custom-venv/" - name: Add start notification to Job Template tower_job_template: @@ -366,8 +365,8 @@ def main(): playbook=dict(), credential=dict(), vault_credential=dict(), - custom_virtualenv=dict(), credentials=dict(type='list', elements='str'), + execution_environment=dict(), forks=dict(type='int'), limit=dict(), verbosity=dict(type='int', choices=[0, 1, 2, 3, 4], default=0), @@ -437,6 +436,10 @@ def main(): organization_id = module.resolve_name_to_id('organizations', organization) search_fields['organization'] = new_fields['organization'] = organization_id + ee = module.params.get('execution_environment') + if ee: + new_fields['execution_environment'] = module.resolve_name_to_id('execution_environments', ee) + # Attempt to look up an existing item based on the provided data existing_item = module.get_one('job_templates', name_or_id=name, **{'data': search_fields}) @@ -460,7 +463,7 @@ def main(): 'host_config_key', 'ask_scm_branch_on_launch', 'ask_diff_mode_on_launch', 'ask_variables_on_launch', 'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_skip_tags_on_launch', 'ask_job_type_on_launch', 'ask_verbosity_on_launch', 'ask_inventory_on_launch', 'ask_credential_on_launch', 'survey_enabled', - 'become_enabled', 'diff_mode', 'allow_simultaneous', 'custom_virtualenv', 'job_slice_count', 'webhook_service', + 'become_enabled', 'diff_mode', 'allow_simultaneous', 'job_slice_count', 'webhook_service', ): field_val = module.params.get(field_name) if field_val is not None: diff --git a/awx_collection/plugins/modules/tower_organization.py b/awx_collection/plugins/modules/tower_organization.py index 0402056bbf..197099d391 100644 --- a/awx_collection/plugins/modules/tower_organization.py +++ b/awx_collection/plugins/modules/tower_organization.py @@ -31,11 +31,10 @@ options: description: - The description to use for the organization. type: str - custom_virtualenv: + default_environment: description: - - Local absolute file path containing a custom Python virtualenv to use. + - Default Execution Environment to use for jobs owned by the Organization. type: str - default: '' max_hosts: description: - The max hosts allowed in this organizations @@ -88,7 +87,6 @@ EXAMPLES = ''' tower_organization: name: "Foo" description: "Foo bar organization using foo-venv" - custom_virtualenv: "/var/lib/awx/venv/foo-venv/" state: present tower_config_file: "~/tower_cli.cfg" @@ -109,7 +107,7 @@ def main(): argument_spec = dict( name=dict(required=True), description=dict(), - custom_virtualenv=dict(), + default_environment=dict(), max_hosts=dict(type='int', default="0"), notification_templates_started=dict(type="list", elements='str'), notification_templates_success=dict(type="list", elements='str'), @@ -125,7 +123,7 @@ def main(): # Extract our parameters name = module.params.get('name') description = module.params.get('description') - custom_virtualenv = module.params.get('custom_virtualenv') + default_ee = module.params.get('default_environment') max_hosts = module.params.get('max_hosts') # instance_group_names = module.params.get('instance_groups') state = module.params.get('state') @@ -173,8 +171,8 @@ def main(): org_fields = {'name': module.get_item_name(organization) if organization else name} if description is not None: org_fields['description'] = description - if custom_virtualenv is not None: - org_fields['custom_virtualenv'] = custom_virtualenv + if default_ee is not None: + org_fields['default_environment'] = module.resolve_name_to_id('execution_environments', default_ee) if max_hosts is not None: org_fields['max_hosts'] = max_hosts diff --git a/awx_collection/plugins/modules/tower_project.py b/awx_collection/plugins/modules/tower_project.py index f08efb3258..a10beaab27 100644 --- a/awx_collection/plugins/modules/tower_project.py +++ b/awx_collection/plugins/modules/tower_project.py @@ -105,11 +105,10 @@ options: type: int aliases: - job_timeout - custom_virtualenv: + default_environment: description: - - Local absolute file path containing a custom Python virtualenv to use + - Default Execution Environment to use for jobs relating to the project. type: str - default: '' organization: description: - Name of organization for project. @@ -169,14 +168,13 @@ EXAMPLES = ''' state: present tower_config_file: "~/tower_cli.cfg" -- name: Add Tower Project with cache timeout and custom virtualenv +- name: Add Tower Project with cache timeout tower_project: name: "Foo" description: "Foo bar project" organization: "test" scm_update_on_launch: True scm_update_cache_timeout: 60 - custom_virtualenv: "/var/lib/awx/var/lib/awx/venv/ansible-2.2" state: present tower_config_file: "~/tower_cli.cfg" @@ -255,7 +253,7 @@ def main(): scm_update_cache_timeout=dict(type='int', default=0), allow_override=dict(type='bool', aliases=['scm_allow_override']), timeout=dict(type='int', default=0, aliases=['job_timeout']), - custom_virtualenv=dict(), + default_environment=dict(), organization=dict(), notification_templates_started=dict(type="list", elements='str'), notification_templates_success=dict(type="list", elements='str'), @@ -279,6 +277,7 @@ def main(): credential = module.params.get('credential') scm_update_on_launch = module.params.get('scm_update_on_launch') scm_update_cache_timeout = module.params.get('scm_update_cache_timeout') + default_ee = module.params.get('default_environment') organization = module.params.get('organization') state = module.params.get('state') wait = module.params.get('wait') @@ -336,6 +335,8 @@ def main(): 'name': module.get_item_name(project) if project else name, 'scm_type': scm_type, 'organization': org_id, + 'scm_update_on_launch': scm_update_on_launch, + 'scm_update_cache_timeout': scm_update_cache_timeout, } for field_name in ( @@ -349,6 +350,8 @@ def main(): if credential is not None: project_fields['credential'] = credential + if default_ee is not None: + project_fields['default_environment'] = module.resolve_name_to_id('execution_environments', default_ee) if scm_type == '': if local_path is not None: project_fields['local_path'] = local_path diff --git a/awx_collection/plugins/modules/tower_workflow_job_template.py b/awx_collection/plugins/modules/tower_workflow_job_template.py index 1374b965c0..7828e55f09 100644 --- a/awx_collection/plugins/modules/tower_workflow_job_template.py +++ b/awx_collection/plugins/modules/tower_workflow_job_template.py @@ -48,6 +48,10 @@ options: description: - Variables which will be made available to jobs ran inside the workflow. type: dict + execution_environment: + description: + - Execution Environment to use for the WFJT. + type: str organization: description: - Organization the workflow job template exists in. @@ -186,6 +190,7 @@ def main(): description=dict(), extra_vars=dict(type='dict'), organization=dict(), + execution_environment=dict(), survey_spec=dict(type='dict', aliases=['survey']), survey_enabled=dict(type='bool'), allow_simultaneous=dict(type='bool'), @@ -224,6 +229,10 @@ def main(): organization_id = module.resolve_name_to_id('organizations', organization) search_fields['organization'] = new_fields['organization'] = organization_id + ee = module.params.get('execution_environment') + if ee: + new_fields['execution_environment'] = module.resolve_name_to_id('execution_environments', ee) + # Attempt to look up an existing item based on the provided data existing_item = module.get_one('workflow_job_templates', name_or_id=name, **{'data': search_fields}) diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index 56b42e73db..cb34660959 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -140,6 +140,7 @@ def determine_state(module_id, endpoint, module, parameter, api_option, module_o if not api_option and module_option and module_option.get('type', 'str') == 'list': return "OK, Field appears to be relation" # TODO, at some point try and check the object model to confirm its actually a relation + return cause_error('Failed, option mismatch') # We made it through all of the checks so we are ok diff --git a/awx_collection/test/awx/test_inventory_source.py b/awx_collection/test/awx/test_inventory_source.py index fa01b16ddb..fabd3d9fb8 100644 --- a/awx_collection/test/awx/test_inventory_source.py +++ b/awx_collection/test/awx/test_inventory_source.py @@ -97,61 +97,6 @@ def test_create_inventory_source_multiple_orgs(run_module, admin_user): } -@pytest.mark.django_db -def test_create_inventory_source_with_venv(run_module, admin_user, base_inventory, mocker, project): - path = '/var/lib/awx/venv/custom-venv/foobar13489435/' - source_path = '/var/lib/awx/example_source_path/' - with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]): - result = run_module('tower_inventory_source', dict( - name='foo', - inventory=base_inventory.name, - state='present', - source='scm', - source_project=project.name, - custom_virtualenv=path, - source_path=source_path - ), admin_user) - assert result.pop('changed'), result - - inv_src = InventorySource.objects.get(name='foo') - assert inv_src.inventory == base_inventory - result.pop('invocation') - - assert inv_src.custom_virtualenv == path - - -@pytest.mark.django_db -def test_custom_venv_no_op(run_module, admin_user, base_inventory, mocker, project): - """If the inventory source is modified, then it should not blank fields - unrelated to the params that the user passed. - This enforces assumptions about the behavior of the AnsibleModule - default argument_spec behavior. - """ - source_path = '/var/lib/awx/example_source_path/' - inv_src = InventorySource.objects.create( - name='foo', - inventory=base_inventory, - source_project=project, - source='scm', - custom_virtualenv='/var/lib/awx/venv/foobar/' - ) - # mock needed due to API behavior, not incorrect client behavior - with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=['/var/lib/awx/venv/foobar/']): - result = run_module('tower_inventory_source', dict( - name='foo', - description='this is the changed description', - inventory=base_inventory.name, - source='scm', # is required, but behavior is arguable - state='present', - source_project=project.name, - source_path=source_path - ), admin_user) - assert result.pop('changed', None), result - inv_src.refresh_from_db() - assert inv_src.custom_virtualenv == '/var/lib/awx/venv/foobar/' - assert inv_src.description == 'this is the changed description' - - @pytest.mark.django_db def test_falsy_value(run_module, admin_user, base_inventory): result = run_module('tower_inventory_source', dict( diff --git a/awx_collection/test/awx/test_organization.py b/awx_collection/test/awx/test_organization.py index 8f4872c303..ee58ab3a2c 100644 --- a/awx_collection/test/awx/test_organization.py +++ b/awx_collection/test/awx/test_organization.py @@ -20,7 +20,6 @@ def test_create_organization(run_module, admin_user): 'validate_certs': None, 'tower_oauthtoken': None, 'tower_config_file': None, - 'custom_virtualenv': None } result = run_module('tower_organization', module_args, admin_user) @@ -37,24 +36,3 @@ def test_create_organization(run_module, admin_user): } assert org.description == 'barfoo' - - -@pytest.mark.django_db -def test_create_organization_with_venv(run_module, admin_user, mocker): - path = '/var/lib/awx/venv/custom-venv/foobar13489435/' - with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]): - result = run_module('tower_organization', { - 'name': 'foo', - 'custom_virtualenv': path, - 'state': 'present' - }, admin_user) - assert result.pop('changed'), result - - org = Organization.objects.get(name='foo') - result.pop('invocation') - assert result == { - "name": "foo", - "id": org.id - } - - assert org.custom_virtualenv == path diff --git a/awx_collection/tests/integration/targets/tower_import/tasks/main.yml b/awx_collection/tests/integration/targets/tower_import/tasks/main.yml index dbfaf5c06a..5c04958ac7 100644 --- a/awx_collection/tests/integration/targets/tower_import/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_import/tasks/main.yml @@ -17,7 +17,6 @@ - name: "{{ org_name1 }}" description: "" max_hosts: 0 - custom_virtualenv: null related: notification_templates: [] notification_templates_started: [] @@ -40,7 +39,6 @@ - name: "{{ org_name1 }}" description: "" max_hosts: 0 - custom_virtualenv: null related: notification_templates: [] notification_templates_started: [] @@ -67,7 +65,6 @@ "name": "{{ org_name2 }}", "description": "", "max_hosts": 0, - "custom_virtualenv": null, "related": { "notification_templates": [], "notification_templates_started": [], diff --git a/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml index d6b174f674..a1523c45e6 100644 --- a/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml @@ -28,22 +28,10 @@ that: - "result is not changed" -- name: "Try adding a bad custom_virtualenv" - tower_organization: - name: "{{ org_name }}" - custom_virtualenv: "/does/not/exit" - register: result - ignore_errors: true - -- assert: - that: - - "result is failed" - - name: "Pass in all parameters" tower_organization: name: "{{ org_name }}" description: "A description" - custom_virtualenv: "" register: result - assert: @@ -54,7 +42,6 @@ tower_organization: name: "{{ org_name }}" description: "A new description" - custom_virtualenv: "" register: result - assert: diff --git a/awx_collection/tools/vars/examples.yml b/awx_collection/tools/vars/examples.yml index 8a8d132881..a927f970b7 100644 --- a/awx_collection/tools/vars/examples.yml +++ b/awx_collection/tools/vars/examples.yml @@ -49,4 +49,3 @@ examples: tower_config_file: "~/tower_cli.cfg" survey_enabled: yes survey_spec: "{{ '{{' }} lookup('file', 'my_survey.json') {{ '}}' }}" - custom_virtualenv: "/var/lib/awx/venv/custom-venv/" diff --git a/awxkit/awxkit/api/pages/__init__.py b/awxkit/awxkit/api/pages/__init__.py index fafe5dc08f..1d78d4ba5e 100644 --- a/awxkit/awxkit/api/pages/__init__.py +++ b/awxkit/awxkit/api/pages/__init__.py @@ -14,6 +14,7 @@ from .teams import * # NOQA from .credentials import * # NOQA from .unified_jobs import * # NOQA from .unified_job_templates import * # NOQA +from .execution_environments import * # NOQA from .projects import * # NOQA from .inventory import * # NOQA from .system_job_templates import * # NOQA diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 3209232352..4edc07857f 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -23,6 +23,7 @@ EXPORTABLE_RESOURCES = [ 'inventory_sources', 'job_templates', 'workflow_job_templates', + 'execution_environments', ] @@ -33,6 +34,7 @@ EXPORTABLE_RELATIONS = [ 'Credentials', 'Hosts', 'Groups', + 'ExecutionEnvironments', ] diff --git a/awxkit/awxkit/api/pages/credentials.py b/awxkit/awxkit/api/pages/credentials.py index f964ae38e4..e5b80cf7d0 100644 --- a/awxkit/awxkit/api/pages/credentials.py +++ b/awxkit/awxkit/api/pages/credentials.py @@ -82,6 +82,7 @@ def config_cred_from_kind(kind): credential_type_name_to_config_kind_map = { 'amazon web services': 'aws', + 'container registry': 'registry', 'ansible galaxy/automation hub api token': 'galaxy', 'ansible tower': 'tower', 'google compute engine': 'gce', diff --git a/awxkit/awxkit/api/pages/execution_environments.py b/awxkit/awxkit/api/pages/execution_environments.py new file mode 100644 index 0000000000..0471b1f1d3 --- /dev/null +++ b/awxkit/awxkit/api/pages/execution_environments.py @@ -0,0 +1,59 @@ +import logging + +from awxkit.api.mixins import DSAdapter, HasCreate, HasCopy +from awxkit.api.pages import ( + Credential, + Organization, +) +from awxkit.api.resources import resources +from awxkit.utils import random_title, PseudoNamespace + +from . import base +from . import page + + +log = logging.getLogger(__name__) + + +class ExecutionEnvironment(HasCreate, HasCopy, base.Base): + + dependencies = [Organization, Credential] + NATURAL_KEY = ('name',) + + # fields are name, image, organization, managed_by_tower, credential + def create(self, name='', image='quay.io/ansible/ansible-runner:devel', credential=None, pull='', **kwargs): + # we do not want to make a credential by default + payload = self.create_payload(name=name, image=image, credential=credential, pull=pull, **kwargs) + ret = self.update_identity(ExecutionEnvironments(self.connection).post(payload)) + return ret + + def create_payload(self, name='', organization=Organization, **kwargs): + self.create_and_update_dependencies(organization) + payload = self.payload(name=name, organization=self.ds.organization, **kwargs) + payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) + return payload + + def payload(self, name='', image=None, organization=None, credential=None, pull='', **kwargs): + payload = PseudoNamespace( + name=name or "EE - {}".format(random_title()), + image=image or random_title(10), + organization=organization.id if organization else None, + credential=credential.id if credential else None, + pull=pull, + **kwargs + ) + + return payload + + +page.register_page([resources.execution_environment, + (resources.execution_environments, 'post'), + (resources.organization_execution_environments, 'post')], ExecutionEnvironment) + + +class ExecutionEnvironments(page.PageList, ExecutionEnvironment): + pass + + +page.register_page([resources.execution_environments, + resources.organization_execution_environments], ExecutionEnvironments) diff --git a/awxkit/awxkit/api/pages/job_templates.py b/awxkit/awxkit/api/pages/job_templates.py index cd45fc0c87..9f008078a9 100644 --- a/awxkit/awxkit/api/pages/job_templates.py +++ b/awxkit/awxkit/api/pages/job_templates.py @@ -101,18 +101,17 @@ class JobTemplate( if kwargs.get('project'): payload.update(project=kwargs.get('project').id, playbook=playbook) - if kwargs.get('inventory'): - payload.update(inventory=kwargs.get('inventory').id) - if kwargs.get('credential'): - payload.update(credential=kwargs.get('credential').id) - if kwargs.get('webhook_credential'): - webhook_cred = kwargs.get('webhook_credential') - if isinstance(webhook_cred, int): - payload.update(webhook_credential=int(webhook_cred)) - elif hasattr(webhook_cred, 'id'): - payload.update(webhook_credential=webhook_cred.id) + + for fk_field in ('inventory', 'credential', 'webhook_credential', 'execution_environment'): + rel_obj = kwargs.get(fk_field) + if rel_obj is None: + continue + elif isinstance(rel_obj, int): + payload.update(**{fk_field: int(rel_obj)}) + elif hasattr(rel_obj, 'id'): + payload.update(**{fk_field: rel_obj.id}) else: - raise AttributeError("Webhook credential must either be integer of pkid or Credential object") + raise AttributeError(f'Related field {fk_field} must be either integer of pkid or object') return payload diff --git a/awxkit/awxkit/api/pages/organizations.py b/awxkit/awxkit/api/pages/organizations.py index e1d13a0013..fe34eb07e7 100644 --- a/awxkit/awxkit/api/pages/organizations.py +++ b/awxkit/awxkit/api/pages/organizations.py @@ -39,10 +39,20 @@ class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base): "disassociate": True, }) - def payload(self, **kwargs): payload = PseudoNamespace(name=kwargs.get('name') or 'Organization - {}'.format(random_title()), description=kwargs.get('description') or random_title(10)) + + for fk_field in ('default_environment',): + rel_obj = kwargs.get(fk_field) + if rel_obj is None: + continue + elif isinstance(rel_obj, int): + payload.update(**{fk_field: int(rel_obj)}) + elif hasattr(rel_obj, 'id'): + payload.update(**{fk_field: rel_obj.id}) + else: + raise AttributeError(f'Related field {fk_field} must be either integer of pkid or object') return payload def create_payload(self, name='', description='', **kwargs): diff --git a/awxkit/awxkit/api/pages/projects.py b/awxkit/awxkit/api/pages/projects.py index e40191260c..bc46f5edb3 100644 --- a/awxkit/awxkit/api/pages/projects.py +++ b/awxkit/awxkit/api/pages/projects.py @@ -43,6 +43,16 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): 'allow_override') update_payload(payload, fields, kwargs) + for fk_field in ('execution_environment', 'default_environment'): + rel_obj = kwargs.get(fk_field) + if rel_obj is None: + continue + elif isinstance(rel_obj, int): + payload.update(**{fk_field: int(rel_obj)}) + elif hasattr(rel_obj, 'id'): + payload.update(**{fk_field: rel_obj.id}) + else: + raise AttributeError(f'Related field {fk_field} must be either integer of pkid or object') return payload def create_payload( diff --git a/awxkit/awxkit/api/pages/unified_jobs.py b/awxkit/awxkit/api/pages/unified_jobs.py index 8e07b71de9..20c6175ed3 100644 --- a/awxkit/awxkit/api/pages/unified_jobs.py +++ b/awxkit/awxkit/api/pages/unified_jobs.py @@ -135,6 +135,28 @@ class UnifiedJob(HasStatus, base.Base): raise return args + @property + def controller_dir(self): + """Returns the path to the private_data_dir on the controller node for the job + This can be used if trying to shell in and inspect the files used by the job + Cannot use job_cwd, because that is path inside EE container + """ + self.get() + job_args = self.job_args + expected_prefix = '/tmp/awx_{}'.format(self.id) + for arg1, arg2 in zip(job_args[:-1], job_args[1:]): + if arg1 == '-v': + if ':' in arg2: + host_loc = arg2.split(':')[0] + if host_loc.startswith(expected_prefix): + return host_loc + raise RuntimeError( + 'Could not find a controller private_data_dir for this job. ' + 'Searched for volume mount to {} inside of args {}'.format( + expected_prefix, job_args + ) + ) + class UnifiedJobs(page.PageList, UnifiedJob): diff --git a/awxkit/awxkit/api/resources.py b/awxkit/awxkit/api/resources.py index d6340cd2d7..997ada1e70 100644 --- a/awxkit/awxkit/api/resources.py +++ b/awxkit/awxkit/api/resources.py @@ -28,6 +28,8 @@ class Resources(object): _credential_types = 'credential_types/' _credentials = 'credentials/' _dashboard = 'dashboard/' + _execution_environment = r'execution_environments/\d+/' + _execution_environments = 'execution_environments/' _fact_view = r'hosts/\d+/fact_view/' _group = r'groups/\d+/' _group_access_list = r'groups/\d+/access_list/' @@ -141,6 +143,7 @@ class Resources(object): _organization_access_list = r'organizations/\d+/access_list/' _organization_admins = r'organizations/\d+/admins/' _organization_applications = r'organizations/\d+/applications/' + _organization_execution_environments = r'organizations/\d+/execution_environments/' _organization_inventories = r'organizations/\d+/inventories/' _organization_users = r'organizations/\d+/users/' _organizations = 'organizations/' diff --git a/docs/custom_virtualenvs.md b/docs/custom_virtualenvs.md deleted file mode 100644 index 1f5b6e6a2d..0000000000 --- a/docs/custom_virtualenvs.md +++ /dev/null @@ -1,168 +0,0 @@ -Managing Custom Python Dependencies -=================================== -awx installations pre-build a special [Python -virtualenv](https://pypi.python.org/pypi/virtualenv) which is automatically -activated for all `ansible-playbook` runs invoked by awx (for example, any time -a Job Template is launched). By default, this virtualenv is located at -`/var/lib/awx/venv/ansible` on the file system. - -awx pre-installs a variety of third-party library/SDK support into this -virtualenv for its integration points with a variety of cloud providers (such -as EC2, OpenStack, Azure, etc...) - -Periodically, awx users want to add additional SDK support into this -virtualenv; this documentation describes the supported way to do so. - -Preparing a New Custom Virtualenv -================================= -awx allows a _different_ virtualenv to be specified and used on Job Template -runs. To choose a custom virtualenv, first we need to create one. Here, we are -using `/opt/my-envs/` as the directory to hold custom venvs. But you can use any -other directory and replace `/opt/my-envs/` with that. Let's create the directory -first if absent: - - NOTE: For docker installations, this directory needs to exist on awx_web AND - awx_task container - - $ sudo mkdir /opt/my-envs - -Now, we need to tell Tower to look into this directory for custom venvs. For that, -we can add this directory to the `CUSTOM_VENV_PATHS` setting as: - - $ HTTP PATCH /api/v2/settings/system/ {'CUSTOM_VENV_PATHS': ["/opt/my-envs/"]} - -If we have venvs spanned over multiple directories, we can add all the paths and -Tower will aggregate venvs from them: - - $ HTTP PATCH /api/v2/settings/system/ {'CUSTOM_VENV_PATHS': ["/path/1/to/venv/", - "/path/2/to/venv/", - "/path/3/to/venv/"]} - -Now that we have the directory setup, we can create a virtual environment in that using: - - $ sudo virtualenv /opt/my-envs/custom-venv - -Multiple versions of Python are supported, though it's important to note that -the semantics for creating virtualenvs in Python 3 has changed slightly: - - $ sudo python3 -m venv /opt/my-envs/custom-venv - -Your newly created virtualenv needs a few base dependencies to properly run -playbooks: -fact gathering): - - $ sudo /opt/my-envs/custom-venv/bin/pip install psutil - -From here, you can install _additional_ Python dependencies that you care -about, such as a per-virtualenv version of ansible itself: - - $ sudo /opt/my-envs/custom-venv/bin/pip install -U "ansible == X.Y.Z" - -...or an additional third-party SDK that's not included with the base awx installation: - - $ sudo /opt/my-envs/custom-venv/bin/pip install -U python-digitalocean - -If you want to copy them, the libraries included in awx's default virtualenv -can be found using `pip freeze`: - - $ sudo /var/lib/awx/venv/ansible/bin/pip freeze - -One important item to keep in mind is that in a clustered awx installation, -you need to ensure that the same custom virtualenv exists on _every_ local file -system at `/opt/my-envs/`. For container-based deployments, this likely -means building these steps into your own custom image building workflow, e.g., - -```diff -diff --git a/Makefile b/Makefile -index aa8b304..eb05f91 100644 ---- a/Makefile -+++ b/Makefile -@@ -164,6 +164,10 @@ requirements_ansible_dev: - $(VENV_BASE)/ansible/bin/pip install pytest mock; \ - fi - -+requirements_custom: -+ mkdir -p /opt/my-envs -+ virtualenv /opt/my-envs/my-custom-env -+ /opt/my-envs/my-custom-env/bin/pip install psutil -+ -diff --git a/installer/roles/image_build/templates/Dockerfile.j2 b/installer/roles/image_build/templates/Dockerfile.j2 -index d3b582ffcb..220ac760a3 100644 ---- a/installer/roles/image_build/templates/Dockerfile.j2 -+++ b/installer/roles/image_build/templates/Dockerfile.j2 -@@ -165,6 +165,7 @@ RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/n - chmod 640 /etc/nginx/nginx.{csr,key,crt} - {% else %} - COPY --from=builder /var/lib/awx /var/lib/awx -+COPY --from=builder /opt/my-envs /opt/my-envs - RUN ln -s /var/lib/awx/venv/awx/bin/awx-manage /usr/bin/awx-manage - {% endif %} -``` - -Once the AWX API is available, update the `CUSTOM_VENV_PATHS` setting as described in `Preparing a New Custom Virtualenv`. - -Kubernetes Custom Virtualenvs -============================= -You can add custom virtual environments without modifying images by including -the following variables in your `install.yml` playbook run. Your variables file -must have a variable called `custom_venvs` with a list of your custom -virtualenvs containing the name, python interpreter, ansible version, and a list -of modules that should be installed in each one: - -```yaml -# venv_vars.yaml ---- -custom_venvs: - - name: dns_team - python: python3 # Defaults to python2 - python_ansible_version: 2.8.1 - python_modules: - - dnspython - - infoblox-client - - name: windows_team - python: python2 - python_ansible_version: 2.8.0 - python_modules: - - winrm - - name: vmware_team - python_ansible_version: 2.7.10 - python_modules: - - pyvmomi -``` - -The virtualenvs will be created in `/opt/custom-venvs` by default, but you can -override that location by setting the variable `custom_venvs_path`. - -You can use the variables file like so: - - $ ansible-playbook -i inventory install.yml --extra-vars "@venv_vars.yaml" - -Once the AWX API is available, you will need to update the `CUSTOM_VENV_PATHS` -setting as described in `Preparing a New Custom Virtualenv`. - -Assigning Custom Virtualenvs -============================ -Once you've created a custom virtualenv, you can assign it at the Organization, -Project, or Job Template level: - -```http -PATCH https://awx-host.example.org/api/v2/organizations/N/ -PATCH https://awx-host.example.org/api/v2/projects/N/ -PATCH https://awx-host.example.org/api/v2/job_templates/N/ - -Content-Type: application/json -{ - "custom_virtualenv": "/opt/my-envs/custom-venv/" -} -``` - -An HTTP `GET` request to `/api/v2/config/` will provide a list of -detected installed virtualenvs: - - { - "custom_virtualenvs": [ - "/opt/my-envs/custom-venv/", - "/opt/my-envs/my-other-custom-venv/", - ], - ... - } diff --git a/docs/licenses/apache-libcloud.txt b/docs/licenses/apache-libcloud.txt deleted file mode 100644 index d645695673..0000000000 --- a/docs/licenses/apache-libcloud.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/appdirs.txt b/docs/licenses/appdirs.txt deleted file mode 100644 index f0bbd69f0c..0000000000 --- a/docs/licenses/appdirs.txt +++ /dev/null @@ -1,22 +0,0 @@ -# This is the MIT license - -Copyright (c) 2010 ActiveState Software Inc. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/licenses/applicationinsights.txt b/docs/licenses/applicationinsights.txt deleted file mode 100644 index cd7af0736c..0000000000 --- a/docs/licenses/applicationinsights.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/argcomplete.txt b/docs/licenses/argcomplete.txt deleted file mode 100644 index f433b1a53f..0000000000 --- a/docs/licenses/argcomplete.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/docs/licenses/azure-cli-core.txt b/docs/licenses/azure-cli-core.txt deleted file mode 100644 index e9acabf9dc..0000000000 --- a/docs/licenses/azure-cli-core.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Microsoft Corporation. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/azure-cli-nspkg.txt b/docs/licenses/azure-cli-nspkg.txt deleted file mode 100644 index 2efacf37b1..0000000000 --- a/docs/licenses/azure-cli-nspkg.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Microsoft Corporation. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-graphrbac.txt b/docs/licenses/azure-graphrbac.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-graphrbac.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-authorization.txt b/docs/licenses/azure-mgmt-authorization.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-authorization.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-automation.txt b/docs/licenses/azure-mgmt-automation.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-automation.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-batch.txt b/docs/licenses/azure-mgmt-batch.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-batch.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-cdn.txt b/docs/licenses/azure-mgmt-cdn.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-cdn.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-compute.txt b/docs/licenses/azure-mgmt-compute.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-compute.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-containerinstance.txt b/docs/licenses/azure-mgmt-containerinstance.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-containerinstance.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-containerregistry.txt b/docs/licenses/azure-mgmt-containerregistry.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-containerregistry.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-containerservice.txt b/docs/licenses/azure-mgmt-containerservice.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-containerservice.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-cosmosdb.txt b/docs/licenses/azure-mgmt-cosmosdb.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-cosmosdb.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-devtestlabs.txt b/docs/licenses/azure-mgmt-devtestlabs.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-devtestlabs.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-dns.txt b/docs/licenses/azure-mgmt-dns.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-dns.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-hdinsight.txt b/docs/licenses/azure-mgmt-hdinsight.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-hdinsight.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-iothub.txt b/docs/licenses/azure-mgmt-iothub.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-iothub.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-keyvault.txt b/docs/licenses/azure-mgmt-keyvault.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-keyvault.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-loganalytics.txt b/docs/licenses/azure-mgmt-loganalytics.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-loganalytics.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-marketplaceordering.txt b/docs/licenses/azure-mgmt-marketplaceordering.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-marketplaceordering.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-monitor.txt b/docs/licenses/azure-mgmt-monitor.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-monitor.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-network.txt b/docs/licenses/azure-mgmt-network.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-network.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-nspkg.txt b/docs/licenses/azure-mgmt-nspkg.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-nspkg.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-rdbms.txt b/docs/licenses/azure-mgmt-rdbms.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-rdbms.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-redis.txt b/docs/licenses/azure-mgmt-redis.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-redis.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-resource.txt b/docs/licenses/azure-mgmt-resource.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-resource.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-servicebus.txt b/docs/licenses/azure-mgmt-servicebus.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-servicebus.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-sql.txt b/docs/licenses/azure-mgmt-sql.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-sql.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-storage.txt b/docs/licenses/azure-mgmt-storage.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-storage.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-trafficmanager.txt b/docs/licenses/azure-mgmt-trafficmanager.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-trafficmanager.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-mgmt-web.txt b/docs/licenses/azure-mgmt-web.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-mgmt-web.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/azure-storage.txt b/docs/licenses/azure-storage.txt deleted file mode 100644 index dc1cf39d13..0000000000 --- a/docs/licenses/azure-storage.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/backports.ssl-match-hostname.txt b/docs/licenses/backports.ssl-match-hostname.txt deleted file mode 100644 index f73bd13921..0000000000 --- a/docs/licenses/backports.ssl-match-hostname.txt +++ /dev/null @@ -1,96 +0,0 @@ -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 - - 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. - - 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. - - 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. - - 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - - 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - - 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. - - 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - - 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). - - 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. - - 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - - 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - - 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - - 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. - - 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. - - -CNRI OPEN SOURCE LICENSE AGREEMENT (for Python 1.6b1) - -IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. - -BY CLICKING ON "ACCEPT" WHERE INDICATED BELOW, OR BY COPYING, INSTALLING OR OTHERWISE USING PYTHON 1.6, beta 1 SOFTWARE, YOU ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS LICENSE AGREEMENT. - - 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6, beta 1 software in source or binary form and its associated documentation, as released at the www.python.org Internet site on August 4, 2000 ("Python 1.6b1"). - - 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6b1 alone or in any derivative version, provided, however, that CNRIs License Agreement is retained in Python 1.6b1, alone or in any derivative version prepared by Licensee. - - Alternately, in lieu of CNRIs License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6, beta 1, is made available subject to the terms and conditions in CNRIs License Agreement. This Agreement may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1011. This Agreement may also be obtained from a proxy server on the Internet using the URL:http://hdl.handle.net/1895.22/1011". - - 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6b1 or any part thereof, and wants to make the derivative work available to the public as provided herein, then Licensee hereby agrees to indicate in any such work the nature of the modifications made to Python 1.6b1. - - 4. CNRI is making Python 1.6b1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6b1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - - 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING PYTHON 1.6b1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - - 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - - 7. This License Agreement shall be governed by and interpreted in all respects by the law of the State of Virginia, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. - - 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6b1, Licensee agrees to be bound by the terms and conditions of this License Agreement. - -ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. - - Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. - - STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - or -Zope Public License (ZPL) Version 2.0 - -This software is Copyright (c) Zope Corporation (tm) and Contributors. All rights reserved. - -This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions in source code must retain the above copyright notice, this list of conditions, and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. The name Zope Corporation (tm) must not be used to endorse or promote products derived from this software without prior written permission from Zope Corporation. - - 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of Zope Corporation. Use of them is covered in a separate agreement (see http://www.zope.com/Marks). - - 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. - -Disclaimer - -THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -This software consists of contributions made by Zope Corporation and many individuals on behalf of Zope Corporation. Specific attributions are listed in the accompanying credits file. \ No newline at end of file diff --git a/docs/licenses/bcrypt.txt b/docs/licenses/bcrypt.txt deleted file mode 100644 index e4f139cb49..0000000000 --- a/docs/licenses/bcrypt.txt +++ /dev/null @@ -1,201 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, -and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by -the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all -other entities that control, are controlled by, or are under common -control with that entity. For the purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or -otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity -exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, -including but not limited to software source code, documentation -source, and configuration files. - -"Object" form shall mean any form resulting from mechanical -transformation or translation of a Source form, including but -not limited to compiled object code, generated documentation, -and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or -Object form, made available under the License, as indicated by a -copyright notice that is included in or attached to the work -(an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object -form, that is based on (or derived from) the Work and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. For the purposes -of this License, Derivative Works shall not include works that remain -separable from, or merely link (or bind by name) to the interfaces of, -the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including -the original version of the Work and any modifications or additions -to that Work or Derivative Works thereof, that is intentionally -submitted to Licensor for inclusion in the Work by the copyright owner -or by an individual or Legal Entity authorized to submit on behalf of -the copyright owner. For the purposes of this definition, "submitted" -means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, -and issue tracking systems that are managed by, or on behalf of, the -Licensor for the purpose of discussing and improving the Work, but -excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity -on behalf of whom a Contribution has been received by Licensor and -subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the -Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -(except as stated in this section) patent license to make, have made, -use, offer to sell, sell, import, and otherwise transfer the Work, -where such license applies only to those patent claims licensable -by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) -with the Work to which such Contribution(s) was submitted. If You -institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work -or a Contribution incorporated within the Work constitutes direct -or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate -as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the -Work or Derivative Works thereof in any medium, with or without -modifications, and in Source or Object form, provided that You -meet the following conditions: - -(a) You must give any other recipients of the Work or -Derivative Works a copy of this License; and - -(b) You must cause any modified files to carry prominent notices -stating that You changed the files; and - -(c) You must retain, in the Source form of any Derivative Works -that You distribute, all copyright, patent, trademark, and -attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of -the Derivative Works; and - -(d) If the Work includes a "NOTICE" text file as part of its -distribution, then any Derivative Works that You distribute must -include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not -pertain to any part of the Derivative Works, in at least one -of the following places: within a NOTICE text file distributed -as part of the Derivative Works; within the Source form or -documentation, if provided along with the Derivative Works; or, -within a display generated by the Derivative Works, if and -wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and -do not modify the License. You may add Your own attribution -notices within Derivative Works that You distribute, alongside -or as an addendum to the NOTICE text from the Work, provided -that such additional attribution notices cannot be construed -as modifying the License. - -You may add Your own copyright statement to Your modifications and -may provide additional or different license terms and conditions -for use, reproduction, or distribution of Your modifications, or -for any such Derivative Works as a whole, provided Your use, -reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, -any Contribution intentionally submitted for inclusion in the Work -by You to the Licensor shall be under the terms and conditions of -this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify -the terms of any separate license agreement you may have executed -with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade -names, trademarks, service marks, or product names of the Licensor, -except as required for reasonable and customary use in describing the -origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or -agreed to in writing, Licensor provides the Work (and each -Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied, including, without limitation, any warranties or conditions -of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any -risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, -whether in tort (including negligence), contract, or otherwise, -unless required by applicable law (such as deliberate and grossly -negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, -incidental, or consequential damages of any character arising as a -result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses), even if such Contributor -has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing -the Work or Derivative Works thereof, You may choose to offer, -and charge a fee for, acceptance of support, warranty, indemnity, -or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only -on Your own behalf and on Your sole responsibility, not on behalf -of any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason -of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following -boilerplate notice, with the fields enclosed by brackets "[]" -replaced with your own identifying information. (Don't include -the brackets!) The text should be enclosed in the appropriate -comment syntax for the file format. We also recommend that a -file or class name and description of purpose be included on the -same "printed page" as the copyright notice for easier -identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/docs/licenses/boto.txt b/docs/licenses/boto.txt deleted file mode 100644 index 07d9e8c75f..0000000000 --- a/docs/licenses/boto.txt +++ /dev/null @@ -1,18 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, dis- -tribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the fol- -lowing conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- -ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/docs/licenses/boto3.txt b/docs/licenses/boto3.txt deleted file mode 100644 index 761ea7cfcd..0000000000 --- a/docs/licenses/boto3.txt +++ /dev/null @@ -1,12 +0,0 @@ -Copyright 2013-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"). You -may not use this file except in compliance with the License. A copy of -the License is located at - - http://aws.amazon.com/apache2.0/ - -or in the "license" file accompanying this file. This file is -distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -ANY KIND, either express or implied. See the License for the specific -language governing permissions and limitations under the License. diff --git a/docs/licenses/botocore.txt b/docs/licenses/botocore.txt deleted file mode 100644 index 4c00dd5630..0000000000 --- a/docs/licenses/botocore.txt +++ /dev/null @@ -1,12 +0,0 @@ -Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"). You -may not use this file except in compliance with the License. A copy of -the License is located at - - http://aws.amazon.com/apache2.0/ - -or in the "license" file accompanying this file. This file is -distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -ANY KIND, either express or implied. See the License for the specific -language governing permissions and limitations under the License. diff --git a/docs/licenses/colorama.txt b/docs/licenses/colorama.txt deleted file mode 100644 index 5f567799f3..0000000000 --- a/docs/licenses/colorama.txt +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2010 Jonathan Hartley -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holders, nor those of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/docs/licenses/decorator.txt b/docs/licenses/decorator.txt deleted file mode 100644 index e9fe3d98df..0000000000 --- a/docs/licenses/decorator.txt +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2005-2017, Michele Simionato -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - Redistributions in bytecode form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. diff --git a/docs/licenses/dogpile.cache.txt b/docs/licenses/dogpile.cache.txt deleted file mode 100644 index 79399ef2e2..0000000000 --- a/docs/licenses/dogpile.cache.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2011-2016 Mike Bayer - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author or contributors may not be used to endorse or - promote products derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff --git a/docs/licenses/enum34.txt b/docs/licenses/enum34.txt deleted file mode 100644 index 9003b8850e..0000000000 --- a/docs/licenses/enum34.txt +++ /dev/null @@ -1,32 +0,0 @@ -Copyright (c) 2013, Ethan Furman. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - Neither the name Ethan Furman nor the names of any - contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/licenses/futures.txt b/docs/licenses/futures.txt deleted file mode 100644 index a8d65b16b6..0000000000 --- a/docs/licenses/futures.txt +++ /dev/null @@ -1,48 +0,0 @@ -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python -alone or in any derivative version, provided, however, that PSF's -License Agreement and PSF's notice of copyright, i.e., "Copyright (c) -2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights -Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/docs/licenses/humanfriendly.txt b/docs/licenses/humanfriendly.txt deleted file mode 100644 index 9b3290e3d2..0000000000 --- a/docs/licenses/humanfriendly.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2018 Peter Odding - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/ipaddress.txt b/docs/licenses/ipaddress.txt deleted file mode 100644 index c1a4d7bffd..0000000000 --- a/docs/licenses/ipaddress.txt +++ /dev/null @@ -1,50 +0,0 @@ -This package is a modified version of cpython's ipaddress module. -It is therefore distributed under the PSF license, as follows: - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/docs/licenses/iso8601.txt b/docs/licenses/iso8601.txt deleted file mode 100644 index 4eb54eaff9..0000000000 --- a/docs/licenses/iso8601.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2007 - 2015 Michael Twomey - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/licenses/jmespath.txt b/docs/licenses/jmespath.txt deleted file mode 100644 index aa68928536..0000000000 --- a/docs/licenses/jmespath.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, dis- -tribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the fol- -lowing conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- -ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/docs/licenses/jsonpatch.txt b/docs/licenses/jsonpatch.txt deleted file mode 100644 index 91404e3cea..0000000000 --- a/docs/licenses/jsonpatch.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2011 Stefan Kögl -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/licenses/jsonpointer.txt b/docs/licenses/jsonpointer.txt deleted file mode 100644 index 91404e3cea..0000000000 --- a/docs/licenses/jsonpointer.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2011 Stefan Kögl -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/licenses/keystoneauth1.txt b/docs/licenses/keystoneauth1.txt deleted file mode 100644 index af613b1332..0000000000 --- a/docs/licenses/keystoneauth1.txt +++ /dev/null @@ -1,209 +0,0 @@ -Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1) -Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1) -Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7) -All rights reserved. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - ---- License for keystoneauth versions prior to 2.1 --- - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of this project nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/licenses/knack.txt b/docs/licenses/knack.txt deleted file mode 100644 index d1ca00f20a..0000000000 --- a/docs/licenses/knack.txt +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE \ No newline at end of file diff --git a/docs/licenses/monotonic.txt b/docs/licenses/monotonic.txt deleted file mode 100644 index 5c304d1a4a..0000000000 --- a/docs/licenses/monotonic.txt +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/munch.txt b/docs/licenses/munch.txt deleted file mode 100644 index 20c5c7dcd0..0000000000 --- a/docs/licenses/munch.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010 David Schoonover - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/docs/licenses/ncclient.txt b/docs/licenses/ncclient.txt deleted file mode 100644 index d645695673..0000000000 --- a/docs/licenses/ncclient.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/netifaces.txt b/docs/licenses/netifaces.txt deleted file mode 100644 index 67c7bfce25..0000000000 --- a/docs/licenses/netifaces.txt +++ /dev/null @@ -1,4 +0,0 @@ -Copyright (c) 2007-2014 Alastair Houghton -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/licenses/ntlm-auth.txt b/docs/licenses/ntlm-auth.txt deleted file mode 100644 index 21429cb1fa..0000000000 --- a/docs/licenses/ntlm-auth.txt +++ /dev/null @@ -1,163 +0,0 @@ -GNU Lesser General Public License -================================= - -_Version 3, 29 June 2007_ -_Copyright © 2007 Free Software Foundation, Inc. <>_ - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - -This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - -### 0. Additional Definitions - -As used herein, “this License” refers to version 3 of the GNU Lesser -General Public License, and the “GNU GPL” refers to version 3 of the GNU -General Public License. - -“The Library” refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - -An “Application” is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - -A “Combined Work” is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the “Linked -Version”. - -The “Minimal Corresponding Source” for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - -The “Corresponding Application Code” for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - -### 1. Exception to Section 3 of the GNU GPL - -You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - -### 2. Conveying Modified Versions - -If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - -* **a)** under this License, provided that you make a good faith effort to -ensure that, in the event an Application does not supply the -function or data, the facility still operates, and performs -whatever part of its purpose remains meaningful, or - -* **b)** under the GNU GPL, with none of the additional permissions of -this License applicable to that copy. - -### 3. Object Code Incorporating Material from Library Header Files - -The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - -* **a)** Give prominent notice with each copy of the object code that the -Library is used in it and that the Library and its use are -covered by this License. -* **b)** Accompany the object code with a copy of the GNU GPL and this license -document. - -### 4. Combined Works - -You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - -* **a)** Give prominent notice with each copy of the Combined Work that -the Library is used in it and that the Library and its use are -covered by this License. - -* **b)** Accompany the Combined Work with a copy of the GNU GPL and this license -document. - -* **c)** For a Combined Work that displays copyright notices during -execution, include the copyright notice for the Library among -these notices, as well as a reference directing the user to the -copies of the GNU GPL and this license document. - -* **d)** Do one of the following: - - **0)** Convey the Minimal Corresponding Source under the terms of this -License, and the Corresponding Application Code in a form -suitable for, and under terms that permit, the user to -recombine or relink the Application with a modified version of -the Linked Version to produce a modified Combined Work, in the -manner specified by section 6 of the GNU GPL for conveying -Corresponding Source. - - **1)** Use a suitable shared library mechanism for linking with the -Library. A suitable mechanism is one that **(a)** uses at run time -a copy of the Library already present on the user's computer -system, and **(b)** will operate properly with a modified version -of the Library that is interface-compatible with the Linked -Version. - -* **e)** Provide Installation Information, but only if you would otherwise -be required to provide such information under section 6 of the -GNU GPL, and only to the extent that such information is -necessary to install and execute a modified version of the -Combined Work produced by recombining or relinking the -Application with a modified version of the Linked Version. (If -you use option **4d0**, the Installation Information must accompany -the Minimal Corresponding Source and Corresponding Application -Code. If you use option **4d1**, you must provide the Installation -Information in the manner specified by section 6 of the GNU GPL -for conveying Corresponding Source.) - -### 5. Combined Libraries - -You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - -* **a)** Accompany the combined library with a copy of the same work based -on the Library, uncombined with any other library facilities, -conveyed under the terms of this License. -* **b)** Give prominent notice with the combined library that part of it -is a work based on the Library, and explaining where to find the -accompanying uncombined form of the same work. - -### 6. Revised Versions of the GNU Lesser General Public License - -The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License “or any later version” -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - -If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file diff --git a/docs/licenses/openstacksdk.txt b/docs/licenses/openstacksdk.txt deleted file mode 100644 index 67db858821..0000000000 --- a/docs/licenses/openstacksdk.txt +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/docs/licenses/os-service-types.txt b/docs/licenses/os-service-types.txt deleted file mode 100644 index 67db858821..0000000000 --- a/docs/licenses/os-service-types.txt +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/docs/licenses/ovirt-engine-sdk-python.txt b/docs/licenses/ovirt-engine-sdk-python.txt deleted file mode 100644 index 4947287f7b..0000000000 --- a/docs/licenses/ovirt-engine-sdk-python.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/docs/licenses/packaging.txt b/docs/licenses/packaging.txt deleted file mode 100644 index f433b1a53f..0000000000 --- a/docs/licenses/packaging.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/docs/licenses/paramiko.txt b/docs/licenses/paramiko.txt deleted file mode 100644 index 733f96c17a..0000000000 --- a/docs/licenses/paramiko.txt +++ /dev/null @@ -1,503 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - -Copyright (C) 1991, 1999 Free Software Foundation, Inc. -51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts -as the successor of the GNU Library Public License, version 2, hence -the version number 2.1.] - - Preamble - -The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - -This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - -When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - -To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - -We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - -Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - -Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - -When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - -We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - -For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - -In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - -Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - -The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - -GNU LESSER GENERAL PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - -The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - -You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - -2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - -a) The modified work must itself be a software library. - -b) You must cause the files modified to carry prominent notices -stating that you changed the files and the date of any change. - -c) You must cause the whole of the work to be licensed at no -charge to all third parties under the terms of this License. - -d) If a facility in the modified Library refers to a function or a -table of data to be supplied by an application program that uses -the facility, other than as an argument passed when the facility -is invoked, then you must make a good faith effort to ensure that, -in the event an application does not supply such function or -table, the facility still operates, and performs whatever part of -its purpose remains meaningful. - -(For example, a function in a library to compute square roots has -a purpose that is entirely well-defined independent of the -application. Therefore, Subsection 2d requires that any -application-supplied function or table used by this function must -be optional: if the application does not supply it, the square -root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - -3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - -Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - -5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - -When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - -If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - -a) Accompany the work with the complete corresponding -machine-readable source code for the Library including whatever -changes were used in the work (which must be distributed under -Sections 1 and 2 above); and, if the work is an executable linked -with the Library, with the complete machine-readable "work that -uses the Library", as object code and/or source code, so that the -user can modify the Library and then relink to produce a modified -executable containing the modified Library. (It is understood -that the user who changes the contents of definitions files in the -Library will not necessarily be able to recompile the application -to use the modified definitions.) - -b) Use a suitable shared library mechanism for linking with the -Library. A suitable mechanism is one that (1) uses at run time a -copy of the library already present on the user's computer system, -rather than copying library functions into the executable, and (2) -will operate properly with a modified version of the library, if -the user installs one, as long as the modified version is -interface-compatible with the version that the work was made with. - -c) Accompany the work with a written offer, valid for at -least three years, to give the same user the materials -specified in Subsection 6a, above, for a charge no more -than the cost of performing this distribution. - -d) If distribution of the work is made by offering access to copy -from a designated place, offer equivalent access to copy the above -specified materials from the same place. - -e) Verify that the user has already received a copy of these -materials or that you have already sent this user a copy. - -For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - -It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - -7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - -a) Accompany the combined library with a copy of the same work -based on the Library, uncombined with any other library -facilities. This must be distributed under the terms of the -Sections above. - -b) Give prominent notice with the combined library of the fact -that part of it is a work based on the Library, and explaining -where to find the accompanying uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - -11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - -13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - -If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - -To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - -Copyright (C) - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in the -library `Frob' (a library for tweaking knobs) written by James Random Hacker. - -, 1 April 1990 -Ty Coon, President of Vice - -That's all there is to it! - diff --git a/docs/licenses/pbr.txt b/docs/licenses/pbr.txt deleted file mode 100644 index 68c771a099..0000000000 --- a/docs/licenses/pbr.txt +++ /dev/null @@ -1,176 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - diff --git a/docs/licenses/pycurl.txt b/docs/licenses/pycurl.txt deleted file mode 100644 index aea8911b01..0000000000 --- a/docs/licenses/pycurl.txt +++ /dev/null @@ -1,11 +0,0 @@ -Copyright (C) 2001-2008 by Kjetil Jacobsen -Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer -Copyright (C) 2013-2017 by Oleg Pudeyev - -All rights reserved. - -PycURL is dual licensed under the LGPL and an MIT/X derivative license -based on the cURL license. A full copy of the LGPL license is included -in the file COPYING-LGPL. A full copy of the MIT/X derivative license is -included in the file COPYING-MIT. You can redistribute and/or modify PycURL -according to the terms of either license. \ No newline at end of file diff --git a/docs/licenses/pygments.txt b/docs/licenses/pygments.txt deleted file mode 100644 index 721db633a4..0000000000 --- a/docs/licenses/pygments.txt +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Pavan Kumar Sunkara - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/pykerberos.txt b/docs/licenses/pykerberos.txt deleted file mode 100644 index d645695673..0000000000 --- a/docs/licenses/pykerberos.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/pynacl.txt b/docs/licenses/pynacl.txt deleted file mode 100644 index f29e18a269..0000000000 --- a/docs/licenses/pynacl.txt +++ /dev/null @@ -1,174 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, -and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by -the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all -other entities that control, are controlled by, or are under common -control with that entity. For the purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or -otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity -exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, -including but not limited to software source code, documentation -source, and configuration files. - -"Object" form shall mean any form resulting from mechanical -transformation or translation of a Source form, including but -not limited to compiled object code, generated documentation, -and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or -Object form, made available under the License, as indicated by a -copyright notice that is included in or attached to the work -(an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object -form, that is based on (or derived from) the Work and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. For the purposes -of this License, Derivative Works shall not include works that remain -separable from, or merely link (or bind by name) to the interfaces of, -the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including -the original version of the Work and any modifications or additions -to that Work or Derivative Works thereof, that is intentionally -submitted to Licensor for inclusion in the Work by the copyright owner -or by an individual or Legal Entity authorized to submit on behalf of -the copyright owner. For the purposes of this definition, "submitted" -means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, -and issue tracking systems that are managed by, or on behalf of, the -Licensor for the purpose of discussing and improving the Work, but -excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity -on behalf of whom a Contribution has been received by Licensor and -subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the -Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -(except as stated in this section) patent license to make, have made, -use, offer to sell, sell, import, and otherwise transfer the Work, -where such license applies only to those patent claims licensable -by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) -with the Work to which such Contribution(s) was submitted. If You -institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work -or a Contribution incorporated within the Work constitutes direct -or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate -as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the -Work or Derivative Works thereof in any medium, with or without -modifications, and in Source or Object form, provided that You -meet the following conditions: - -(a) You must give any other recipients of the Work or -Derivative Works a copy of this License; and - -(b) You must cause any modified files to carry prominent notices -stating that You changed the files; and - -(c) You must retain, in the Source form of any Derivative Works -that You distribute, all copyright, patent, trademark, and -attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of -the Derivative Works; and - -(d) If the Work includes a "NOTICE" text file as part of its -distribution, then any Derivative Works that You distribute must -include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not -pertain to any part of the Derivative Works, in at least one -of the following places: within a NOTICE text file distributed -as part of the Derivative Works; within the Source form or -documentation, if provided along with the Derivative Works; or, -within a display generated by the Derivative Works, if and -wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and -do not modify the License. You may add Your own attribution -notices within Derivative Works that You distribute, alongside -or as an addendum to the NOTICE text from the Work, provided -that such additional attribution notices cannot be construed -as modifying the License. - -You may add Your own copyright statement to Your modifications and -may provide additional or different license terms and conditions -for use, reproduction, or distribution of Your modifications, or -for any such Derivative Works as a whole, provided Your use, -reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, -any Contribution intentionally submitted for inclusion in the Work -by You to the Licensor shall be under the terms and conditions of -this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify -the terms of any separate license agreement you may have executed -with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade -names, trademarks, service marks, or product names of the Licensor, -except as required for reasonable and customary use in describing the -origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or -agreed to in writing, Licensor provides the Work (and each -Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied, including, without limitation, any warranties or conditions -of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any -risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, -whether in tort (including negligence), contract, or otherwise, -unless required by applicable law (such as deliberate and grossly -negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, -incidental, or consequential damages of any character arising as a -result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses), even if such Contributor -has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing -the Work or Derivative Works thereof, You may choose to offer, -and charge a fee for, acceptance of support, warranty, indemnity, -or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only -on Your own behalf and on Your sole responsibility, not on behalf -of any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason -of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/docs/licenses/pyvmomi.txt b/docs/licenses/pyvmomi.txt deleted file mode 100644 index f02b4ae7ab..0000000000 --- a/docs/licenses/pyvmomi.txt +++ /dev/null @@ -1,203 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/docs/licenses/pywinrm.txt b/docs/licenses/pywinrm.txt deleted file mode 100644 index 3e21facc07..0000000000 --- a/docs/licenses/pywinrm.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2013 Alexey Diyan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/receptor.txt b/docs/licenses/receptor.txt new file mode 100644 index 0000000000..bb0a7c7983 --- /dev/null +++ b/docs/licenses/receptor.txt @@ -0,0 +1,168 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. diff --git a/docs/licenses/requests-credssp.txt b/docs/licenses/requests-credssp.txt deleted file mode 100644 index 6f494085eb..0000000000 --- a/docs/licenses/requests-credssp.txt +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2016 Jordan Borean - -Permission to use, copy, modify and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/requests-kerberos.txt b/docs/licenses/requests-kerberos.txt deleted file mode 100644 index 581f115e78..0000000000 --- a/docs/licenses/requests-kerberos.txt +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2012 Kenneth Reitz - -Permission to use, copy, modify and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/docs/licenses/requests-ntlm.txt b/docs/licenses/requests-ntlm.txt deleted file mode 100644 index 5f3e18de12..0000000000 --- a/docs/licenses/requests-ntlm.txt +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2013 Ben Toews - -Permission to use, copy, modify and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/docs/licenses/requestsexceptions.txt b/docs/licenses/requestsexceptions.txt deleted file mode 100644 index d645695673..0000000000 --- a/docs/licenses/requestsexceptions.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/ruamel.ordereddict.txt b/docs/licenses/ruamel.ordereddict.txt deleted file mode 100644 index 0c12e55403..0000000000 --- a/docs/licenses/ruamel.ordereddict.txt +++ /dev/null @@ -1,23 +0,0 @@ - - The MIT License (MIT) - - Copyright (c) 2007-2017 Anthon van der Neut/Ruamel BVBA - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - diff --git a/docs/licenses/s3transfer.txt b/docs/licenses/s3transfer.txt deleted file mode 100644 index 6b0b1270ff..0000000000 --- a/docs/licenses/s3transfer.txt +++ /dev/null @@ -1,203 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/docs/licenses/selectors2.txt b/docs/licenses/selectors2.txt deleted file mode 100644 index a2f665b427..0000000000 --- a/docs/licenses/selectors2.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Seth Michael Larson - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/licenses/stevedore.txt b/docs/licenses/stevedore.txt deleted file mode 100644 index d645695673..0000000000 --- a/docs/licenses/stevedore.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/licenses/tabulate.txt b/docs/licenses/tabulate.txt deleted file mode 100644 index 7d6e69259c..0000000000 --- a/docs/licenses/tabulate.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2011-2017 Sergey Astanin - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/docs/licenses/typing.txt b/docs/licenses/typing.txt deleted file mode 100644 index 583f9f6e61..0000000000 --- a/docs/licenses/typing.txt +++ /dev/null @@ -1,254 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/docs/licenses/wheel.txt b/docs/licenses/wheel.txt deleted file mode 100644 index c3441e6cc8..0000000000 --- a/docs/licenses/wheel.txt +++ /dev/null @@ -1,22 +0,0 @@ -"wheel" copyright (c) 2012-2014 Daniel Holth and -contributors. - -The MIT License - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/licenses/xmltodict.txt b/docs/licenses/xmltodict.txt deleted file mode 100644 index a462778cff..0000000000 --- a/docs/licenses/xmltodict.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (C) 2012 Martin Blech and individual contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/installer/dockerfile.yml b/installer/dockerfile.yml deleted file mode 100644 index 9b6bfdf974..0000000000 --- a/installer/dockerfile.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Render AWX Dockerfile and sources - hosts: localhost - gather_facts: true - roles: - - {role: dockerfile} diff --git a/installer/install.yml b/installer/install.yml deleted file mode 100644 index a0c6d71f90..0000000000 --- a/installer/install.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Build and deploy AWX - hosts: all - roles: - - {role: check_vars} - - {role: kubernetes, when: "openshift_host is defined or kubernetes_context is defined"} diff --git a/installer/inventory b/installer/inventory deleted file mode 100644 index b9e7d22cfa..0000000000 --- a/installer/inventory +++ /dev/null @@ -1,173 +0,0 @@ -localhost ansible_connection=local ansible_python_interpreter="/usr/bin/env python3" - -[all:vars] - -# Remove these lines if you want to run a local image build -# Otherwise the setup playbook will install the official Ansible images. Versions may -# be selected based on: latest, 1, 1.0, 1.0.0, 1.0.0.123 -# by default the base will be used to search for ansible/awx -dockerhub_base=ansible - -# Openshift Install -# Will need to set -e openshift_password=developer -e docker_registry_password=$(oc whoami -t) -# or set -e openshift_token=TOKEN -# openshift_host=127.0.0.1:8443 -# openshift_project=awx -# openshift_user=developer -# openshift_skip_tls_verify=False -# openshift_pg_emptydir=True - -# Kubernetes Install -# kubernetes_context=test-cluster -# kubernetes_namespace=awx -# kubernetes_web_svc_type=NodePort -# Optional Kubernetes Variables -# pg_image_registry=docker.io -# pg_serviceaccount=awx -# pg_volume_capacity=5 -# pg_persistence_storageClass=StorageClassName -# pg_persistence_existingclaim=postgres_pvc -# pg_cpu_limit=1000 -# pg_mem_limit=2 - -# Kubernetes Ingress Configuration -# You can use the variables below to configure Kubernetes Ingress -# Set hostname -# kubernetes_ingress_hostname=awx.example.org -# Add annotations. The example below shows an annotation to be used with Traefik but other Ingress controllers are also supported -# kubernetes_ingress_annotations={'kubernetes.io/ingress.class': 'traefik', 'traefik.ingress.kubernetes.io/redirect-entry-point': 'https'} -# Specify a secret for TLS termination -# kubernetes_ingress_tls_secret=awx-cert - -# Kubernetes and Openshift Install Resource Requests -# These are the request and limit values for a pod's container for task/web/redis/management. -# The total amount of requested resources for a pod is the sum of all -# resources requested by all containers in the pod -# A cpu_request of 1500 is 1.5 cores for the container to start out with. -# A cpu_limit defines the maximum cores that that container can reserve. -# A mem_request of 2 is for 2 gigabytes of memory for the container -# A mem_limit defines the maximum memory that that container can reserve. -# Default values for these entries can be found in ./roles/kubernetes/defaults/main.yml -# task_cpu_request=1500 -# task_mem_request=2 -# task_cpu_limit=2000 -# task_mem_limit=4 -# web_cpu_limit=1000 -# web_mem_limit=2 -# redis_cpu_limit=1000 -# redis_mem_limit=3 -# management_cpu_limit=2000 -# management_mem_limit=2 - -# Common Docker parameters -awx_task_hostname=awx -awx_web_hostname=awxweb -# Local directory that is mounted in the awx_postgres docker container to place the db in -postgres_data_dir="~/.awx/pgdocker" -host_port=80 -host_port_ssl=443 -#ssl_certificate= -# Optional key file -#ssl_certificate_key= -docker_compose_dir="~/.awx/awxcompose" - -# Required for Openshift when building the image on your own -# Optional for Openshift if using Dockerhub or another prebuilt registry -# Required for Docker Compose Install if building the image on your own -# Optional for Docker Compose Install if using Dockerhub or another prebuilt registry -# Define if you want the image pushed to a registry. The container definition will also use these images -# docker_registry=172.30.1.1:5000 -# docker_registry_repository=awx -# docker_registry_username=developer - - -# Set pg_hostname if you have an external postgres server, otherwise -# a new postgres service will be created -# pg_hostname=postgresql -pg_username=awx -# pg_password should be random 10 character alphanumeric string, when postgresql is running on kubernetes -# NB: it's a limitation of the "official" postgres helm chart -pg_password=awxpass -pg_database=awx -pg_port=5432 -#pg_sslmode=require - -# If requiring SSL communication (e.g. pg_sslmode='verify-full') with Postgres -# and using a self-signed certificate or a certificate signed by a custom CA -# set pg_root_ca_file to a file containing the self-signed certificate or the -# root CA certificate chain. -# pg_root_ca_file='example_root_ca.crt' - -# The following variable is only required when using the provided -# containerized postgres deployment on OpenShift -# pg_admin_password=postgrespass - -# This will create or update a default admin (superuser) account in AWX, if not provided -# then these default values are used -admin_user=admin -# admin_password=password - -# Whether or not to create preload data for demonstration purposes -create_preload_data=True - -# AWX Secret key -# It's *very* important that this stay the same between upgrades or you will lose the ability to decrypt -# your credentials -secret_key=awxsecret - -# By default a broadcast websocket secret will be generated. -# If you would like to *rerun the playbook*, you need to set a unique password. -# Otherwise it would generate a new one every playbook run. -# broadcast_websocket_secret= - -# Build AWX with official logos -# Requires cloning awx-logos repo as a sibling of this project. -# Review the trademark guidelines at https://github.com/ansible/awx-logos/blob/master/TRADEMARKS.md -# awx_official=false - -# Proxy -#http_proxy=http://proxy:3128 -#https_proxy=http://proxy:3128 -#no_proxy=mycorp.org - -# Container networking configuration -# Set the awx_task and awx_web containers' search domain(s) -#awx_container_search_domains=example.com,ansible.com -# Alternate DNS servers -#awx_alternate_dns_servers="10.1.2.3,10.2.3.4" - -# AWX project data folder. If you need access to the location where AWX stores the projects -# it manages from the docker host, you can set this to turn it into a volume for the container. -#project_data_dir=/var/lib/awx/projects - -# AWX custom virtual environment folder. Only usable for local install. -#custom_venv_dir=/opt/my-envs/ - -# CA Trust directory. If you need to provide custom CA certificates, supplying -# this variable causes this directory on the host to be bind mounted over -# /etc/pki/ca-trust in the awx_task and awx_web containers. -# If you are deploying on openshift or kubernetes, set the variable to /etc/pki/ca-trust instead, -# as the awx_web and awx_task containers will not run the `update-ca-trust` command. -#ca_trust_dir=/etc/pki/ca-trust/source/anchors - -# Include /etc/nginx/awx_extra.conf -# Note the use of glob pattern for nginx -# which makes include "optional" - i.e. not fail -# if file is absent -#extra_nginx_include="/etc/nginx/awx_extra[.]conf" - -# Docker compose explicit subnet. Set to avoid overlapping your existing LAN networks. -#docker_compose_subnet="172.17.0.1/16" -# -# Allow for different docker logging drivers -# By Default; the logger will be json-file, however you can override -# that by uncommenting the docker_logger below. -# Be aware that journald may rate limit your log messages if you choose it. -# See: https://docs.docker.com/config/containers/logging/configure/ -# docker_logger=journald -# - -# Add extra hosts to docker compose file. This might be necessary to -# sneak in servernames. For example for DMZ self-signed CA certificates. -# Equivialent to using the --add-host parameter with "docker run". -#docker_compose_extra_hosts="otherserver.local:192.168.0.1,ldap-server.local:192.168.0.2" diff --git a/installer/roles/check_vars/tasks/check_openshift.yml b/installer/roles/check_vars/tasks/check_openshift.yml deleted file mode 100644 index a2bebf6b16..0000000000 --- a/installer/roles/check_vars/tasks/check_openshift.yml +++ /dev/null @@ -1,48 +0,0 @@ -# check_openshift.yml ---- -- name: openshift_project should be defined - assert: - that: - - openshift_project is defined and openshift_project != '' - msg: "Set the value of 'openshift_project' in the inventory file." - -- name: openshift_user should be defined - assert: - that: - - openshift_user is defined and openshift_user != '' - msg: "Set the value of 'openshift_user' in the inventory file." - -- name: openshift_password or openshift_token should be defined - assert: - that: - - (openshift_password is defined and openshift_password != '') or - (openshift_token is defined and openshift_token != '') - msg: "Set the value of 'openshift_password' or 'openshift_token' in the inventory file." - -- name: docker_registry should be defined if not using dockerhub - assert: - that: - - docker_registry is defined and docker_registry != '' - msg: "Set the value of 'docker_registry' in the inventory file." - when: dockerhub_base is not defined - -- name: docker_registry_repository should be defined if not using dockerhub - assert: - that: - - docker_registry_repository is defined and docker_registry_repository != '' - msg: "Set the value of 'docker_registry_repository' in the inventory file." - when: dockerhub_base is not defined - -- name: docker_registry_username should be defined if not using dockerhub - assert: - that: - - docker_registry_username is defined and docker_registry_username != '' - msg: "Set the value of 'docker_registry_username' in the inventory file." - when: dockerhub_base is not defined - -- name: docker_registry_password should be defined - assert: - that: - - docker_registry_password is defined and docker_registry_password != '' - msg: "Set the value of 'docker_registry_password' in the inventory file." - when: dockerhub_base is not defined diff --git a/installer/roles/check_vars/tasks/main.yml b/installer/roles/check_vars/tasks/main.yml deleted file mode 100644 index 9ac079e560..0000000000 --- a/installer/roles/check_vars/tasks/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -# main.yml ---- -- name: admin_password should be defined - assert: - that: - - admin_password is defined and admin_password != '' - msg: "Set the value of 'admin_password' in the inventory file." - -- include_tasks: check_openshift.yml - when: openshift_host is defined and openshift_host != '' diff --git a/installer/roles/kubernetes/defaults/main.yml b/installer/roles/kubernetes/defaults/main.yml deleted file mode 100644 index 754ff86fe0..0000000000 --- a/installer/roles/kubernetes/defaults/main.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -dockerhub_version: "{{ lookup('file', playbook_dir + '/../VERSION') }}" -create_preload_data: true - -admin_user: 'admin' -admin_email: 'root@localhost' -admin_password: '' - -kubernetes_base_path: "{{ local_base_config_path|default('/tmp') }}/{{ kubernetes_deployment_name }}-config" - -kubernetes_awx_version: "{{ dockerhub_version }}" -kubernetes_awx_image: "ansible/awx" -kubernetes_web_svc_type: "NodePort" - -awx_psp_create: false -awx_psp_name: 'awx' -awx_psp_privileged: true - -web_mem_request: 1 -web_cpu_request: 500 -web_security_context_enabled: true -web_security_context_privileged: false - -task_mem_request: 2 -task_cpu_request: 1500 -task_security_context_enabled: true -task_security_context_privileged: true - -redis_mem_request: 2 -redis_cpu_request: 500 -redis_security_context_enabled: true -redis_security_context_privileged: false -redis_security_context_user: 1001 - -kubernetes_redis_image: "redis" -kubernetes_redis_image_tag: "latest" -kubernetes_redis_config_mount_path: "/usr/local/etc/redis/redis.conf" - -openshift_pg_emptydir: false -openshift_pg_pvc_name: postgresql - -kubernetes_deployment_name: awx -kubernetes_serviceaccount_name: awx - -kubernetes_deployment_replica_size: 1 - -postgress_activate_wait: 60 - -restore_backup_file: "./tower-openshift-backup-latest.tar.gz" - -insights_url_base: "https://example.org" -automation_analytics_url: "https://example.org" -insights_agent_mime: "application/example" - -custom_venvs_path: "/opt/custom-venvs" -custom_venvs_python: "python2" - -ca_trust_bundle: "/etc/pki/tls/certs/ca-bundle.crt" - -container_groups_image: "ansible/ansible-runner" - -uwsgi_bash: "bash -c" diff --git a/installer/roles/kubernetes/handlers/main.yml b/installer/roles/kubernetes/handlers/main.yml deleted file mode 100644 index 89b3fa701c..0000000000 --- a/installer/roles/kubernetes/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: remove-rmq_cert_tempdir - file: - state: absent - path: "{{ rmq_cert_tempdir.path }}" diff --git a/installer/roles/kubernetes/tasks/backup.yml b/installer/roles/kubernetes/tasks/backup.yml deleted file mode 100644 index d2d41bbbc1..0000000000 --- a/installer/roles/kubernetes/tasks/backup.yml +++ /dev/null @@ -1,82 +0,0 @@ ---- -- name: Determine the timestamp for the backup. - set_fact: - now: '{{ lookup("pipe", "date +%F-%T") }}' - -- include_tasks: openshift_auth.yml - when: openshift_host is defined - -- include_tasks: kubernetes_auth.yml - when: kubernetes_context is defined - -- name: Use kubectl or oc - set_fact: - kubectl_or_oc: "{{ openshift_oc_bin if openshift_oc_bin is defined else 'kubectl' }}" - -- name: Delete any existing management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Template management pod - set_fact: - management_pod: "{{ lookup('template', 'management-pod.yml.j2') }}" - -- name: Create management pod - shell: | - echo {{ management_pod | quote }} | {{ kubectl_or_oc }} apply -f - - -- name: Wait for management pod to start - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pod ansible-tower-management -o jsonpath="{.status.phase}" - register: result - until: result.stdout == "Running" - retries: 60 - delay: 10 - -- name: Create directory for backup - file: - state: directory - path: "{{ playbook_dir }}/tower-openshift-backup-{{ now }}" - -- name: Precreate file for database dump - file: - path: "{{ playbook_dir }}/tower-openshift-backup-{{ now }}/tower.db" - state: touch - mode: 0600 - -- name: Dump database - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "PGPASSWORD={{ pg_password | quote }} \ - pg_dump --clean --create \ - --host='{{ pg_hostname | default('postgresql') }}' \ - --port={{ pg_port | default('5432') }} \ - --username='{{ pg_username }}' \ - --dbname='{{ pg_database }}'" > {{ playbook_dir }}/tower-openshift-backup-{{ now }}/tower.db - no_log: true - -- name: Copy inventory into backup directory - copy: - src: "{{ inventory_file }}" - dest: "{{ playbook_dir }}/tower-openshift-backup-{{ now }}/" - mode: 0600 - -- name: Delete management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Create backup archive - archive: - path: "{{ playbook_dir }}/tower-openshift-backup-{{ now }}" - dest: "{{ item }}" - with_items: - - "{{ playbook_dir }}/tower-openshift-backup-{{ now }}.tar.gz" - - "{{ playbook_dir }}/tower-openshift-backup-latest.tar.gz" - -- name: Remove temporary backup directory - file: - path: "{{ playbook_dir }}/tower-openshift-backup-{{ now }}" - state: absent diff --git a/installer/roles/kubernetes/tasks/kubernetes.yml b/installer/roles/kubernetes/tasks/kubernetes.yml deleted file mode 100644 index 89370bed1f..0000000000 --- a/installer/roles/kubernetes/tasks/kubernetes.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- name: Get Namespace Detail - shell: "kubectl get namespace {{ kubernetes_namespace }}" - register: namespace_details - ignore_errors: true - -- name: Create AWX Kubernetes Project - shell: "kubectl create namespace {{ kubernetes_namespace }}" - when: namespace_details.rc != 0 - -- name: Set postgresql service name - set_fact: - postgresql_service_name: "{{ kubernetes_deployment_name }}-postgresql" - when: "pg_hostname is not defined or pg_hostname == ''" - -- name: Get Kubernetes API version - command: | - kubectl version -o json - register: kube_version - -- name: Extract server version from command output - set_fact: - kube_api_version: "{{ (kube_version.stdout | from_json).serverVersion.gitVersion[1:] }}" diff --git a/installer/roles/kubernetes/tasks/kubernetes_auth.yml b/installer/roles/kubernetes/tasks/kubernetes_auth.yml deleted file mode 100644 index 7f9d29456b..0000000000 --- a/installer/roles/kubernetes/tasks/kubernetes_auth.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -- name: Set the Kubernetes Context - shell: "kubectl config use-context {{ kubernetes_context }}" diff --git a/installer/roles/kubernetes/tasks/main.yml b/installer/roles/kubernetes/tasks/main.yml deleted file mode 100644 index dc6639b56b..0000000000 --- a/installer/roles/kubernetes/tasks/main.yml +++ /dev/null @@ -1,320 +0,0 @@ ---- -- name: Generate broadcast websocket secret - set_fact: - broadcast_websocket_secret: "{{ lookup('password', '/dev/null length=128') }}" - run_once: true - no_log: true - when: broadcast_websocket_secret is not defined - -- fail: - msg: "Only set one of kubernetes_context or openshift_host" - when: openshift_host is defined and kubernetes_context is defined - -- include_tasks: "{{ tasks }}" - with_items: - - openshift_auth.yml - - openshift.yml - loop_control: - loop_var: tasks - when: openshift_host is defined - -- include_tasks: "{{ tasks }}" - with_items: - - kubernetes_auth.yml - - kubernetes.yml - loop_control: - loop_var: tasks - when: kubernetes_context is defined - -- name: Use kubectl or oc - set_fact: - kubectl_or_oc: "{{ openshift_oc_bin if openshift_oc_bin is defined else 'kubectl' }}" - -- set_fact: - deployment_object: "deployment" - -- name: Record deployment size - shell: | - {{ kubectl_or_oc }} get {{ deployment_object }} \ - {{ kubernetes_deployment_name }} \ - -n {{ kubernetes_namespace }} -o=jsonpath='{.status.replicas}' - register: deployment_details - ignore_errors: true - -- name: Set expected post-deployment Replicas value - set_fact: - kubernetes_deployment_replica_size: "{{ deployment_details.stdout | int }}" - when: deployment_details.rc == 0 - -- name: Delete existing Deployment (or StatefulSet) - shell: | - {{ kubectl_or_oc }} delete sts \ - {{ kubernetes_deployment_name }} -n {{ kubernetes_namespace }} --ignore-not-found - {{ kubectl_or_oc }} delete {{ deployment_object }} \ - {{ kubernetes_deployment_name }} -n {{ kubernetes_namespace }} --ignore-not-found - -- name: Get Postgres Service Detail - shell: "{{ kubectl_or_oc }} describe svc {{ postgresql_service_name }} -n {{ kubernetes_namespace }}" - register: postgres_svc_details - ignore_errors: true - when: "pg_hostname is not defined or pg_hostname == ''" - -- name: Deploy PostgreSQL (OpenShift) - block: - - name: Template PostgreSQL Deployment (OpenShift) - template: - src: postgresql-persistent.yml.j2 - dest: "{{ kubernetes_base_path }}/postgresql-persistent.yml" - mode: '0600' - - - name: Deploy and Activate Postgres (OpenShift) - shell: | - {{ openshift_oc_bin }} new-app --file={{ kubernetes_base_path }}/postgresql-persistent.yml \ - -e MEMORY_LIMIT={{ pg_memory_limit|default('512') }}Mi \ - -e DATABASE_SERVICE_NAME=postgresql \ - -e POSTGRESQL_MAX_CONNECTIONS={{ pg_max_connections|default(1024) }} \ - -e POSTGRESQL_USER={{ pg_username }} \ - -e POSTGRESQL_PASSWORD={{ pg_password | quote }} \ - -e POSTGRESQL_DATABASE={{ pg_database | quote }} \ - -e POSTGRESQL_VERSION=12 \ - -n {{ kubernetes_namespace }} - register: openshift_pg_activate - no_log: true - when: - - pg_hostname is not defined or pg_hostname == '' - - postgres_svc_details is defined and postgres_svc_details.rc != 0 - - openshift_host is defined - -- name: Deploy PostgreSQL (Kubernetes) - block: - - name: Create Temporary Values File (Kubernetes) - tempfile: - state: file - suffix: .yml - register: values_file - - - name: Populate Temporary Values File (Kubernetes) - template: - src: postgresql-values.yml.j2 - dest: "{{ values_file.path }}" - no_log: true - - - name: Deploy and Activate Postgres (Kubernetes) - shell: | - helm repo add stable https://charts.helm.sh/stable - helm repo update - helm upgrade {{ postgresql_service_name }} \ - --install \ - --namespace {{ kubernetes_namespace }} \ - --version="8.3.0" \ - --values {{ values_file.path }} \ - stable/postgresql - register: kubernetes_pg_activate - no_log: true - - - name: Remove tempfile - file: - path: "{{ values_file.path }}" - state: absent - when: - - pg_hostname is not defined or pg_hostname == '' - - postgres_svc_details is defined and postgres_svc_details.rc != 0 - - kubernetes_context is defined - -- name: Set postgresql hostname to helm package service (Kubernetes) - set_fact: - pg_hostname: "{{ postgresql_service_name }}" - when: - - pg_hostname is not defined or pg_hostname == '' - - kubernetes_context is defined - -- name: Wait for Postgres to activate - pause: - seconds: "{{ postgress_activate_wait }}" - when: openshift_pg_activate.changed or kubernetes_pg_activate.changed - -- name: Check postgres version and upgrade Postgres if necessary (Openshift) - block: - - name: Check if Postgres 10 is being used - shell: | - POD=$({{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pods -l=name=postgresql --field-selector status.phase=Running -o jsonpath="{.items[0].metadata.name}") - {{ kubectl_or_oc }} exec $POD -n {{ kubernetes_namespace }} -- bash -c "psql -tAc 'select version()'" - register: pg_version - - name: Upgrade postgres if necessary - block: - - name: Set new pg image - shell: | - IMAGE=registry.redhat.io/rhel-8/postgresql-12 - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} set image dc/postgresql postgresql=$IMAGE - - - name: Wait for change to take affect - pause: - seconds: 5 - - - name: Set env var for pg upgrade - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} set env dc/postgresql POSTGRESQL_UPGRADE=copy - - - name: Wait for change to take affect - pause: - seconds: 5 - - - name: Set env var for new pg version - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} set env dc/postgresql POSTGRESQL_VERSION=12 - - - name: Wait for Postgres to redeploy - pause: - seconds: "{{ postgress_activate_wait }}" - - - name: Wait for Postgres to finish upgrading - shell: | - POD=$({{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pods -l=name=postgresql -o jsonpath="{.items[0].metadata.name}") - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} logs $POD | grep 'Upgrade DONE' - register: pg_upgrade_logs - retries: 360 - delay: 10 - until: pg_upgrade_logs is success - - - name: Unset upgrade env var - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} set env dc/postgresql POSTGRESQL_UPGRADE- - - - name: Wait for Postgres to redeploy - pause: - seconds: "{{ postgress_activate_wait }}" - when: "pg_version is success and '10' in pg_version.stdout" - when: - - pg_hostname is not defined or pg_hostname == '' - - postgres_svc_details is defined and postgres_svc_details.rc != 0 - - openshift_host is defined - -- name: Set image names if using custom registry - block: - - name: Set awx image name - set_fact: - kubernetes_awx_image: "{{ docker_registry }}/{{ docker_registry_repository }}/{{ awx_image }}" - when: kubernetes_awx_image is not defined - when: docker_registry is defined - -- name: Determine Deployment api version - set_fact: - kubernetes_deployment_api_version: "{{ 'apps/v1' if kube_api_version is version('1.9', '>=') else 'apps/v1beta1' }}" - -- name: Use Custom Root CA file for PosgtreSQL SSL communication - block: - - name: Get Root CA file contents - set_fact: - postgres_root_ca_cert: "{{ lookup('file', pg_root_ca_file) }}" - no_log: true - - - name: Render Root CA template - set_fact: - postgres_root_ca: "{{ lookup('template', 'postgres_root_ca.yml.j2') }}" - no_log: true - - - name: Apply Root CA template - shell: | - echo {{ postgres_root_ca | quote }} | {{ kubectl_or_oc }} apply -f - - no_log: true - - - name: Set Root CA file name - set_fact: - postgres_root_ca_filename: 'postgres_root_ca.crt' - - - name: Set Root CA file location - set_fact: - ca_trust_bundle: '/etc/tower/{{ postgres_root_ca_filename }}' - when: - - pg_root_ca_file is defined - - pg_root_ca_file != '' - -- name: Render deployment templates - set_fact: - "{{ item }}": "{{ lookup('template', item + '.yml.j2') }}" - with_items: - - 'configmap' - - 'secret' - - 'deployment' - - 'supervisor' - no_log: true - -- name: Apply Deployment - shell: | - echo {{ item | quote }} | {{ kubectl_or_oc }} apply -f - - with_items: - - "{{ configmap }}" - - "{{ secret }}" - - "{{ deployment }}" - - "{{ supervisor }}" - no_log: true - -- name: Delete any existing management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Template management pod - set_fact: - management_pod: "{{ lookup('template', 'management-pod.yml.j2') }}" - -- name: Create management pod - shell: | - echo {{ management_pod | quote }} | {{ kubectl_or_oc }} apply -f - - -- name: Wait for management pod to start - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pod ansible-tower-management -o jsonpath="{.status.phase}" - register: result - until: result.stdout == "Running" - retries: 60 - delay: 10 - -- name: Migrate database - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "awx-manage migrate --noinput" - -- name: Check for Tower Super users - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "echo 'from django.contrib.auth.models import User; nsu = User.objects.filter(is_superuser=True).count(); exit(0 if nsu > 0 else 1)' | awx-manage shell" - register: super_check - ignore_errors: true - changed_when: super_check.rc > 0 - -- name: create django super user if it does not exist - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('{{ admin_user }}', '{{ admin_email }}', '{{ admin_password }}')\" | awx-manage shell" - no_log: true - when: super_check.rc > 0 - -- name: update django super user password - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "awx-manage update_password --username='{{ admin_user }}' --password='{{ admin_password }}'" - no_log: true - register: result - changed_when: "'Password updated' in result.stdout" - -- name: Create the default organization if it is needed. - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} exec ansible-tower-management -- \ - bash -c "awx-manage create_preload_data" - register: cdo - changed_when: "'added' in cdo.stdout" - when: create_preload_data | bool - -- name: Delete management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Scale up deployment - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - scale {{ deployment_object }} {{ kubernetes_deployment_name }} --replicas={{ replicas | default(kubernetes_deployment_replica_size) }} diff --git a/installer/roles/kubernetes/tasks/openshift.yml b/installer/roles/kubernetes/tasks/openshift.yml deleted file mode 100644 index c85ab961ba..0000000000 --- a/installer/roles/kubernetes/tasks/openshift.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- -- name: Get Project Detail - shell: "{{ openshift_oc_bin }} get project {{ openshift_project }}" - register: project_details - ignore_errors: true - -- name: Create AWX Openshift Project - shell: "{{ openshift_oc_bin }} new-project {{ openshift_project }}" - when: project_details.rc != 0 - -- name: Ensure PostgreSQL PVC is available - block: - - name: Check PVC status - command: "{{ openshift_oc_bin }} get pvc {{ openshift_pg_pvc_name }} -n {{ openshift_project }} -o=jsonpath='{.status.phase}'" - register: pg_pvc_status - ignore_errors: true - - - name: Ensure PostgreSQL PVC is available - assert: - that: - - pg_pvc_status.stdout in ["Bound", "Pending"] - msg: "Ensure a PVC named '{{ openshift_pg_pvc_name }}' is available in the namespace '{{ openshift_project }}'." - when: - - pg_hostname is not defined or pg_hostname == '' - - openshift_pg_emptydir is defined and (openshift_pg_emptydir | bool) != true - -- name: Set postgresql service name - set_fact: - postgresql_service_name: "postgresql" - when: "pg_hostname is not defined or pg_hostname == ''" - -- name: Add privileged SCC to service account - shell: | - {{ openshift_oc_bin }} adm policy add-scc-to-user privileged system:serviceaccount:{{ openshift_project }}:awx - -# https://github.com/openshift/origin/issues/19182#issuecomment-378233606 -# If oc version ever grows a -o json option, remove the following tasks -# and go with the approach in kubernetes.yml. -- name: Get Kubernetes Config - command: | - {{ openshift_oc_bin }} config view -o json - register: kube_config_cmd - no_log: true - -- name: Convert kube config to dictionary - set_fact: - kube_config: "{{ kube_config_cmd.stdout | from_json }}" - no_log: true - -- name: Extract current context from kube config - set_fact: - current_kube_context: "{{ kube_config['current-context'] }}" - -- name: Find cluster for current context - set_fact: - kube_cluster: | - {{ (kube_config.contexts | - selectattr("name", "match", current_kube_context) | - list)[0].context.cluster }} - -- name: Find server for current context - set_fact: - kube_server: | - {{ (kube_config.clusters | - selectattr("name", "match", kube_cluster|trim) | - list)[0].cluster.server }} - -- name: Get kube version from api server - uri: - url: "{{ kube_server | trim }}/version" - validate_certs: false - register: kube_version - -- name: Extract server version from command output - set_fact: - kube_api_version: "{{ kube_version.json.gitVersion[1:] }}" diff --git a/installer/roles/kubernetes/tasks/openshift_auth.yml b/installer/roles/kubernetes/tasks/openshift_auth.yml deleted file mode 100644 index 1b53cda59a..0000000000 --- a/installer/roles/kubernetes/tasks/openshift_auth.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -- include_vars: openshift.yml - -- name: Set kubernetes_namespace - set_fact: - kubernetes_namespace: "{{ openshift_project }}" - -- name: Ensure workspace directories exist - file: - path: "{{ item }}" - state: directory - with_items: - - "{{ kubernetes_base_path }}" - - "{{ openshift_oc_config_file | dirname }}" - -- name: Authenticate with OpenShift via user and password - shell: | - {{ openshift_oc_bin }} login {{ openshift_host }} \ - -u {{ openshift_user }} \ - -p {{ openshift_password | quote }} \ - --insecure-skip-tls-verify={{ openshift_skip_tls_verify | default(false) | bool }} - when: - - openshift_user is defined - - openshift_password is defined - - openshift_token is not defined - register: openshift_auth_result - ignore_errors: true - no_log: true - -- name: OpenShift authentication failed on TLS verification - fail: - msg: "Failed to verify TLS, consider settings openshift_skip_tls_verify=True {{ openshift_auth_result.stderr | default('certificate does not match hostname') }}" - when: - - openshift_skip_tls_verify is not defined or not openshift_skip_tls_verify - - openshift_auth_result.rc is defined and openshift_auth_result.rc != 0 - - openshift_auth_result.stderr is defined and (openshift_auth_result.stderr | search("certificate that does not match its hostname")) - -- name: OpenShift authentication failed - fail: - msg: "{{ openshift_auth_result.stderr | default('Invalid credentials') }}" - when: openshift_auth_result.rc is defined and openshift_auth_result.rc != 0 - -- name: Authenticate with OpenShift via token - shell: | - {{ openshift_oc_bin }} login {{ openshift_host }} \ - --token {{ openshift_token }} \ - --insecure-skip-tls-verify={{ openshift_skip_tls_verify | default(false) | bool }} - when: openshift_token is defined - register: openshift_auth_result - ignore_errors: true - no_log: true - -- name: OpenShift authentication failed - fail: - msg: "{{ openshift_auth_result.stderr | default('Invalid token') }}" - when: openshift_auth_result.rc is defined and openshift_auth_result.rc != 0 diff --git a/installer/roles/kubernetes/tasks/rekey.yml b/installer/roles/kubernetes/tasks/rekey.yml deleted file mode 100644 index 91ed2828ed..0000000000 --- a/installer/roles/kubernetes/tasks/rekey.yml +++ /dev/null @@ -1,72 +0,0 @@ ---- -- include_tasks: openshift_auth.yml - when: openshift_host is defined - -- include_tasks: kubernetes_auth.yml - when: kubernetes_context is defined - -- name: Use kubectl or oc - set_fact: - kubectl_or_oc: "{{ openshift_oc_bin if openshift_oc_bin is defined else 'kubectl' }}" - -- set_fact: - deployment_object: "deployment" - -- name: Record deployment size - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get {{ deployment_object }} {{ kubernetes_deployment_name }} -o jsonpath="{.status.replicas}" - register: deployment_size - -- name: Scale deployment down - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - scale {{ deployment_object }} {{ kubernetes_deployment_name }} --replicas=0 - -- name: Wait for scale down - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} get pods \ - -o jsonpath='{.items[*].metadata.name}' \ - | tr -s '[[:space:]]' '\n' \ - | grep {{ kubernetes_deployment_name }} \ - | grep -v postgres | wc -l - register: tower_pods - until: (tower_pods.stdout | trim) == '0' - retries: 30 - -- name: Delete any existing management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Template management pod - set_fact: - management_pod: "{{ lookup('template', 'management-pod.yml.j2') }}" - -- name: Create management pod - shell: | - echo {{ management_pod | quote }} | {{ kubectl_or_oc }} apply -f - - -- name: Wait for management pod to start - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pod ansible-tower-management -o jsonpath="{.status.phase}" - register: result - until: result.stdout == "Running" - retries: 60 - delay: 10 - -- name: generate a new SECRET_KEY - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - exec -i ansible-tower-management -- bash -c "awx-manage regenerate_secret_key" - register: new_key - -- name: print the new SECRET_KEY - debug: - msg: "{{ new_key.stdout }}" - -- name: Delete management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found diff --git a/installer/roles/kubernetes/tasks/restore.yml b/installer/roles/kubernetes/tasks/restore.yml deleted file mode 100644 index 9d0358e751..0000000000 --- a/installer/roles/kubernetes/tasks/restore.yml +++ /dev/null @@ -1,145 +0,0 @@ ---- -- include_tasks: openshift_auth.yml - when: openshift_host is defined - -- include_tasks: kubernetes_auth.yml - when: kubernetes_context is defined - -- name: Use kubectl or oc - set_fact: - kubectl_or_oc: "{{ openshift_oc_bin if openshift_oc_bin is defined else 'kubectl' }}" - -- name: Remove any present restore directories - file: - state: absent - path: "{{ playbook_dir }}/tower-openshift-restore" - -- name: Create directory for restore data - file: - state: directory - path: "{{ playbook_dir }}/tower-openshift-restore" - -- name: Unarchive Tower backup - unarchive: - src: "{{ restore_backup_file }}" - dest: "{{ playbook_dir }}/tower-openshift-restore" - extra_opts: [--strip-components=1] - -- name: Verify if common.tar.gz exists - stat: - path: "{{ playbook_dir }}/tower-openshift-restore/common.tar.gz" - register: common_tarball - -- name: Unarchive Tower backup from common.tar.gz - unarchive: - src: "{{ playbook_dir }}/tower-openshift-restore/common.tar.gz" - dest: "{{ playbook_dir }}/tower-openshift-restore" - extra_opts: [--strip-components=1] - when: common_tarball.stat.exists - -- set_fact: - deployment_object: "deployment" - -- name: Record deployment size - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get {{ deployment_object }} {{ kubernetes_deployment_name }} -o jsonpath="{.status.replicas}" - register: deployment_size - -- name: Scale deployment down - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - scale {{ deployment_object }} {{ kubernetes_deployment_name }} --replicas=0 - -- name: Delete management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - -- name: Wait for scale down - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} get pods \ - -o jsonpath='{.items[*].metadata.name}' \ - | tr -s '[[:space:]]' '\n' \ - | grep {{ kubernetes_deployment_name }} \ - | grep -v postgres | wc -l - register: tower_pods - until: (tower_pods.stdout | trim) == '0' - retries: 30 - -- name: Setup Management Pod & Restore (External DB) - block: - - name: Delete any existing management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - - - name: Template management pod - set_fact: - management_pod: "{{ lookup('template', 'management-pod.yml.j2') }}" - - - name: Create management pod - shell: | - echo {{ management_pod | quote }} | {{ kubectl_or_oc }} apply -f - - - - name: Wait for management pod to start - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pod ansible-tower-management -o jsonpath="{.status.phase}" - register: result - until: result.stdout == "Running" - retries: 60 - delay: 10 - - - name: Perform a PostgreSQL restore (for External Postgres) - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - exec -i ansible-tower-management -- bash -c "PGPASSWORD={{ pg_password | quote }} \ - psql \ - --host={{ pg_hostname | default('postgresql') }} \ - --port={{ pg_port | default('5432') }} \ - --username={{ pg_username }} \ - --dbname=template1" < {{ playbook_dir }}/tower-openshift-restore/tower.db - no_log: true - - - name: Delete management pod - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - delete pod ansible-tower-management --grace-period=0 --ignore-not-found - when: pg_hostname is defined or pg_hostname != '' - -- name: Restore (Containerized DB) - block: - - name: Temporarily grant createdb role - shell: | - POD=$({{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pods -l=name=postgresql --field-selector status.phase=Running -o jsonpath="{.items[0].metadata.name}") - {{ kubectl_or_oc }} exec $POD -n {{ kubernetes_namespace }} -- bash -c "\ - psql --dbname=template1 -c 'ALTER USER \"{{ pg_username }}\" CREATEDB;'" - - - name: Perform a PostgreSQL restore - shell: | - POD=$({{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pods -l=name=postgresql --field-selector status.phase=Running -o jsonpath="{.items[0].metadata.name}") - {{ kubectl_or_oc }} exec -i $POD -n {{ kubernetes_namespace }} -- bash -c "\ - psql --dbname=template1" < {{ playbook_dir }}/tower-openshift-restore/tower.db - no_log: true - - - name: Revoke createdb role - shell: | - POD=$({{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - get pods -l=name=postgresql --field-selector status.phase=Running -o jsonpath="{.items[0].metadata.name}") - {{ kubectl_or_oc }} exec $POD -n {{ kubernetes_namespace }} -- bash -c "\ - psql --dbname=template1 -c 'ALTER USER \"{{ pg_username }}\" NOCREATEDB;'" - when: pg_hostname is not defined or pg_hostname == '' - -- name: Remove restore directory - file: - state: absent - path: "{{ playbook_dir }}/tower-openshift-restore" - -- name: Scale deployment back up - shell: | - {{ kubectl_or_oc }} -n {{ kubernetes_namespace }} \ - scale {{ deployment_object }} {{ kubernetes_deployment_name }} --replicas={{ deployment_size.stdout }} - when: deployment_size.stdout != '' diff --git a/installer/roles/kubernetes/templates/configmap.yml.j2 b/installer/roles/kubernetes/templates/configmap.yml.j2 deleted file mode 100644 index 2d7fd50aac..0000000000 --- a/installer/roles/kubernetes/templates/configmap.yml.j2 +++ /dev/null @@ -1,206 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ kubernetes_deployment_name }}-config - namespace: {{ kubernetes_namespace }} -data: - {{ kubernetes_deployment_name }}_nginx_conf: | - #user awx; - - worker_processes 1; - - pid /tmp/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - server_tokens off; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /dev/stdout main; - - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - - sendfile on; - #tcp_nopush on; - #gzip on; - - upstream uwsgi { - server 127.0.0.1:8050; - } - - upstream daphne { - server 127.0.0.1:8051; - } - - {% if ssl_certificate is defined %} - server { - listen 8052 default_server; - server_name _; - - # Redirect all HTTP links to the matching HTTPS page - return 301 https://$host$request_uri; - } - {%endif %} - - server { - {% if ssl_certificate is defined %} - listen 8053 ssl; - - ssl_certificate /etc/nginx/awxweb.pem; - ssl_certificate_key /etc/nginx/awxweb.pem; - {% else %} - listen 8052 default_server; - {% endif %} - - # If you have a domain name, this is where to add it - server_name _; - keepalive_timeout 65; - - # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) - add_header Strict-Transport-Security max-age=15768000; - - # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009) - add_header X-Frame-Options "DENY"; - - location /nginx_status { - stub_status on; - access_log off; - allow 127.0.0.1; - deny all; - } - - location /static/ { - alias /var/lib/awx/public/static/; - } - - location /favicon.ico { alias /var/lib/awx/public/static/favicon.ico; } - - location /websocket { - # Pass request to the upstream alias - proxy_pass http://daphne; - # Require http version 1.1 to allow for upgrade requests - proxy_http_version 1.1; - # We want proxy_buffering off for proxying to websockets. - proxy_buffering off; - # http://en.wikipedia.org/wiki/X-Forwarded-For - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # enable this if you use HTTPS: - proxy_set_header X-Forwarded-Proto https; - # pass the Host: header from the client for the sake of redirects - proxy_set_header Host $http_host; - # We've set the Host header, so we don't need Nginx to muddle - # about with redirects - proxy_redirect off; - # Depending on the request value, set the Upgrade and - # connection headers - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - } - - location / { - # Add trailing / if missing - rewrite ^(.*)$http_host(.*[^/])$ $1$http_host$2/ permanent; - uwsgi_read_timeout 120s; - uwsgi_pass uwsgi; - include /etc/nginx/uwsgi_params; - {%- if extra_nginx_include is defined %} - include {{ extra_nginx_include }}; - {%- endif %} - proxy_set_header X-Forwarded-Port 443; - uwsgi_param HTTP_X_FORWARDED_PORT 443; - } - } - } - - {{ kubernetes_deployment_name }}_settings: | - import os - import socket - ADMINS = () - - AWX_PROOT_ENABLED = True - - # Automatically deprovision pods that go offline - AWX_AUTO_DEPROVISION_INSTANCES = True - - SYSTEM_TASK_ABS_CPU = {{ ((task_cpu_request|int / 1000) * 4)|int }} - SYSTEM_TASK_ABS_MEM = {{ ((task_mem_request|int * 1024) / 100)|int }} - - INSIGHTS_URL_BASE = "{{ insights_url_base }}" - INSIGHTS_AGENT_MIME = "{{ insights_agent_mime }}" - AUTOMATION_ANALYTICS_URL = "{{ automation_analytics_url }}" - - #Autoprovisioning should replace this - CLUSTER_HOST_ID = socket.gethostname() - SYSTEM_UUID = os.environ.get('MY_POD_UID', '00000000-0000-0000-0000-000000000000') - - SESSION_COOKIE_SECURE = False - CSRF_COOKIE_SECURE = False - - REMOTE_HOST_HEADERS = ['HTTP_X_FORWARDED_FOR'] - - STATIC_ROOT = '/var/lib/awx/public/static' - PROJECTS_ROOT = '/var/lib/awx/projects' - AWX_ANSIBLE_COLLECTIONS_PATHS = '/var/lib/awx/vendor/awx_ansible_collections' - JOBOUTPUT_ROOT = '/var/lib/awx/job_status' - SECRET_KEY = open('/etc/tower/SECRET_KEY', 'rb').read().strip() - ALLOWED_HOSTS = ['*'] - SERVER_EMAIL = 'root@localhost' - DEFAULT_FROM_EMAIL = 'webmaster@localhost' - EMAIL_SUBJECT_PREFIX = '[AWX] ' - EMAIL_HOST = 'localhost' - EMAIL_PORT = 25 - EMAIL_HOST_USER = '' - EMAIL_HOST_PASSWORD = '' - EMAIL_USE_TLS = False - - LOGGING['handlers']['console'] = { - '()': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'simple', - 'filters': ['guid'], - } - - LOGGING['loggers']['django.request']['handlers'] = ['console'] - LOGGING['loggers']['rest_framework.request']['handlers'] = ['console'] - LOGGING['loggers']['awx']['handlers'] = ['console', 'external_logger'] - LOGGING['loggers']['awx.main.commands.run_callback_receiver']['handlers'] = ['console'] - LOGGING['loggers']['awx.main.commands.inventory_import']['handlers'] = ['console'] - LOGGING['loggers']['awx.main.tasks']['handlers'] = ['console', 'external_logger'] - LOGGING['loggers']['awx.main.scheduler']['handlers'] = ['console', 'external_logger'] - LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console'] - LOGGING['loggers']['social']['handlers'] = ['console'] - LOGGING['loggers']['system_tracking_migrations']['handlers'] = ['console'] - LOGGING['loggers']['rbac_migrations']['handlers'] = ['console'] - LOGGING['loggers']['awx.isolated.manager.playbooks']['handlers'] = ['console'] - LOGGING['handlers']['callback_receiver'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['fact_receiver'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['task_system'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['tower_warnings'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['rbac_migrations'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['system_tracking_migrations'] = {'class': 'logging.NullHandler'} - LOGGING['handlers']['management_playbooks'] = {'class': 'logging.NullHandler'} - - USE_X_FORWARDED_PORT = True - - AWX_CONTAINER_GROUP_DEFAULT_IMAGE = "{{ container_groups_image }}" - REDHAT_CANDLEPIN_HOST = "{{ candlepin_host | default(omit) }}" - REDHAT_CANDLEPIN_VERIFY = "{{ candlepin_verify | default(omit) }}" - BROADCAST_WEBSOCKET_PORT = 8052 - BROADCAST_WEBSOCKET_PROTOCOL = 'http' - - {{ kubernetes_deployment_name }}_redis_conf: | - unixsocket /var/run/redis/redis.sock - unixsocketperm 660 - port 0 - bind 127.0.0.1 diff --git a/installer/roles/kubernetes/templates/credentials.py.j2 b/installer/roles/kubernetes/templates/credentials.py.j2 deleted file mode 100644 index 74995988d8..0000000000 --- a/installer/roles/kubernetes/templates/credentials.py.j2 +++ /dev/null @@ -1,16 +0,0 @@ -DATABASES = { - 'default': { - 'ATOMIC_REQUESTS': True, - 'ENGINE': 'awx.main.db.profiled_pg', - 'NAME': "{{ pg_database }}", - 'USER': "{{ pg_username }}", - 'PASSWORD': "{{ pg_password }}", - 'HOST': "{{ pg_hostname|default('postgresql') }}", - 'PORT': "{{ pg_port }}", - 'OPTIONS': { 'sslmode': '{{ pg_sslmode|default("prefer") }}', - 'sslrootcert': '{{ ca_trust_bundle }}', - }, - } -} - -BROADCAST_WEBSOCKET_SECRET = "{{ broadcast_websocket_secret | b64encode }}" diff --git a/installer/roles/kubernetes/templates/deployment.yml.j2 b/installer/roles/kubernetes/templates/deployment.yml.j2 deleted file mode 100644 index da329d23de..0000000000 --- a/installer/roles/kubernetes/templates/deployment.yml.j2 +++ /dev/null @@ -1,556 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ kubernetes_serviceaccount_name }} - namespace: {{ kubernetes_namespace }} -{% if kubernetes_service_account_annotations is defined %} - annotations: -{% for key, value in kubernetes_service_account_annotations.items() %} - {{ key }}: "{{ value }}" -{% endfor %} -{% endif %} -{% if kubernetes_image_pull_secrets is defined %} -imagePullSecrets: - - name: "{{ kubernetes_image_pull_secrets }}" -{% endif %} - -{% if awx_psp_create is defined and awx_psp_create | bool %} ---- -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ awx_psp_name }}-psp -spec: -{% if awx_psp_privileged is defined %} - privileged: {{ awx_psp_privileged }} - allowPrivilegeEscalation: {{ awx_psp_privileged }} -{% endif %} - requiredDropCapabilities: - - ALL - volumes: - - 'configMap' - - 'emptyDir' - - 'projected' - - 'secret' - - 'downwardAPI' - - 'persistentVolumeClaim' - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'MustRunAsNonRoot' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - namespace: {{ kubernetes_namespace }} - name: {{ awx_psp_name }}-role -rules: -- apiGroups: - - policy - resources: - - podsecuritypolicies - resourceNames: - - {{ awx_psp_name }}-psp - verbs: - - use - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ awx_psp_name }}-role-binding - namespace: {{ kubernetes_namespace }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ awx_psp_name }}-role -subjects: -- kind: ServiceAccount - name: {{ kubernetes_serviceaccount_name }} - namespace: {{ kubernetes_namespace }} -{% endif %} - ---- -apiVersion: {{ kubernetes_deployment_api_version }} -kind: Deployment -metadata: - name: {{ kubernetes_deployment_name }} - namespace: {{ kubernetes_namespace }} -{% if kubernetes_deployment_annotations is defined %} - annotations: -{% for key, value in kubernetes_deployment_annotations.items() %} - {{ key }}: "{{ value }}" -{% endfor %} -{% endif %} -{% if openshift_host is defined %} - labels: - app: {{ kubernetes_deployment_name }} -{% endif %} -spec: - replicas: 1 -{% if kubernetes_deployment_api_version == "apps/v1" %} - selector: - matchLabels: - app: {{ kubernetes_deployment_name }} -{% endif %} - template: - metadata: -{% if kubernetes_pod_annotations is defined %} - annotations: -{% for key, value in kubernetes_pod_annotations.items() %} - {{ key }}: "{{ value }}" -{% endfor %} -{% endif %} - labels: - name: {{ kubernetes_deployment_name }}-web-deploy - service: django - app: {{ kubernetes_deployment_name }} - spec: - serviceAccountName: {{ kubernetes_serviceaccount_name }} - terminationGracePeriodSeconds: 10 -{% if custom_venvs is defined %} -{% set trusted_hosts = "" %} - initContainers: - - image: 'centos:7' - name: init-custom-venvs -{% if http_proxy is defined or https_proxy is defined %} -{% set trusted_hosts = "--trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org" %} - env: -{% if http_proxy is defined %} - - name: http_proxy - value: {{ http_proxy }} -{% endif %} -{% if https_proxy is defined %} - - name: https_proxy - value: {{ https_proxy }} -{% endif %} -{% if no_proxy is defined %} - - name: no_proxy - value: {{ no_proxy }} -{% endif %} -{% endif %} - command: - - sh - - '-c' - - >- - yum install -y ansible curl python-setuptools epel-release \ - openssl openssl-devel gcc python-devel && - yum install -y python-virtualenv python36 python36-devel && - mkdir -p {{ custom_venvs_path }} && -{% for custom_venv in custom_venvs %} - virtualenv -p {{ custom_venv.python | default(custom_venvs_python) }} \ - {{ custom_venvs_path }}/{{ custom_venv.name }} && - source {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/activate && - {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U pip && - {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U psutil \ - "ansible=={{ custom_venv.python_ansible_version }}" && -{% if custom_venv.python_modules is defined %} - {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U \ - {% for module in custom_venv.python_modules %}{{ module }} {% endfor %} && -{% endif %} - deactivate && -{% endfor %} - : - volumeMounts: - - name: custom-venvs - mountPath: {{ custom_venvs_path }} -{% endif %} - containers: - - name: {{ kubernetes_deployment_name }}-web -{% if web_security_context_enabled is defined and web_security_context_enabled | bool %} - securityContext: -{% if web_security_context_privileged is defined %} - privileged: {{ web_security_context_privileged }} -{% endif %} -{% endif %} - image: "{{ kubernetes_awx_image }}:{{ kubernetes_awx_version }}" - imagePullPolicy: Always - ports: - - containerPort: 8052 -{% if ca_trust_dir is defined %} - env: - - name: REQUESTS_CA_BUNDLE - value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem -{% endif %} - volumeMounts: -{% if postgres_root_ca_cert is defined %} - - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - mountPath: {{ ca_trust_bundle }} - subPath: {{ postgres_root_ca_filename }} - readOnly: true -{% endif %} - - name: supervisor-socket - mountPath: "/var/run/supervisor" - - name: rsyslog-socket - mountPath: "/var/run/awx-rsyslog" - - name: rsyslog-dir - mountPath: "/var/lib/awx/rsyslog" -{% if ca_trust_dir is defined %} - - name: {{ kubernetes_deployment_name }}-ca-trust-dir - mountPath: "{{ ca_trust_dir }}" - readOnly: true -{% endif %} -{% if project_data_dir is defined %} - - name: {{ kubernetes_deployment_name }}-project-data-dir - mountPath: "/var/lib/awx/projects" - readOnly: false -{% endif %} -{% if custom_venvs is defined %} - - name: custom-venvs - mountPath: {{ custom_venvs_path }} -{% endif %} - - name: {{ kubernetes_deployment_name }}-application-config - mountPath: "/etc/tower/settings.py" - subPath: settings.py - readOnly: true - - - name: {{ kubernetes_deployment_name }}-nginx-config - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - readOnly: true - - - name: "{{ kubernetes_deployment_name }}-application-credentials" - mountPath: "/etc/tower/conf.d/" - readOnly: true - - - name: {{ kubernetes_deployment_name }}-supervisor-web-config - mountPath: "/etc/supervisord.conf" - subPath: supervisor.conf - readOnly: true - - - name: {{ kubernetes_deployment_name }}-supervisor-task-config - mountPath: "/etc/supervisord_task.conf" - subPath: supervisor_task.conf - readOnly: true - - - name: {{ kubernetes_deployment_name }}-secret-key - mountPath: "/etc/tower/SECRET_KEY" - subPath: SECRET_KEY - readOnly: true - - - name: {{ kubernetes_deployment_name }}-redis-socket - mountPath: "/var/run/redis" - - resources: - requests: - memory: "{{ web_mem_request }}Gi" - cpu: "{{ web_cpu_request }}m" -{% if web_mem_limit is defined or web_cpu_limit is defined %} - limits: -{% endif %} -{% if web_mem_limit is defined %} - memory: "{{ web_mem_limit }}Gi" -{% endif %} -{% if web_cpu_limit is defined %} - cpu: "{{ web_cpu_limit }}m" -{% endif %} - - name: {{ kubernetes_deployment_name }}-task -{% if task_security_context_enabled is defined and task_security_context_enabled | bool %} - securityContext: -{% if task_security_context_privileged is defined %} - privileged: {{ task_security_context_privileged }} -{% endif %} -{% endif %} - image: "{{ kubernetes_awx_image }}:{{ kubernetes_awx_version }}" - command: - - /usr/bin/launch_awx_task.sh - imagePullPolicy: Always - volumeMounts: -{% if postgres_root_ca_cert is defined %} - - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - mountPath: {{ ca_trust_bundle }} - subPath: {{ postgres_root_ca_filename }} - readOnly: true -{% endif %} - - name: supervisor-socket - mountPath: "/var/run/supervisor" - - name: rsyslog-socket - mountPath: "/var/run/awx-rsyslog" - - name: rsyslog-dir - mountPath: "/var/lib/awx/rsyslog" -{% if ca_trust_dir is defined %} - - name: {{ kubernetes_deployment_name }}-ca-trust-dir - mountPath: "{{ ca_trust_dir }}" - readOnly: true -{% endif %} -{% if custom_venvs is defined %} - - name: custom-venvs - mountPath: {{ custom_venvs_path }} -{% endif %} - - name: {{ kubernetes_deployment_name }}-application-config - mountPath: "/etc/tower/settings.py" - subPath: settings.py - readOnly: true - - - name: "{{ kubernetes_deployment_name }}-application-credentials" - mountPath: "/etc/tower/conf.d/" - readOnly: true - - - name: {{ kubernetes_deployment_name }}-supervisor-web-config - mountPath: "/etc/supervisord.conf" - subPath: supervisor.conf - readOnly: true - - - name: {{ kubernetes_deployment_name }}-supervisor-task-config - mountPath: "/etc/supervisord_task.conf" - subPath: supervisor_task.conf - readOnly: true - - - name: {{ kubernetes_deployment_name }}-secret-key - mountPath: "/etc/tower/SECRET_KEY" - subPath: SECRET_KEY - readOnly: true - - - name: {{ kubernetes_deployment_name }}-redis-socket - mountPath: "/var/run/redis" - env: - - name: SUPERVISOR_WEB_CONFIG_PATH - value: "/etc/supervisord.conf" - - name: AWX_SKIP_MIGRATIONS - value: "1" - - name: MY_POD_UID - valueFrom: - fieldRef: - fieldPath: metadata.uid - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP -{% if ca_trust_dir is defined %} - - name: REQUESTS_CA_BUNDLE - value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem -{% endif %} - resources: - requests: - memory: "{{ task_mem_request }}Gi" - cpu: "{{ task_cpu_request }}m" -{% if task_mem_limit is defined or task_cpu_limit is defined %} - limits: -{% endif %} -{% if task_mem_limit is defined %} - memory: "{{ task_mem_limit }}Gi" -{% endif %} -{% if task_cpu_limit is defined %} - cpu: "{{ task_cpu_limit }}m" -{% endif %} - - name: {{ kubernetes_deployment_name }}-redis -{% if redis_security_context_enabled is defined and redis_security_context_enabled | bool %} - securityContext: -{% if redis_security_context_privileged is defined %} - privileged: {{ redis_security_context_privileged }} -{% endif %} -{% if redis_security_context_user is defined %} - runAsUser: {{ redis_security_context_user }} -{% endif %} -{% endif %} - image: {{ kubernetes_redis_image }}:{{ kubernetes_redis_image_tag }} - imagePullPolicy: Always - args: ["redis-server", "{{ kubernetes_redis_config_mount_path }}"] - volumeMounts: - - name: {{ kubernetes_deployment_name }}-redis-config - mountPath: "{{ kubernetes_redis_config_mount_path }}" - subPath: redis.conf - readOnly: true - - - name: {{ kubernetes_deployment_name }}-redis-socket - mountPath: "/var/run/redis" - resources: - requests: - memory: "{{ redis_mem_request }}Gi" - cpu: "{{ redis_cpu_request }}m" -{% if redis_mem_limit is defined or redis_cpu_limit is defined %} - limits: -{% endif %} -{% if redis_mem_limit is defined %} - memory: "{{ redis_mem_limit }}Gi" -{% endif %} -{% if redis_cpu_limit is defined %} - cpu: "{{ redis_cpu_limit }}m" -{% endif %} -{% if tolerations is defined %} - tolerations: -{{ tolerations | to_nice_yaml(indent=2) | indent(width=8, indentfirst=True) }} -{% endif %} -{% if node_selector is defined %} - nodeSelector: -{{ node_selector | to_nice_yaml(indent=2) | indent(width=8, indentfirst=True) }} -{% endif %} -{% if affinity is defined %} - affinity: -{{ affinity | to_nice_yaml(indent=2) | indent(width=8, indentfirst=True) }} -{% endif %} - volumes: -{% if postgres_root_ca_cert is defined %} - - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - configMap: - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - items: - - key: postgres_root_ca.crt - path: postgres_root_ca.crt -{% endif %} - - name: supervisor-socket - emptyDir: {} - - name: rsyslog-socket - emptyDir: {} - - name: rsyslog-dir - emptyDir: {} -{% if ca_trust_dir is defined %} - - name: {{ kubernetes_deployment_name }}-ca-trust-dir - hostPath: - path: "{{ ca_trust_dir }}" - type: Directory -{% endif %} -{% if project_data_dir is defined %} - - name: {{ kubernetes_deployment_name }}-project-data-dir - hostPath: - path: "{{ project_data_dir }}" - type: Directory -{% endif %} -{% if custom_venvs is defined %} - - name: custom-venvs - emptyDir: {} -{% endif %} - - name: {{ kubernetes_deployment_name }}-application-config - configMap: - name: {{ kubernetes_deployment_name }}-config - items: - - key: {{ kubernetes_deployment_name }}_settings - path: settings.py - - - name: {{ kubernetes_deployment_name }}-nginx-config - configMap: - name: {{ kubernetes_deployment_name }}-config - items: - - key: {{ kubernetes_deployment_name }}_nginx_conf - path: nginx.conf - - - name: {{ kubernetes_deployment_name }}-redis-config - configMap: - name: {{ kubernetes_deployment_name }}-config - items: - - key: {{ kubernetes_deployment_name }}_redis_conf - path: redis.conf - - - name: "{{ kubernetes_deployment_name }}-application-credentials" - secret: - secretName: "{{ kubernetes_deployment_name }}-secrets" - items: - - key: credentials_py - path: 'credentials.py' - - key: environment_sh - path: 'environment.sh' - - - name: {{ kubernetes_deployment_name }}-supervisor-web-config - configMap: - name: {{ kubernetes_deployment_name }}-supervisor-config - items: - - key: supervisor-web-config - path: 'supervisor.conf' - - - name: {{ kubernetes_deployment_name }}-supervisor-task-config - configMap: - name: {{ kubernetes_deployment_name }}-supervisor-config - items: - - key: supervisor-task-config - path: 'supervisor_task.conf' - - - name: {{ kubernetes_deployment_name }}-secret-key - secret: - secretName: "{{ kubernetes_deployment_name }}-secrets" - items: - - key: secret_key - path: SECRET_KEY - - - name: {{ kubernetes_deployment_name }}-redis-socket - emptyDir: {} - ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ kubernetes_deployment_name }}-web-svc - namespace: {{ kubernetes_namespace }} - labels: - name: {{ kubernetes_deployment_name }}-web-svc -{% if kubernetes_service_annotations is defined %} - annotations: -{% for key, value in kubernetes_service_annotations.items() %} - {{ key }}: "{{ value }}" -{% endfor %} -{% endif %} -spec: - type: {{ kubernetes_web_svc_type }} - ports: - - name: http - port: 80 -{% if kubernetes_web_svc_type == "ClusterIP" %} - nodePort: null -{% endif %} - targetPort: 8052 - selector: - name: {{ kubernetes_deployment_name }}-web-deploy - -{% if kubernetes_context is defined %} ---- -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ kubernetes_deployment_name }}-web-svc - namespace: {{ kubernetes_namespace }} -{% if kubernetes_ingress_annotations is defined %} - annotations: -{% for key, value in kubernetes_ingress_annotations.items() %} - {{ key }}: "{{ value }}" -{% endfor %} -{% endif %} - -spec: -{% if kubernetes_ingress_hostname is defined %} - rules: - - host: {{ kubernetes_ingress_hostname }} - http: - paths: - - path: / - backend: - serviceName: {{ kubernetes_deployment_name }}-web-svc - servicePort: 80 -{% else %} - backend: - serviceName: {{ kubernetes_deployment_name }}-web-svc - servicePort: 80 -{% endif %} -{% if kubernetes_ingress_tls_secret is defined %} - tls: - - hosts: - - {{ kubernetes_ingress_hostname }} - secretName: {{ kubernetes_ingress_tls_secret }} -{% endif %} -{% endif %} -{% if openshift_host is defined %} ---- -apiVersion: v1 -kind: Route -metadata: - name: {{ kubernetes_deployment_name }}-web-svc - namespace: {{ kubernetes_namespace }} -spec: - port: - targetPort: http - tls: - insecureEdgeTerminationPolicy: Redirect - termination: edge - to: - kind: Service - name: {{ kubernetes_deployment_name }}-web-svc - weight: 100 - wildcardPolicy: None -{% endif %} diff --git a/installer/roles/kubernetes/templates/environment.sh.j2 b/installer/roles/kubernetes/templates/environment.sh.j2 deleted file mode 100644 index 45fd3fba8e..0000000000 --- a/installer/roles/kubernetes/templates/environment.sh.j2 +++ /dev/null @@ -1,5 +0,0 @@ -DATABASE_USER={{ pg_username }} -DATABASE_NAME={{ pg_database }} -DATABASE_HOST={{ pg_hostname|default('postgresql') }} -DATABASE_PORT={{ pg_port|default('5432') }} -DATABASE_PASSWORD={{ pg_password | quote }} diff --git a/installer/roles/kubernetes/templates/management-pod.yml.j2 b/installer/roles/kubernetes/templates/management-pod.yml.j2 deleted file mode 100644 index 2a41853fa8..0000000000 --- a/installer/roles/kubernetes/templates/management-pod.yml.j2 +++ /dev/null @@ -1,106 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: ansible-tower-management - namespace: {{ kubernetes_namespace }} -{% if kubernetes_pod_annotations is defined %} - annotations: -{% for key, value in kubernetes_pod_annotations.items() %} - {{ key }}: {{ value | quote }} -{% endfor %} -{% endif %} -spec: -{% if kubernetes_image_pull_secrets is defined %} - imagePullSecrets: - - name: "{{ kubernetes_image_pull_secrets }}" -{% endif %} - containers: - - name: ansible-tower-management - image: "{{ kubernetes_awx_image }}:{{ kubernetes_awx_version }}" - imagePullPolicy: Always - command: ["sleep", "infinity"] - volumeMounts: -{% if ca_trust_dir is defined %} - - name: {{ kubernetes_deployment_name }}-ca-trust-dir - mountPath: "/etc/pki/ca-trust/source/anchors/" - readOnly: true - -{% endif %} - - name: {{ kubernetes_deployment_name }}-application-config - mountPath: "/etc/tower/settings.py" - subPath: settings.py - readOnly: true -{% if postgres_root_ca_cert is defined %} - - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - mountPath: {{ ca_trust_bundle }} - subPath: {{ postgres_root_ca_filename }} - readOnly: true -{% endif %} - - name: "{{ kubernetes_deployment_name }}-application-credentials" - mountPath: "/etc/tower/conf.d/" - readOnly: true - - - name: {{ kubernetes_deployment_name }}-secret-key - mountPath: "/etc/tower/SECRET_KEY" - subPath: SECRET_KEY - readOnly: true - resources: -{% if management_mem_limit is defined or management_cpu_limit is defined %} - limits: -{% endif %} -{% if management_mem_limit is defined %} - memory: "{{ management_mem_limit }}Gi" -{% endif %} -{% if management_cpu_limit is defined %} - cpu: "{{ management_cpu_limit }}m" -{% endif %} -{% if tolerations is defined %} - tolerations: -{{ tolerations | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} -{% if node_selector is defined %} - nodeSelector: -{{ node_selector | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} -{% if affinity is defined %} - affinity: -{{ affinity | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} - volumes: -{% if ca_trust_dir is defined %} - - name: {{ kubernetes_deployment_name }}-ca-trust-dir - hostPath: - path: "{{ ca_trust_dir }}" - type: Directory - -{% endif %} - - name: {{ kubernetes_deployment_name }}-application-config - configMap: - name: {{ kubernetes_deployment_name }}-config - items: - - key: {{ kubernetes_deployment_name }}_settings - path: settings.py -{% if postgres_root_ca_cert is defined %} - - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - configMap: - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - items: - - key: postgres_root_ca.crt - path: postgres_root_ca.crt -{% endif %} - - name: {{ kubernetes_deployment_name }}-secret-key - secret: - secretName: "{{ kubernetes_deployment_name }}-secrets" - items: - - key: secret_key - path: SECRET_KEY - - - name: "{{ kubernetes_deployment_name }}-application-credentials" - secret: - secretName: "{{ kubernetes_deployment_name }}-secrets" - items: - - key: credentials_py - path: 'credentials.py' - - restartPolicy: Never diff --git a/installer/roles/kubernetes/templates/postgres_root_ca.yml.j2 b/installer/roles/kubernetes/templates/postgres_root_ca.yml.j2 deleted file mode 100644 index 091bfff196..0000000000 --- a/installer/roles/kubernetes/templates/postgres_root_ca.yml.j2 +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ kubernetes_deployment_name }}-postgres-root-ca-cert - namespace: {{ kubernetes_namespace }} -data: - postgres_root_ca.crt: | - {{ postgres_root_ca_cert | indent(width=4) }} diff --git a/installer/roles/kubernetes/templates/postgresql-persistent.yml.j2 b/installer/roles/kubernetes/templates/postgresql-persistent.yml.j2 deleted file mode 100644 index c688083cbb..0000000000 --- a/installer/roles/kubernetes/templates/postgresql-persistent.yml.j2 +++ /dev/null @@ -1,176 +0,0 @@ -apiVersion: v1 -kind: Template -labels: - template: postgresql-persistent-template -message: |- - The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}. - - Username: ${POSTGRESQL_USER} - Password: ${POSTGRESQL_PASSWORD} - Database Name: ${POSTGRESQL_DATABASE} - Connection URL: postgresql://${DATABASE_SERVICE_NAME}:5432/ - - For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/. -metadata: - annotations: - description: |- - PostgreSQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/. - - NOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template. - iconClass: icon-postgresql - openshift.io/display-name: PostgreSQL (Persistent) - tags: database,postgresql - template.openshift.io/documentation-url: https://docs.openshift.org/latest/using_images/db_images/postgresql.html - template.openshift.io/long-description: This template provides a standalone - PostgreSQL server with a database created. The database is stored on persistent - storage. The database name, username, and password are chosen via parameters - when provisioning this service. - template.openshift.io/provider-display-name: Red Hat, Inc. - template.openshift.io/support-url: https://access.redhat.com - name: postgresql-persistent -objects: -- apiVersion: v1 - kind: Secret - metadata: - annotations: - template.openshift.io/expose-database_name: '{.data[''database-name'']}' - template.openshift.io/expose-password: '{.data[''database-password'']}' - template.openshift.io/expose-admin_password: '{.data[''database-admin-password'']}' - template.openshift.io/expose-username: '{.data[''database-user'']}' - name: ${DATABASE_SERVICE_NAME} - stringData: - database-name: ${POSTGRESQL_DATABASE} - database-password: ${POSTGRESQL_PASSWORD} - database-admin-password: ${POSTGRESQL_PASSWORD} - database-user: ${POSTGRESQL_USER} -- apiVersion: v1 - kind: Service - metadata: - annotations: - template.openshift.io/expose-uri: postgres://{.spec.clusterIP}:{.spec.ports[?(.name=="postgresql")].port} - name: ${DATABASE_SERVICE_NAME} - spec: - ports: - - name: postgresql - nodePort: 0 - port: 5432 - protocol: TCP - targetPort: 5432 - selector: - name: ${DATABASE_SERVICE_NAME} - sessionAffinity: None - type: ClusterIP - status: - loadBalancer: {} -- apiVersion: v1 - kind: DeploymentConfig - metadata: - annotations: - template.alpha.openshift.io/wait-for-ready: "true" - name: ${DATABASE_SERVICE_NAME} - spec: - replicas: 1 - selector: - name: ${DATABASE_SERVICE_NAME} - strategy: - type: Recreate - template: - metadata: - labels: - name: ${DATABASE_SERVICE_NAME} - spec: - containers: - - capabilities: {} - env: - - name: POSTGRESQL_USER - valueFrom: - secretKeyRef: - key: database-user - name: ${DATABASE_SERVICE_NAME} - - name: POSTGRESQL_PASSWORD - valueFrom: - secretKeyRef: - key: database-password - name: ${DATABASE_SERVICE_NAME} - - name: POSTGRESQL_DATABASE - valueFrom: - secretKeyRef: - key: database-name - name: ${DATABASE_SERVICE_NAME} - - name: POSTGRESQL_MAX_CONNECTIONS - value: ${POSTGRESQL_MAX_CONNECTIONS} - image: registry.redhat.io/rhel8/postgresql-12 - imagePullPolicy: IfNotPresent - livenessProbe: - exec: - command: - - /usr/libexec/check-container - - --live - initialDelaySeconds: 120 - timeoutSeconds: 10 - name: postgresql - ports: - - containerPort: 5432 - protocol: TCP - readinessProbe: - exec: - command: - - /usr/libexec/check-container - initialDelaySeconds: 5 - timeoutSeconds: 1 - resources: - limits: - memory: ${MEMORY_LIMIT} - securityContext: - capabilities: {} - privileged: false - terminationMessagePath: /dev/termination-log - volumeMounts: - - mountPath: /var/lib/pgsql/data - name: ${DATABASE_SERVICE_NAME}-data - dnsPolicy: ClusterFirst - restartPolicy: Always - volumes: - - name: ${DATABASE_SERVICE_NAME}-data -{% if openshift_pg_emptydir | bool %} - emptyDir: {} -{% else %} - persistentVolumeClaim: - claimName: {{ openshift_pg_pvc_name }} -{% endif %} - triggers: - - type: ConfigChange - status: {} -parameters: -- description: Maximum amount of memory the container can use. - displayName: Memory Limit - name: MEMORY_LIMIT - required: true - value: 512Mi -- description: The OpenShift Namespace where the ImageStream resides. - displayName: Namespace - name: NAMESPACE - value: openshift -- description: The name of the OpenShift Service exposed for the database. - displayName: Database Service Name - name: DATABASE_SERVICE_NAME - required: true - value: postgresql -- description: Username for PostgreSQL user that will be used for accessing the - database. - displayName: PostgreSQL Connection Username - from: user[A-Z0-9]{3} - generate: expression - name: POSTGRESQL_USER - required: true -- description: Password for the PostgreSQL connection user. - displayName: PostgreSQL Connection Password - from: '[a-zA-Z0-9]{16}' - generate: expression - name: POSTGRESQL_PASSWORD - required: true -- description: Name of the PostgreSQL database accessed. - displayName: PostgreSQL Database Name - name: POSTGRESQL_DATABASE - required: true - value: sampledb diff --git a/installer/roles/kubernetes/templates/postgresql-values.yml.j2 b/installer/roles/kubernetes/templates/postgresql-values.yml.j2 deleted file mode 100644 index 4fca12a7b7..0000000000 --- a/installer/roles/kubernetes/templates/postgresql-values.yml.j2 +++ /dev/null @@ -1,64 +0,0 @@ -postgresqlUsername: {{ pg_username }} -postgresqlPassword: {{ pg_password }} -postgresqlDatabase: {{ pg_database }} -persistence: - size: {{ pg_volume_capacity|default('5') }}Gi -{% if pg_persistence_storageClass is defined %} - storageClass: {{ pg_persistence_storageClass }} -{% endif %} -{% if pg_persistence_existingclaim is defined %} - existingClaim: {{ pg_persistence_existingclaim }} -{% endif %} -{% if pg_cpu_limit is defined or pg_mem_limit is defined %} -resources: - limits: -{% if pg_cpu_limit is defined %} - cpu: {{ pg_cpu_limit | string }}m -{% endif %} -{% if pg_mem_limit is defined %} - memory: {{ pg_mem_limit | string }}Gi -{% endif %} -{% endif %} -{% if tolerations is defined or node_selector is defined or affinity is defined %} -master: -{% if tolerations is defined %} - tolerations: -{{ tolerations | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} -{% if node_selector is defined %} - nodeSelector: -{{ node_selector | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} -{% if affinity is defined %} - affinity: -{{ affinity | to_nice_yaml(indent=2) | indent(width=4, indentfirst=True) }} -{% endif %} -{% endif %} -image: -{% if pg_image_registry is defined %} -# The default bitnami image from the chart doesn't work on ARM - registry: {{ pg_image_registry }} -{% endif %} -{% if pg_image_registry is not defined %} - registry: docker.io/bitnami -{% endif %} - repository: postgresql - tag: '12.5.0' -volumePermissions: - image: -{% if pg_image_registry is defined %} - registry: {{ pg_image_registry }} -{% endif %} - # The default bitnami image from the chart doesn't work on ARM - repository: alpine - tag: '3' -{% if pg_image_registry is defined %} -metrics: - image: - registry: {{ pg_image_registry }} -{% endif %} -{% if pg_serviceaccount is defined %} -serviceAccount: - enabled: true - name: {{ pg_serviceaccount }} -{% endif %} diff --git a/installer/roles/kubernetes/templates/secret.yml.j2 b/installer/roles/kubernetes/templates/secret.yml.j2 deleted file mode 100644 index 989cb4485f..0000000000 --- a/installer/roles/kubernetes/templates/secret.yml.j2 +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - namespace: {{ kubernetes_namespace }} - name: "{{ kubernetes_deployment_name }}-secrets" -type: Opaque -data: - secret_key: "{{ secret_key | b64encode }}" - credentials_py: "{{ lookup('template', 'credentials.py.j2') | b64encode }}" - environment_sh: "{{ lookup('template', 'environment.sh.j2') | b64encode }}" diff --git a/installer/roles/kubernetes/templates/supervisor.yml.j2 b/installer/roles/kubernetes/templates/supervisor.yml.j2 deleted file mode 100644 index da93f29e5d..0000000000 --- a/installer/roles/kubernetes/templates/supervisor.yml.j2 +++ /dev/null @@ -1,149 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ kubernetes_deployment_name }}-supervisor-config - namespace: {{ kubernetes_namespace }} -data: - supervisor-web-config: | - [supervisord] - nodaemon = True - umask = 022 - logfile = /dev/stdout - logfile_maxbytes = 0 - pidfile = /var/run/supervisor/supervisor.web.pid - - [program:nginx] - command = nginx -g "daemon off;" - autostart = true - autorestart = true - stopwaitsecs = 5 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:uwsgi] - command = {{ uwsgi_bash }} '/var/lib/awx/venv/awx/bin/uwsgi --socket 127.0.0.1:8050 --module=awx.wsgi:application --vacuum --processes=5 --harakiri=120 --no-orphans --master --max-requests=1000 --master-fifo=/var/lib/awx/awxfifo --lazy-apps -b 32768' - directory = /var/lib/awx - autostart = true - autorestart = true - stopwaitsecs = 15 - stopsignal = INT - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:daphne] - command = {{ uwsgi_bash }} '/var/lib/awx/venv/awx/bin/daphne -b 127.0.0.1 -p 8051 awx.asgi:channel_layer' - directory = /var/lib/awx - autostart = true - autorestart = true - stopwaitsecs = 5 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:wsbroadcast] - command = awx-manage run_wsbroadcast - directory = /var/lib/awx - autostart = true - autorestart = true - stopwaitsecs = 5 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:awx-rsyslogd] - command = rsyslogd -n -i /var/run/awx-rsyslog/rsyslog.pid -f /var/lib/awx/rsyslog/rsyslog.conf - autostart = true - autorestart = true - stopwaitsecs = 5 - startretries = 10 - stopsignal=TERM - stopasgroup=true - killasgroup=true - redirect_stderr=true - stdout_logfile=/dev/stderr - stdout_logfile_maxbytes=0 - - [group:tower-processes] - programs=nginx,uwsgi,daphne,wsbroadcast,awx-rsyslogd - priority=5 - - # TODO: Exit Handler - - [eventlistener:awx-config-watcher] - command=/usr/bin/config-watcher - stderr_logfile=/dev/stdout - stderr_logfile_maxbytes=0 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - events=TICK_60 - priority=0 - - [unix_http_server] - file=/var/run/supervisor/supervisor.web.sock - - [supervisorctl] - serverurl=unix:///var/run/supervisor/supervisor.web.sock ; use a unix:// URL for a unix socket - - [rpcinterface:supervisor] - supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - - supervisor-task-config: | - [supervisord] - nodaemon = True - umask = 022 - logfile = /dev/stdout - logfile_maxbytes = 0 - pidfile = /var/run/supervisor/supervisor.pid - - [program:dispatcher] - command = awx-manage run_dispatcher - directory = /var/lib/awx - environment = LANGUAGE="en_US.UTF-8",LANG="en_US.UTF-8",LC_ALL="en_US.UTF-8",LC_CTYPE="en_US.UTF-8" - autostart = true - autorestart = true - stopwaitsecs = 5 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:callback-receiver] - command = awx-manage run_callback_receiver - directory = /var/lib/awx - autostart = true - autorestart = true - stopwaitsecs = 5 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [group:tower-processes] - programs=dispatcher,callback-receiver - priority=5 - - # TODO: Exit Handler - - [eventlistener:awx-config-watcher] - command=/usr/bin/config-watcher - stderr_logfile=/dev/stdout - stderr_logfile_maxbytes=0 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - events=TICK_60 - priority=0 - - [unix_http_server] - file=/var/run/supervisor/supervisor.sock - - [supervisorctl] - serverurl=unix:///var/run/supervisor/supervisor.sock ; use a unix:// URL for a unix socket - - [rpcinterface:supervisor] - supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface diff --git a/installer/roles/kubernetes/vars/openshift.yml b/installer/roles/kubernetes/vars/openshift.yml deleted file mode 100644 index 6e1fd30a3c..0000000000 --- a/installer/roles/kubernetes/vars/openshift.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -openshift_oc_config_file: "{{ kubernetes_base_path }}/.kube/config" -openshift_oc_bin: "oc --kubeconfig={{ openshift_oc_config_file }}" diff --git a/requirements/collections_requirements.yml b/requirements/collections_requirements.yml deleted file mode 100644 index 2a8f46fa24..0000000000 --- a/requirements/collections_requirements.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -collections: - - name: awx.awx - - name: azure.azcollection - - name: amazon.aws - - name: theforeman.foreman - - name: google.cloud - - name: openstack.cloud - - name: community.vmware - - name: ovirt.ovirt - - name: community.kubernetes # required for isolated management playbooks - - name: ansible.posix # required for isolated management playbooks diff --git a/requirements/requirements.in b/requirements/requirements.in index 93b5b4f72e..f54094ae72 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -40,7 +40,7 @@ pygerduty pyparsing python3-saml python-ldap>=3.3.1 # https://github.com/python-ldap/python-ldap/issues/270 -pyyaml>=5.3.1 # minimum version to pull in new pyyaml for CVE-2017-18342 +pyyaml>=5.4.1 # minimum to fix https://github.com/yaml/pyyaml/issues/478 schedule==0.6.0 social-auth-core==3.3.1 # see UPGRADE BLOCKERs social-auth-app-django==3.1.0 # see UPGRADE BLOCKERs diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5829401c78..6256afd061 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,7 +1,7 @@ adal==1.2.2 # via msrestazure aiohttp==3.6.2 # via -r /awx_devel/requirements/requirements.in aioredis==1.3.1 # via channels-redis -ansible-runner==1.4.7 # via -r /awx_devel/requirements/requirements.in +# ansible-runner==1.4.7 # via -r /awx_devel/requirements/requirements.in ansiconv==1.0.0 # via -r /awx_devel/requirements/requirements.in asciichartpy==1.5.25 # via -r /awx_devel/requirements/requirements.in asgiref==3.2.5 # via channels, channels-redis, daphne @@ -100,7 +100,7 @@ python-string-utils==1.0.0 # via openshift python3-openid==3.1.0 # via social-auth-core python3-saml==1.9.0 # via -r /awx_devel/requirements/requirements.in pytz==2019.3 # via django, irc, tempora, twilio -pyyaml==5.3.1 # via -r /awx_devel/requirements/requirements.in, ansible-runner, djangorestframework-yaml, kubernetes +pyyaml==5.4.1 # via -r /awx_devel/requirements/requirements.in, ansible-runner, djangorestframework-yaml, kubernetes redis==3.4.1 # via -r /awx_devel/requirements/requirements.in, django-redis requests-oauthlib==1.3.0 # via kubernetes, msrest, social-auth-core requests==2.23.0 # via -r /awx_devel/requirements/requirements.in, adal, azure-keyvault, django-oauth-toolkit, kubernetes, msrest, requests-oauthlib, slackclient, social-auth-core, twilio diff --git a/requirements/requirements_ansible.in b/requirements/requirements_ansible.in deleted file mode 100644 index ec3c2984f5..0000000000 --- a/requirements/requirements_ansible.in +++ /dev/null @@ -1,68 +0,0 @@ -# GCE -apache-libcloud==2.5.0 -google-auth==1.6.2 # needed for gce inventory imports -# Azure -# azure deps from https://github.com/ansible/ansible/blob/stable-2.9/packaging/requirements/requirements-azure.txt -packaging -azure-cli-core==2.0.35 -azure-cli-nspkg==3.0.2 -azure-common==1.1.11 -azure-mgmt-authorization==0.51.1 -azure-mgmt-batch==5.0.1 -azure-mgmt-cdn==3.0.0 -azure-mgmt-compute==4.4.0 -azure-mgmt-containerinstance==1.4.0 -azure-mgmt-containerregistry==2.0.0 -azure-mgmt-containerservice==4.4.0 -azure-mgmt-dns==2.1.0 -azure-mgmt-keyvault==1.1.0 -azure-mgmt-marketplaceordering==0.1.0 -azure-mgmt-monitor==0.5.2 -azure-mgmt-network==2.3.0 -azure-mgmt-nspkg==2.0.0 -azure-mgmt-redis==5.0.0 -azure-mgmt-resource==2.1.0 -azure-mgmt-rdbms==1.4.1 -azure-mgmt-servicebus==0.5.3 -azure-mgmt-sql==0.10.0 -azure-mgmt-storage==3.1.0 -azure-mgmt-trafficmanager==0.50.0 -azure-mgmt-web==0.41.0 -azure-nspkg==2.0.0 -azure-storage==0.35.1 -msrest==0.6.1 -msrestazure==0.5.0 -azure-keyvault==1.0.0a1 -azure-graphrbac==0.40.0 -azure-mgmt-cosmosdb==0.5.2 -azure-mgmt-hdinsight==0.1.0 -azure-mgmt-devtestlabs==3.0.0 -azure-mgmt-loganalytics==0.2.0 -azure-mgmt-automation==0.1.1 -azure-mgmt-iothub==0.7.0 -# AWS -boto==2.47.0 # last which does not break ec2 scripts -boto3==1.9.223 -jinja2==2.10.1 # required for native jinja2 types for inventory compat mode -# netconf for network modules -ncclient==0.6.3 -# netaddr filter -netaddr -# oVirt/RHV -ovirt-engine-sdk-python==4.3.0 # minimum set inside Ansible facts module requirements -pycurl==7.43.0.1 # higher versions will not install without SSL backend specified -# AWX usage -psutil==5.7.0 # same as AWX requirement -# VMware -pyvmomi==6.7.3 -# WinRM -backports.ssl-match-hostname==3.5.0.1 -pywinrm[kerberos]==0.3.0 -requests -requests-credssp==1.0.2 # For windows authentication awx/issues/1144 -# OpenStack -openstacksdk==0.37.0 -# Openshift/k8s -openshift>=0.11.0 # minimum version to pull in new pyyaml for CVE-2017-18342 -pip==19.3.1 # see upgrade blockers -setuptools==41.6.0 # see upgrade blockers diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt deleted file mode 100644 index 05355fb8b8..0000000000 --- a/requirements/requirements_ansible.txt +++ /dev/null @@ -1,125 +0,0 @@ -adal==1.2.2 # via msrestazure -apache-libcloud==2.5.0 # via -r /awx_devel/requirements/requirements_ansible.in -appdirs==1.4.3 # via openstacksdk -applicationinsights==0.11.9 # via azure-cli-core -argcomplete==1.10.3 # via azure-cli-core, knack -azure-cli-core==2.0.35 # via -r /awx_devel/requirements/requirements_ansible.in -azure-cli-nspkg==3.0.2 # via -r /awx_devel/requirements/requirements_ansible.in, azure-cli-core -azure-common==1.1.11 # via -r /awx_devel/requirements/requirements_ansible.in, azure-graphrbac, azure-mgmt-authorization, azure-mgmt-automation, azure-mgmt-batch, azure-mgmt-cdn, azure-mgmt-compute, azure-mgmt-containerinstance, azure-mgmt-containerregistry, azure-mgmt-containerservice, azure-mgmt-cosmosdb, azure-mgmt-devtestlabs, azure-mgmt-dns, azure-mgmt-hdinsight, azure-mgmt-iothub, azure-mgmt-keyvault, azure-mgmt-loganalytics, azure-mgmt-marketplaceordering, azure-mgmt-monitor, azure-mgmt-network, azure-mgmt-rdbms, azure-mgmt-redis, azure-mgmt-resource, azure-mgmt-servicebus, azure-mgmt-sql, azure-mgmt-storage, azure-mgmt-trafficmanager, azure-mgmt-web, azure-storage -azure-graphrbac==0.40.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-keyvault==1.0.0a1 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-authorization==0.51.1 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-automation==0.1.1 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-batch==5.0.1 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-cdn==3.0.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-compute==4.4.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-containerinstance==1.4.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-containerregistry==2.0.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-containerservice==4.4.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-cosmosdb==0.5.2 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-devtestlabs==3.0.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-dns==2.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-hdinsight==0.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-iothub==0.7.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-keyvault==1.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-loganalytics==0.2.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-marketplaceordering==0.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-monitor==0.5.2 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-network==2.3.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-nspkg==2.0.0 # via -r /awx_devel/requirements/requirements_ansible.in, azure-mgmt-authorization, azure-mgmt-automation, azure-mgmt-batch, azure-mgmt-cdn, azure-mgmt-compute, azure-mgmt-containerinstance, azure-mgmt-containerregistry, azure-mgmt-containerservice, azure-mgmt-cosmosdb, azure-mgmt-devtestlabs, azure-mgmt-dns, azure-mgmt-hdinsight, azure-mgmt-iothub, azure-mgmt-keyvault, azure-mgmt-loganalytics, azure-mgmt-marketplaceordering, azure-mgmt-monitor, azure-mgmt-network, azure-mgmt-rdbms, azure-mgmt-redis, azure-mgmt-resource, azure-mgmt-servicebus, azure-mgmt-sql, azure-mgmt-storage, azure-mgmt-trafficmanager, azure-mgmt-web -azure-mgmt-rdbms==1.4.1 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-redis==5.0.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-resource==2.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-servicebus==0.5.3 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-sql==0.10.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-storage==3.1.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-trafficmanager==0.50.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-mgmt-web==0.41.0 # via -r /awx_devel/requirements/requirements_ansible.in -azure-nspkg==2.0.0 # via -r /awx_devel/requirements/requirements_ansible.in, azure-cli-nspkg, azure-common, azure-graphrbac, azure-keyvault, azure-mgmt-nspkg, azure-storage -azure-storage==0.35.1 # via -r /awx_devel/requirements/requirements_ansible.in -backports.ssl-match-hostname==3.5.0.1 # via -r /awx_devel/requirements/requirements_ansible.in -bcrypt==3.1.7 # via paramiko -boto3==1.9.223 # via -r /awx_devel/requirements/requirements_ansible.in -boto==2.47.0 # via -r /awx_devel/requirements/requirements_ansible.in -botocore==1.12.253 # via boto3, s3transfer -cachetools==3.1.1 # via google-auth -cffi==1.13.2 # via bcrypt, cryptography, pynacl -chardet==3.0.4 # via requests -colorama==0.4.3 # via azure-cli-core, knack -cryptography==2.8 # via adal, azure-keyvault, azure-storage, openstacksdk, paramiko, pyopenssl, requests-credssp, requests-kerberos, requests-ntlm -decorator==4.4.1 # via dogpile.cache, openstacksdk -docutils==0.15.2 # via botocore -dogpile.cache==0.9.0 # via openstacksdk -enum34==1.1.6; python_version < "3" # via cryptography, knack, msrest, ovirt-engine-sdk-python -futures==3.3.0; python_version < "3" # via openstacksdk, s3transfer -google-auth==1.6.2 # via -r /awx_devel/requirements/requirements_ansible.in, kubernetes -humanfriendly==4.18 # via azure-cli-core -idna==2.8 # via requests -ipaddress==1.0.23; python_version < "3" # via cryptography, kubernetes, openstacksdk -iso8601==0.1.12 # via keystoneauth1, openstacksdk -isodate==0.6.0 # via msrest -jinja2==2.10.1 # via -r /awx_devel/requirements/requirements_ansible.in, openshift -jmespath==0.9.4 # via azure-cli-core, boto3, botocore, knack, openstacksdk -jsonpatch==1.24 # via openstacksdk -jsonpointer==2.0 # via jsonpatch -keystoneauth1==3.18.0 # via openstacksdk -knack==0.3.3 # via azure-cli-core -kubernetes==11.0.0 # via openshift -lxml==4.4.2 # via ncclient -markupsafe==1.1.1 # via jinja2 -monotonic==1.5; python_version < "3" # via humanfriendly -msrest==0.6.1 # via -r /awx_devel/requirements/requirements_ansible.in, azure-cli-core, azure-keyvault, azure-mgmt-authorization, azure-mgmt-automation, azure-mgmt-compute, azure-mgmt-containerinstance, azure-mgmt-containerservice, azure-mgmt-cosmosdb, azure-mgmt-devtestlabs, azure-mgmt-dns, azure-mgmt-hdinsight, azure-mgmt-iothub, azure-mgmt-keyvault, azure-mgmt-network, azure-mgmt-rdbms, azure-mgmt-resource, azure-mgmt-servicebus, azure-mgmt-sql, azure-mgmt-storage, azure-mgmt-web, msrestazure -msrestazure==0.5.0 # via -r /awx_devel/requirements/requirements_ansible.in, azure-cli-core, azure-graphrbac, azure-keyvault, azure-mgmt-authorization, azure-mgmt-automation, azure-mgmt-batch, azure-mgmt-cdn, azure-mgmt-compute, azure-mgmt-containerinstance, azure-mgmt-containerregistry, azure-mgmt-containerservice, azure-mgmt-cosmosdb, azure-mgmt-devtestlabs, azure-mgmt-dns, azure-mgmt-hdinsight, azure-mgmt-iothub, azure-mgmt-keyvault, azure-mgmt-loganalytics, azure-mgmt-marketplaceordering, azure-mgmt-monitor, azure-mgmt-network, azure-mgmt-rdbms, azure-mgmt-redis, azure-mgmt-resource, azure-mgmt-servicebus, azure-mgmt-sql, azure-mgmt-storage, azure-mgmt-trafficmanager, azure-mgmt-web -munch==2.5.0 # via openstacksdk -ncclient==0.6.3 # via -r /awx_devel/requirements/requirements_ansible.in -netaddr==0.7.19 # via -r /awx_devel/requirements/requirements_ansible.in -netifaces==0.10.9 # via openstacksdk -ntlm-auth==1.4.0 # via requests-credssp, requests-ntlm -oauthlib==3.1.0 # via requests-oauthlib -openshift==0.11.2 # via -r /awx_devel/requirements/requirements_ansible.in -openstacksdk==0.37.0 # via -r /awx_devel/requirements/requirements_ansible.in -os-service-types==1.7.0 # via keystoneauth1, openstacksdk -ovirt-engine-sdk-python==4.3.0 # via -r /awx_devel/requirements/requirements_ansible.in -packaging==19.2 # via -r /awx_devel/requirements/requirements_ansible.in -paramiko==2.7.1 # via azure-cli-core, ncclient -pbr==5.4.4 # via keystoneauth1, openstacksdk, os-service-types, stevedore -psutil==5.7.0 # via -r /awx_devel/requirements/requirements_ansible.in -pyasn1-modules==0.2.7 # via google-auth -pyasn1==0.4.8 # via pyasn1-modules, requests-credssp, rsa -pycparser==2.19 # via cffi -pycurl==7.43.0.1 # via -r /awx_devel/requirements/requirements_ansible.in, ovirt-engine-sdk-python -pygments==2.5.2 # via azure-cli-core, knack -pyjwt==1.7.1 # via adal, azure-cli-core -pykerberos==1.2.1 # via requests-kerberos -pynacl==1.4.0 # via paramiko -pyopenssl==19.1.0 # via azure-cli-core, requests-credssp -pyparsing==2.4.5 # via packaging -python-dateutil==2.8.1 # via adal, azure-storage, botocore, kubernetes -python-string-utils==0.6.0; python_version < "3" # via openshift -pyvmomi==6.7.3 # via -r /awx_devel/requirements/requirements_ansible.in -pywinrm[kerberos]==0.3.0 # via -r /awx_devel/requirements/requirements_ansible.in -pyyaml==5.2 # via azure-cli-core, knack, kubernetes, openstacksdk -requests-credssp==1.0.2 # via -r /awx_devel/requirements/requirements_ansible.in -requests-kerberos==0.12.0 # via pywinrm -requests-ntlm==1.1.0 # via pywinrm -requests-oauthlib==1.3.0 # via kubernetes, msrest -requests==2.22.0 # via -r /awx_devel/requirements/requirements_ansible.in, adal, apache-libcloud, azure-cli-core, azure-keyvault, azure-storage, keystoneauth1, kubernetes, msrest, pyvmomi, pywinrm, requests-credssp, requests-kerberos, requests-ntlm, requests-oauthlib -requestsexceptions==1.4.0 # via openstacksdk -rsa==4.0 # via google-auth -ruamel.ordereddict==0.4.14; python_version < "3" # via ruamel.yaml -ruamel.yaml.clib==0.2.0 # via ruamel.yaml -ruamel.yaml==0.16.10 # via openshift -s3transfer==0.2.1 # via boto3 -selectors2==2.0.1 # via ncclient -six==1.13.0 # via azure-cli-core, bcrypt, cryptography, google-auth, isodate, keystoneauth1, knack, kubernetes, munch, ncclient, openshift, openstacksdk, ovirt-engine-sdk-python, packaging, pynacl, pyopenssl, python-dateutil, pyvmomi, pywinrm, requests-credssp, stevedore, websocket-client -stevedore==1.31.0 # via keystoneauth1 -tabulate==0.8.2 # via azure-cli-core, knack -typing==3.7.4.1; python_version < "3" # via msrest -urllib3==1.25.7 # via botocore, kubernetes, requests -websocket-client==0.57.0 # via kubernetes -wheel==0.33.6 # via azure-cli-core (overriden, see upgrade blockers) -xmltodict==0.12.0 # via pywinrm - -# The following packages are considered to be unsafe in a requirements file: -pip==19.3.1 # via -r /awx_devel/requirements/requirements_ansible.in, azure-cli-core -setuptools==41.6.0 # via -r /awx_devel/requirements/requirements_ansible.in, kubernetes, ncclient diff --git a/requirements/requirements_ansible_git.txt b/requirements/requirements_ansible_git.txt deleted file mode 100644 index 340cbfdcc7..0000000000 --- a/requirements/requirements_ansible_git.txt +++ /dev/null @@ -1 +0,0 @@ -git+https://github.com/ansible/system-certifi.git@devel#egg=certifi diff --git a/requirements/requirements_ansible_uninstall.txt b/requirements/requirements_ansible_uninstall.txt deleted file mode 100644 index 56cbaa5f19..0000000000 --- a/requirements/requirements_ansible_uninstall.txt +++ /dev/null @@ -1 +0,0 @@ -rsa # stop adding new crypto libs diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index fe51fff164..4788e153a2 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -20,5 +20,6 @@ matplotlib backports.tempfile # support in unit tests for py32+ tempfile.TemporaryDirectory mockldap sdb +remote-pdb gprof2dot atomicwrites==1.4.0 diff --git a/requirements/requirements_git.txt b/requirements/requirements_git.txt index 340cbfdcc7..74f1fb4a9e 100644 --- a/requirements/requirements_git.txt +++ b/requirements/requirements_git.txt @@ -1 +1,3 @@ git+https://github.com/ansible/system-certifi.git@devel#egg=certifi +git+git://github.com/ansible/ansible-runner@devel#egg=ansible-runner +git+https://github.com/project-receptor/receptor.git@#egg=receptorctl&subdirectory=receptorctl diff --git a/tools/ansible/roles/dockerfile/files/launch_awx.sh b/tools/ansible/roles/dockerfile/files/launch_awx.sh old mode 100644 new mode 100755 index 839f7cf746..7b5e86f0bd --- a/tools/ansible/roles/dockerfile/files/launch_awx.sh +++ b/tools/ansible/roles/dockerfile/files/launch_awx.sh @@ -10,7 +10,7 @@ if [ -n "${AWX_KUBE_DEVEL}" ]; then make awx-link popd - export SDB_NOTIFY_HOST=$(ip route | head -n1 | awk '{print $3}') + export SDB_NOTIFY_HOST=$MY_POD_IP fi source /etc/tower/conf.d/environment.sh diff --git a/tools/ansible/roles/dockerfile/files/launch_awx_task.sh b/tools/ansible/roles/dockerfile/files/launch_awx_task.sh old mode 100644 new mode 100755 index 120cc9e3f8..8b9774a477 --- a/tools/ansible/roles/dockerfile/files/launch_awx_task.sh +++ b/tools/ansible/roles/dockerfile/files/launch_awx_task.sh @@ -10,7 +10,7 @@ if [ -n "${AWX_KUBE_DEVEL}" ]; then make awx-link popd - export SDB_NOTIFY_HOST=$(ip route | head -n1 | awk '{print $3}') + export SDB_NOTIFY_HOST=$MY_POD_IP fi source /etc/tower/conf.d/environment.sh diff --git a/tools/ansible/roles/dockerfile/templates/Dockerfile.j2 b/tools/ansible/roles/dockerfile/templates/Dockerfile.j2 index 7acf365c25..203f64b64e 100644 --- a/tools/ansible/roles/dockerfile/templates/Dockerfile.j2 +++ b/tools/ansible/roles/dockerfile/templates/Dockerfile.j2 @@ -4,9 +4,6 @@ ### DO NOT EDIT ### -# Locations - set globally to be used across stages -ARG COLLECTION_BASE="/var/lib/awx/vendor/awx_ansible_collections" - # Build container FROM centos:8 as builder @@ -22,7 +19,7 @@ RUN dnf -y update && \ dnf -y install epel-release 'dnf-command(config-manager)' && \ dnf module -y enable 'postgresql:12' && \ dnf config-manager --set-enabled powertools && \ - dnf -y install ansible \ + dnf -y install \ gcc \ gcc-c++ \ git-core \ @@ -51,21 +48,16 @@ RUN python3 -m ensurepip && pip3 install "virtualenv < 20" # Install & build requirements ADD Makefile /tmp/Makefile RUN mkdir /tmp/requirements -ADD requirements/requirements_ansible.txt \ - requirements/requirements_ansible_uninstall.txt \ - requirements/requirements_ansible_git.txt \ - requirements/requirements.txt \ +ADD requirements/requirements.txt \ requirements/requirements_tower_uninstall.txt \ requirements/requirements_git.txt \ - requirements/collections_requirements.yml \ /tmp/requirements/ -RUN cd /tmp && make requirements_awx requirements_ansible_py3 -RUN cd /tmp && make requirements_collections +RUN cd /tmp && make requirements_awx {% if (build_dev|bool) or (kube_dev|bool) %} ADD requirements/requirements_dev.txt /tmp/requirements -RUN cd /tmp && make requirements_awx_dev requirements_ansible_dev +RUN cd /tmp && make requirements_awx_dev {% else %} # Use the distro provided npm to bootstrap our required version of node RUN npm install -g n && n 14.15.1 && dnf remove -y nodejs @@ -80,8 +72,6 @@ RUN make sdist && \ # Final container(s) FROM centos:8 -ARG COLLECTION_BASE - ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 @@ -95,8 +85,6 @@ RUN dnf -y update && \ dnf module -y enable 'postgresql:12' && \ dnf config-manager --set-enabled powertools && \ dnf -y install acl \ - ansible \ - bubblewrap \ git-core \ git-lfs \ glibc-langpack-en \ @@ -143,6 +131,7 @@ RUN cd /usr/local/bin && \ {% if (build_dev|bool) or (kube_dev|bool) %} # Install development/test requirements RUN dnf -y install \ + crun \ gdb \ gtk3 \ gettext \ @@ -169,12 +158,18 @@ RUN dnf -y install \ RUN dnf --enablerepo=debuginfo -y install python3-debuginfo || : {% endif %} +{% if build_dev|bool %} +RUN dnf install -y podman +RUN echo -e 'cgroup_manager = "cgroupfs"\nevents_logger = "file"' > /etc/containers/libpod.conf +{% endif %} + # Copy app from builder COPY --from=builder /var/lib/awx /var/lib/awx RUN ln -s /var/lib/awx/venv/awx/bin/awx-manage /usr/bin/awx-manage {%if build_dev|bool %} +COPY --from=quay.io/project-receptor/receptor:0.9.6 /usr/bin/receptor /usr/bin/receptor RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/nginx/nginx.csr \ -subj "/C=US/ST=North Carolina/L=Durham/O=Ansible/OU=AWX Development/CN=awx.localhost" && \ openssl x509 -req -days 365 -in /etc/nginx/nginx.csr -signkey /etc/nginx/nginx.key -out /etc/nginx/nginx.crt && \ @@ -211,27 +206,29 @@ RUN for dir in \ /var/lib/awx \ /var/lib/awx/rsyslog \ /var/lib/awx/rsyslog/conf.d \ + /var/lib/awx/.local/share/containers/storage \ /var/run/awx-rsyslog \ /var/log/tower \ /var/log/nginx \ /var/lib/postgresql \ /var/run/supervisor \ + /var/run/receptor \ /var/lib/nginx ; \ do mkdir -m 0775 -p $dir ; chmod g+rw $dir ; chgrp root $dir ; done && \ for file in \ + /etc/subuid \ + /etc/subgid \ + /etc/group \ /etc/passwd \ /var/lib/awx/rsyslog/rsyslog.conf ; \ do touch $file ; chmod g+rw $file ; chgrp root $file ; done -# Adjust any remaining permissions -RUN chmod u+s /usr/bin/bwrap ; \ - chgrp -R root ${COLLECTION_BASE} ; \ - chmod -R g+rw ${COLLECTION_BASE} - {% if (build_dev|bool) or (kube_dev|bool) %} RUN for dir in \ /var/lib/awx/venv \ + /var/lib/awx/venv/awx/bin \ /var/lib/awx/venv/awx/lib/python3.6 \ + /var/lib/awx/venv/awx/lib/python3.6/site-packages \ /var/lib/awx/projects \ /var/lib/awx/rsyslog \ /var/run/awx-rsyslog \ @@ -253,6 +250,7 @@ ENV HOME="/var/lib/awx" ENV PATH="/usr/pgsql-10/bin:${PATH}" {% if build_dev|bool %} +ENV PATH="/var/lib/awx/venv/awx/bin/:${PATH}" EXPOSE 8043 8013 8080 22 @@ -265,4 +263,5 @@ EXPOSE 8052 ENTRYPOINT ["/usr/bin/tini", "--"] CMD /usr/bin/launch_awx.sh VOLUME /var/lib/nginx +VOLUME /var/lib/awx/.local/share/containers/storage {% endif %} diff --git a/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 b/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 index b9fe0be41a..994323e6a8 100644 --- a/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 +++ b/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 @@ -16,6 +16,8 @@ directory = /var/lib/awx autostart = true autorestart = true stopwaitsecs = 5 +stopasgroup=true +killasgroup=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr @@ -32,6 +34,8 @@ directory = /var/lib/awx autostart = true autorestart = true stopwaitsecs = 5 +stopasgroup=true +killasgroup=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr diff --git a/tools/ansible/roles/image_build/defaults/main.yml b/tools/ansible/roles/image_build/defaults/main.yml index 0d45e047d8..076a4f47b3 100644 --- a/tools/ansible/roles/image_build/defaults/main.yml +++ b/tools/ansible/roles/image_build/defaults/main.yml @@ -1,5 +1,5 @@ --- -create_preload_data: true +awx_image: quay.io/ansible/awx # Helper vars to construct the proper download URL for the current architecture tini_architecture: '{{ { "x86_64": "amd64", "aarch64": "arm64", "armv7": "arm" }[ansible_facts.architecture] }}' diff --git a/tools/ansible/roles/image_build/tasks/main.yml b/tools/ansible/roles/image_build/tasks/main.yml index ae6e30b7a3..13c5c48a3d 100644 --- a/tools/ansible/roles/image_build/tasks/main.yml +++ b/tools/ansible/roles/image_build/tasks/main.yml @@ -17,10 +17,6 @@ dest: "../awx/ui_next/public/static/media/" when: awx_official|default(false)|bool -- name: Set awx image name - set_fact: - awx_image: "{{ awx_image|default('awx') }}" - # Calling Docker directly because docker-py doesnt support BuildKit - name: Build AWX image command: docker build -t {{ awx_image }}:{{ awx_version }} -f ../../{{ dockerfile_name }} ../.. diff --git a/tools/audit-api-license.sh b/tools/audit-api-license.sh deleted file mode 100755 index 6cf93f4022..0000000000 --- a/tools/audit-api-license.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -echo "---------- requirements.txt --------------" -for each in `cat requirements/requirements.txt| awk -F= '{print $1}' | tr -d "[]"` -do - if [ ! -f docs/licenses/$each.txt ]; then - echo No license for $each - fi -done -echo "---------- end requirements.txt --------------" - - -echo "---------- requirements_ansible.txt --------------" -for each in `cat requirements/requirements_ansible.txt| awk -F= '{print $1}' | tr -d "[]"` -do - if [ ! -f docs/licenses/$each.txt ]; then - echo No license for $each - fi -done -echo "---------- end requirements_ansible.txt --------------" diff --git a/tools/docker-compose-cluster.yml b/tools/docker-compose-cluster.yml index 8532b6e942..6065069125 100644 --- a/tools/docker-compose-cluster.yml +++ b/tools/docker-compose-cluster.yml @@ -14,6 +14,7 @@ services: - "8013:8013" - "8043:8043" - "1936:1936" + awx-1: user: ${CURRENT_UID} container_name: tools_awx_1_1 @@ -31,8 +32,11 @@ services: - "../:/awx_devel" - "./redis/redis_socket_ha_1:/var/run/redis/" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" + - "./docker-compose-cluster/awx-1-receptor.conf:/etc/receptor/receptor.conf" ports: + - "2222:2222" - "5899-5999:5899-5999" + awx-2: user: ${CURRENT_UID} container_name: tools_awx_2_1 @@ -50,8 +54,11 @@ services: - "../:/awx_devel" - "./redis/redis_socket_ha_2:/var/run/redis/" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" + - "./docker-compose-cluster/awx-2-receptor.conf:/etc/receptor/receptor.conf" ports: + - "2223:2222" - "7899-7999:7899-7999" + awx-3: user: ${CURRENT_UID} container_name: tools_awx_3_1 @@ -69,8 +76,11 @@ services: - "../:/awx_devel" - "./redis/redis_socket_ha_3:/var/run/redis/" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" + - "./docker-compose-cluster/awx-3-receptor.conf:/etc/receptor/receptor.conf" ports: + - "2224:2222" - "8899-8999:8899-8999" + redis_1: user: ${CURRENT_UID} image: redis:latest @@ -79,6 +89,7 @@ services: volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/redis_socket_ha_1:/var/run/redis/" + redis_2: user: ${CURRENT_UID} image: redis:latest @@ -95,6 +106,14 @@ services: volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/redis_socket_ha_3:/var/run/redis/" + postgres: image: postgres:12 container_name: tools_postgres_1 + environment: + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - "awx_db:/var/lib/postgresql/data" + +volumes: + awx_db: diff --git a/tools/docker-compose-cluster/awx-1-receptor.conf b/tools/docker-compose-cluster/awx-1-receptor.conf new file mode 100644 index 0000000000..dcaca8263f --- /dev/null +++ b/tools/docker-compose-cluster/awx-1-receptor.conf @@ -0,0 +1,23 @@ +--- +- log-level: debug + +- control-service: + service: control + filename: /var/run/receptor/receptor.sock + +- tcp-listener: + port: 2200 + +- tcp-peer: + address: awx-2:2200 + +- work-command: + worktype: worker + command: ansible-runner + params: worker + +- work-kubernetes: + worktype: ocp + namespace: receptor + image: quay.io/shanemcd/ee + authmethod: kubeconfig diff --git a/tools/docker-compose-cluster/awx-2-receptor.conf b/tools/docker-compose-cluster/awx-2-receptor.conf new file mode 100644 index 0000000000..bf9d4889a0 --- /dev/null +++ b/tools/docker-compose-cluster/awx-2-receptor.conf @@ -0,0 +1,23 @@ +--- +- log-level: debug + +- control-service: + service: control + filename: /var/run/receptor/receptor.sock + +- tcp-listener: + port: 2200 + +- tcp-peer: + address: awx-3:2200 + +- work-command: + worktype: worker + command: ansible-runner + params: worker + +- work-kubernetes: + worktype: ocp + namespace: receptor + image: quay.io/shanemcd/ee + authmethod: kubeconfig diff --git a/tools/docker-compose-cluster/awx-3-receptor.conf b/tools/docker-compose-cluster/awx-3-receptor.conf new file mode 100644 index 0000000000..ac5db0d284 --- /dev/null +++ b/tools/docker-compose-cluster/awx-3-receptor.conf @@ -0,0 +1,23 @@ +--- +- log-level: debug + +- control-service: + service: control + filename: /var/run/receptor/receptor.sock + +- tcp-listener: + port: 2200 + +- tcp-peer: + address: awx-1:2200 + +- work-command: + worktype: worker + command: ansible-runner + params: worker + +- work-kubernetes: + worktype: ocp + namespace: receptor + image: quay.io/shanemcd/ee + authmethod: kubeconfig diff --git a/tools/docker-compose/Dockerfile-haproxy b/tools/docker-compose/Dockerfile-haproxy deleted file mode 100644 index 9d38924939..0000000000 --- a/tools/docker-compose/Dockerfile-haproxy +++ /dev/null @@ -1,2 +0,0 @@ -FROM haproxy:1.6-alpine -COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg diff --git a/tools/docker-compose/README.md b/tools/docker-compose/README.md index 88a3ea1883..d381614a75 100644 --- a/tools/docker-compose/README.md +++ b/tools/docker-compose/README.md @@ -170,3 +170,15 @@ In order to launch all developer services: `launch_awx.sh` also calls `bootstrap_development.sh` so if all you are doing is launching the supervisor to start all services, you don't need to call `bootstrap_development.sh` first. + +### Start a cluster + +Certain features or bugs are only applicable when running a cluster of AWX nodes. To bring up a 3 node cluster development environment simply run the below command. + +```bash +(host)$ CLUSTER_NODE_COUNT=3 make docker-compose +``` + +`CLUSTER_NODE_COUNT` is configurable and defaults to 1, effectively a non-clustered AWX. + +Note that you may see multiple messages of the form `2021-03-04 20:11:47,666 WARNING [-] awx.main.wsbroadcast Connection from awx_2 to awx_5 failed: 'Cannot connect to host awx_5:8013 ssl:False [Name or service not known]'.`. This can happen when you bring up a cluster of many nodes, say 10, then you bring up a cluster of less nodes, say 3. In this example, there will be 7 `Instance` records in the database that represent AWX instances. The AWX development environment mimics the VM deployment (vs. kubernetes) and expects the missing nodes to be brought back to healthy by the admin. The warning message you are seeing is all of the AWX nodes trying to connect the websocket backplane. You can manually delete the `Instance` records from the database i.e. `Instance.objects.get(hostname='awx_9').delete()` to stop the warnings. diff --git a/tools/docker-compose/ansible/roles/sources/defaults/main.yml b/tools/docker-compose/ansible/roles/sources/defaults/main.yml index f867d7ef2a..ed0cfee862 100644 --- a/tools/docker-compose/ansible/roles/sources/defaults/main.yml +++ b/tools/docker-compose/ansible/roles/sources/defaults/main.yml @@ -5,3 +5,4 @@ awx_image: 'quay.io/ansible/awx_devel' pg_port: 5432 pg_username: 'awx' pg_database: 'awx' +cluster_node_count: 1 diff --git a/tools/docker-compose/ansible/roles/sources/tasks/main.yml b/tools/docker-compose/ansible/roles/sources/tasks/main.yml index 33ace141f1..55937b4051 100644 --- a/tools/docker-compose/ansible/roles/sources/tasks/main.yml +++ b/tools/docker-compose/ansible/roles/sources/tasks/main.yml @@ -44,6 +44,7 @@ with_items: - "database.py" - "websocket_secret.py" + - "haproxy.cfg" - name: Delete old local_settings.py file: diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index 72181cfb0b..6be54588d7 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -1,28 +1,26 @@ --- version: '2' services: +{% for i in range(cluster_node_count|int) %} + {% set container_postfix = loop.index %} + {% set awx_sdb_port_start = 7899 + (loop.index0*100) | int %} + {% set awx_sdb_port_end = 7999 + (loop.index0*100) | int %} # Primary AWX Development Container - awx: + awx_{{ container_postfix }}: user: "{{ ansible_user_uid }}" image: "{{ awx_image }}:{{ awx_image_tag }}" - container_name: tools_awx_1 - hostname: awx + container_name: tools_awx_{{ container_postfix }} + hostname: awx_{{ container_postfix }} command: launch_awx.sh environment: OS: "{{ os_info.stdout }}" SDB_HOST: 0.0.0.0 - SDB_PORT: 7899 + SDB_PORT: {{ awx_sdb_port_start }} AWX_GROUP_QUEUES: tower - ports: - - "8888:8888" - - "8080:8080" - - "8013:8013" - - "8043:8043" - - "6899:6899" # default port range for sdb-listen - - "7899-7999:7899-7999" # default port range for sdb-listen + RECEPTORCTL_SOCKET: /var/run/receptor/receptor.sock links: - postgres - - redis + - redis_{{ container_postfix }} working_dir: "/awx_devel" volumes: - "../../../:/awx_devel" @@ -31,17 +29,51 @@ services: - "../../docker-compose/_sources/websocket_secret.py:/etc/tower/conf.d/websocket_secret.py" - "../../docker-compose/_sources/local_settings.py:/etc/tower/conf.d/local_settings.py" - "../../docker-compose/_sources/SECRET_KEY:/etc/tower/SECRET_KEY" - - "redis_socket:/var/run/redis/:rw" + - "../../docker-compose/receptor.conf:/etc/receptor/receptor.conf" + - "/sys/fs/cgroup:/sys/fs/cgroup" + - "~/.kube/config:/var/lib/awx/.kube/config" + - "redis_socket_{{ container_postfix }}:/var/run/redis/:rw" + - "receptor_{{ container_postfix }}:/var/run/receptor/" privileged: true tty: true + ports: + - "6899:6899" + - "{{ awx_sdb_port_start }}-{{ awx_sdb_port_end }}:{{ awx_sdb_port_start }}-{{ awx_sdb_port_end }}" # sdb-listen +{% if cluster_node_count|int == 1 %} + - "8080:8080" # unused but mapped for debugging + - "8888:8888" # jupyter notebook + - "8013:8013" # http + - "8043:8043" # https +{% else %} + haproxy: + image: haproxy + volumes: + - "./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg" + depends_on: +{% for i in range(cluster_node_count|int) -%} + {% set container_postfix = loop.index %} + - "awx_{{ container_postfix }}" +{% endfor %} + ports: + - "8013:8013" + - "8043:8043" + - "1936:1936" +{% endif %} + redis_{{ container_postfix }}: + image: redis:latest + container_name: tools_redis_{{ container_postfix }} + volumes: + - "../../redis/redis.conf:/usr/local/etc/redis/redis.conf" + - "redis_socket_{{ container_postfix }}:/var/run/redis/:rw" + entrypoint: ["redis-server"] + command: ["/usr/local/etc/redis/redis.conf"] +{% endfor %} # A useful container that simply passes through log messages to the console # helpful for testing awx/tower logging # logstash: # build: # context: ./docker-compose # dockerfile: Dockerfile-logstash - - # Postgres Database Container postgres: image: postgres:12 container_name: tools_postgres_1 @@ -52,15 +84,10 @@ services: POSTGRES_PASSWORD: {{ pg_password }} volumes: - "awx_db:/var/lib/postgresql/data" - redis: - image: redis:latest - container_name: tools_redis_1 - volumes: - - "../../redis/redis.conf:/usr/local/etc/redis/redis.conf" - - "redis_socket:/var/run/redis/:rw" - entrypoint: ["redis-server"] - command: ["/usr/local/etc/redis/redis.conf"] - volumes: awx_db: - redis_socket: +{% for i in range(cluster_node_count|int) -%} + {% set container_postfix = loop.index %} + receptor_{{ container_postfix }}: + redis_socket_{{ container_postfix }}: +{% endfor -%} diff --git a/tools/docker-compose/haproxy.cfg b/tools/docker-compose/ansible/roles/sources/templates/haproxy.cfg.j2 similarity index 67% rename from tools/docker-compose/haproxy.cfg rename to tools/docker-compose/ansible/roles/sources/templates/haproxy.cfg.j2 index d37cbf691a..33837ded1f 100644 --- a/tools/docker-compose/haproxy.cfg +++ b/tools/docker-compose/ansible/roles/sources/templates/haproxy.cfg.j2 @@ -1,5 +1,4 @@ global - debug stats socket /tmp/admin.sock stats timeout 30s @@ -30,16 +29,18 @@ backend nodes http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } option httpchk HEAD / HTTP/1.1\r\nHost:localhost - server awx-1 awx-1:8013 check - server awx-2 awx-2:8013 check - server awx-3 awx-3:8013 check +{% for i in range(cluster_node_count|int) %} + {% set container_postfix = loop.index %} + server tools_awx_{{ container_postfix }} tools_awx_{{ container_postfix }}:8013 check +{% endfor %} backend nodes_ssl mode tcp balance roundrobin - server awx-1 awx-1:8043 - server awx-2 awx-2:8043 - server awx-3 awx-3:8043 +{% for i in range(cluster_node_count|int) %} + {% set container_postfix = loop.index %} + server tools_awx_{{ container_postfix }} tools_awx_{{ container_postfix }}:8043 check +{% endfor %} listen stats bind *:1936 diff --git a/tools/docker-compose/bootstrap_development.sh b/tools/docker-compose/bootstrap_development.sh index 1d3e399bf7..b1cd48e4eb 100755 --- a/tools/docker-compose/bootstrap_development.sh +++ b/tools/docker-compose/bootstrap_development.sh @@ -1,14 +1,6 @@ #!/bin/bash set +x -# Wait for the databases to come up -ansible -i "127.0.0.1," -c local -v -m wait_for -a "host=postgres port=5432" all -ansible -i "127.0.0.1," -c local -v -m wait_for -a "path=/var/run/redis/redis.sock" all - -# In case AWX in the container wants to connect to itself, use "docker exec" to attach to the container otherwise -# TODO: FIX -#/etc/init.d/ssh start - # Move to the source directory so we can bootstrap if [ -f "/awx_devel/manage.py" ]; then cd /awx_devel @@ -23,6 +15,15 @@ make version_file make migrate make init + +if output=$(awx-manage createsuperuser --noinput --username=admin --email=admin@localhost 2> /dev/null); then + echo $output + admin_password=$(openssl rand -base64 12) + echo "Admin password: ${admin_password}" + awx-manage update_password --username=admin --password=${admin_password} +fi +awx-manage create_preload_data + mkdir -p /awx_devel/awx/public/static mkdir -p /awx_devel/awx/ui/static mkdir -p /awx_devel/awx/ui_next/build/static diff --git a/tools/docker-compose/entrypoint.sh b/tools/docker-compose/entrypoint.sh index 8ed9bf2abd..006435000a 100755 --- a/tools/docker-compose/entrypoint.sh +++ b/tools/docker-compose/entrypoint.sh @@ -2,13 +2,33 @@ if [ `id -u` -ge 500 ] || [ -z "${CURRENT_UID}" ]; then - cat << EOF > /tmp/passwd +cat << EOF > /etc/passwd root:x:0:0:root:/root:/bin/bash -awx:x:`id -u`:`id -g`:,,,:/tmp:/bin/bash +awx:x:`id -u`:`id -g`:,,,:/var/lib/awx:/bin/bash EOF - cat /tmp/passwd > /etc/passwd - rm /tmp/passwd +cat <> /etc/group +awx:x:`id -u`:awx +EOF + +cat < /etc/subuid +awx:100000:50001 +EOF + +cat < /etc/subgid +awx:100000:50001 +EOF + +fi + +# Required to get rootless podman working after +# writing out the sub*id files above +podman system migrate + +if [[ "$OS" == *"Docker Desktop"* ]]; then + export SDB_NOTIFY_HOST='docker.for.mac.host.internal' +else + export SDB_NOTIFY_HOST=$(ip route | head -n1 | awk '{print $3}') fi exec $@ diff --git a/tools/docker-compose/receptor.conf b/tools/docker-compose/receptor.conf new file mode 100644 index 0000000000..d5ac25cf2d --- /dev/null +++ b/tools/docker-compose/receptor.conf @@ -0,0 +1,28 @@ +--- +- log-level: debug + +- control-service: + service: control + filename: /var/run/receptor/receptor.sock + +- local-only: + +- work-command: + worktype: local + command: ansible-runner + params: worker + allowruntimeparams: true + +- work-kubernetes: + worktype: kubernetes-runtime-auth + authmethod: runtime + allowruntimeauth: true + allowruntimepod: true + allowruntimeparams: true + +- work-kubernetes: + worktype: kubernetes-incluster-auth + authmethod: incluster + allowruntimeauth: true + allowruntimepod: true + allowruntimeparams: true diff --git a/tools/docker-compose/supervisor.conf b/tools/docker-compose/supervisor.conf index 82e8962a9b..fc2eb2d028 100644 --- a/tools/docker-compose/supervisor.conf +++ b/tools/docker-compose/supervisor.conf @@ -83,6 +83,17 @@ redirect_stderr=true stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 +[program:awx-receptor] +command = receptor --config /etc/receptor/receptor.conf +autostart = true +autorestart = true +stopsignal = KILL +stopasgroup = true +killasgroup = true +redirect_stderr=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 + [group:tower-processes] programs=awx-dispatcher,awx-receiver,awx-uwsgi,awx-daphne,awx-nginx,awx-wsbroadcast,awx-rsyslogd priority=5