From c816f2c905b5918f298c08b62b8b8c3143562672 Mon Sep 17 00:00:00 2001 From: Drew Erny Date: Tue, 14 Jun 2016 17:50:02 -0700 Subject: [PATCH 01/71] Remove the node leader column, show leader as status. Removes the leader column from node ls and shows whether a node is the leader in the manager status column instead. Signed-off-by: Drew Erny (cherry picked from commit 4104c1dc131b62c033c702719909815fba679774) --- api/client/node/list.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/api/client/node/list.go b/api/client/node/list.go index c21cb943d9..374ecaccb3 100644 --- a/api/client/node/list.go +++ b/api/client/node/list.go @@ -16,7 +16,7 @@ import ( ) const ( - listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" + listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\n" ) type listOptions struct { @@ -74,7 +74,7 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) { // Ignore flushing errors defer writer.Flush() - fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS", "LEADER") + fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS") for _, node := range nodes { name := node.Spec.Name availability := string(node.Spec.Availability) @@ -84,14 +84,13 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) { name = node.Description.Hostname } - leader := "" - if node.ManagerStatus != nil && node.ManagerStatus.Leader { - leader = "Yes" - } - reachability := "" if node.ManagerStatus != nil { - reachability = string(node.ManagerStatus.Reachability) + if node.ManagerStatus.Leader { + reachability = "Leader" + } else { + reachability = string(node.ManagerStatus.Reachability) + } } ID := node.ID @@ -107,8 +106,7 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) { client.PrettyPrint(membership), client.PrettyPrint(string(node.Status.State)), client.PrettyPrint(availability), - client.PrettyPrint(reachability), - leader) + client.PrettyPrint(reachability)) } } From 92ff142da2f17f792f2ec7f4047b21339d8f8f76 Mon Sep 17 00:00:00 2001 From: Victoria Bialas Date: Mon, 13 Jun 2016 11:08:11 -0700 Subject: [PATCH 02/71] surfacing Learn by example topics to top level of Docker Engine docs fixing links after moving surfacing tutorials fixing more links for the newly located tutorials WIP: merging 3 getting started tutorials into one to cover all platforms, added new files fixing broken images and links in 3-in-1 getting started, re-wrote story flow, linked to Toolbox, d4mac,d4win Signed-off-by: Victoria Bialas (cherry picked from commit 0254c12e1e0be7a93e3b28e61586ac66168a9b7c) --- docs/getstarted/index.md | 42 ++++ docs/getstarted/last_page.md | 69 ++++++ docs/getstarted/linux_install_help.md | 44 ++++ docs/getstarted/step_five.md | 78 ++++++ docs/getstarted/step_four.md | 227 ++++++++++++++++++ docs/getstarted/step_one.md | 141 +++++++++++ docs/getstarted/step_six.md | 205 ++++++++++++++++ docs/getstarted/step_three.md | 143 +++++++++++ docs/getstarted/step_two.md | 46 ++++ docs/getstarted/tutimg/add_repository.png | Bin 0 -> 6633 bytes docs/getstarted/tutimg/browse_and_search.png | Bin 0 -> 17969 bytes .../getstarted/tutimg/container_explainer.png | Bin 0 -> 17618 bytes docs/getstarted/tutimg/hub_signup.png | Bin 0 -> 37999 bytes docs/getstarted/tutimg/image_found.png | Bin 0 -> 71399 bytes docs/getstarted/tutimg/line_one.png | Bin 0 -> 17357 bytes docs/getstarted/tutimg/new_image.png | Bin 0 -> 70306 bytes docs/getstarted/tutimg/tagger.png | Bin 0 -> 26665 bytes docs/getstarted/tutimg/whale_repo.png | Bin 0 -> 139133 bytes 18 files changed, 995 insertions(+) create mode 100644 docs/getstarted/index.md create mode 100644 docs/getstarted/last_page.md create mode 100644 docs/getstarted/linux_install_help.md create mode 100644 docs/getstarted/step_five.md create mode 100644 docs/getstarted/step_four.md create mode 100644 docs/getstarted/step_one.md create mode 100644 docs/getstarted/step_six.md create mode 100644 docs/getstarted/step_three.md create mode 100644 docs/getstarted/step_two.md create mode 100644 docs/getstarted/tutimg/add_repository.png create mode 100644 docs/getstarted/tutimg/browse_and_search.png create mode 100644 docs/getstarted/tutimg/container_explainer.png create mode 100644 docs/getstarted/tutimg/hub_signup.png create mode 100644 docs/getstarted/tutimg/image_found.png create mode 100644 docs/getstarted/tutimg/line_one.png create mode 100644 docs/getstarted/tutimg/new_image.png create mode 100644 docs/getstarted/tutimg/tagger.png create mode 100644 docs/getstarted/tutimg/whale_repo.png diff --git a/docs/getstarted/index.md b/docs/getstarted/index.md new file mode 100644 index 0000000000..b13b1c3866 --- /dev/null +++ b/docs/getstarted/index.md @@ -0,0 +1,42 @@ + + + +# Get Started with Docker + +This tutorial is a for non-technical users who are interested in learning more about Docker. By following these steps, you'll learn fundamental Docker features while working through some simple tasks. You'll learn how to: + +* install Docker software for your platform +* run a software image in a container +* browse for an image on Docker Hub +* create your own image and run it in a container +* create a Docker Hub account and an image repository +* create an image of your own +* push your image to Docker Hub for others to use + +The getting started was user tested to reduce the chance of users having problems. For the best chance of success, follow the steps as written the first time before exploring on your own. It takes approximately 45 minutes to complete. + + +### Make sure you understand... + +This getting started uses Docker Engine CLI commands entered on the command line of a terminal window. You don't need to be a wizard at the command line, but you should be familiar with how to open your favorite shell or terminal, and run basic commands in that environment. It helps (but isn't required) to know how to navigate a directory tree, manipulate files, list running process, and so forth. + + +Go to [the next page to install](step_one.md). + + +  diff --git a/docs/getstarted/last_page.md b/docs/getstarted/last_page.md new file mode 100644 index 0000000000..bb3372b96c --- /dev/null +++ b/docs/getstarted/last_page.md @@ -0,0 +1,69 @@ + + +# Learning more + +This getting started provided very basic essentials for using Docker on Mac, Windows, and Linux. If you want to learn more with regard to end-to-end development, start with the full install instructions and feature overviews, then follow up with more advanced tutorials and user guides. + +Depending on your interest, the Docker documentation contains a wealth of information. Here are some places to start: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
If you are looking forWhere to find it
More about Docker for Mac, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystemGetting Started with Docker for Mac
More about Docker for Windows, More about Docker for Mac, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystemGetting Started with Docker for Windows
More about Docker ToolboxDocker Toolbox Overview
More about Docker for Linux distributionsInstall Docker Engine on Linux
More advanced tutorials on running containers, building your own images, networking containers, managing data for containers, and storing images on Docker Hub Learn by example
Information about the Docker product lineThe product explainer is a good place to start.
How to set up an automated build on Docker HubDocker Hub documentation
How to run a multi-container application with ComposeDocker Compose documentation
+ + + + +  diff --git a/docs/getstarted/linux_install_help.md b/docs/getstarted/linux_install_help.md new file mode 100644 index 0000000000..f9e73d071c --- /dev/null +++ b/docs/getstarted/linux_install_help.md @@ -0,0 +1,44 @@ + + +# Example: Install Docker on Ubuntu Linux + +This installation procedure for users who are unfamiliar with package +managers, and just want to try out the Getting Started tutorial while running Docker on Linux. If you are comfortable with package managers, prefer not to use +`curl`, or have problems installing and want to troubleshoot, please use our +`apt` and `yum` repositories instead for your installation. + +1. Log into your Ubuntu installation as a user with `sudo` privileges. + +2. Verify that you have `curl` installed. + + $ which curl + + If `curl` isn't installed, install it after updating your manager: + + $ sudo apt-get update + $ sudo apt-get install curl + +3. Get the latest Docker package. + + $ curl -fsSL https://get.docker.com/ | sh + + The system prompts you for your `sudo` password. Then, it downloads and + installs Docker and its dependencies. + + >**Note**: If your company is behind a filtering proxy, you may find that the + >`apt-key` + >command fails for the Docker repo during installation. To work around this, + >add the key directly using the following: + > + > $ curl -fsSL https://get.docker.com/gpg | sudo apt-key add - diff --git a/docs/getstarted/step_five.md b/docs/getstarted/step_five.md new file mode 100644 index 0000000000..1060fd3108 --- /dev/null +++ b/docs/getstarted/step_five.md @@ -0,0 +1,78 @@ + + +# Create a Docker Hub account & repository + +You've built something really cool, you should share it. In this next section, +you'll do just that. You'll need a Docker Hub account. Then, you'll push your +image up to it so other people with Docker Engine can run it. + + +## Step 1: Sign up for an account + +1. Use your browser to navigate to the Docker Hub signup page. + + Your browser displays the page. + + ![Docker Hub signup](tutimg/hub_signup.png) + +2. Fill out the form on the signup page. + + Docker Hub is free. Docker does need a name, password, and email address. + +3. Press **Signup**. + + The browser displays the welcome to Docker Hub page. + +## Step 2: Verify your email and add a repository + +Before you can share anything on the hub, you need to verify your email address. + +1. Open your email inbox. + +2. Look for the email titled `Please confirm email for your Docker Hub account`. + + If you don't see the email, check your Spam folder or wait a moment for the email to arrive. + +2. Open the email and click the **Confirm Your Email** button. + + The browser opens Docker Hub to your profile page. + +4. Choose **Create Repository**. + + The browser opens the **Create Repository** page. + +5. Provide a Repository Name and Short Description. + +6. Make sure Visibility is set to **Public**. + + When you are done, your form should look similar to the following: + + ![Add repo](tutimg/add_repository.png) + +6. Press **Create** when you are done. + + Docker Hub creates your new repository. + +## Where to go next + +On this page, you opened an account on Docker Hub and created a new repository. +In the next section, you populate the repository [by tagging and pushing the +image you created earlier](step_six.md). + + +  diff --git a/docs/getstarted/step_four.md b/docs/getstarted/step_four.md new file mode 100644 index 0000000000..d1f96ae74b --- /dev/null +++ b/docs/getstarted/step_four.md @@ -0,0 +1,227 @@ + + +# Build your own image + +The `whalesay` image could be improved. It would be nice if you didn't have to +think of something to say. And you type a lot to get `whalesay` to talk. + + docker run docker/whalesay cowsay boo-boo + +In this next section, you will improve the `whalesay` image by building a new version that "talks on its own" and requires fewer words to run. + +## Step 1: Write a Dockerfile + +In this step, you use your favorite text editor to write a short Dockerfile. A +Dockerfile describes the software that is "baked" into an image. It isn't just +ingredients tho, it can tell the software what environment to use or what +commands to run. Your recipe is going to be very short. + +1. Go back to your command terminal window. + +2. Make a new directory by typing `mkdir mydockerbuild` and pressing RETURN. + + $ mkdir mydockerbuild + + This directory serves as the "context" for your build. The context just means it contains all the things you need to build your image. + +3. Change to your new directory. + + $ cd mydockerbuild + + Right now the directory is empty. + +4. Create a Dockerfile in the directory by typing `touch Dockerfile` and pressing RETURN. + + $ touch Dockerfile + + The command appears to do nothing but it actually creates the Dockerfile in the current directory. Just type `ls Dockerfile` to see it. + + $ ls Dockerfile + Dockerfile + +5. Open the `Dockerfile` in a visual text editor like Atom or Sublime, or a text based editor like `vi`, or `nano` (https://www.nano-editor.org/). + +6. Add a line to the file like this: + + FROM docker/whalesay:latest + + The `FROM` keyword tells Docker which image your image is based on. Whalesay is cute and has the `cowsay` program already, so we'll start there. + +7. Now, add the `fortunes` program to the image. + + RUN apt-get -y update && apt-get install -y fortunes + + The `fortunes` program has a command that prints out wise sayings for our + whale to say. So, the first step is to install it. This line installs the + software into the image. + +8. Once the image has the software it needs, you instruct the software to run + when the image is loaded. + + CMD /usr/games/fortune -a | cowsay + + This line tells the `fortune` program to pass a nifty quote to the `cowsay` program. + +9. Check your work, your file should look like this: + + FROM docker/whalesay:latest + RUN apt-get -y update && apt-get install -y fortunes + CMD /usr/games/fortune -a | cowsay + +10. Save and close your Dockerfile. + + At this point, you have all your software ingredients and behaviors described in a Dockerfile. You are ready to build a new image. + +## Step 2: Build an image from your Dockerfile + +1. At the command line, make sure the Dockerfile is in the current directory by typing `cat Dockerfile` + + $ cat Dockerfile + FROM docker/whalesay:latest + + RUN apt-get -y update && apt-get install -y fortunes + + CMD /usr/games/fortune -a | cowsay + +2. Now, build your new image by typing the `docker build -t docker-whale .` command in your terminal (don't forget the . period). + + $ docker build -t docker-whale . + Sending build context to Docker daemon 158.8 MB + ...snip... + Removing intermediate container a8e6faa88df3 + Successfully built 7d9495d03763 + + The command takes several seconds to run and reports its outcome. Before + you do anything with the new image, take a minute to learn about the + Dockerfile build process. + +## Step 3: Learn about the build process + +The `docker build -t docker-whale .` command takes the `Dockerfile` in the +current directory, and builds an image called `docker-whale` on your local +machine. The command takes about a minute and its output looks really long and +complex. In this section, you learn what each message means. + +First Docker checks to make sure it has everything it needs to build. + + Sending build context to Docker daemon 158.8 MB + +Then, Docker loads with the `whalesay` image. It already has this image +locally as you might recall from the last page. So, Docker doesn't need to +download it. + + Step 0 : FROM docker/whalesay:latest + ---> fb434121fc77 + +Docker moves onto the next step which is to update the `apt-get` package +manager. This takes a lot of lines, no need to list them all again here. + + Step 1 : RUN apt-get -y update && apt-get install -y fortunes + ---> Running in 27d224dfa5b2 + Ign http://archive.ubuntu.com trusty InRelease + Ign http://archive.ubuntu.com trusty-updates InRelease + Ign http://archive.ubuntu.com trusty-security InRelease + Hit http://archive.ubuntu.com trusty Release.gpg + ....snip... + Get:15 http://archive.ubuntu.com trusty-security/restricted amd64 Packages [14.8 kB] + Get:16 http://archive.ubuntu.com trusty-security/universe amd64 Packages [134 kB] + Reading package lists... + ---> eb06e47a01d2 + +Then, Docker installs the new `fortunes` software. + + Removing intermediate container e2a84b5f390f + Step 2 : RUN apt-get install -y fortunes + ---> Running in 23aa52c1897c + Reading package lists... + Building dependency tree... + Reading state information... + The following extra packages will be installed: + fortune-mod fortunes-min librecode0 + Suggested packages: + x11-utils bsdmainutils + The following NEW packages will be installed: + fortune-mod fortunes fortunes-min librecode0 + 0 upgraded, 4 newly installed, 0 to remove and 3 not upgraded. + Need to get 1961 kB of archives. + After this operation, 4817 kB of additional disk space will be used. + Get:1 http://archive.ubuntu.com/ubuntu/ trusty/main librecode0 amd64 3.6-21 [771 kB] + ...snip...... + Setting up fortunes (1:1.99.1-7) ... + Processing triggers for libc-bin (2.19-0ubuntu6.6) ... + ---> c81071adeeb5 + Removing intermediate container 23aa52c1897c + +Finally, Docker finishes the build and reports its outcome. + + Step 3 : CMD /usr/games/fortune -a | cowsay + ---> Running in a8e6faa88df3 + ---> 7d9495d03763 + Removing intermediate container a8e6faa88df3 + Successfully built 7d9495d03763 + + +## Step 4: Run your new docker-whale + +In this step, you verify the new images is on your computer and then you run your new image. + +1. Open a command line terminal. + +2. Type `docker images` and press RETURN. + + This command, you might remember, lists the images you have locally. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + docker-whale latest 7d9495d03763 4 minutes ago 273.7 MB + docker/whalesay latest fb434121fc77 4 hours ago 247 MB + hello-world latest 91c95931e552 5 weeks ago 910 B + +3. Run your new image by typing `docker run docker-whale` and pressing RETURN. + + $ docker run docker-whale + _________________________________________ + / "He was a modest, good-humored boy. It \ + \ was Oxford that made him insufferable." / + ----------------------------------------- + \ + \ + \ + ## . + ## ## ## == + ## ## ## ## === + /""""""""""""""""___/ === + ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ + \______ o __/ + \ \ __/ + \____\______/ + +As you can see, you've made the whale a lot smarter. It finds its own +things to say and the command line is a lot shorter! You may also notice +that Docker didn't have to download anything. That is because the image was +built locally and is already available. + +## Where to go next + +On this page, you learned to build an image by writing your own Dockerfile. +You ran your image in a container. You also just used Linux from your Mac yet +again. In the next section, you take the first step in sharing your image by +[creating a Docker Hub account](step_five.md). + + +  diff --git a/docs/getstarted/step_one.md b/docs/getstarted/step_one.md new file mode 100644 index 0000000000..b7570fbd1a --- /dev/null +++ b/docs/getstarted/step_one.md @@ -0,0 +1,141 @@ + + +# Install Docker + +## Step 1: Get Docker + +### Docker for Mac + +Docker for Mac is our newest offering for the Mac. It runs as a native Mac application and uses xhyve to virutalize the Docker Engine environment and Linux kernel-specific features for the Docker daemon. + +Get Docker for Mac + +#### Install Prequisites + +- Mac must be a 2010 or newer model, with Intel's hardware support for memory management unit (MMU) virtualization; i.e., Extended Page Tables (EPT) + +- OS X 10.10.3 Yosemite or newer + +- At least 4GB of RAM + +- VirtualBox prior to version 4.3.30 must NOT be installed (it is incompatible with Docker for Mac). Docker for Mac will error out on install in this case. Uninstall the older version of VirtualBox and re-try the install. + +#### Docker Toolbox for the Mac + +If you have an earlier Mac that doesn't meet the Docker for Mac prerequisites, get Docker Toolbox for the Mac. + +See [Docker Toolbox Overview](toolbox/overview/) for help on installing Docker with Toolbox. + +### Docker for Windows + +Docker for Windows is our newest offering for PCs. It runs as a native Windows application and uses Hyper-V to virutalize the Docker Engine environment and Linux kernel-specific features for the Docker daemon. + +Get Docker for Windows + + +#### Install Prequisites + +* 64bit Windows 10 Pro, Enterprise and Education (1511 November update, Build 10586 or later). In the future we will support more versions of Windows 10. + +* The Hyper-V package must be enabled. The Docker for Windows installer will enable it for you, if needed. (This requires a reboot). + +#### Docker Toolbox for Windows + +If you have an earlier Windows system that doesn't meet the Docker for Windows prerequisites, get Docker Toolbox. + +See [Docker Toolbox Overview](toolbox/overview/) for help on installing Docker with Toolbox. + +### Docker for Linux +Docker Engine runs navitvely on Linux distributions. + +For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](/engine/installation/). + +## Step 2: Install Docker + +* For install instructions for Docker for Mac, see [Getting Started with Docker for Mac](/docker-for-mac/). + +* For install instructions for Docker for Windows, see [Getting Started with Docker for Windows](/docker-for-windows/). + +* For install instructions for Docker Toolbox, see [Docker Toolbox Overview](toolbox/overview/). + +* For a simple example of installing Docker on Ubuntu Linux so that you can work through this tutorial, see [Installing Docker on Ubuntu Linux (Example)](linux_install_help.md). + + For full install instructions for Docker on Linux, see [Install Docker Engine](/engine/installation/) and select the flavor of Linux you want to use. + +## Step 3: Verify your installation + +1. Open a command-line terminal, and run some Docker commands to verify that Docker is working as expected. + + Some good commands to try are `docker version` to check that you have the latest release installed and `docker ps` to see if you have any running containers. (Probably not, since you just started.) + +2. Type the `docker run hello-world` command and press RETURN. + + The command does some work for you, if everything runs well, the command's + output looks like this: + + $ docker run hello-world + Unable to find image 'hello-world:latest' locally + latest: Pulling from library/hello-world + 535020c3e8ad: Pull complete + af340544ed62: Pull complete + Digest: sha256:a68868bfe696c00866942e8f5ca39e3e31b79c1e50feaee4ce5e28df2f051d5c + Status: Downloaded newer image for hello-world:latest + + Hello from Docker. + This message shows that your installation appears to be working correctly. + + To generate this message, Docker took the following steps: + 1. The Docker Engine CLI client contacted the Docker Engine daemon. + 2. The Docker Engine daemon pulled the "hello-world" image from the Docker Hub. + 3. The Docker Engine daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker Engine daemon streamed that output to the Docker Engine CLI client, which sent it + to your terminal. + + To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash + + Share images, automate workflows, and more with a free Docker Hub account: + https://hub.docker.com + + For more examples and ideas, visit: + https://docs.docker.com/userguide/ + +3. Run `docker ps -a` to show all containers on the system. + + $ docker ps -a + + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 592376ff3eb8 hello-world "/hello" 25 seconds ago Exited (0) 24 seconds ago prickly_wozniak + + You should see your `hello-world` container listed in the output for the `docker ps -a` command. + + The command `docker ps` shows only currently running containers. Since `hello-world` already ran and exited, it wouldn't show up with a `docker ps`. + +## Looking for troubleshooting help? + +Typically, the above steps work out-of-the-box, but some scenarios can cause problems. If your `docker run hello-world` didn't work and resulted in errors, check out [Troubleshooting](/tutorials/faqs/troubleshoot.md) for quick fixes to common problems. + +## Where to go next + +At this point, you have successfully installed the Docker software. Leave the +Docker Quickstart Terminal window open. Now, go to the next page to [read a very +short introduction Docker images and containers](step_two.md). + + +  diff --git a/docs/getstarted/step_six.md b/docs/getstarted/step_six.md new file mode 100644 index 0000000000..3e413e7ac7 --- /dev/null +++ b/docs/getstarted/step_six.md @@ -0,0 +1,205 @@ + + + +# Tag, push, and pull your image + +In this section, you tag and push your `docker-whale` image to your newly +created repository. When you are done, you test the repository by pulling your +new image. + +## Step 1: Tag and push the image + +If you don't already have a terminal open, open one now: + +1. Go back to your command line terminal. + +2. At the prompt, type `docker images` to list the images you currently have: + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + docker-whale latest 7d9495d03763 38 minutes ago 273.7 MB + 5dac217f722c 45 minutes ago 273.7 MB + docker/whalesay latest fb434121fc77 4 hours ago 247 MB + hello-world latest 91c95931e552 5 weeks ago 910 B + +5. Find the `IMAGE ID` for your `docker-whale` image. + + In this example, the id is `7d9495d03763`. + + Notice that currently, the `REPOSITORY` shows the repo name `docker-whale` + but not the namespace. You need to include the `namespace` for Docker Hub to + associate it with your account. The `namespace` is the same as your Docker + Hub account name. You need to rename the image to + `YOUR_DOCKERHUB_NAME/docker-whale`. + +6. Use `IMAGE ID` and the `docker tag` command to tag your `docker-whale` image. + + The command you type looks like this: + + ![Docker tag command](tutimg/tagger.png) + + Of course, your account name will be your own. So, you type the command with + your image's ID and your account name and press RETURN. + + $ docker tag 7d9495d03763 maryatdocker/docker-whale:latest + +7. Type the `docker images` command again to see your newly tagged image. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + maryatdocker/docker-whale latest 7d9495d03763 5 minutes ago 273.7 MB + docker-whale latest 7d9495d03763 2 hours ago 273.7 MB + 5dac217f722c 5 hours ago 273.7 MB + docker/whalesay latest fb434121fc77 5 hours ago 247 MB + hello-world latest 91c95931e552 5 weeks ago 910 B + +8. Use the `docker login` command to log into the Docker Hub from the command line. + + The format for the login command is: + + docker login --username=yourhubusername --email=youremail@company.com + + When prompted, enter your password and press enter. So, for example: + + $ docker login --username=maryatdocker --email=mary@docker.com + Password: + WARNING: login credentials saved in C:\Users\sven\.docker\config.json + Login Succeeded + +9. Type the `docker push` command to push your image to your new repository. + + $ docker push maryatdocker/docker-whale + The push refers to a repository [maryatdocker/docker-whale] (len: 1) + 7d9495d03763: Image already exists + c81071adeeb5: Image successfully pushed + eb06e47a01d2: Image successfully pushed + fb434121fc77: Image successfully pushed + 5d5bd9951e26: Image successfully pushed + 99da72cfe067: Image successfully pushed + 1722f41ddcb5: Image successfully pushed + 5b74edbcaa5b: Image successfully pushed + 676c4a1897e6: Image successfully pushed + 07f8e8c5e660: Image successfully pushed + 37bea4ee0c81: Image successfully pushed + a82efea989f9: Image successfully pushed + e9e06b06e14c: Image successfully pushed + Digest: sha256:ad89e88beb7dc73bf55d456e2c600e0a39dd6c9500d7cd8d1025626c4b985011 + +10. Return to your profile on Docker Hub to see your new image. + + ![Docker tag command](tutimg/new_image.png) + +## Step 2: Pull your new image + +In this last section, you'll pull the image you just pushed to hub. Before you +do that though, you'll need to remove the original image from your local +machine. If you left the original image on your machine. Docker would not pull +from the hub — why would it? The two images are identical. + +1. Make sure Docker is running, and open a command line terminal. + +2. At the prompt, type `docker images` to list the images you currently have on your local machine. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + maryatdocker/docker-whale latest 7d9495d03763 5 minutes ago 273.7 MB + docker-whale latest 7d9495d03763 2 hours ago 273.7 MB + 5dac217f722c 5 hours ago 273.7 MB + docker/whalesay latest fb434121fc77 5 hours ago 247 MB + hello-world latest 91c95931e552 5 weeks ago 910 B + + To make a good test, you need to remove the `maryatdocker/docker-whale` and + `docker-whale` images from your local system. Removing them forces the next + `docker pull` to get the image from your repository. + +3. Use the `docker rmi` to remove the `maryatdocker/docker-whale` and `docker-whale` +images. + + You can use an ID or the name to remove an image. + + $ docker rmi -f 7d9495d03763 + $ docker rmi -f docker-whale + +4. Pull and load a new image from your repository using the `docker run` command. + + The command you type should include your username from Docker Hub. + + docker run yourusername/docker-whale + + Since the image is no longer available on your local system, Docker downloads it. + + $ docker run maryatdocker/docker-whale + Unable to find image 'maryatdocker/docker-whale:latest' locally + latest: Pulling from maryatdocker/docker-whale + eb06e47a01d2: Pull complete + c81071adeeb5: Pull complete + 7d9495d03763: Already exists + e9e06b06e14c: Already exists + a82efea989f9: Already exists + 37bea4ee0c81: Already exists + 07f8e8c5e660: Already exists + 676c4a1897e6: Already exists + 5b74edbcaa5b: Already exists + 1722f41ddcb5: Already exists + 99da72cfe067: Already exists + 5d5bd9951e26: Already exists + fb434121fc77: Already exists + Digest: sha256:ad89e88beb7dc73bf55d456e2c600e0a39dd6c9500d7cd8d1025626c4b985011 + Status: Downloaded newer image for maryatdocker/docker-whale:latest + ________________________________________ + / Having wandered helplessly into a \ + | blinding snowstorm Sam was greatly | + | relieved to see a sturdy Saint Bernard | + | dog bounding toward him with the | + | traditional keg of brandy strapped to | + | his collar. | + | | + | "At last," cried Sam, "man's best | + \ friend -- and a great big dog, too!" / + ---------------------------------------- + \ + \ + \ + ## . + ## ## ## == + ## ## ## ## === + /""""""""""""""""___/ === + ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ + \______ o __/ + \ \ __/ + \____\______/ + +## Where to go next + +You've done a lot, you've done all of the following fundamental Docker tasks. + +* installed Docker +* run a software image in a container +* located an interesting image on Docker Hub +* run the image on your own machine +* modified an image to create your own and run it +* create a Docker Hub account and repository +* pushed your image to Docker Hub for others to share + + + + +You've only scratched the surface of what Docker can do. Go to the next page to [learn more](last_page.md). + + +  diff --git a/docs/getstarted/step_three.md b/docs/getstarted/step_three.md new file mode 100644 index 0000000000..2f21641d31 --- /dev/null +++ b/docs/getstarted/step_three.md @@ -0,0 +1,143 @@ + + +# Find and run the whalesay image + +People all over the world create Docker images. You can find these images by +browsing the Docker Hub. In this next section, you'll search for and find the +image you'll use in the rest of this getting started. + +## Step 1: Locate the whalesay image + +1. Open your browser and browse to the Docker Hub. + + ![Browse Docker Hub](tutimg/browse_and_search.png) + + The Docker Hub contains images from individuals like you and official images + from organizations like RedHat, IBM, Google, and a whole lot more. + +2. Click **Browse & Search**. + + The browser opens the search page. + +3. Enter the word `whalesay` in the search bar. + + ![Browse Docker Hub](tutimg/image_found.png) + +4. Click on the **docker/whalesay** image in the results. + + The browser displays the repository for the **whalesay** image. + + ![Browse Docker Hub](tutimg/whale_repo.png) + + Each image repository contains information about an image. It should + include information such as what kind of software the image contains and + how to use it. You may notice that the **whalesay** image is based on a + Linux distribution called Ubuntu. In the next step, you run the **whalesay** image on your machine. + +## Step 2: Run the whalesay image + +Make sure Docker is running. On Docker for Mac and Docker for Windows, this is indicated by the Docker whale showing in the status bar. + +1. Open a command-line terminal. + +2. Type the `docker run docker/whalesay cowsay boo` command and press RETURN. + + This command runs the **whalesay** image in a container. Your terminal should look like the following: + + $ docker run docker/whalesay cowsay boo + Unable to find image 'docker/whalesay:latest' locally + latest: Pulling from docker/whalesay + e9e06b06e14c: Pull complete + a82efea989f9: Pull complete + 37bea4ee0c81: Pull complete + 07f8e8c5e660: Pull complete + 676c4a1897e6: Pull complete + 5b74edbcaa5b: Pull complete + 1722f41ddcb5: Pull complete + 99da72cfe067: Pull complete + 5d5bd9951e26: Pull complete + fb434121fc77: Already exists + Digest: sha256:d6ee73f978a366cf97974115abe9c4099ed59c6f75c23d03c64446bb9cd49163 + Status: Downloaded newer image for docker/whalesay:latest + _____ + < boo > + ----- + \ + \ + \ + ## . + ## ## ## == + ## ## ## ## === + /""""""""""""""""___/ === + ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ + \______ o __/ + \ \ __/ + \____\______/ + + The first time you run a software image, the `docker` command looks for it + on your local system. If the image isn't there, then `docker` gets it from + the hub. + +5. While still in the command line terminal, type `docker images` command and press RETURN. + + The command lists all the images on your local system. You should see + `docker/whalesay` in the list. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + docker/whalesay latest fb434121fc77 3 hours ago 247 MB + hello-world latest 91c95931e552 5 weeks ago 910 B + + When you run an image in a container, Docker downloads the image to your + computer. This local copy of the image saves you time. Docker only + downloads the image again if the image's source changes on the hub. You + can, of course, delete the image yourself. You'll learn more about that + later. Let's leave the image there for now because we are going to use it + later. + +6. Take a moment to play with the **whalesay** container a bit. + + Try running the `whalesay` image again with a word or phrase. Try a long or + short phrase. Can you break the cow? + + $ docker run docker/whalesay cowsay boo-boo + _________ + < boo-boo > + --------- + \ + \ + \ + ## . + ## ## ## == + ## ## ## ## === + /""""""""""""""""___/ === + ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ + \______ o __/ + \ \ __/ + \____\______/ + +## Where to go next + +On this page, you learned to search for images on Docker Hub. You used your +command line to run an image. Think about it, effectively you ran a piece of +Linux software on your Mac computer. You learned that running an image copies +it on your computer. Now, you are ready to create your own Docker image. +Go on to the next part [to build your own image](step_four.md). + + +  diff --git a/docs/getstarted/step_two.md b/docs/getstarted/step_two.md new file mode 100644 index 0000000000..d12c0a1415 --- /dev/null +++ b/docs/getstarted/step_two.md @@ -0,0 +1,46 @@ + + +# Learn about images & containers + +Docker Engine provides the core Docker technology that enables images and +containers. As the last step in your installation, you ran the +`docker run hello-world` command. The command you ran had three parts. + +![Container Explainer](tutimg/container_explainer.png) + +An *image* is a filesystem and parameters to use at runtime. It doesn't have +state and never changes. A *container* is a running instance of an image. +When you ran the command, Docker Engine: + +* checked to see if you had the `hello-world` software image +* downloaded the image from the Docker Hub (more about the hub later) +* loaded the image into the container and "ran" it + +Depending on how it was built, an image might run a simple, single command and then exit. This is what `Hello-World` did. + +A Docker image, though, is capable of much more. An image can start software as complex as a database, wait for you (or someone else) to add data, store the data for later use, and then wait for the next person. + +Who built the `hello-world` software image though? In this case, Docker did but anyone can. Docker Engine lets people (or companies) create and share software through Docker images. Using Docker Engine, you don't have to worry about whether your computer can run the software in a Docker image — a Docker container *can always run it*. + +## Where to go next + +See, that was quick wasn't it? Now, you are ready to do some really fun stuff with Docker. +Go on to the next part [to find and run the whalesay image](step_three.md). + + +  diff --git a/docs/getstarted/tutimg/add_repository.png b/docs/getstarted/tutimg/add_repository.png new file mode 100644 index 0000000000000000000000000000000000000000..91f89f046758c6317ec7410740372e398cf94f17 GIT binary patch literal 6633 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 literal 0 HcmV?d00001 diff --git a/docs/getstarted/tutimg/browse_and_search.png b/docs/getstarted/tutimg/browse_and_search.png new file mode 100644 index 0000000000000000000000000000000000000000..0ead0794b44ce0eb949f212207e7579312624902 GIT binary patch literal 17969 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 literal 0 HcmV?d00001 diff --git a/docs/getstarted/tutimg/hub_signup.png b/docs/getstarted/tutimg/hub_signup.png new file mode 100644 index 0000000000000000000000000000000000000000..83795ea19cfeab8b4d47a1cdca1958bdb2c12562 GIT binary patch literal 37999 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

bXeAivaQx-&(9nFj<%SQ2MCDdXFW!&eK-*w_g4 zTN-;MnVXwo6tZpBtoo^_D0oGZ${rQubW7ezi~VQIr`yUsIkYZN*4v>}^mMrhcA4|d zc#|yh&$-y*^J+GF-AR1OkT!)38`SHx?UBmUZMet_w#59Dl$I(irRPHo zA^pq7bU}=Y|F|Z7)6MkU!6Z_@9q@f}8ulhpl*+3Lbo)egBIN$UIeMRts-vznfsPn2 zeNksg0lOih%>dh$QPun&XWx!5KE$=LG=);MZ@wkT@yJc(qWE8ddBt)pi2wNZeQNQ( z?2;&D4DCVdP1-@=jE8OVo$o;4&kO&OyPib>DGR;tu7w{;uawv90kdnmHb}_8AMa$u zz3?V|NRwC0COiqR(bR@XCk7x)q7J5Dx$cl~&5vi(Rr3J+IhH|OI^h-btRNlyit=fl*%iy=_+8g52sg3Fo0V7~LOJbI14S04nE0IzbvtR%uy zca!XjcNaqi51CM!7AC{itdf?AD+H@qEEl~>Qg`}4J}@1?7{=Z@0F|DbfJ*kr99lbW z;dj77YQtNXeux8_GDc6~#6E!|EnrsIV~VQVp9#%WtYe?ABko&R<$DOT`|Z7>j*qim zAl`*K$Si0(fq0-g6ElmBf$AfrK+g;EOJn(Aa_7ZGtKZiV{YC)wn;ZWVolfbR`#PWL ZJRCz~sbRMLz;A~R>gX}=mLt&x{{dd36uJNa literal 0 HcmV?d00001 diff --git a/docs/getstarted/tutimg/image_found.png b/docs/getstarted/tutimg/image_found.png new file mode 100644 index 0000000000000000000000000000000000000000..3d58069e086188680f7ffe25ba5ec50d8e52c43c GIT binary patch literal 71399 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 Tyt??C4ieQDT1pkqU%mf7mIMx~ literal 0 HcmV?d00001 diff --git a/docs/getstarted/tutimg/new_image.png b/docs/getstarted/tutimg/new_image.png new file mode 100644 index 0000000000000000000000000000000000000000..c801f36555fb7443b7a2692cea760aa85f4d7d73 GIT binary patch literal 70306 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

1ALfqUB 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 literal 0 HcmV?d00001 diff --git a/docs/getstarted/tutimg/tagger.png b/docs/getstarted/tutimg/tagger.png new file mode 100644 index 0000000000000000000000000000000000000000..f4b8fc65d10faf8203508a2357a1c8abf56c701a GIT binary patch literal 26665 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 literal 0 HcmV?d00001 From ce6211d2528082d58aab4fa7c90bcf75457948fe Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Wed, 15 Jun 2016 11:19:03 +0900 Subject: [PATCH 03/71] Update experimental/README.md Signed-off-by: Akihiro Suda (cherry picked from commit fec98ecca1d82ec1f2e0bd4d5e2c180ef834dc52) --- experimental/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/README.md b/experimental/README.md index 67abbe1cdf..1098dd0793 100644 --- a/experimental/README.md +++ b/experimental/README.md @@ -49,9 +49,9 @@ refined or entirely removed. To download the latest experimental `docker` binary for Linux, use the following URLs: - https://experimental.docker.com/builds/Linux/i386/docker-latest + https://experimental.docker.com/builds/Linux/i386/docker-latest.tgz - https://experimental.docker.com/builds/Linux/x86_64/docker-latest + https://experimental.docker.com/builds/Linux/x86_64/docker-latest.tgz After downloading the appropriate binary, you can follow the instructions [here](https://docs.docker.com/installation/binaries/#get-the-docker-binary) to run the `docker` daemon. From 034d555d300ef5c8b0fd4f03baeb553da38397a2 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 6 Jun 2016 11:57:11 +0200 Subject: [PATCH 04/71] daemon: allow tmpfs to trump over VOLUME(s) Signed-off-by: Antonio Murdaca (cherry picked from commit 756f6cef4a1379e59d6511ee000e435d23659153) --- container/container_windows.go | 3 ++- daemon/oci_linux.go | 5 +++-- daemon/volumes.go | 3 ++- daemon/volumes_unix.go | 8 ++++++++ integration-cli/docker_cli_run_unix_test.go | 17 +++++++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/container/container_windows.go b/container/container_windows.go index c860df56ef..38560bb59a 100644 --- a/container/container_windows.go +++ b/container/container_windows.go @@ -54,7 +54,8 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun // TmpfsMounts returns the list of tmpfs mounts func (container *Container) TmpfsMounts() []Mount { - return nil + var mounts []Mount + return mounts } // UpdateContainer updates configuration of a container diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index afec89a196..9492c17249 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -480,9 +480,10 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c } if m.Source == "tmpfs" { + data := c.HostConfig.Tmpfs[m.Destination] options := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode} - if m.Data != "" { - options = append(options, strings.Split(m.Data, ",")...) + if data != "" { + options = append(options, strings.Split(data, ",")...) } merged, err := mount.MergeTmpfsOptions(options) diff --git a/daemon/volumes.go b/daemon/volumes.go index fd70ce4a92..d178d410ab 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -129,7 +129,8 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo return err } - if binds[bind.Destination] { + _, tmpfsExists := hostConfig.Tmpfs[bind.Destination] + if binds[bind.Destination] || tmpfsExists { return fmt.Errorf("Duplicate mount point '%s'", bind.Destination) } diff --git a/daemon/volumes_unix.go b/daemon/volumes_unix.go index 5b2cd4bbfa..ca0628c89b 100644 --- a/daemon/volumes_unix.go +++ b/daemon/volumes_unix.go @@ -16,7 +16,15 @@ import ( // /etc/resolv.conf, and if it is not, appends it to the array of mounts. func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) { var mounts []container.Mount + // TODO: tmpfs mounts should be part of Mountpoints + tmpfsMounts := make(map[string]bool) + for _, m := range c.TmpfsMounts() { + tmpfsMounts[m.Destination] = true + } for _, m := range c.MountPoints { + if tmpfsMounts[m.Destination] { + continue + } if err := daemon.lazyInitializeVolume(c.ID, m); err != nil { return nil, err } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 07032147b8..164a51549c 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -829,6 +829,23 @@ func (s *DockerSuite) TestRunTmpfsMounts(c *check.C) { } } +func (s *DockerSuite) TestRunTmpfsMountsOverrideImageVolumes(c *check.C) { + name := "img-with-volumes" + _, err := buildImage( + name, + ` + FROM busybox + VOLUME /run + RUN touch /run/stuff + `, + true) + if err != nil { + c.Fatal(err) + } + out, _ := dockerCmd(c, "run", "--tmpfs", "/run", name, "ls", "/run") + c.Assert(out, checker.Not(checker.Contains), "stuff") +} + // Test case for #22420 func (s *DockerSuite) TestRunTmpfsMountsWithOptions(c *check.C) { testRequires(c, DaemonIsLinux) From ca16a4c81d1160368307d883beb4e7bb6c67dace Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 15 Jun 2016 10:18:18 -0700 Subject: [PATCH 05/71] Fix hanging integration tests Signed-off-by: Tonis Tiigi (cherry picked from commit 22f827ababc1910bc48762d7980913497dbc915d) --- integration-cli/docker_cli_network_unix_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index d5c5713621..25f08f3cb8 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -1634,7 +1634,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartRestoreBridgeNetwork(t *check.C) { t.Fatal(err) } - // start a new container try to publist port 80:80 will failed + // start a new container, trying to publish port 80:80 should fail out, err := s.d.Cmd("run", "-p", "80:80", "-d", "busybox", "top") if err == nil || !strings.Contains(out, "Bind for 0.0.0.0:80 failed: port is already allocated") { t.Fatalf("80 port is allocated to old running container, it should failed on allocating to new container") @@ -1645,7 +1645,17 @@ func (s *DockerDaemonSuite) TestDaemonRestartRestoreBridgeNetwork(t *check.C) { if err != nil { t.Fatal(err) } - _, err = s.d.Cmd("run", "-p", "80:80", "-d", "busybox", "top") + id, err := s.d.Cmd("run", "-p", "80:80", "-d", "busybox", "top") + if err != nil { + t.Fatal(err) + } + + // Cleanup because these containers will not be shut down by daemon + out, err = s.d.Cmd("stop", newCon) + if err != nil { + t.Fatalf("err: %v %v", err, string(out)) + } + _, err = s.d.Cmd("stop", strings.TrimSpace(id)) if err != nil { t.Fatal(err) } From f1d5c3374c98a93e6202a0181f5cde02332b34eb Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 14 Jun 2016 19:40:25 -0700 Subject: [PATCH 06/71] Vendoring libnetwork 0d517a9e4e5cbdb889b3257eebd2351addcd46d4 Signed-off-by: Madhu Venugopal (cherry picked from commit f2f97f768e51f817336cd56456158bc32ab3b977) --- hack/vendor.sh | 2 +- .../src/github.com/docker/libnetwork/agent.go | 10 +- .../github.com/docker/libnetwork/agent.pb.go | 107 +++++++++++++----- .../github.com/docker/libnetwork/agent.proto | 3 + .../drivers/macvlan/macvlan_endpoint.go | 4 + .../github.com/docker/libnetwork/endpoint.go | 13 ++- .../docker/libnetwork/service_linux.go | 16 ++- .../docker/libnetwork/service_unsupported.go | 4 +- 8 files changed, 123 insertions(+), 36 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 9ff8122273..cf2052c693 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -65,7 +65,7 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 #get libnetwork packages -clone git github.com/docker/libnetwork 452dff166e0abd9455b07c835613197f078a34de +clone git github.com/docker/libnetwork 0d517a9e4e5cbdb889b3257eebd2351addcd46d4 clone git github.com/docker/go-events 39718a26497694185f8fb58a7d6f31947f3dc42d clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec diff --git a/vendor/src/github.com/docker/libnetwork/agent.go b/vendor/src/github.com/docker/libnetwork/agent.go index 7276beefe5..6215459d8d 100644 --- a/vendor/src/github.com/docker/libnetwork/agent.go +++ b/vendor/src/github.com/docker/libnetwork/agent.go @@ -353,7 +353,7 @@ func (ep *endpoint) addToCluster() error { ingressPorts = ep.ingressPorts } - if err := c.addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), ep.virtualIP, ingressPorts, ep.Iface().Address().IP); err != nil { + if err := c.addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), ep.virtualIP, ingressPorts, ep.svcAliases, ep.Iface().Address().IP); err != nil { return err } } @@ -364,6 +364,7 @@ func (ep *endpoint) addToCluster() error { ServiceID: ep.svcID, VirtualIP: ep.virtualIP.String(), IngressPorts: ingressPorts, + Aliases: ep.svcAliases, EndpointIP: ep.Iface().Address().IP.String(), }) @@ -399,7 +400,7 @@ func (ep *endpoint) deleteFromCluster() error { ingressPorts = ep.ingressPorts } - if err := c.rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), ep.virtualIP, ingressPorts, ep.Iface().Address().IP); err != nil { + if err := c.rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), ep.virtualIP, ingressPorts, ep.svcAliases, ep.Iface().Address().IP); err != nil { return err } } @@ -554,6 +555,7 @@ func (c *controller) handleEpTableEvent(ev events.Event) { vip := net.ParseIP(epRec.VirtualIP) ip := net.ParseIP(epRec.EndpointIP) ingressPorts := epRec.IngressPorts + aliases := epRec.Aliases if name == "" || ip == nil { logrus.Errorf("Invalid endpoint name/ip received while handling service table event %s", value) @@ -562,7 +564,7 @@ func (c *controller) handleEpTableEvent(ev events.Event) { if isAdd { if svcID != "" { - if err := c.addServiceBinding(svcName, svcID, nid, eid, vip, ingressPorts, ip); err != nil { + if err := c.addServiceBinding(svcName, svcID, nid, eid, vip, ingressPorts, aliases, ip); err != nil { logrus.Errorf("Failed adding service binding for value %s: %v", value, err) return } @@ -571,7 +573,7 @@ func (c *controller) handleEpTableEvent(ev events.Event) { n.addSvcRecords(name, ip, nil, true) } else { if svcID != "" { - if err := c.rmServiceBinding(svcName, svcID, nid, eid, vip, ingressPorts, ip); err != nil { + if err := c.rmServiceBinding(svcName, svcID, nid, eid, vip, ingressPorts, aliases, ip); err != nil { logrus.Errorf("Failed adding service binding for value %s: %v", value, err) return } diff --git a/vendor/src/github.com/docker/libnetwork/agent.pb.go b/vendor/src/github.com/docker/libnetwork/agent.pb.go index 19b30422ad..e9d2c8dee9 100644 --- a/vendor/src/github.com/docker/libnetwork/agent.pb.go +++ b/vendor/src/github.com/docker/libnetwork/agent.pb.go @@ -72,6 +72,8 @@ type EndpointRecord struct { EndpointIP string `protobuf:"bytes,5,opt,name=endpoint_ip,json=endpointIp,proto3" json:"endpoint_ip,omitempty"` // IngressPorts exposed by the service to which this endpoint belongs. IngressPorts []*PortConfig `protobuf:"bytes,6,rep,name=ingress_ports,json=ingressPorts" json:"ingress_ports,omitempty"` + // A list of aliases which are alternate names for the service + Aliases []string `protobuf:"bytes,7,rep,name=aliases" json:"aliases,omitempty"` } func (m *EndpointRecord) Reset() { *m = EndpointRecord{} } @@ -120,7 +122,7 @@ func (this *EndpointRecord) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 10) + s := make([]string, 0, 11) s = append(s, "&libnetwork.EndpointRecord{") s = append(s, "Name: "+fmt.Sprintf("%#v", this.Name)+",\n") s = append(s, "ServiceName: "+fmt.Sprintf("%#v", this.ServiceName)+",\n") @@ -130,6 +132,7 @@ func (this *EndpointRecord) GoString() string { if this.IngressPorts != nil { s = append(s, "IngressPorts: "+fmt.Sprintf("%#v", this.IngressPorts)+",\n") } + s = append(s, "Aliases: "+fmt.Sprintf("%#v", this.Aliases)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -228,6 +231,21 @@ func (m *EndpointRecord) MarshalTo(data []byte) (int, error) { i += n } } + if len(m.Aliases) > 0 { + for _, s := range m.Aliases { + data[i] = 0x3a + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } return i, nil } @@ -326,6 +344,12 @@ func (m *EndpointRecord) Size() (n int) { n += 1 + l + sovAgent(uint64(l)) } } + if len(m.Aliases) > 0 { + for _, s := range m.Aliases { + l = len(s) + n += 1 + l + sovAgent(uint64(l)) + } + } return n } @@ -372,6 +396,7 @@ func (this *EndpointRecord) String() string { `VirtualIP:` + fmt.Sprintf("%v", this.VirtualIP) + `,`, `EndpointIP:` + fmt.Sprintf("%v", this.EndpointIP) + `,`, `IngressPorts:` + strings.Replace(fmt.Sprintf("%v", this.IngressPorts), "PortConfig", "PortConfig", 1) + `,`, + `Aliases:` + fmt.Sprintf("%v", this.Aliases) + `,`, `}`, }, "") return s @@ -602,6 +627,35 @@ func (m *EndpointRecord) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Aliases", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAgent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAgent + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Aliases = append(m.Aliases, string(data[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAgent(data[iNdEx:]) @@ -865,29 +919,30 @@ var ( ) var fileDescriptorAgent = []byte{ - // 384 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x3f, 0x6f, 0xda, 0x40, - 0x18, 0xc6, 0x31, 0xb8, 0x08, 0xbf, 0xc6, 0x2e, 0x3a, 0x55, 0x95, 0xc5, 0x60, 0x28, 0x52, 0x25, - 0x86, 0xca, 0x48, 0x74, 0x64, 0x03, 0x3a, 0x78, 0xa9, 0x2c, 0xf7, 0xcf, 0x8a, 0x0c, 0xbe, 0xba, - 0xa7, 0xba, 0x3e, 0xeb, 0x7c, 0xd0, 0xb5, 0x63, 0x94, 0x2d, 0x1f, 0x20, 0x53, 0xbe, 0x4c, 0xc6, - 0x8c, 0x99, 0xa2, 0xc0, 0x9a, 0x25, 0x1f, 0x21, 0x77, 0x67, 0x1b, 0x14, 0x89, 0xe1, 0x95, 0x4e, - 0xbf, 0xe7, 0xf7, 0x9e, 0x5e, 0x3d, 0x60, 0x46, 0x09, 0xce, 0xb8, 0x97, 0x33, 0xca, 0x29, 0x82, - 0x94, 0xac, 0x33, 0xcc, 0xff, 0x51, 0xf6, 0xa7, 0xff, 0x2e, 0xa1, 0x09, 0x55, 0x78, 0x22, 0x5f, - 0xa5, 0x31, 0xba, 0x6a, 0x82, 0xfd, 0x25, 0x8b, 0x73, 0x4a, 0x32, 0x1e, 0xe2, 0x0d, 0x65, 0x31, - 0x42, 0xa0, 0x67, 0xd1, 0x5f, 0xec, 0x68, 0x43, 0x6d, 0x6c, 0x84, 0xea, 0x8d, 0x3e, 0x40, 0xb7, - 0xc0, 0x6c, 0x47, 0x36, 0x78, 0xa5, 0xb2, 0xa6, 0xca, 0xcc, 0x8a, 0x7d, 0x95, 0xca, 0x27, 0x80, - 0x5a, 0x21, 0xb1, 0xd3, 0x92, 0xc2, 0xdc, 0x3a, 0x3c, 0x0c, 0x8c, 0x6f, 0x25, 0xf5, 0x97, 0xa1, - 0x51, 0x09, 0x7e, 0x2c, 0xed, 0x1d, 0x61, 0x7c, 0x1b, 0xa5, 0x2b, 0x92, 0x3b, 0xfa, 0xc9, 0xfe, - 0x59, 0x52, 0x3f, 0x08, 0x8d, 0x4a, 0xf0, 0x73, 0x34, 0x01, 0x13, 0x57, 0x47, 0x4a, 0xfd, 0x8d, - 0xd2, 0x6d, 0xa1, 0x43, 0x7d, 0xbb, 0xf0, 0xa1, 0x56, 0xc4, 0xc2, 0x0c, 0x2c, 0x92, 0x25, 0x0c, - 0x17, 0xc5, 0x2a, 0xa7, 0x8c, 0x17, 0x4e, 0x7b, 0xd8, 0x1a, 0x9b, 0xd3, 0xf7, 0xde, 0xa9, 0x10, - 0x2f, 0x10, 0xc1, 0x82, 0x66, 0xbf, 0x48, 0x12, 0x76, 0x2b, 0x59, 0xa2, 0x62, 0xf4, 0xa4, 0x01, - 0x9c, 0xc2, 0xb3, 0x7d, 0xcc, 0xa0, 0xa3, 0xfa, 0xdb, 0xd0, 0x54, 0x75, 0x61, 0x4f, 0x07, 0xe7, - 0xbf, 0xf6, 0x82, 0x4a, 0x0b, 0x8f, 0x0b, 0x68, 0x00, 0x26, 0x8f, 0x58, 0x82, 0xb9, 0xba, 0x4d, - 0x55, 0x65, 0x85, 0x50, 0x22, 0xb9, 0x89, 0x3e, 0x82, 0x9d, 0x6f, 0xd7, 0x29, 0x29, 0x7e, 0xe3, - 0xb8, 0x74, 0x74, 0xe5, 0x58, 0x47, 0x2a, 0xb5, 0xd1, 0x12, 0x3a, 0xf5, 0xef, 0xc8, 0x81, 0xd6, - 0xf7, 0x45, 0xd0, 0x6b, 0xf4, 0xdf, 0x5e, 0x5e, 0x0f, 0xcd, 0x1a, 0x0b, 0x24, 0x93, 0x1f, 0xcb, - 0xa0, 0xa7, 0xbd, 0x4e, 0x04, 0xea, 0xeb, 0x17, 0x37, 0x6e, 0x63, 0xee, 0xdc, 0xef, 0xdd, 0xc6, - 0xf3, 0xde, 0xd5, 0xfe, 0x1f, 0x5c, 0xed, 0x56, 0xcc, 0x9d, 0x98, 0x47, 0x31, 0xeb, 0xb6, 0xba, - 0xf8, 0xf3, 0x4b, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x6d, 0x44, 0x68, 0x53, 0x02, 0x00, 0x00, + // 397 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xbf, 0xae, 0xd3, 0x30, + 0x14, 0xc6, 0x9b, 0xdb, 0x70, 0x6f, 0x73, 0xd2, 0x84, 0xca, 0x42, 0x28, 0xea, 0x90, 0x96, 0x4a, + 0x48, 0x1d, 0x50, 0x2a, 0x95, 0xb1, 0x5b, 0x5b, 0x86, 0x2c, 0x28, 0x32, 0x7f, 0xd6, 0x2a, 0x6d, + 0x4c, 0xb0, 0x08, 0x71, 0x64, 0xbb, 0x65, 0x65, 0x44, 0xbc, 0x03, 0x13, 0x23, 0x2f, 0xc2, 0xc8, + 0xc8, 0x84, 0x68, 0x57, 0x16, 0x1e, 0x01, 0xdb, 0x49, 0x5a, 0x21, 0x75, 0xb0, 0xe4, 0xfc, 0xce, + 0xef, 0x4b, 0x4e, 0x3e, 0x70, 0xd3, 0x9c, 0x94, 0x32, 0xaa, 0x38, 0x93, 0x0c, 0x41, 0x41, 0xb7, + 0x25, 0x91, 0x1f, 0x18, 0x7f, 0x37, 0x7c, 0x90, 0xb3, 0x9c, 0x19, 0x3c, 0xd3, 0xb7, 0xda, 0x98, + 0x7c, 0xbb, 0x01, 0xff, 0x59, 0x99, 0x55, 0x8c, 0x96, 0x12, 0x93, 0x1d, 0xe3, 0x19, 0x42, 0x60, + 0x97, 0xe9, 0x7b, 0x12, 0x58, 0x63, 0x6b, 0xea, 0x60, 0x73, 0x47, 0x8f, 0xa0, 0x2f, 0x08, 0x3f, + 0xd0, 0x1d, 0xd9, 0x98, 0xd9, 0x8d, 0x99, 0xb9, 0x0d, 0x7b, 0xae, 0x95, 0x27, 0x00, 0xad, 0x42, + 0xb3, 0xa0, 0xab, 0x85, 0xa5, 0x77, 0xfa, 0x35, 0x72, 0x5e, 0xd4, 0x34, 0x5e, 0x63, 0xa7, 0x11, + 0xe2, 0x4c, 0xdb, 0x07, 0xca, 0xe5, 0x3e, 0x2d, 0x36, 0xb4, 0x0a, 0xec, 0x8b, 0xfd, 0xba, 0xa6, + 0x71, 0x82, 0x9d, 0x46, 0x88, 0x2b, 0x34, 0x03, 0x97, 0x34, 0x4b, 0x6a, 0xfd, 0x9e, 0xd1, 0x7d, + 0xa5, 0x43, 0xbb, 0xbb, 0xf2, 0xa1, 0x55, 0x54, 0x60, 0x01, 0x1e, 0x2d, 0x73, 0x4e, 0x84, 0xd8, + 0x54, 0x8c, 0x4b, 0x11, 0xdc, 0x8e, 0xbb, 0x53, 0x77, 0xfe, 0x30, 0xba, 0x14, 0x12, 0x25, 0x6a, + 0xb0, 0x62, 0xe5, 0x1b, 0x9a, 0xe3, 0x7e, 0x23, 0x6b, 0x24, 0x50, 0x00, 0x77, 0x69, 0x41, 0x53, + 0x41, 0x44, 0x70, 0xa7, 0x62, 0x0e, 0x6e, 0x1f, 0x27, 0x7f, 0x2c, 0x80, 0x4b, 0xec, 0x6a, 0x53, + 0x0b, 0xe8, 0x99, 0x66, 0x77, 0xac, 0x30, 0x2d, 0xf9, 0xf3, 0xd1, 0xf5, 0x8f, 0x46, 0x49, 0xa3, + 0xe1, 0x73, 0x00, 0x8d, 0xc0, 0x95, 0x29, 0xcf, 0x89, 0x34, 0x5b, 0x9b, 0x12, 0x3d, 0x0c, 0x35, + 0xd2, 0x49, 0xf4, 0x18, 0xfc, 0x6a, 0xbf, 0x2d, 0xa8, 0x78, 0x4b, 0xb2, 0xda, 0xb1, 0x8d, 0xe3, + 0x9d, 0xa9, 0xd6, 0x26, 0x6b, 0xe8, 0xb5, 0x6f, 0x57, 0x7f, 0xd3, 0x7d, 0xb9, 0x4a, 0x06, 0x9d, + 0xe1, 0xfd, 0xcf, 0x5f, 0xc6, 0x6e, 0x8b, 0x15, 0xd2, 0x93, 0x57, 0xeb, 0x64, 0x60, 0xfd, 0x3f, + 0x51, 0x68, 0x68, 0x7f, 0xfa, 0x1a, 0x76, 0x96, 0xc1, 0xcf, 0x63, 0xd8, 0xf9, 0x7b, 0x0c, 0xad, + 0x8f, 0xa7, 0xd0, 0xfa, 0xae, 0xce, 0x0f, 0x75, 0x7e, 0xab, 0xb3, 0xbd, 0x35, 0x1b, 0x3f, 0xfd, + 0x17, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x58, 0xc7, 0xbd, 0x6d, 0x02, 0x00, 0x00, } diff --git a/vendor/src/github.com/docker/libnetwork/agent.proto b/vendor/src/github.com/docker/libnetwork/agent.proto index 5d2b096755..2157ce40cd 100644 --- a/vendor/src/github.com/docker/libnetwork/agent.proto +++ b/vendor/src/github.com/docker/libnetwork/agent.proto @@ -31,6 +31,9 @@ message EndpointRecord { // IngressPorts exposed by the service to which this endpoint belongs. repeated PortConfig ingress_ports = 6; + + // A list of aliases which are alternate names for the service + repeated string aliases = 7; } // PortConfig specifies an exposed port which can be diff --git a/vendor/src/github.com/docker/libnetwork/drivers/macvlan/macvlan_endpoint.go b/vendor/src/github.com/docker/libnetwork/drivers/macvlan/macvlan_endpoint.go index 54844c93da..d76858ea36 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/macvlan/macvlan_endpoint.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/macvlan/macvlan_endpoint.go @@ -83,8 +83,12 @@ func (d *driver) DeleteEndpoint(nid, eid string) error { if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil { ns.NlHandle().LinkDel(link) } + if err := d.storeDelete(ep); err != nil { logrus.Warnf("Failed to remove macvlan endpoint %s from store: %v", ep.id[0:7], err) } + + n.deleteEndpoint(ep.id) + return nil } diff --git a/vendor/src/github.com/docker/libnetwork/endpoint.go b/vendor/src/github.com/docker/libnetwork/endpoint.go index 043c3f1643..1c9bf8291c 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint.go @@ -70,6 +70,7 @@ type endpoint struct { svcID string svcName string virtualIP net.IP + svcAliases []string ingressPorts []*PortConfig dbIndex uint64 dbExists bool @@ -98,6 +99,7 @@ func (ep *endpoint) MarshalJSON() ([]byte, error) { epMap["svcID"] = ep.svcID epMap["virtualIP"] = ep.virtualIP.String() epMap["ingressPorts"] = ep.ingressPorts + epMap["svcAliases"] = ep.svcAliases return json.Marshal(epMap) } @@ -198,6 +200,11 @@ func (ep *endpoint) UnmarshalJSON(b []byte) (err error) { ep.virtualIP = net.ParseIP(vip.(string)) } + sal, _ := json.Marshal(epMap["svcAliases"]) + var svcAliases []string + json.Unmarshal(sal, &svcAliases) + ep.svcAliases = svcAliases + pc, _ := json.Marshal(epMap["ingressPorts"]) var ingressPorts []*PortConfig json.Unmarshal(pc, &ingressPorts) @@ -231,6 +238,9 @@ func (ep *endpoint) CopyTo(o datastore.KVObject) error { dstEp.svcID = ep.svcID dstEp.virtualIP = ep.virtualIP + dstEp.svcAliases = make([]string, len(ep.svcAliases)) + copy(dstEp.svcAliases, ep.svcAliases) + dstEp.ingressPorts = make([]*PortConfig, len(ep.ingressPorts)) copy(dstEp.ingressPorts, ep.ingressPorts) @@ -935,12 +945,13 @@ func CreateOptionAlias(name string, alias string) EndpointOption { } // CreateOptionService function returns an option setter for setting service binding configuration -func CreateOptionService(name, id string, vip net.IP, ingressPorts []*PortConfig) EndpointOption { +func CreateOptionService(name, id string, vip net.IP, ingressPorts []*PortConfig, aliases []string) EndpointOption { return func(ep *endpoint) { ep.svcName = name ep.svcID = id ep.virtualIP = vip ep.ingressPorts = ingressPorts + ep.svcAliases = aliases } } diff --git a/vendor/src/github.com/docker/libnetwork/service_linux.go b/vendor/src/github.com/docker/libnetwork/service_linux.go index 204c59d9ff..1d36c4538d 100644 --- a/vendor/src/github.com/docker/libnetwork/service_linux.go +++ b/vendor/src/github.com/docker/libnetwork/service_linux.go @@ -37,7 +37,7 @@ func newService(name string, id string, ingressPorts []*PortConfig) *service { } } -func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, ip net.IP) error { +func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error { var ( s *service addService bool @@ -61,6 +61,9 @@ func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, i // Add endpoint IP to special "tasks.svc_name" so that the // applications have access to DNS RR. n.(*network).addSvcRecords("tasks."+name, ip, nil, false) + for _, alias := range aliases { + n.(*network).addSvcRecords("tasks."+alias, ip, nil, false) + } // Add service name to vip in DNS, if vip is valid. Otherwise resort to DNS RR svcIP := vip @@ -68,6 +71,9 @@ func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, i svcIP = ip } n.(*network).addSvcRecords(name, svcIP, nil, false) + for _, alias := range aliases { + n.(*network).addSvcRecords(alias, svcIP, nil, false) + } s.Lock() defer s.Unlock() @@ -107,7 +113,7 @@ func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, i return nil } -func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, ip net.IP) error { +func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error { var rmService bool n, err := c.NetworkByID(nid) @@ -125,6 +131,9 @@ func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, in // Delete the special "tasks.svc_name" backend record. n.(*network).deleteSvcRecords("tasks."+name, ip, nil, false) + for _, alias := range aliases { + n.(*network).deleteSvcRecords("tasks."+alias, ip, nil, false) + } // Make sure to remove the right IP since if vip is // not valid we would have added a DNS RR record. @@ -133,6 +142,9 @@ func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, in svcIP = ip } n.(*network).deleteSvcRecords(name, svcIP, nil, false) + for _, alias := range aliases { + n.(*network).deleteSvcRecords(alias, svcIP, nil, false) + } s.Lock() defer s.Unlock() diff --git a/vendor/src/github.com/docker/libnetwork/service_unsupported.go b/vendor/src/github.com/docker/libnetwork/service_unsupported.go index 67984e2aba..9668dcc07e 100644 --- a/vendor/src/github.com/docker/libnetwork/service_unsupported.go +++ b/vendor/src/github.com/docker/libnetwork/service_unsupported.go @@ -7,11 +7,11 @@ import ( "net" ) -func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, ip net.IP) error { +func (c *controller) addServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error { return fmt.Errorf("not supported") } -func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, ip net.IP) error { +func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, ingressPorts []*PortConfig, aliases []string, ip net.IP) error { return fmt.Errorf("not supported") } From 588b76c2a17630762f42c3d5f9dc256b40818fec Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 14 Jun 2016 19:47:35 -0700 Subject: [PATCH 07/71] Vendoring swarmkit 682e0b69be208176d6055cba855a5e9cf15c7cb4 Signed-off-by: Madhu Venugopal (cherry picked from commit 5af8e2a9c08439049da9a9abc24145c3d1f2e9de) --- hack/vendor.sh | 2 +- .../docker/swarmkit/api/objects.pb.go | 184 ++++++++++++------ .../docker/swarmkit/api/objects.proto | 3 + .../swarmkit/manager/allocator/network.go | 6 +- 4 files changed, 132 insertions(+), 63 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index cf2052c693..6f393d2f82 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -139,7 +139,7 @@ clone git github.com/docker/docker-credential-helpers v0.3.0 clone git github.com/docker/containerd 860f3a94940894ac0a106eff4bd1616a67407ee2 # cluster -clone git github.com/docker/swarmkit 25572005febb76c2cc5f7e37d878615e6fe330f9 +clone git github.com/docker/swarmkit 682e0b69be208176d6055cba855a5e9cf15c7cb4 clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 clone git github.com/gogo/protobuf 43a2e0b1c32252bfbbdf81f7faa7a88fb3fa4028 clone git github.com/cloudflare/cfssl 92f037e39eb103fb30f9151be40d9ed267fc4ae2 diff --git a/vendor/src/github.com/docker/swarmkit/api/objects.pb.go b/vendor/src/github.com/docker/swarmkit/api/objects.pb.go index cc99bf387f..5a9ce55423 100644 --- a/vendor/src/github.com/docker/swarmkit/api/objects.pb.go +++ b/vendor/src/github.com/docker/swarmkit/api/objects.pb.go @@ -175,6 +175,8 @@ type NetworkAttachment struct { // List of IPv4/IPv6 addresses that are assigned to the object // as part of getting attached to this network. Addresses []string `protobuf:"bytes,2,rep,name=addresses" json:"addresses,omitempty"` + // List of aliases by which a task is resolved in a network + Aliases []string `protobuf:"bytes,3,rep,name=aliases" json:"aliases,omitempty"` } func (m *NetworkAttachment) Reset() { *m = NetworkAttachment{} } @@ -360,6 +362,13 @@ func (m *NetworkAttachment) Copy() *NetworkAttachment { } } + if m.Aliases != nil { + o.Aliases = make([]string, 0, len(m.Aliases)) + for _, v := range m.Aliases { + o.Aliases = append(o.Aliases, v) + } + } + return o } @@ -514,12 +523,13 @@ func (this *NetworkAttachment) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 6) + s := make([]string, 0, 7) s = append(s, "&api.NetworkAttachment{") if this.Network != nil { s = append(s, "Network: "+fmt.Sprintf("%#v", this.Network)+",\n") } s = append(s, "Addresses: "+fmt.Sprintf("%#v", this.Addresses)+",\n") + s = append(s, "Aliases: "+fmt.Sprintf("%#v", this.Aliases)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -995,6 +1005,21 @@ func (m *NetworkAttachment) MarshalTo(data []byte) (int, error) { i += copy(data[i:], s) } } + if len(m.Aliases) > 0 { + for _, s := range m.Aliases { + data[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } return i, nil } @@ -1308,6 +1333,12 @@ func (m *NetworkAttachment) Size() (n int) { n += 1 + l + sovObjects(uint64(l)) } } + if len(m.Aliases) > 0 { + for _, s := range m.Aliases { + l = len(s) + n += 1 + l + sovObjects(uint64(l)) + } + } return n } @@ -1464,6 +1495,7 @@ func (this *NetworkAttachment) String() string { s := strings.Join([]string{`&NetworkAttachment{`, `Network:` + strings.Replace(fmt.Sprintf("%v", this.Network), "Network", "Network", 1) + `,`, `Addresses:` + fmt.Sprintf("%v", this.Addresses) + `,`, + `Aliases:` + fmt.Sprintf("%v", this.Aliases) + `,`, `}`, }, "") return s @@ -2854,6 +2886,35 @@ func (m *NetworkAttachment) Unmarshal(data []byte) error { } m.Addresses = append(m.Addresses, string(data[iNdEx:postIndex])) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Aliases", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowObjects + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthObjects + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Aliases = append(m.Aliases, string(data[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipObjects(data[iNdEx:]) @@ -3405,65 +3466,66 @@ var ( ) var fileDescriptorObjects = []byte{ - // 949 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x6e, 0x1b, 0x45, - 0x18, 0xaf, 0x9d, 0x8d, 0xed, 0xfd, 0x9c, 0x44, 0x62, 0xa8, 0x2a, 0x37, 0x84, 0xa4, 0xb8, 0x02, + // 965 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xaf, 0xed, 0xad, 0xed, 0x7d, 0x4e, 0x22, 0x31, 0x54, 0xd5, 0x36, 0x84, 0xa4, 0xb8, 0x02, 0x71, 0x40, 0xae, 0x28, 0x05, 0x81, 0xa0, 0x42, 0xb6, 0x13, 0x81, 0x05, 0x81, 0x68, 0x5a, 0x85, - 0xa3, 0x35, 0xd9, 0x9d, 0xa6, 0x8b, 0xed, 0xdd, 0xd5, 0xcc, 0xc4, 0x55, 0x6e, 0x3c, 0x01, 0x12, - 0x2f, 0xc0, 0xab, 0x70, 0x8d, 0x38, 0x71, 0xe4, 0x54, 0xd1, 0xde, 0x38, 0xc1, 0x23, 0xf0, 0xcd, - 0xec, 0xb7, 0xeb, 0xad, 0xbc, 0x8e, 0x1a, 0x09, 0xe5, 0xb0, 0xf2, 0xce, 0xec, 0xef, 0xf7, 0x9b, - 0xef, 0xff, 0x18, 0x36, 0x93, 0x93, 0x1f, 0x65, 0x60, 0x74, 0x2f, 0x55, 0x89, 0x49, 0x18, 0x0b, - 0x93, 0x60, 0x22, 0x55, 0x4f, 0x3f, 0x13, 0x6a, 0x36, 0x89, 0x4c, 0x6f, 0xfe, 0xe1, 0x76, 0xdb, - 0x9c, 0xa7, 0x92, 0x00, 0xdb, 0x6d, 0x9d, 0xca, 0x20, 0x5f, 0xdc, 0x36, 0xd1, 0x4c, 0x6a, 0x23, - 0x66, 0xe9, 0xbd, 0xe2, 0x8d, 0x3e, 0xdd, 0x3c, 0x4d, 0x4e, 0x13, 0xf7, 0x7a, 0xcf, 0xbe, 0x65, - 0xbb, 0xdd, 0xdf, 0x6a, 0xe0, 0x1d, 0x4a, 0x23, 0xd8, 0xe7, 0xd0, 0x9c, 0x4b, 0xa5, 0xa3, 0x24, - 0xee, 0xd4, 0xee, 0xd4, 0xde, 0x6f, 0xdf, 0x7f, 0xab, 0xb7, 0x7c, 0x72, 0xef, 0x38, 0x83, 0x0c, - 0xbc, 0x8b, 0xe7, 0x7b, 0x37, 0x78, 0xce, 0x60, 0x5f, 0x00, 0x04, 0x4a, 0x0a, 0x23, 0xc3, 0xb1, - 0x30, 0x9d, 0xba, 0xe3, 0xbf, 0x5d, 0xc5, 0x7f, 0x9c, 0x1b, 0xc5, 0x7d, 0x22, 0xf4, 0x8d, 0x65, - 0x9f, 0xa5, 0x61, 0xce, 0x5e, 0x7b, 0x2d, 0x36, 0x11, 0xfa, 0xa6, 0xfb, 0xf7, 0x1a, 0x78, 0xdf, - 0x25, 0xa1, 0x64, 0xb7, 0xa0, 0x1e, 0x85, 0xce, 0x78, 0x7f, 0xd0, 0x78, 0xf9, 0x7c, 0xaf, 0x3e, - 0xda, 0xe7, 0xb8, 0xc3, 0xee, 0x83, 0x37, 0x43, 0x0f, 0xc9, 0xac, 0x4e, 0x95, 0xb0, 0x8d, 0x00, - 0xf9, 0xe4, 0xb0, 0xec, 0x13, 0xf0, 0x6c, 0x58, 0xc9, 0x98, 0x9d, 0x2a, 0x8e, 0x3d, 0xf3, 0x11, - 0x62, 0x72, 0x9e, 0xc5, 0xb3, 0x03, 0x68, 0x87, 0x52, 0x07, 0x2a, 0x4a, 0x8d, 0x8d, 0xa4, 0xe7, - 0xe8, 0x77, 0x57, 0xd1, 0xf7, 0x17, 0x50, 0x5e, 0xe6, 0x61, 0x44, 0x1a, 0xe8, 0xa7, 0x39, 0xd3, - 0x9d, 0x75, 0xa7, 0xb0, 0xbb, 0xd2, 0x00, 0x87, 0x22, 0x13, 0x88, 0xc3, 0xbe, 0x86, 0xad, 0x99, - 0x88, 0xc5, 0xa9, 0x54, 0x63, 0x52, 0x69, 0x38, 0x95, 0x77, 0x2a, 0x5d, 0xcf, 0x90, 0x99, 0x10, - 0xdf, 0x9c, 0x95, 0x97, 0xe8, 0x0e, 0x08, 0x63, 0x44, 0xf0, 0x74, 0x26, 0x63, 0xd3, 0x69, 0x3a, - 0x95, 0x77, 0x2b, 0x6d, 0x91, 0xe6, 0x59, 0xa2, 0x26, 0xfd, 0x02, 0xcc, 0x4b, 0x44, 0xf6, 0x15, - 0xb4, 0x03, 0xa9, 0x4c, 0xf4, 0x24, 0x0a, 0x30, 0x69, 0x9d, 0x96, 0xd3, 0xd9, 0xab, 0xd2, 0x19, - 0x2e, 0x60, 0xe4, 0x54, 0x99, 0xd9, 0xfd, 0xbd, 0x06, 0xcd, 0x47, 0x52, 0xcd, 0xa3, 0xe0, 0xff, - 0x4d, 0xf7, 0x67, 0xaf, 0xa4, 0xbb, 0xd2, 0x32, 0x3a, 0x76, 0x29, 0xe3, 0x9f, 0x42, 0x4b, 0xc6, - 0x61, 0x9a, 0x44, 0x18, 0x20, 0x6f, 0x75, 0xb5, 0x1c, 0x10, 0x86, 0x17, 0xe8, 0xee, 0xaf, 0x75, - 0x68, 0xe5, 0xdb, 0xec, 0x01, 0x59, 0x90, 0xf5, 0xde, 0x9d, 0xcb, 0x24, 0xac, 0x09, 0x74, 0xf8, - 0x03, 0x58, 0x4f, 0x13, 0x65, 0x34, 0x3a, 0xbb, 0xb6, 0xaa, 0x4c, 0x8e, 0x10, 0x30, 0x4c, 0xe2, - 0x27, 0xd1, 0x29, 0xcf, 0xc0, 0xec, 0x07, 0x68, 0xcf, 0x23, 0x65, 0xce, 0xc4, 0x74, 0x1c, 0xa5, - 0x1a, 0x9d, 0xb6, 0xdc, 0xf7, 0x2e, 0x3b, 0xb2, 0x77, 0x9c, 0xe1, 0x47, 0x47, 0x83, 0x2d, 0x0c, - 0x35, 0x14, 0x4b, 0xcd, 0x81, 0xa4, 0x46, 0xa9, 0xde, 0x3e, 0x04, 0xbf, 0xf8, 0xc2, 0x3e, 0x00, - 0x88, 0xb3, 0xaa, 0x18, 0x17, 0x79, 0xda, 0x44, 0xb2, 0x4f, 0xb5, 0x82, 0xe9, 0xf2, 0x09, 0x30, - 0x0a, 0x19, 0x03, 0x4f, 0x84, 0xa1, 0x72, 0x59, 0xf3, 0xb9, 0x7b, 0xef, 0xfe, 0xb2, 0x0e, 0xde, - 0x63, 0xa1, 0x27, 0xd7, 0xdd, 0xd9, 0xf6, 0xcc, 0xa5, 0x3c, 0xa3, 0x3b, 0x3a, 0x2b, 0x01, 0xeb, - 0x8e, 0xb7, 0x70, 0x87, 0x0a, 0xc3, 0xba, 0x43, 0x80, 0xcc, 0x1d, 0x3d, 0x4d, 0x8c, 0x6b, 0x5f, - 0x8f, 0xbb, 0x77, 0x76, 0x17, 0x9a, 0x31, 0xb6, 0xac, 0xa5, 0x37, 0x1c, 0x1d, 0x90, 0xde, 0xb0, - 0x5d, 0x8c, 0xdc, 0x86, 0xfd, 0x84, 0x44, 0x6c, 0x15, 0x11, 0xc7, 0x09, 0xb6, 0x1f, 0xce, 0x01, - 0x4d, 0x2d, 0x57, 0x59, 0x90, 0xfd, 0x05, 0x2c, 0x6f, 0x95, 0x12, 0x93, 0x1d, 0xc3, 0x9b, 0xb9, - 0xbd, 0x65, 0xc1, 0xd6, 0x55, 0x04, 0x19, 0x29, 0x94, 0xbe, 0x94, 0x46, 0x93, 0xbf, 0x7a, 0x34, - 0xb9, 0x08, 0x56, 0x8d, 0xa6, 0x01, 0x6c, 0xe2, 0x9c, 0x8b, 0x14, 0x8e, 0x7a, 0xbb, 0x23, 0x3b, - 0x80, 0x22, 0x5b, 0x2b, 0xa6, 0x3d, 0x89, 0x48, 0xbe, 0x41, 0x1c, 0xb7, 0x62, 0x7d, 0x68, 0x51, - 0xdd, 0xe8, 0x4e, 0xdb, 0xd5, 0xee, 0x6b, 0x8e, 0xa4, 0x82, 0xf6, 0x4a, 0xd3, 0x6e, 0x5c, 0xa9, - 0x69, 0x9f, 0xc2, 0x1b, 0x4b, 0xc2, 0xec, 0x63, 0xcc, 0x6c, 0xb6, 0x79, 0xd9, 0xdd, 0x49, 0x3c, - 0x9e, 0x63, 0xd9, 0x0e, 0xf8, 0xb6, 0xce, 0xa5, 0xd6, 0x32, 0xeb, 0x60, 0x9f, 0x2f, 0x36, 0xba, - 0x3f, 0xd7, 0xa1, 0x49, 0x94, 0xeb, 0x9e, 0x75, 0x74, 0xec, 0x52, 0x0f, 0x3c, 0x84, 0x8d, 0x50, - 0x45, 0x73, 0xba, 0x57, 0x24, 0xcd, 0xbb, 0xed, 0x2a, 0x89, 0x7d, 0x87, 0xc3, 0x5b, 0xcd, 0xfd, - 0x66, 0x89, 0x7b, 0x08, 0x5e, 0x94, 0x8a, 0x19, 0xdd, 0x69, 0x95, 0x27, 0x8f, 0x8e, 0xfa, 0x87, - 0xdf, 0xa7, 0x59, 0x0d, 0xb6, 0xd0, 0x51, 0xcf, 0x6e, 0x70, 0x47, 0xeb, 0xfe, 0x83, 0x01, 0x19, - 0x4e, 0xcf, 0xb4, 0x91, 0xea, 0xba, 0x03, 0x42, 0xc7, 0x2e, 0x05, 0x64, 0x08, 0x4d, 0x95, 0x24, - 0x66, 0x1c, 0x88, 0xcb, 0x62, 0xc1, 0x11, 0x32, 0xec, 0x0f, 0xb6, 0x2c, 0xd1, 0xb6, 0x7c, 0xb6, - 0xe6, 0x0d, 0x4b, 0x1d, 0x0a, 0x1c, 0xc7, 0xb7, 0xf2, 0x41, 0x79, 0x82, 0x3b, 0xda, 0x28, 0x91, - 0x8e, 0x27, 0xf2, 0xdc, 0x5e, 0xfe, 0x6b, 0xab, 0xae, 0xed, 0x83, 0x38, 0x50, 0xe7, 0x2e, 0x50, - 0xdf, 0xc8, 0x73, 0x7e, 0x93, 0x04, 0x06, 0x39, 0x1f, 0x37, 0x35, 0xfb, 0x12, 0x76, 0x64, 0x01, - 0xb3, 0x8a, 0xe3, 0x29, 0xfe, 0x77, 0xc2, 0x2b, 0x60, 0x1c, 0x4c, 0x51, 0xd1, 0x4d, 0x21, 0x8f, - 0xdf, 0x96, 0x65, 0xa9, 0x6f, 0x33, 0xc4, 0xd0, 0x02, 0x06, 0x3b, 0x17, 0x2f, 0x76, 0x6f, 0xfc, - 0x89, 0xcf, 0xbf, 0x2f, 0x76, 0x6b, 0x3f, 0xbd, 0xdc, 0xad, 0x5d, 0xe0, 0xf3, 0x07, 0x3e, 0x7f, - 0xe1, 0x73, 0xd2, 0x70, 0xff, 0x20, 0x3f, 0xfa, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x76, 0xa2, 0xea, - 0x9b, 0xb1, 0x0a, 0x00, 0x00, + 0xa3, 0x35, 0xd9, 0x9d, 0x86, 0xc5, 0xf6, 0xee, 0x6a, 0x66, 0xe2, 0x2a, 0x37, 0xc4, 0x07, 0x40, + 0xe2, 0x0b, 0xf0, 0x55, 0xb8, 0x46, 0x9c, 0x38, 0x72, 0xaa, 0x68, 0x6f, 0x9c, 0xe0, 0x23, 0xf0, + 0x66, 0xf6, 0xad, 0xbd, 0x95, 0xd7, 0x51, 0x2b, 0xa1, 0x1c, 0x56, 0x9e, 0x3f, 0xbf, 0xdf, 0x6f, + 0xde, 0x7b, 0xf3, 0xde, 0x1b, 0xc3, 0x66, 0x7a, 0xf2, 0x83, 0x0c, 0x8d, 0xee, 0x65, 0x2a, 0x35, + 0x29, 0x63, 0x51, 0x1a, 0x4e, 0xa4, 0xea, 0xe9, 0x27, 0x42, 0xcd, 0x26, 0xb1, 0xe9, 0xcd, 0xdf, + 0xdf, 0xee, 0x98, 0xf3, 0x4c, 0x12, 0x60, 0xbb, 0xa3, 0x33, 0x19, 0x16, 0x93, 0x5b, 0x26, 0x9e, + 0x49, 0x6d, 0xc4, 0x2c, 0xbb, 0xbb, 0x18, 0xd1, 0xd6, 0x8d, 0xd3, 0xf4, 0x34, 0x75, 0xc3, 0xbb, + 0x76, 0x94, 0xaf, 0x76, 0x7f, 0xab, 0x81, 0x77, 0x28, 0x8d, 0x60, 0x9f, 0x42, 0x6b, 0x2e, 0x95, + 0x8e, 0xd3, 0x24, 0xa8, 0xdd, 0xae, 0xbd, 0xdb, 0xb9, 0xf7, 0x46, 0x6f, 0xf5, 0xe4, 0xde, 0x71, + 0x0e, 0x19, 0x78, 0x17, 0x4f, 0xf7, 0xae, 0xf1, 0x82, 0xc1, 0x3e, 0x03, 0x08, 0x95, 0x14, 0x46, + 0x46, 0x63, 0x61, 0x82, 0xba, 0xe3, 0xbf, 0x59, 0xc5, 0x7f, 0x54, 0x18, 0xc5, 0x7d, 0x22, 0xf4, + 0x8d, 0x65, 0x9f, 0x65, 0x51, 0xc1, 0x6e, 0xbc, 0x14, 0x9b, 0x08, 0x7d, 0xd3, 0xfd, 0xbb, 0x01, + 0xde, 0x37, 0x69, 0x24, 0xd9, 0x4d, 0xa8, 0xc7, 0x91, 0x33, 0xde, 0x1f, 0x34, 0x9f, 0x3f, 0xdd, + 0xab, 0x8f, 0xf6, 0x39, 0xae, 0xb0, 0x7b, 0xe0, 0xcd, 0xd0, 0x43, 0x32, 0x2b, 0xa8, 0x12, 0xb6, + 0x11, 0x20, 0x9f, 0x1c, 0x96, 0x7d, 0x04, 0x9e, 0x0d, 0x2b, 0x19, 0xb3, 0x53, 0xc5, 0xb1, 0x67, + 0x3e, 0x44, 0x4c, 0xc1, 0xb3, 0x78, 0x76, 0x00, 0x9d, 0x48, 0xea, 0x50, 0xc5, 0x99, 0xb1, 0x91, + 0xf4, 0x1c, 0xfd, 0xce, 0x3a, 0xfa, 0xfe, 0x12, 0xca, 0xcb, 0x3c, 0x8c, 0x48, 0x13, 0xfd, 0x34, + 0x67, 0x3a, 0xb8, 0xee, 0x14, 0x76, 0xd7, 0x1a, 0xe0, 0x50, 0x64, 0x02, 0x71, 0xd8, 0x97, 0xb0, + 0x35, 0x13, 0x89, 0x38, 0x95, 0x6a, 0x4c, 0x2a, 0x4d, 0xa7, 0xf2, 0x56, 0xa5, 0xeb, 0x39, 0x32, + 0x17, 0xe2, 0x9b, 0xb3, 0xf2, 0x14, 0xdd, 0x01, 0x61, 0x8c, 0x08, 0xbf, 0x9f, 0xc9, 0xc4, 0x04, + 0x2d, 0xa7, 0xf2, 0x76, 0xa5, 0x2d, 0xd2, 0x3c, 0x49, 0xd5, 0xa4, 0xbf, 0x00, 0xf3, 0x12, 0x91, + 0x7d, 0x01, 0x9d, 0x50, 0x2a, 0x13, 0x3f, 0x8e, 0x43, 0xbc, 0xb4, 0xa0, 0xed, 0x74, 0xf6, 0xaa, + 0x74, 0x86, 0x4b, 0x18, 0x39, 0x55, 0x66, 0x76, 0x7f, 0xaf, 0x41, 0xeb, 0xa1, 0x54, 0xf3, 0x38, + 0xfc, 0x7f, 0xaf, 0xfb, 0x93, 0x17, 0xae, 0xbb, 0xd2, 0x32, 0x3a, 0x76, 0xe5, 0xc6, 0x3f, 0x86, + 0xb6, 0x4c, 0xa2, 0x2c, 0x8d, 0x31, 0x40, 0xde, 0xfa, 0x6c, 0x39, 0x20, 0x0c, 0x5f, 0xa0, 0xbb, + 0xbf, 0xd6, 0xa1, 0x5d, 0x2c, 0xb3, 0xfb, 0x64, 0x41, 0x5e, 0x7b, 0xb7, 0x2f, 0x93, 0xb0, 0x26, + 0xd0, 0xe1, 0xf7, 0xe1, 0x7a, 0x96, 0x2a, 0xa3, 0xd1, 0xd9, 0xc6, 0xba, 0x34, 0x39, 0x42, 0xc0, + 0x30, 0x4d, 0x1e, 0xc7, 0xa7, 0x3c, 0x07, 0xb3, 0xef, 0xa0, 0x33, 0x8f, 0x95, 0x39, 0x13, 0xd3, + 0x71, 0x9c, 0x69, 0x74, 0xda, 0x72, 0xdf, 0xb9, 0xec, 0xc8, 0xde, 0x71, 0x8e, 0x1f, 0x1d, 0x0d, + 0xb6, 0x30, 0xd4, 0xb0, 0x98, 0x6a, 0x0e, 0x24, 0x35, 0xca, 0xf4, 0xf6, 0x21, 0xf8, 0x8b, 0x1d, + 0xf6, 0x1e, 0x40, 0x92, 0x67, 0xc5, 0x78, 0x71, 0x4f, 0x9b, 0x48, 0xf6, 0x29, 0x57, 0xf0, 0xba, + 0x7c, 0x02, 0x8c, 0x22, 0xc6, 0xc0, 0x13, 0x51, 0xa4, 0xdc, 0xad, 0xf9, 0xdc, 0x8d, 0xbb, 0xbf, + 0x5c, 0x07, 0xef, 0x91, 0xd0, 0x93, 0xab, 0xae, 0x6c, 0x7b, 0xe6, 0xca, 0x3d, 0xa3, 0x3b, 0x3a, + 0x4f, 0x01, 0xeb, 0x8e, 0xb7, 0x74, 0x87, 0x12, 0xc3, 0xba, 0x43, 0x80, 0xdc, 0x1d, 0x3d, 0x4d, + 0x8d, 0x2b, 0x5f, 0x8f, 0xbb, 0x31, 0xbb, 0x03, 0xad, 0x04, 0x4b, 0xd6, 0xd2, 0x9b, 0x8e, 0x0e, + 0x48, 0x6f, 0xda, 0x2a, 0x46, 0x6e, 0xd3, 0x6e, 0x21, 0x11, 0x4b, 0x45, 0x24, 0x49, 0x8a, 0xe5, + 0x87, 0x7d, 0x40, 0x53, 0xc9, 0x55, 0x26, 0x64, 0x7f, 0x09, 0x2b, 0x4a, 0xa5, 0xc4, 0x64, 0xc7, + 0xf0, 0x7a, 0x61, 0x6f, 0x59, 0xb0, 0xfd, 0x2a, 0x82, 0x8c, 0x14, 0x4a, 0x3b, 0xa5, 0xd6, 0xe4, + 0xaf, 0x6f, 0x4d, 0x2e, 0x82, 0x55, 0xad, 0x69, 0x00, 0x9b, 0xd8, 0xe7, 0x62, 0x85, 0xad, 0xde, + 0xae, 0xc8, 0x00, 0x50, 0x64, 0x6b, 0x4d, 0xb7, 0x27, 0x11, 0xc9, 0x37, 0x88, 0xe3, 0x66, 0xac, + 0x0f, 0x6d, 0xca, 0x1b, 0x1d, 0x74, 0x5c, 0xee, 0xbe, 0x64, 0x4b, 0x5a, 0xd0, 0x5e, 0x28, 0xda, + 0x8d, 0x57, 0x2a, 0xda, 0x9f, 0x6a, 0xf0, 0xda, 0x8a, 0x32, 0xfb, 0x10, 0xaf, 0x36, 0x5f, 0xbc, + 0xec, 0xf1, 0x24, 0x1e, 0x2f, 0xb0, 0x6c, 0x07, 0x7c, 0x9b, 0xe8, 0x52, 0x6b, 0x99, 0x97, 0xb0, + 0xcf, 0x97, 0x0b, 0x2c, 0x80, 0x96, 0x98, 0xc6, 0xc2, 0xee, 0x35, 0xdc, 0x5e, 0x31, 0xed, 0xfe, + 0x5c, 0x87, 0x16, 0x89, 0x5d, 0x75, 0x1b, 0xa4, 0x63, 0x57, 0xca, 0xe3, 0x01, 0x6c, 0x44, 0x2a, + 0x9e, 0xd3, 0x93, 0x23, 0xa9, 0x15, 0x6e, 0x57, 0x49, 0xec, 0x3b, 0x1c, 0x3e, 0x78, 0xee, 0x37, + 0xbf, 0xd3, 0x07, 0xe0, 0xc5, 0x99, 0x98, 0xd1, 0x73, 0x57, 0x79, 0xf2, 0xe8, 0xa8, 0x7f, 0xf8, + 0x6d, 0x96, 0xa7, 0x67, 0x1b, 0x1d, 0xf5, 0xec, 0x02, 0x77, 0xb4, 0xee, 0x3f, 0x18, 0x90, 0xe1, + 0xf4, 0x4c, 0x1b, 0xa9, 0xae, 0x3a, 0x20, 0x74, 0xec, 0x4a, 0x40, 0x86, 0xd0, 0x52, 0x69, 0x6a, + 0xc6, 0xa1, 0xb8, 0x2c, 0x16, 0x1c, 0x21, 0xc3, 0xfe, 0x60, 0xcb, 0x12, 0x6d, 0x37, 0xc8, 0xe7, + 0xbc, 0x69, 0xa9, 0x43, 0x81, 0x9d, 0xfa, 0x66, 0xd1, 0x43, 0x4f, 0x70, 0x45, 0x1b, 0x25, 0xb2, + 0xf1, 0x44, 0x9e, 0xdb, 0xff, 0x05, 0x8d, 0x75, 0x2f, 0xfa, 0x41, 0x12, 0xaa, 0x73, 0x17, 0xa8, + 0xaf, 0xe4, 0x39, 0xbf, 0x41, 0x02, 0x83, 0x82, 0x8f, 0x8b, 0x9a, 0x7d, 0x0e, 0x3b, 0x72, 0x01, + 0xb3, 0x8a, 0xe3, 0x29, 0xfe, 0xad, 0xc2, 0xd7, 0x61, 0x1c, 0x4e, 0x51, 0xd1, 0x35, 0x28, 0x8f, + 0xdf, 0x92, 0x65, 0xa9, 0xaf, 0x73, 0xc4, 0xd0, 0x02, 0x06, 0x3b, 0x17, 0xcf, 0x76, 0xaf, 0xfd, + 0x89, 0xdf, 0xbf, 0xcf, 0x76, 0x6b, 0x3f, 0x3e, 0xdf, 0xad, 0x5d, 0xe0, 0xf7, 0x07, 0x7e, 0x7f, + 0xe1, 0x77, 0xd2, 0x74, 0x7f, 0x2e, 0x3f, 0xf8, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x55, 0xec, + 0x86, 0xcc, 0x0a, 0x00, 0x00, } diff --git a/vendor/src/github.com/docker/swarmkit/api/objects.proto b/vendor/src/github.com/docker/swarmkit/api/objects.proto index 39a1fe9033..56d61a4b9c 100644 --- a/vendor/src/github.com/docker/swarmkit/api/objects.proto +++ b/vendor/src/github.com/docker/swarmkit/api/objects.proto @@ -168,6 +168,9 @@ message NetworkAttachment { // List of IPv4/IPv6 addresses that are assigned to the object // as part of getting attached to this network. repeated string addresses = 2; + + // List of aliases by which a task is resolved in a network + repeated string aliases = 3; } message Network { diff --git a/vendor/src/github.com/docker/swarmkit/manager/allocator/network.go b/vendor/src/github.com/docker/swarmkit/manager/allocator/network.go index 962ab88435..e9a55b0798 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/allocator/network.go +++ b/vendor/src/github.com/docker/swarmkit/manager/allocator/network.go @@ -442,7 +442,11 @@ func (a *Allocator) taskCreateNetworkAttachments(t *api.Task, s *api.Service) { for _, na := range s.Spec.Networks { n := store.GetNetwork(tx, na.Target) if n != nil { - networks = append(networks, &api.NetworkAttachment{Network: n}) + var aliases []string + for _, a := range na.Aliases { + aliases = append(aliases, a) + } + networks = append(networks, &api.NetworkAttachment{Network: n, Aliases: aliases}) } } }) From 60a86590aa610b55d94422aabba70e883af5c392 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 14 Jun 2016 14:55:05 -0700 Subject: [PATCH 08/71] Use service alias and configure container's --net-alias Signed-off-by: Madhu Venugopal (cherry picked from commit 07e39e9e7283a159efa18bc8f72cb2dc0e4f700f) --- container/container.go | 2 +- daemon/cluster/cluster.go | 2 +- daemon/cluster/executor/container/adapter.go | 1 - daemon/cluster/executor/container/container.go | 4 ++++ daemon/cluster/provider/network.go | 1 + 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/container/container.go b/container/container.go index ac1ea199fb..d34226922a 100644 --- a/container/container.go +++ b/container/container.go @@ -823,7 +823,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC }) } - createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs)) + createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs, svcCfg.Aliases[n.ID()])) } if !containertypes.NetworkMode(n.Name()).IsUserDefined() { diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index 6f3143bd6b..d2f972e721 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -918,7 +918,7 @@ func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.S if err != nil { return err } - s.Networks[i] = types.NetworkAttachmentConfig{Target: apiNetwork.ID} + s.Networks[i].Target = apiNetwork.ID } return nil } diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index f24d91bb8e..32768ace8e 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -126,7 +126,6 @@ func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backe if nc != nil { for n, ep := range nc.EndpointsConfig { - logrus.Errorf("CONNECT %s : %v", n, ep.IPAMConfig.IPv4Address) if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil { return err } diff --git a/daemon/cluster/executor/container/container.go b/daemon/cluster/executor/container/container.go index 1326bf1a8b..fcd0cba157 100644 --- a/daemon/cluster/executor/container/container.go +++ b/daemon/cluster/executor/container/container.go @@ -348,6 +348,7 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig { log.Printf("Creating service config in agent for t = %+v", c.task) svcCfg := &clustertypes.ServiceConfig{ Name: c.task.ServiceAnnotations.Name, + Aliases: make(map[string][]string), ID: c.task.ServiceID, VirtualAddresses: make(map[string]*clustertypes.VirtualAddress), } @@ -357,6 +358,9 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig { // We support only IPv4 virtual IP for now. IPv4: c.virtualIP(na.Network.ID), } + if len(na.Aliases) > 0 { + svcCfg.Aliases[na.Network.ID] = na.Aliases + } } if c.task.Endpoint != nil { diff --git a/daemon/cluster/provider/network.go b/daemon/cluster/provider/network.go index d959c15ceb..a383343d7c 100644 --- a/daemon/cluster/provider/network.go +++ b/daemon/cluster/provider/network.go @@ -31,6 +31,7 @@ type PortConfig struct { type ServiceConfig struct { ID string Name string + Aliases map[string][]string VirtualAddresses map[string]*VirtualAddress ExposedPorts []*PortConfig } From 7af9b80f23ba76f749d771c9e0a922862c6eb25f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 15 Jun 2016 04:46:40 +0000 Subject: [PATCH 09/71] fix links for #23505 Signed-off-by: Sven Dowideit (cherry picked from commit fa94eb46041ad309468dbbd29faba2053f402096) --- docs/getstarted/step_one.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/getstarted/step_one.md b/docs/getstarted/step_one.md index b7570fbd1a..537888cbfb 100644 --- a/docs/getstarted/step_one.md +++ b/docs/getstarted/step_one.md @@ -39,7 +39,7 @@ Docker for Mac is our newest offering for the Mac. It runs as a native Mac appli If you have an earlier Mac that doesn't meet the Docker for Mac prerequisites, get Docker Toolbox for the Mac. -See [Docker Toolbox Overview](toolbox/overview/) for help on installing Docker with Toolbox. +See [Docker Toolbox Overview](/toolbox/overview.md) for help on installing Docker with Toolbox. ### Docker for Windows @@ -58,24 +58,24 @@ Docker for Windows is our newest offering for PCs. It runs as a native Windows a If you have an earlier Windows system that doesn't meet the Docker for Windows prerequisites, get Docker Toolbox. -See [Docker Toolbox Overview](toolbox/overview/) for help on installing Docker with Toolbox. +See [Docker Toolbox Overview](/toolbox/overview.md) for help on installing Docker with Toolbox. ### Docker for Linux Docker Engine runs navitvely on Linux distributions. -For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](/engine/installation/). +For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](../installation/index.md). ## Step 2: Install Docker -* For install instructions for Docker for Mac, see [Getting Started with Docker for Mac](/docker-for-mac/). +* For install instructions for Docker for Mac, see [Getting Started with Docker for Mac](/docker-for-mac/index.md). -* For install instructions for Docker for Windows, see [Getting Started with Docker for Windows](/docker-for-windows/). +* For install instructions for Docker for Windows, see [Getting Started with Docker for Windows](/docker-for-windows/index.md). -* For install instructions for Docker Toolbox, see [Docker Toolbox Overview](toolbox/overview/). +* For install instructions for Docker Toolbox, see [Docker Toolbox Overview](/toolbox/overview.md). * For a simple example of installing Docker on Ubuntu Linux so that you can work through this tutorial, see [Installing Docker on Ubuntu Linux (Example)](linux_install_help.md). - For full install instructions for Docker on Linux, see [Install Docker Engine](/engine/installation/) and select the flavor of Linux you want to use. + For full install instructions for Docker on Linux, see [Install Docker Engine](../installation/index.md) and select the flavor of Linux you want to use. ## Step 3: Verify your installation @@ -129,7 +129,7 @@ For full instructions on getting Docker for various Linux distributions, see [In ## Looking for troubleshooting help? -Typically, the above steps work out-of-the-box, but some scenarios can cause problems. If your `docker run hello-world` didn't work and resulted in errors, check out [Troubleshooting](/tutorials/faqs/troubleshoot.md) for quick fixes to common problems. +Typically, the above steps work out-of-the-box, but some scenarios can cause problems. If your `docker run hello-world` didn't work and resulted in errors, check out [Troubleshooting](/toolbox/faqs/troubleshoot.md) for quick fixes to common problems. ## Where to go next From 2b36087597b67b23c8309fe9330a60f17952496b Mon Sep 17 00:00:00 2001 From: Shoubhik Bose Date: Tue, 14 Jun 2016 09:44:09 +0000 Subject: [PATCH 10/71] Fixes #23376 Broken URL for Centos yum repo for docker changed to now have a hardcoded centos version 7 Signed-off-by: Shoubhik Bose (cherry picked from commit d3fad80cc7823eddb823dd53424a28ee81043f18) --- docs/installation/linux/centos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/linux/centos.md b/docs/installation/linux/centos.md index 1647f76611..08d8c40a19 100644 --- a/docs/installation/linux/centos.md +++ b/docs/installation/linux/centos.md @@ -57,7 +57,7 @@ package manager. $ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF' [dockerrepo] name=Docker Repository - baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/ + baseurl=https://yum.dockerproject.org/repo/main/centos/7/ enabled=1 gpgcheck=1 gpgkey=https://yum.dockerproject.org/gpg From 38305cb67608ee0b909c8639f41ef8dab8bd3255 Mon Sep 17 00:00:00 2001 From: Victoria Bialas Date: Wed, 15 Jun 2016 18:22:24 -0700 Subject: [PATCH 11/71] fixing links and merge conflicts updated link to faqs/troubleshoot to point to new location /toolbox fixed docker-for-mac link error Signed-off-by: Victoria Bialas (cherry picked from commit 1d80f8ca193139755de935fa6567a32734038cac) --- docs/getstarted/last_page.md | 17 +++++++++-------- docs/getstarted/step_four.md | 6 ++---- docs/getstarted/step_one.md | 5 ++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/getstarted/last_page.md b/docs/getstarted/last_page.md index bb3372b96c..9b45019693 100644 --- a/docs/getstarted/last_page.md +++ b/docs/getstarted/last_page.md @@ -30,27 +30,27 @@ Depending on your interest, the Docker documentation contains a wealth of inform More about Docker for Mac, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystem - Getting Started with Docker for Mac + [Getting Started with Docker for Mac](/docker-for-mac/index.md) - More about Docker for Windows, More about Docker for Mac, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystem - Getting Started with Docker for Windows + More about Docker for Windows, More about Docker for Windows, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystem + [Getting Started with Docker for Windows](/docker-for-mac/index.md) More about Docker Toolbox - Docker Toolbox Overview + [Docker Toolbox Overview](/toolbox/overview.md) More about Docker for Linux distributions - Install Docker Engine on Linux + [Install Docker Engine on Linux](/engine/installation/linux/index.md) More advanced tutorials on running containers, building your own images, networking containers, managing data for containers, and storing images on Docker Hub - Learn by example + [Learn by example](/engine/tutorials/index.md) Information about the Docker product line - The product explainer is a good place to start. + The product explainer is a good place to start. @@ -59,7 +59,8 @@ Depending on your interest, the Docker documentation contains a wealth of inform How to run a multi-container application with Compose - Docker Compose documentation + [Docker Compose documentation](/compose/overview.md) + diff --git a/docs/getstarted/step_four.md b/docs/getstarted/step_four.md index d1f96ae74b..27656b3d21 100644 --- a/docs/getstarted/step_four.md +++ b/docs/getstarted/step_four.md @@ -60,15 +60,13 @@ commands to run. Your recipe is going to be very short. FROM docker/whalesay:latest - The `FROM` keyword tells Docker which image your image is based on. Whalesay is cute and has the `cowsay` program already, so we'll start there. + The `FROM` keyword tells Docker which image your image is based on. Whalesay is cute and has the `cowsay` program already, so we'll start there. 7. Now, add the `fortunes` program to the image. RUN apt-get -y update && apt-get install -y fortunes - The `fortunes` program has a command that prints out wise sayings for our - whale to say. So, the first step is to install it. This line installs the - software into the image. + The `fortunes` program has a command that prints out wise sayings for our whale to say. So, the first step is to install it. This line installs the software into the image. 8. Once the image has the software it needs, you instruct the software to run when the image is loaded. diff --git a/docs/getstarted/step_one.md b/docs/getstarted/step_one.md index 537888cbfb..c61a2d1e04 100644 --- a/docs/getstarted/step_one.md +++ b/docs/getstarted/step_one.md @@ -47,7 +47,6 @@ Docker for Windows is our newest offering for PCs. It runs as a native Windows a Get Docker for Windows - #### Install Prequisites * 64bit Windows 10 Pro, Enterprise and Education (1511 November update, Build 10586 or later). In the future we will support more versions of Windows 10. @@ -63,7 +62,7 @@ See [Docker Toolbox Overview](/toolbox/overview.md) for help on installing Docke ### Docker for Linux Docker Engine runs navitvely on Linux distributions. -For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](../installation/index.md). +For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](/engine/installation/index.md). ## Step 2: Install Docker @@ -75,7 +74,7 @@ For full instructions on getting Docker for various Linux distributions, see [In * For a simple example of installing Docker on Ubuntu Linux so that you can work through this tutorial, see [Installing Docker on Ubuntu Linux (Example)](linux_install_help.md). - For full install instructions for Docker on Linux, see [Install Docker Engine](../installation/index.md) and select the flavor of Linux you want to use. + For full install instructions for Docker on Linux, see [Install Docker Engine](/engine/installation/index.md) and select the flavor of Linux you want to use. ## Step 3: Verify your installation From cdb04519e238d658825f6db8437b389d7dfd88f2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 15 Jun 2016 15:07:04 -0700 Subject: [PATCH 12/71] Update libnetwork to 96d45528599c32354230480a1ebc0 This fixes an issue with hanging tests due to store. Fixes #23560 Signed-off-by: Michael Crosby (cherry picked from commit e927df7b4b58099113b5086371b1d80ff517f564) --- hack/vendor.sh | 2 +- .../docker/libnetwork/controller.go | 2 ++ .../libnetwork/drivers/overlay/encryption.go | 30 +++++++++---------- .../github.com/docker/libnetwork/endpoint.go | 4 +-- .../github.com/docker/libnetwork/network.go | 11 ++++--- .../src/github.com/docker/libnetwork/store.go | 9 ++++++ 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 6f393d2f82..3366ef3a65 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -65,7 +65,7 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 #get libnetwork packages -clone git github.com/docker/libnetwork 0d517a9e4e5cbdb889b3257eebd2351addcd46d4 +clone git github.com/docker/libnetwork 96d45528599c32354230480a1ebc0657cd4d077f clone git github.com/docker/go-events 39718a26497694185f8fb58a7d6f31947f3dc42d clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec diff --git a/vendor/src/github.com/docker/libnetwork/controller.go b/vendor/src/github.com/docker/libnetwork/controller.go index 3655707bf6..b19f51d707 100644 --- a/vendor/src/github.com/docker/libnetwork/controller.go +++ b/vendor/src/github.com/docker/libnetwork/controller.go @@ -203,6 +203,8 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { } } + c.WalkNetworks(populateSpecial) + // Reserve pools first before doing cleanup. Otherwise the // cleanups of endpoint/network and sandbox below will // generate many unnecessary warnings diff --git a/vendor/src/github.com/docker/libnetwork/drivers/overlay/encryption.go b/vendor/src/github.com/docker/libnetwork/drivers/overlay/encryption.go index 0f9a5e4767..f27f86004d 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/overlay/encryption.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/overlay/encryption.go @@ -70,7 +70,7 @@ func (e *encrMap) String() string { } func (d *driver) checkEncryption(nid string, rIP net.IP, vxlanID uint32, isLocal, add bool) error { - log.Infof("checkEncryption(%s, %v, %d, %t)", nid[0:7], rIP, vxlanID, isLocal) + log.Debugf("checkEncryption(%s, %v, %d, %t)", nid[0:7], rIP, vxlanID, isLocal) n := d.network(nid) if n == nil || !n.secure { @@ -120,7 +120,7 @@ func (d *driver) checkEncryption(nid string, rIP net.IP, vxlanID uint32, isLocal } func setupEncryption(localIP, remoteIP net.IP, vni uint32, em *encrMap, keys []*key) error { - log.Infof("Programming encryption for vxlan %d between %s and %s", vni, localIP, remoteIP) + log.Debugf("Programming encryption for vxlan %d between %s and %s", vni, localIP, remoteIP) rIPs := remoteIP.String() indices := make([]*spi, 0, len(keys)) @@ -242,7 +242,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f } if add != exists { - log.Infof("%s: rSA{%s}", action, rSA) + log.Debugf("%s: rSA{%s}", action, rSA) if err := xfrmProgram(rSA); err != nil { log.Warnf("Failed %s rSA{%s}: %v", action, rSA, err) } @@ -267,7 +267,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f } if add != exists { - log.Infof("%s fSA{%s}", action, fSA) + log.Debugf("%s fSA{%s}", action, fSA) if err := xfrmProgram(fSA); err != nil { log.Warnf("Failed %s fSA{%s}: %v.", action, fSA, err) } @@ -313,7 +313,7 @@ func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error { } if add != exists { - log.Infof("%s fSP{%s}", action, fPol) + log.Debugf("%s fSP{%s}", action, fPol) if err := xfrmProgram(fPol); err != nil { log.Warnf("%s fSP{%s}: %v", action, fPol, err) } @@ -380,16 +380,16 @@ func (d *driver) setKeys(keys []*key) error { return types.ForbiddenErrorf("initial keys are already present") } d.keys = keys - log.Infof("Initial encryption keys: %v", d.keys) + log.Debugf("Initial encryption keys: %v", d.keys) return nil } // updateKeys allows to add a new key and/or change the primary key and/or prune an existing key // The primary key is the key used in transmission and will go in first position in the list. func (d *driver) updateKeys(newKey, primary, pruneKey *key) error { - log.Infof("Updating Keys. New: %v, Primary: %v, Pruned: %v", newKey, primary, pruneKey) + log.Debugf("Updating Keys. New: %v, Primary: %v, Pruned: %v", newKey, primary, pruneKey) - log.Infof("Current: %v", d.keys) + log.Debugf("Current: %v", d.keys) var ( newIdx = -1 @@ -444,7 +444,7 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error { } d.Unlock() - log.Infof("Updated: %v", d.keys) + log.Debugf("Updated: %v", d.keys) return nil } @@ -458,10 +458,10 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error { // Spis and keys are sorted in such away the one in position 0 is the primary func updateNodeKey(lIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, delIdx int) []*spi { - log.Infof("Updating keys for node: %s (%d,%d,%d)", rIP, newIdx, priIdx, delIdx) + log.Debugf("Updating keys for node: %s (%d,%d,%d)", rIP, newIdx, priIdx, delIdx) spis := idxs - log.Infof("Current: %v", spis) + log.Debugf("Current: %v", spis) // add new if newIdx != -1 { @@ -482,7 +482,7 @@ func updateNodeKey(lIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, Crypt: &netlink.XfrmStateAlgo{Name: "cbc(aes)", Key: curKeys[delIdx].value}, Limits: netlink.XfrmStateLimits{TimeSoft: timeout}, } - log.Infof("Updating rSA0{%s}", rSA0) + log.Debugf("Updating rSA0{%s}", rSA0) if err := ns.NlHandle().XfrmStateUpdate(rSA0); err != nil { log.Warnf("Failed to update rSA0{%s}: %v", rSA0, err) } @@ -518,7 +518,7 @@ func updateNodeKey(lIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, }, }, } - log.Infof("Updating fSP{%s}", fSP1) + log.Debugf("Updating fSP{%s}", fSP1) if err := ns.NlHandle().XfrmPolicyUpdate(fSP1); err != nil { log.Warnf("Failed to update fSP{%s}: %v", fSP1, err) } @@ -533,7 +533,7 @@ func updateNodeKey(lIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, Crypt: &netlink.XfrmStateAlgo{Name: "cbc(aes)", Key: curKeys[0].value}, Limits: netlink.XfrmStateLimits{TimeHard: timeout}, } - log.Infof("Removing fSA0{%s}", fSA0) + log.Debugf("Removing fSA0{%s}", fSA0) if err := ns.NlHandle().XfrmStateUpdate(fSA0); err != nil { log.Warnf("Failed to remove fSA0{%s}: %v", fSA0, err) } @@ -553,7 +553,7 @@ func updateNodeKey(lIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, priIdx, spis = append(spis[:delIdx], spis[delIdx+1:]...) } - log.Infof("Updated: %v", spis) + log.Debugf("Updated: %v", spis) return spis } diff --git a/vendor/src/github.com/docker/libnetwork/endpoint.go b/vendor/src/github.com/docker/libnetwork/endpoint.go index 1c9bf8291c..3fa1b6dc3e 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint.go @@ -987,7 +987,7 @@ func (ep *endpoint) assignAddress(ipam ipamapi.Ipam, assignIPv4, assignIPv6 bool var err error n := ep.getNetwork() - if n.Type() == "host" || n.Type() == "null" { + if n.hasSpecialDriver() { return nil } @@ -1067,7 +1067,7 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error { func (ep *endpoint) releaseAddress() { n := ep.getNetwork() - if n.Type() == "host" || n.Type() == "null" { + if n.hasSpecialDriver() { return } diff --git a/vendor/src/github.com/docker/libnetwork/network.go b/vendor/src/github.com/docker/libnetwork/network.go index c4e988fadd..1bf003ebf3 100644 --- a/vendor/src/github.com/docker/libnetwork/network.go +++ b/vendor/src/github.com/docker/libnetwork/network.go @@ -1123,8 +1123,7 @@ func (n *network) getController() *controller { } func (n *network) ipamAllocate() error { - // For now also exclude bridge from using new ipam - if n.Type() == "host" || n.Type() == "null" { + if n.hasSpecialDriver() { return nil } @@ -1295,8 +1294,7 @@ func (n *network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error { } func (n *network) ipamRelease() { - // For now exclude host and null - if n.Type() == "host" || n.Type() == "null" { + if n.hasSpecialDriver() { return } ipam, _, err := n.getController().getIPAMDriver(n.ipamType) @@ -1504,3 +1502,8 @@ func (n *network) TableEventRegister(tableName string) error { n.driverTables = append(n.driverTables, tableName) return nil } + +// Special drivers are ones which do not need to perform any network plumbing +func (n *network) hasSpecialDriver() bool { + return n.Type() == "host" || n.Type() == "null" +} diff --git a/vendor/src/github.com/docker/libnetwork/store.go b/vendor/src/github.com/docker/libnetwork/store.go index 714d56bd5a..b622836498 100644 --- a/vendor/src/github.com/docker/libnetwork/store.go +++ b/vendor/src/github.com/docker/libnetwork/store.go @@ -464,3 +464,12 @@ func (c *controller) networkCleanup() { } } } + +var populateSpecial NetworkWalker = func(nw Network) bool { + if n := nw.(*network); n.hasSpecialDriver() { + if err := n.getController().addNetwork(n); err != nil { + log.Warnf("Failed to populate network %q with driver %q", nw.Name(), nw.Type()) + } + } + return false +} From e324ec639bb582c2b0275d15ab9aadcd80417026 Mon Sep 17 00:00:00 2001 From: Kenfe-Mickael Laventure Date: Tue, 14 Jun 2016 18:59:22 -0700 Subject: [PATCH 13/71] Fix TestRunWithRuntime* on arm Signed-off-by: Kenfe-Mickael Laventure (cherry picked from commit bd6317031bb8f7e1a82cbfd254fe60e0de703524) --- integration-cli/docker_cli_daemon_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index ac3fe75c28..b866f8ba04 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -2401,7 +2401,7 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromConfigFile(c *check.C) { } ` ioutil.WriteFile(configName, []byte(config), 0644) - err = s.d.Start("--config-file", configName) + err = s.d.StartWithBusybox("--config-file", configName) c.Assert(err, check.IsNil) // Run with default runtime @@ -2497,7 +2497,7 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromConfigFile(c *check.C) { } func (s *DockerDaemonSuite) TestRunWithRuntimeFromCommandLine(c *check.C) { - err := s.d.Start("--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager") + err := s.d.StartWithBusybox("--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager") c.Assert(err, check.IsNil) // Run with default runtime @@ -2519,7 +2519,7 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromCommandLine(c *check.C) { // Start a daemon without any extra runtimes s.d.Stop() - err = s.d.Start() + err = s.d.StartWithBusybox() c.Assert(err, check.IsNil) // Run with default runtime @@ -2546,7 +2546,7 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromCommandLine(c *check.C) { // Check that we can select a default runtime s.d.Stop() - err = s.d.Start("--default-runtime=vm", "--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager") + err = s.d.StartWithBusybox("--default-runtime=vm", "--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager") c.Assert(err, check.IsNil) out, err = s.d.Cmd("run", "--rm", "busybox", "ls") From 41d72e28c3444b97278df0b011adf7bb6764cf24 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 15 Jun 2016 17:57:59 -0400 Subject: [PATCH 14/71] Change SCALE to REPLICAS. Signed-off-by: Daniel Nephin (cherry picked from commit 4df03499486a7f62f94bd1fb8e2625b0ef932fe5) --- api/client/service/list.go | 2 +- api/client/service/scale.go | 2 +- docs/swarm/swarm-tutorial/deploy-service.md | 4 ++-- experimental/docker-stacks.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/client/service/list.go b/api/client/service/list.go index c1246c86d5..aba1a53cdb 100644 --- a/api/client/service/list.go +++ b/api/client/service/list.go @@ -71,7 +71,7 @@ func printTable(out io.Writer, services []swarm.Service) { // Ignore flushing errors defer writer.Flush() - fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "SCALE", "IMAGE", "COMMAND") + fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "REPLICAS", "IMAGE", "COMMAND") for _, service := range services { scale := "" if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil { diff --git a/api/client/service/scale.go b/api/client/service/scale.go index ae528b55d3..2962f29091 100644 --- a/api/client/service/scale.go +++ b/api/client/service/scale.go @@ -14,7 +14,7 @@ import ( func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command { return &cobra.Command{ - Use: "scale SERVICE=SCALE [SERVICE=SCALE...]", + Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]", Short: "Scale one or multiple services", Args: scaleArgs, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/docs/swarm/swarm-tutorial/deploy-service.md b/docs/swarm/swarm-tutorial/deploy-service.md index cbfc5affa7..5f32c16a60 100644 --- a/docs/swarm/swarm-tutorial/deploy-service.md +++ b/docs/swarm/swarm-tutorial/deploy-service.md @@ -39,8 +39,8 @@ example, the tutorial uses a machine named `manager1`. ``` $ docker service ls - ID NAME SCALE IMAGE COMMAND - 2zs4helqu64f helloworld 1 alpine ping docker.com + ID NAME REPLICAS IMAGE COMMAND + 2zs4helqu64f helloworld 1 alpine ping docker.com ``` ## What's next? diff --git a/experimental/docker-stacks.md b/experimental/docker-stacks.md index 31a02b6f54..83ee31654a 100644 --- a/experimental/docker-stacks.md +++ b/experimental/docker-stacks.md @@ -64,7 +64,7 @@ We can verify that services were correctly created: ```bash # docker service ls - ID NAME SCALE IMAGE + ID NAME REPLICAS IMAGE COMMAND 29bv0vnlm903 vossibility-stack_lookupd 1 nsqio/nsq@sha256:eeba05599f31eba418e96e71e0984c3dc96963ceb66924dd37a47bf7ce18a662 /nsqlookupd 4awt47624qwh vossibility-stack_nsqd 1 nsqio/nsq@sha256:eeba05599f31eba418e96e71e0984c3dc96963ceb66924dd37a47bf7ce18a662 /nsqd --data-path=/data --lookupd-tcp-address=lookupd:4160 From d2a9560e71d443b6cb75f27d158a2f215e5bbca0 Mon Sep 17 00:00:00 2001 From: Anusha Ragunathan Date: Wed, 15 Jun 2016 09:17:05 -0700 Subject: [PATCH 15/71] Avoid back and forth conversion between strings and bytes. Signed-off-by: Anusha Ragunathan (cherry picked from commit d6d448aab8e303a351b763f6a77b909fadb7344b) --- cli/error.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cli/error.go b/cli/error.go index 902d1b6e49..e421c7f7c7 100644 --- a/cli/error.go +++ b/cli/error.go @@ -1,21 +1,20 @@ package cli -import "bytes" +import "strings" // Errors is a list of errors. // Useful in a loop if you don't want to return the error right away and you want to display after the loop, // all the errors that happened during the loop. type Errors []error -func (errs Errors) Error() string { - if len(errs) < 1 { +func (errList Errors) Error() string { + if len(errList) < 1 { return "" } - var buf bytes.Buffer - buf.WriteString(errs[0].Error()) - for _, err := range errs[1:] { - buf.WriteString(", ") - buf.WriteString(err.Error()) + + out := make([]string, len(errList)) + for i := range errList { + out[i] = errList[i].Error() } - return buf.String() + return strings.Join(out, ", ") } From ad2821698757d702389f9c0841130336cc5648e5 Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Wed, 15 Jun 2016 16:25:27 -0400 Subject: [PATCH 16/71] Move mlock back into the default ungated seccomp profile Do not gate with CAP_IPC_LOCK as unprivileged use is now allowed in Linux. This returns it to how it was in 1.11. Fixes #23587 Signed-off-by: Justin Cormack (cherry picked from commit bdf01cf5deee11deb426eb33e1c8433c410084b6) --- profiles/seccomp/default.json | 15 +++++++++++++ profiles/seccomp/seccomp_default.go | 33 +++++++++++++---------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/profiles/seccomp/default.json b/profiles/seccomp/default.json index 51ec3d43bb..40af6ad3b5 100755 --- a/profiles/seccomp/default.json +++ b/profiles/seccomp/default.json @@ -686,6 +686,21 @@ "action": "SCMP_ACT_ALLOW", "args": [] }, + { + "name": "mlock", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mlock2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mlockall", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, { "name": "mmap", "action": "SCMP_ACT_ALLOW", diff --git a/profiles/seccomp/seccomp_default.go b/profiles/seccomp/seccomp_default.go index 87ae358ff5..9e8d47f929 100644 --- a/profiles/seccomp/seccomp_default.go +++ b/profiles/seccomp/seccomp_default.go @@ -721,6 +721,21 @@ func DefaultProfile(rs *specs.Spec) *types.Seccomp { Action: types.ActAllow, Args: []*types.Arg{}, }, + { + Name: "mlock", + Action: types.ActAllow, + Args: []*types.Arg{}, + }, + { + Name: "mlock2", + Action: types.ActAllow, + Args: []*types.Arg{}, + }, + { + Name: "mlockall", + Action: types.ActAllow, + Args: []*types.Arg{}, + }, { Name: "mmap", Action: types.ActAllow, @@ -1663,24 +1678,6 @@ func DefaultProfile(rs *specs.Spec) *types.Seccomp { Args: []*types.Arg{}, }, }...) - case "CAP_IPC_LOCK": - syscalls = append(syscalls, []*types.Syscall{ - { - Name: "mlock", - Action: types.ActAllow, - Args: []*types.Arg{}, - }, - { - Name: "mlock2", - Action: types.ActAllow, - Args: []*types.Arg{}, - }, - { - Name: "mlockall", - Action: types.ActAllow, - Args: []*types.Arg{}, - }, - }...) case "CAP_SYS_ADMIN": capSysAdmin = true syscalls = append(syscalls, []*types.Syscall{ From 5e41ec703d9e335269a9c1f7c8053b09d8d487ec Mon Sep 17 00:00:00 2001 From: Tom Barlow Date: Wed, 15 Jun 2016 15:41:57 +0100 Subject: [PATCH 17/71] Change 'invald' to 'invalid' in volume opts errors Signed-off-by: Tom Barlow (cherry picked from commit bf988fc6ff90e1477d3fdab79cc7239511f32aba) --- api/client/service/opts.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/service/opts.go b/api/client/service/opts.go index 58c5f94f2d..184e5c1b84 100644 --- a/api/client/service/opts.go +++ b/api/client/service/opts.go @@ -177,7 +177,7 @@ func (m *MountOpt) Set(value string) error { } if len(parts) != 2 { - return fmt.Errorf("invald field '%s' must be a key=value pair", field) + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) } key, value := parts[0], parts[1] @@ -191,14 +191,14 @@ func (m *MountOpt) Set(value string) error { case "writable": mount.Writable, err = strconv.ParseBool(value) if err != nil { - return fmt.Errorf("invald value for writable: %s", err.Error()) + return fmt.Errorf("invalid value for writable: %s", err.Error()) } case "bind-propagation": mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value)) case "volume-populate": volumeOptions().Populate, err = strconv.ParseBool(value) if err != nil { - return fmt.Errorf("invald value for populate: %s", err.Error()) + return fmt.Errorf("invalid value for populate: %s", err.Error()) } case "volume-label": setValueOnMap(volumeOptions().Labels, value) From 187b6607a4c6de1416afc052c32bb0a14ccb97a9 Mon Sep 17 00:00:00 2001 From: "Arnaud Porterie (icecrime)" Date: Wed, 15 Jun 2016 21:13:04 -0700 Subject: [PATCH 18/71] Service `--update-parallelism` defauls to 0 The `--update-parallelism` flaag should default to 0, which is interpreted by the backend as unlimited. In other words, by default all services should update simultaneously. Signed-off-by: Arnaud Porterie (icecrime) (cherry picked from commit f22d0174f33e084445c5845c95aca346e187fb02) --- api/client/service/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/service/opts.go b/api/client/service/opts.go index 184e5c1b84..c2cebf09bd 100644 --- a/api/client/service/opts.go +++ b/api/client/service/opts.go @@ -456,7 +456,7 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) { flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints") - flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously") + flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously") flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates") flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments") From c82f23095a02ea05d778843b865477f90df02244 Mon Sep 17 00:00:00 2001 From: Anusha Ragunathan Date: Wed, 15 Jun 2016 10:29:01 -0700 Subject: [PATCH 19/71] Add accept-permissions flag for install. Signed-off-by: Anusha Ragunathan (cherry picked from commit ec4857da485768db8dadda3511122d136d896dde) --- api/client/plugin/install.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index dd38bd27d3..4caf220504 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -13,21 +13,31 @@ import ( "golang.org/x/net/context" ) +type pluginOptions struct { + name string + grantPerms bool +} + func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { + var options pluginOptions cmd := &cobra.Command{ Use: "install", Short: "Install a plugin", Args: cli.RequiresMinArgs(1), // TODO: allow for set args RunE: func(cmd *cobra.Command, args []string) error { - return runInstall(dockerCli, args[0], args[1:]) + options.name = args[0] + return runInstall(dockerCli, options) }, } + flags := cmd.Flags() + flags.BoolVar(&options.grantPerms, "grant-permissions", true, "grant all permissions necessary to run the plugin") + return cmd } -func runInstall(dockerCli *client.DockerCli, name string, args []string) error { - named, err := reference.ParseNamed(name) // FIXME: validate +func runInstall(dockerCli *client.DockerCli, options pluginOptions) error { + named, err := reference.ParseNamed(options.name) // FIXME: validate if err != nil { return err } @@ -46,6 +56,6 @@ func runInstall(dockerCli *client.DockerCli, name string, args []string) error { if err != nil { return err } - // TODO: pass acceptAllPermissions and noEnable flag - return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, false, false, dockerCli.In(), dockerCli.Out()) + // TODO: pass noEnable flag + return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, options.grantPerms, false, dockerCli.In(), dockerCli.Out()) } From 3e44703caea49159a58ff3f9d5e2f5b4a40d7317 Mon Sep 17 00:00:00 2001 From: Anusha Ragunathan Date: Wed, 15 Jun 2016 10:18:55 -0700 Subject: [PATCH 20/71] Add basic integration tests for plugins. Signed-off-by: Anusha Ragunathan (cherry picked from commit a2d48c9e4e2abadcba582de47891837b0a80b24c) --- api/client/plugin/install.go | 2 +- integration-cli/docker_cli_plugins_test.go | 36 ++++++++++++++++++++++ integration-cli/requirements.go | 4 +++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 integration-cli/docker_cli_plugins_test.go diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index 4caf220504..ef890b1f90 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -31,7 +31,7 @@ func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVar(&options.grantPerms, "grant-permissions", true, "grant all permissions necessary to run the plugin") + flags.BoolVar(&options.grantPerms, "grant-all-permissions", true, "grant all permissions necessary to run the plugin") return cmd } diff --git a/integration-cli/docker_cli_plugins_test.go b/integration-cli/docker_cli_plugins_test.go new file mode 100644 index 0000000000..ad031b7ff6 --- /dev/null +++ b/integration-cli/docker_cli_plugins_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/docker/docker/pkg/integration/checker" + "github.com/go-check/check" +) + +func (s *DockerSuite) TestPluginBasicOps(c *check.C) { + testRequires(c, DaemonIsLinux, ExperimentalDaemon) + name := "tiborvass/no-remove" + tag := "latest" + nameWithTag := name + ":" + tag + + _, _, err := dockerCmdWithError("plugin", "install", name) + c.Assert(err, checker.IsNil) + + out, _, err := dockerCmdWithError("plugin", "ls") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name) + c.Assert(out, checker.Contains, tag) + c.Assert(out, checker.Contains, "true") + + out, _, err = dockerCmdWithError("plugin", "inspect", nameWithTag) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, "A test plugin for Docker") + + out, _, err = dockerCmdWithError("plugin", "remove", nameWithTag) + c.Assert(out, checker.Contains, "is active") + + _, _, err = dockerCmdWithError("plugin", "disable", nameWithTag) + c.Assert(err, checker.IsNil) + + out, _, err = dockerCmdWithError("plugin", "remove", nameWithTag) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, nameWithTag) +} diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 70275e9cd8..56a1596c2d 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -30,6 +30,10 @@ var ( func() bool { return daemonPlatform == "linux" }, "Test requires a Linux daemon", } + ExperimentalDaemon = testRequirement{ + func() bool { return utils.ExperimentalBuild() }, + "Test requires an experimental daemon", + } NotExperimentalDaemon = testRequirement{ func() bool { return !utils.ExperimentalBuild() }, "Test requires a non experimental daemon", From 979ad079254df3cdb2264f5374b79c7f20b27785 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Thu, 16 Jun 2016 13:07:06 +0100 Subject: [PATCH 21/71] Remove out-of-date health test The test was waiting for the container to exit after failing its healthcheck. However, we no longer automatically terminate containers, so this waited instead for the container to time-out by itself. Signed-off-by: Thomas Leonard (cherry picked from commit 18a59bb8691c6f6f6a7a86f56bbcfc5418509ed8) --- integration-cli/docker_cli_health_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_cli_health_test.go b/integration-cli/docker_cli_health_test.go index b374dba357..d231965332 100644 --- a/integration-cli/docker_cli_health_test.go +++ b/integration-cli/docker_cli_health_test.go @@ -127,12 +127,10 @@ func (s *DockerSuite) TestHealth(c *check.C) { c.Check(last.ExitCode, checker.Equals, 0) c.Check(last.Output, checker.Equals, "OK\n") - // Fail the check, which should now make it exit + // Fail the check dockerCmd(c, "exec", "fatal_healthcheck", "rm", "/status") - waitForStatus(c, "fatal_healthcheck", "running", "exited") + waitForHealthStatus(c, "fatal_healthcheck", "healthy", "unhealthy") - out, _ = dockerCmd(c, "inspect", "--format={{.State.Health.Status}}", "fatal_healthcheck") - c.Check(out, checker.Equals, "unhealthy\n") failsStr, _ := dockerCmd(c, "inspect", "--format={{.State.Health.FailingStreak}}", "fatal_healthcheck") fails, err := strconv.Atoi(strings.TrimSpace(failsStr)) c.Check(err, check.IsNil) From af3b1c7370a8bae2d1ee1162b2feb5482270521e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 16 Jun 2016 11:33:12 +0000 Subject: [PATCH 22/71] advisory can't be in the [menu] section Signed-off-by: Sven Dowideit (cherry picked from commit bc033cb706fd22e3934968b0dfdf93da962e36a8) --- docs/swarm/index.md | 2 +- docs/swarm/key-concepts.md | 2 +- docs/swarm/menu.md | 2 +- docs/swarm/swarm-tutorial/add-nodes.md | 2 +- docs/swarm/swarm-tutorial/create-swarm.md | 2 +- docs/swarm/swarm-tutorial/delete-service.md | 2 +- docs/swarm/swarm-tutorial/deploy-service.md | 2 +- docs/swarm/swarm-tutorial/drain-node.md | 1 + docs/swarm/swarm-tutorial/index.md | 2 +- docs/swarm/swarm-tutorial/inspect-service.md | 2 +- docs/swarm/swarm-tutorial/menu.md | 2 +- docs/swarm/swarm-tutorial/rolling-update.md | 2 +- docs/swarm/swarm-tutorial/scale-service.md | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/swarm/index.md b/docs/swarm/index.md index abfd033502..ebf3168a33 100644 --- a/docs/swarm/index.md +++ b/docs/swarm/index.md @@ -3,11 +3,11 @@ title = "Swarm overview" description = "Docker Swarm overview" keywords = ["docker, container, cluster, swarm"] +advisory = "rc" [menu.main] identifier="swarm_overview" parent="engine_swarm" weight="1" -advisory = "rc" +++ # Docker Swarm overview diff --git a/docs/swarm/key-concepts.md b/docs/swarm/key-concepts.md index 34d7ce1f5d..814e2be17f 100644 --- a/docs/swarm/key-concepts.md +++ b/docs/swarm/key-concepts.md @@ -3,11 +3,11 @@ title = "Swarm key concepts" description = "Introducing key concepts for Docker Swarm" keywords = ["docker, container, cluster, swarm"] +advisory = "rc" [menu.main] identifier="swarm-concepts" parent="engine_swarm" weight="2" -advisory = "rc" +++ # Docker Swarm key concepts diff --git a/docs/swarm/menu.md b/docs/swarm/menu.md index df43027f2f..7864943325 100644 --- a/docs/swarm/menu.md +++ b/docs/swarm/menu.md @@ -3,11 +3,11 @@ title = "Manage a Swarm (1.12 RC)" description = "How to use Docker Swarm to create and manage Docker Engine clusters" keywords = [" docker, documentation, developer, "] +advisory = "rc" [menu.main] identifier = "engine_swarm" parent = "engine_use" weight = 0 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/add-nodes.md b/docs/swarm/swarm-tutorial/add-nodes.md index d14c07698c..e052879ec0 100644 --- a/docs/swarm/swarm-tutorial/add-nodes.md +++ b/docs/swarm/swarm-tutorial/add-nodes.md @@ -3,11 +3,11 @@ title = "Add nodes to the Swarm" description = "Add nodes to the Swarm" keywords = ["tutorial, cluster management, swarm"] +advisory = "rc" [menu.main] identifier="add-nodes" parent="swarm-tutorial" weight=13 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/create-swarm.md b/docs/swarm/swarm-tutorial/create-swarm.md index 2f55e8c339..6f82a4de18 100644 --- a/docs/swarm/swarm-tutorial/create-swarm.md +++ b/docs/swarm/swarm-tutorial/create-swarm.md @@ -3,11 +3,11 @@ title = "Create a Swarm" description = "Initialize the Swarm" keywords = ["tutorial, cluster management, swarm"] +advisory = "rc" [menu.main] identifier="initialize-swarm" parent="swarm-tutorial" weight=12 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/delete-service.md b/docs/swarm/swarm-tutorial/delete-service.md index d037796ac2..8e74ef84f6 100644 --- a/docs/swarm/swarm-tutorial/delete-service.md +++ b/docs/swarm/swarm-tutorial/delete-service.md @@ -3,11 +3,11 @@ title = "Delete the service" description = "Remove the service on the Swarm" keywords = ["tutorial, cluster management, swarm, service"] +advisory = "rc" [menu.main] identifier="swarm-tutorial-delete-service" parent="swarm-tutorial" weight=19 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/deploy-service.md b/docs/swarm/swarm-tutorial/deploy-service.md index 5f32c16a60..f6f0e08932 100644 --- a/docs/swarm/swarm-tutorial/deploy-service.md +++ b/docs/swarm/swarm-tutorial/deploy-service.md @@ -3,11 +3,11 @@ title = "Deploy a service" description = "Deploy the application" keywords = ["tutorial, cluster management, swarm"] +advisory = "rc" [menu.main] identifier="deploy-application" parent="swarm-tutorial" weight=16 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/drain-node.md b/docs/swarm/swarm-tutorial/drain-node.md index 4299788462..8b8fb4ff03 100644 --- a/docs/swarm/swarm-tutorial/drain-node.md +++ b/docs/swarm/swarm-tutorial/drain-node.md @@ -3,6 +3,7 @@ title = "Drain a node" description = "Drain nodes on the Swarm" keywords = ["tutorial, cluster management, swarm, service, drain"] +advisory = "rc" [menu.main] identifier="swarm-tutorial-drain-node" parent="swarm-tutorial" diff --git a/docs/swarm/swarm-tutorial/index.md b/docs/swarm/swarm-tutorial/index.md index 4d4fdb07b9..366a574b46 100644 --- a/docs/swarm/swarm-tutorial/index.md +++ b/docs/swarm/swarm-tutorial/index.md @@ -3,11 +3,11 @@ title = "Set up for the tutorial" description = "Getting Started tutorial for Docker Swarm" keywords = ["tutorial, cluster management, swarm"] +advisory = "rc" [menu.main] identifier="tutorial-setup" parent="swarm-tutorial" weight=11 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/inspect-service.md b/docs/swarm/swarm-tutorial/inspect-service.md index a5d4455141..c0ca3f5c1a 100644 --- a/docs/swarm/swarm-tutorial/inspect-service.md +++ b/docs/swarm/swarm-tutorial/inspect-service.md @@ -3,11 +3,11 @@ title = "Inspect the service" description = "Inspect the application" keywords = ["tutorial, cluster management, swarm"] +advisory = "rc" [menu.main] identifier="inspect-application" parent="swarm-tutorial" weight=17 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/menu.md b/docs/swarm/swarm-tutorial/menu.md index aec86ae87d..dd3721ef4e 100644 --- a/docs/swarm/swarm-tutorial/menu.md +++ b/docs/swarm/swarm-tutorial/menu.md @@ -3,11 +3,11 @@ title = "Get started with Swarm" description = "Getting started tutorial for Docker Swarm" keywords = ["cluster, swarm, tutorial"] +advisory = "rc" [menu.main] identifier="swarm-tutorial" parent="engine_swarm" weight=10 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/rolling-update.md b/docs/swarm/swarm-tutorial/rolling-update.md index 58ddaad95f..a7b072a875 100644 --- a/docs/swarm/swarm-tutorial/rolling-update.md +++ b/docs/swarm/swarm-tutorial/rolling-update.md @@ -3,11 +3,11 @@ title = "Apply rolling updates" description = "Apply rolling updates to a service on the Swarm" keywords = ["tutorial, cluster management, swarm, service, rolling-update"] +advisory = "rc" [menu.main] identifier="swarm-tutorial-rolling-update" parent="swarm-tutorial" weight=20 -advisory = "rc" +++ diff --git a/docs/swarm/swarm-tutorial/scale-service.md b/docs/swarm/swarm-tutorial/scale-service.md index 29fe65c2d1..2228e2eb00 100644 --- a/docs/swarm/swarm-tutorial/scale-service.md +++ b/docs/swarm/swarm-tutorial/scale-service.md @@ -3,11 +3,11 @@ title = "Scale the service" description = "Scale the service running in the Swarm" keywords = ["tutorial, cluster management, swarm, scale"] +advisory = "rc" [menu.main] identifier="swarm-tutorial-scale-service" parent="swarm-tutorial" weight=18 -advisory = "rc" +++ From 2401c0223df828226df013902d1ef7b33f90002a Mon Sep 17 00:00:00 2001 From: Adrian Moisey Date: Wed, 15 Jun 2016 12:38:02 +0200 Subject: [PATCH 23/71] Fix URL to issue Signed-off-by: Adrian Moisey (cherry picked from commit 88d4da7f422a4ac341efa85541271b1c0e9bcfa3) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c7c8a055..35b402b48c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -214,7 +214,7 @@ be found. ### Misc -+ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/c)) ++ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/21385)) + Support for building the Docker cli for OpenBSD was added ([#21325](https://github.com/docker/docker/pull/21325)) + Labels can now be applied at network, volume and image creation ([#21270](https://github.com/docker/docker/pull/21270)) * The `dockremap` is now created as a system user ([#21266](https://github.com/docker/docker/pull/21266)) From bb8996d62b1e60f1d7a1e3a079815b35d03c7986 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 15 Jun 2016 13:11:23 -0400 Subject: [PATCH 24/71] Fix String() for some option types and add unit tests for them. Signed-off-by: Daniel Nephin (cherry picked from commit c26e7d8c9105e16048268a01dd1e556af43902cd) --- api/client/service/opts.go | 11 +-- api/client/service/opts_test.go | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 api/client/service/opts_test.go diff --git a/api/client/service/opts.go b/api/client/service/opts.go index c2cebf09bd..54b6582c13 100644 --- a/api/client/service/opts.go +++ b/api/client/service/opts.go @@ -28,7 +28,7 @@ type int64Value interface { type memBytes int64 func (m *memBytes) String() string { - return strconv.FormatInt(m.Value(), 10) + return units.BytesSize(float64(m.Value())) } func (m *memBytes) Set(value string) error { @@ -48,7 +48,7 @@ func (m *memBytes) Value() int64 { type nanoCPUs int64 func (c *nanoCPUs) String() string { - return strconv.FormatInt(c.Value(), 10) + return big.NewRat(c.Value(), 1e9).FloatString(3) } func (c *nanoCPUs) Set(value string) error { @@ -191,14 +191,14 @@ func (m *MountOpt) Set(value string) error { case "writable": mount.Writable, err = strconv.ParseBool(value) if err != nil { - return fmt.Errorf("invalid value for writable: %s", err.Error()) + return fmt.Errorf("invalid value for writable: %s", value) } case "bind-propagation": mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value)) case "volume-populate": volumeOptions().Populate, err = strconv.ParseBool(value) if err != nil { - return fmt.Errorf("invalid value for populate: %s", err.Error()) + return fmt.Errorf("invalid value for populate: %s", value) } case "volume-label": setValueOnMap(volumeOptions().Labels, value) @@ -235,7 +235,8 @@ func (m *MountOpt) Type() string { func (m *MountOpt) String() string { mounts := []string{} for _, mount := range m.values { - mounts = append(mounts, fmt.Sprintf("%v", mount)) + repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) + mounts = append(mounts, repr) } return strings.Join(mounts, ", ") } diff --git a/api/client/service/opts_test.go b/api/client/service/opts_test.go new file mode 100644 index 0000000000..189e7c11cd --- /dev/null +++ b/api/client/service/opts_test.go @@ -0,0 +1,137 @@ +package service + +import ( + "strings" + "testing" + "time" + + "github.com/docker/engine-api/types/swarm" +) + +func assertEqual(t *testing.T, actual, expected interface{}) { + if expected != actual { + t.Fatalf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) + } +} + +func assertNilError(t *testing.T, err error) { + if err != nil { + t.Fatalf("Expected no error, got: %s", err.Error()) + } +} + +func assertError(t *testing.T, err error, contains string) { + if err == nil { + t.Fatalf("Expected an error, but error was nil") + } + + if !strings.Contains(err.Error(), contains) { + t.Fatalf("Expected error to contain '%s', got '%s'", contains, err.Error()) + } +} + +func TestMemBytesString(t *testing.T) { + var mem memBytes = 1048576 + assertEqual(t, mem.String(), "1 MiB") +} + +func TestMemBytesSetAndValue(t *testing.T) { + var mem memBytes + assertNilError(t, mem.Set("5kb")) + assertEqual(t, mem.Value(), int64(5120)) +} + +func TestNanoCPUsString(t *testing.T) { + var cpus nanoCPUs = 6100000000 + assertEqual(t, cpus.String(), "6.100") +} + +func TestNanoCPUsSetAndValue(t *testing.T) { + var cpus nanoCPUs + assertNilError(t, cpus.Set("0.35")) + assertEqual(t, cpus.Value(), int64(350000000)) +} + +func TestDurationOptString(t *testing.T) { + dur := time.Duration(300 * 10e8) + duration := DurationOpt{value: &dur} + assertEqual(t, duration.String(), "5m0s") +} + +func TestDurationOptSetAndValue(t *testing.T) { + var duration DurationOpt + assertNilError(t, duration.Set("300s")) + assertEqual(t, *duration.Value(), time.Duration(300*10e8)) +} + +func TestUint64OptString(t *testing.T) { + value := uint64(2345678) + opt := Uint64Opt{value: &value} + assertEqual(t, opt.String(), "2345678") + + opt = Uint64Opt{} + assertEqual(t, opt.String(), "none") +} + +func TestUint64OptSetAndValue(t *testing.T) { + var opt Uint64Opt + assertNilError(t, opt.Set("14445")) + assertEqual(t, *opt.Value(), uint64(14445)) +} + +func TestMountOptString(t *testing.T) { + mount := MountOpt{ + values: []swarm.Mount{ + { + Type: swarm.MountType("BIND"), + Source: "/home/path", + Target: "/target", + }, + { + Type: swarm.MountType("VOLUME"), + Source: "foo", + Target: "/target/foo", + }, + }, + } + expected := "BIND /home/path /target, VOLUME foo /target/foo" + assertEqual(t, mount.String(), expected) +} + +func TestMountOptSetNoError(t *testing.T) { + var mount MountOpt + assertNilError(t, mount.Set("type=bind,target=/target,source=/foo")) + + mounts := mount.Value() + assertEqual(t, len(mounts), 1) + assertEqual(t, mounts[0], swarm.Mount{ + Type: swarm.MountType("BIND"), + Source: "/foo", + Target: "/target", + }) +} + +func TestMountOptSetErrorNoType(t *testing.T) { + var mount MountOpt + assertError(t, mount.Set("target=/target,source=/foo"), "type is required") +} + +func TestMountOptSetErrorNoTarget(t *testing.T) { + var mount MountOpt + assertError(t, mount.Set("type=VOLUME,source=/foo"), "target is required") +} + +func TestMountOptSetErrorInvalidKey(t *testing.T) { + var mount MountOpt + assertError(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'") +} + +func TestMountOptSetErrorInvalidField(t *testing.T) { + var mount MountOpt + assertError(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'") +} + +func TestMountOptSetErrorInvalidWritable(t *testing.T) { + var mount MountOpt + assertError(t, mount.Set("type=VOLUME,writable=yes"), "invalid value for writable: yes") +} From dd5573bc60ba5714cb0519a2878a26f8e942c12b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Jun 2016 17:55:06 +0200 Subject: [PATCH 25/71] Update release script installation instructions Instructions for installing the static binaries has changed, so updated the instructions. The comment on top already requires the steps to be executed as root, so removing the 'sudo' Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e233c8dd7208b358b950731a6ba93cf9dcb80eb5) --- hack/release.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hack/release.sh b/hack/release.sh index bc33f703f4..9ed3aeea4a 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -272,10 +272,11 @@ release_binaries() { # TODO create redirect from builds/*/i686 to builds/*/i386 cat < Date: Thu, 16 Jun 2016 11:46:04 -0700 Subject: [PATCH 26/71] Set systemd KillMode Change the kill mode to process so that systemd does not kill container processes when the daemon is shutdown but only the docker daemon Signed-off-by: Michael Crosby (cherry picked from commit db435f526ac5703276ad1add28188c0c8c6e4c2a) --- contrib/init/systemd/docker.service | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/init/systemd/docker.service b/contrib/init/systemd/docker.service index 9b0ce63ebc..92d72467e2 100644 --- a/contrib/init/systemd/docker.service +++ b/contrib/init/systemd/docker.service @@ -20,6 +20,8 @@ LimitCORE=infinity TimeoutStartSec=0 # set delegate yes so that systemd does not reset the cgroups of docker containers Delegate=yes +# kill only the docker process, not all processes in the cgroup +KillMode=process [Install] WantedBy=multi-user.target From 7cc76facbabadfc73f789d4c693f441210851f0a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jun 2016 14:30:54 -0700 Subject: [PATCH 27/71] fix docker swarm init/update --secret Signed-off-by: Victor Vieux (cherry picked from commit 085895342c7f797f0eef7ec956438e33489f211f) --- api/client/container/restart.go | 3 +- api/client/container/stop.go | 3 +- api/client/swarm/init.go | 15 ++-- api/client/swarm/opts.go | 2 +- api/client/swarm/update.go | 16 ++-- daemon/cluster/cluster.go | 6 +- daemon/cluster/convert/swarm.go | 41 +++++++-- hack/vendor.sh | 2 +- integration-cli/daemon_swarm.go | 21 +++-- .../engine-api/client/container_restart.go | 6 +- .../engine-api/client/container_stop.go | 6 +- .../docker/engine-api/client/interface.go | 87 +++++++++++++------ .../client/interface_experimental.go | 13 +++ .../engine-api/types/container/host_config.go | 2 +- .../docker/engine-api/types/swarm/swarm.go | 2 +- 15 files changed, 159 insertions(+), 66 deletions(-) diff --git a/api/client/container/restart.go b/api/client/container/restart.go index 98618b5073..04544e4b97 100644 --- a/api/client/container/restart.go +++ b/api/client/container/restart.go @@ -41,7 +41,8 @@ func runRestart(dockerCli *client.DockerCli, opts *restartOptions) error { ctx := context.Background() var errs []string for _, name := range opts.containers { - if err := dockerCli.Client().ContainerRestart(ctx, name, time.Duration(opts.nSeconds)*time.Second); err != nil { + timeout := time.Duration(opts.nSeconds) * time.Second + if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil { errs = append(errs, err.Error()) } else { fmt.Fprintf(dockerCli.Out(), "%s\n", name) diff --git a/api/client/container/stop.go b/api/client/container/stop.go index 40b02b73d4..b05b0e3dfe 100644 --- a/api/client/container/stop.go +++ b/api/client/container/stop.go @@ -43,7 +43,8 @@ func runStop(dockerCli *client.DockerCli, opts *stopOptions) error { var errs []string for _, container := range opts.containers { - if err := dockerCli.Client().ContainerStop(ctx, container, time.Duration(opts.time)*time.Second); err != nil { + timeout := time.Duration(opts.time) * time.Second + if err := dockerCli.Client().ContainerStop(ctx, container, &timeout); err != nil { errs = append(errs, err.Error()) } else { fmt.Fprintf(dockerCli.Out(), "%s\n", container) diff --git a/api/client/swarm/init.go b/api/client/swarm/init.go index 0c66246390..d16c09793b 100644 --- a/api/client/swarm/init.go +++ b/api/client/swarm/init.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/cli" "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) type initOptions struct { @@ -19,6 +20,7 @@ type initOptions struct { } func newInitCommand(dockerCli *client.DockerCli) *cobra.Command { + var flags *pflag.FlagSet opts := initOptions{ listenAddr: NewNodeAddrOption(), autoAccept: NewAutoAcceptOption(), @@ -29,11 +31,11 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command { Short: "Initialize a Swarm.", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runInit(dockerCli, opts) + return runInit(dockerCli, flags, opts) }, } - flags := cmd.Flags() + flags = cmd.Flags() flags.Var(&opts.listenAddr, "listen-addr", "Listen address") flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager, or none)") flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster") @@ -41,7 +43,7 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command { return cmd } -func runInit(dockerCli *client.DockerCli, opts initOptions) error { +func runInit(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts initOptions) error { client := dockerCli.Client() ctx := context.Background() @@ -50,8 +52,11 @@ func runInit(dockerCli *client.DockerCli, opts initOptions) error { ForceNewCluster: opts.forceNewCluster, } - req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(opts.secret) - + if flags.Changed("secret") { + req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret) + } else { + req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil) + } nodeID, err := client.SwarmInit(ctx, req) if err != nil { return err diff --git a/api/client/swarm/opts.go b/api/client/swarm/opts.go index fa543b6596..741f0cf8e8 100644 --- a/api/client/swarm/opts.go +++ b/api/client/swarm/opts.go @@ -102,7 +102,7 @@ func (o *AutoAcceptOption) Type() string { } // Policies returns a representation of this option for the api -func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy { +func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy { policies := []swarm.Policy{} for _, p := range defaultPolicies { if len(o.values) != 0 { diff --git a/api/client/swarm/update.go b/api/client/swarm/update.go index 0a851a632a..a26b0d59f2 100644 --- a/api/client/swarm/update.go +++ b/api/client/swarm/update.go @@ -54,6 +54,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOpt if err != nil { return err } + err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec) if err != nil { return err @@ -68,18 +69,17 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error { if flags.Changed("auto-accept") { value := flags.Lookup("auto-accept").Value.(*AutoAcceptOption) - if len(spec.AcceptancePolicy.Policies) > 0 { - spec.AcceptancePolicy.Policies = value.Policies(spec.AcceptancePolicy.Policies[0].Secret) - } else { - spec.AcceptancePolicy.Policies = value.Policies("") - } + spec.AcceptancePolicy.Policies = value.Policies(nil) } + var psecret *string if flags.Changed("secret") { secret, _ := flags.GetString("secret") - for _, policy := range spec.AcceptancePolicy.Policies { - policy.Secret = secret - } + psecret = &secret + } + + for i := range spec.AcceptancePolicy.Policies { + spec.AcceptancePolicy.Policies[i].Secret = psecret } if flags.Changed("task-history-limit") { diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index d2f972e721..b8e56432a3 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -444,12 +444,12 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error { return ErrNoManager } - swarmSpec, err := convert.SwarmSpecToGRPC(spec) + swarm, err := getSwarm(c.getRequestContext(), c.client) if err != nil { return err } - swarm, err := getSwarm(c.getRequestContext(), c.client) + swarmSpec, err := convert.SwarmSpecToGRPCandMerge(spec, &swarm.Spec) if err != nil { return err } @@ -1030,7 +1030,7 @@ func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.Acceptan } spec := &cluster.Spec - if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy); err != nil { + if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy, nil); err != nil { return fmt.Errorf("error updating cluster settings: %v", err) } _, err := client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{ diff --git a/daemon/cluster/convert/swarm.go b/daemon/cluster/convert/swarm.go index cb9d7d0821..84ed73c0c1 100644 --- a/daemon/cluster/convert/swarm.go +++ b/daemon/cluster/convert/swarm.go @@ -49,7 +49,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm { Autoaccept: policy.Autoaccept, } if policy.Secret != nil { - p.Secret = string(policy.Secret.Data) + secret := string(policy.Secret.Data) + p.Secret = &secret } swarm.Spec.AcceptancePolicy.Policies = append(swarm.Spec.AcceptancePolicy.Policies, p) } @@ -57,8 +58,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm { return swarm } -// SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec. -func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) { +// SwarmSpecToGRPCandMerge converts a Spec to a grpc ClusterSpec and merge AcceptancePolicy from an existing grpc ClusterSpec if provided. +func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (swarmapi.ClusterSpec, error) { spec := swarmapi.ClusterSpec{ Annotations: swarmapi.Annotations{ Name: s.Name, @@ -82,14 +83,15 @@ func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) { }, } - if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy); err != nil { + if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil { return swarmapi.ClusterSpec{}, err } + return spec, nil } // SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy. -func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy) error { +func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error { spec.AcceptancePolicy.Policies = nil for _, p := range acceptancePolicy.Policies { role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))] @@ -102,11 +104,20 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic Autoaccept: p.Autoaccept, } - if p.Secret != "" { - hashPwd, _ := bcrypt.GenerateFromPassword([]byte(p.Secret), 0) + if p.Secret != nil { + if *p.Secret == "" { // if provided secret is empty, it means erase previous secret. + policy.Secret = nil + } else { // if provided secret is not empty, we generate a new one. + hashPwd, _ := bcrypt.GenerateFromPassword([]byte(*p.Secret), 0) + policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{ + Data: hashPwd, + Alg: "bcrypt", + } + } + } else if oldSecret := getOldSecret(oldSpec, policy.Role); oldSecret != nil { // else use the old one. policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{ - Data: hashPwd, - Alg: "bcrypt", + Data: oldSecret.Data, + Alg: oldSecret.Alg, } } @@ -114,3 +125,15 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic } return nil } + +func getOldSecret(oldSpec *swarmapi.ClusterSpec, role swarmapi.NodeRole) *swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret { + if oldSpec == nil { + return nil + } + for _, p := range oldSpec.AcceptancePolicy.Policies { + if p.Role == role { + return p.Secret + } + } + return nil +} diff --git a/hack/vendor.sh b/hack/vendor.sh index 3366ef3a65..3d4490dd90 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -60,7 +60,7 @@ clone git golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://gith clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d -clone git github.com/docker/engine-api f3b5ad20d4576de14c96603db522dec530d03f62 +clone git github.com/docker/engine-api f50fbe5f9c4c8eeed591549d2c8187f4076f3717 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 diff --git a/integration-cli/daemon_swarm.go b/integration-cli/daemon_swarm.go index 0b553d34f8..526ac21e27 100644 --- a/integration-cli/daemon_swarm.go +++ b/integration-cli/daemon_swarm.go @@ -26,11 +26,16 @@ func (d *SwarmDaemon) Init(autoAccept map[string]bool, secret string) error { ListenAddr: d.listenAddr, } for _, role := range []swarm.NodeRole{swarm.NodeRoleManager, swarm.NodeRoleWorker} { - req.Spec.AcceptancePolicy.Policies = append(req.Spec.AcceptancePolicy.Policies, swarm.Policy{ + policy := swarm.Policy{ Role: role, Autoaccept: autoAccept[strings.ToLower(string(role))], - Secret: secret, - }) + } + + if secret != "" { + policy.Secret = &secret + } + + req.Spec.AcceptancePolicy.Policies = append(req.Spec.AcceptancePolicy.Policies, policy) } status, out, err := d.SockRequest("POST", "/swarm/init", req) if status != http.StatusOK { @@ -49,13 +54,17 @@ func (d *SwarmDaemon) Init(autoAccept map[string]bool, secret string) error { // Join joins a current daemon with existing cluster. func (d *SwarmDaemon) Join(remoteAddr, secret, cahash string, manager bool) error { - status, out, err := d.SockRequest("POST", "/swarm/join", swarm.JoinRequest{ + req := swarm.JoinRequest{ ListenAddr: d.listenAddr, RemoteAddrs: []string{remoteAddr}, Manager: manager, - Secret: secret, CACertHash: cahash, - }) + } + + if secret != "" { + req.Secret = secret + } + status, out, err := d.SockRequest("POST", "/swarm/join", req) if status != http.StatusOK { return fmt.Errorf("joining swarm: invalid statuscode %v, %q", status, out) } diff --git a/vendor/src/github.com/docker/engine-api/client/container_restart.go b/vendor/src/github.com/docker/engine-api/client/container_restart.go index 9ae2e4b2bb..93c042d085 100644 --- a/vendor/src/github.com/docker/engine-api/client/container_restart.go +++ b/vendor/src/github.com/docker/engine-api/client/container_restart.go @@ -11,9 +11,11 @@ import ( // ContainerRestart stops and starts a container again. // It makes the daemon to wait for the container to be up again for // a specific amount of time, given the timeout. -func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout time.Duration) error { +func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error { query := url.Values{} - query.Set("t", timetypes.DurationToSecondsString(timeout)) + if timeout != nil { + query.Set("t", timetypes.DurationToSecondsString(*timeout)) + } resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil) ensureReaderClosed(resp) return err diff --git a/vendor/src/github.com/docker/engine-api/client/container_stop.go b/vendor/src/github.com/docker/engine-api/client/container_stop.go index 890650d69a..1fc577f2b9 100644 --- a/vendor/src/github.com/docker/engine-api/client/container_stop.go +++ b/vendor/src/github.com/docker/engine-api/client/container_stop.go @@ -10,9 +10,11 @@ import ( // ContainerStop stops a container without terminating the process. // The process is blocked until the container stops or the timeout expires. -func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout time.Duration) error { +func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error { query := url.Values{} - query.Set("t", timetypes.DurationToSecondsString(timeout)) + if timeout != nil { + query.Set("t", timetypes.DurationToSecondsString(*timeout)) + } resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil) ensureReaderClosed(resp) return err diff --git a/vendor/src/github.com/docker/engine-api/client/interface.go b/vendor/src/github.com/docker/engine-api/client/interface.go index 95d775561f..7fb04aced3 100644 --- a/vendor/src/github.com/docker/engine-api/client/interface.go +++ b/vendor/src/github.com/docker/engine-api/client/interface.go @@ -15,26 +15,21 @@ import ( // CommonAPIClient is the common methods between stable and experimental versions of APIClient. type CommonAPIClient interface { + ContainerAPIClient + ImageAPIClient + NodeAPIClient + NetworkAPIClient + ServiceAPIClient + SwarmAPIClient + SystemAPIClient + VolumeAPIClient ClientVersion() string - CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error - CheckpointDelete(ctx context.Context, container string, checkpointID string) error - CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) - SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) - SwarmJoin(ctx context.Context, req swarm.JoinRequest) error - SwarmLeave(ctx context.Context, force bool) error - SwarmInspect(ctx context.Context) (swarm.Swarm, error) - SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec) error - NodeInspect(ctx context.Context, nodeID string) (swarm.Node, error) - NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) - NodeRemove(ctx context.Context, nodeID string) error - NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error - ServiceCreate(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error) - ServiceInspect(ctx context.Context, serviceID string) (swarm.Service, error) - ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) - ServiceRemove(ctx context.Context, serviceID string) error - ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec) error - TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) - TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) + ServerVersion(ctx context.Context) (types.Version, error) + UpdateClientVersion(v string) +} + +// ContainerAPIClient defines API client methods for the containers +type ContainerAPIClient interface { ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) @@ -54,18 +49,21 @@ type CommonAPIClient interface { ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error ContainerRename(ctx context.Context, container, newContainerName string) error ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error - ContainerRestart(ctx context.Context, container string, timeout time.Duration) error + ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error) ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error) ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error - ContainerStop(ctx context.Context, container string, timeout time.Duration) error + ContainerStop(ctx context.Context, container string, timeout *time.Duration) error ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error) ContainerUnpause(ctx context.Context, container string) error ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) error ContainerWait(ctx context.Context, container string) (int, error) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error - Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) +} + +// ImageAPIClient defines API client methods for the images +type ImageAPIClient interface { ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error) @@ -79,7 +77,10 @@ type CommonAPIClient interface { ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) ImageTag(ctx context.Context, image, ref string) error - Info(ctx context.Context) (types.Info, error) +} + +// NetworkAPIClient defines API client methods for the networks +type NetworkAPIClient interface { NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error @@ -87,9 +88,45 @@ type CommonAPIClient interface { NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(ctx context.Context, networkID string) error +} + +// NodeAPIClient defines API client methods for the nodes +type NodeAPIClient interface { + NodeInspect(ctx context.Context, nodeID string) (swarm.Node, error) + NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) + NodeRemove(ctx context.Context, nodeID string) error + NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error +} + +// ServiceAPIClient defines API client methods for the services +type ServiceAPIClient interface { + ServiceCreate(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error) + ServiceInspect(ctx context.Context, serviceID string) (swarm.Service, error) + ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) + ServiceRemove(ctx context.Context, serviceID string) error + ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec) error + TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) + TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) +} + +// SwarmAPIClient defines API client methods for the swarm +type SwarmAPIClient interface { + SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) + SwarmJoin(ctx context.Context, req swarm.JoinRequest) error + SwarmLeave(ctx context.Context, force bool) error + SwarmInspect(ctx context.Context) (swarm.Swarm, error) + SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec) error +} + +// SystemAPIClient defines API client methods for the system +type SystemAPIClient interface { + Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) + Info(ctx context.Context) (types.Info, error) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) - ServerVersion(ctx context.Context) (types.Version, error) - UpdateClientVersion(v string) +} + +// VolumeAPIClient defines API client methods for the volumes +type VolumeAPIClient interface { VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) diff --git a/vendor/src/github.com/docker/engine-api/client/interface_experimental.go b/vendor/src/github.com/docker/engine-api/client/interface_experimental.go index c968e530bf..a63ae70bb6 100644 --- a/vendor/src/github.com/docker/engine-api/client/interface_experimental.go +++ b/vendor/src/github.com/docker/engine-api/client/interface_experimental.go @@ -12,6 +12,19 @@ import ( // APIClient is an interface that clients that talk with a docker server must implement. type APIClient interface { CommonAPIClient + CheckpointAPIClient + PluginAPIClient +} + +// CheckpointAPIClient defines API client methods for the checkpoints +type CheckpointAPIClient interface { + CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error + CheckpointDelete(ctx context.Context, container string, checkpointID string) error + CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) +} + +// PluginAPIClient defines API client methods for the plugins +type PluginAPIClient interface { PluginList(ctx context.Context) (types.PluginsListResponse, error) PluginRemove(ctx context.Context, name string) error PluginEnable(ctx context.Context, name string) error diff --git a/vendor/src/github.com/docker/engine-api/types/container/host_config.go b/vendor/src/github.com/docker/engine-api/types/container/host_config.go index 43c33434c8..8e653fcf8d 100644 --- a/vendor/src/github.com/docker/engine-api/types/container/host_config.go +++ b/vendor/src/github.com/docker/engine-api/types/container/host_config.go @@ -303,7 +303,7 @@ type HostConfig struct { PublishAllPorts bool // Should docker publish all exposed port for the container ReadonlyRootfs bool // Is the container root filesystem in read-only SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. - StorageOpt map[string]string // Storage driver options per container. + StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container UTSMode UTSMode // UTS namespace to use for the container UsernsMode UsernsMode // The user namespace to use for the container diff --git a/vendor/src/github.com/docker/engine-api/types/swarm/swarm.go b/vendor/src/github.com/docker/engine-api/types/swarm/swarm.go index 052b6b3a7f..27a0ab6d14 100644 --- a/vendor/src/github.com/docker/engine-api/types/swarm/swarm.go +++ b/vendor/src/github.com/docker/engine-api/types/swarm/swarm.go @@ -29,7 +29,7 @@ type AcceptancePolicy struct { type Policy struct { Role NodeRole Autoaccept bool - Secret string `json:",omitempty"` + Secret *string `json:",omitempty"` } // OrchestrationConfig represents ochestration configuration. From 7f4bca0f90b3affa7f4992a2d3a44a44c142f41b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jun 2016 17:20:18 -0700 Subject: [PATCH 28/71] use same hash for same secret Signed-off-by: Victor Vieux (cherry picked from commit a579ce8ed307024ededd527819bfdbf38e970fbf) --- daemon/cluster/convert/swarm.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/daemon/cluster/convert/swarm.go b/daemon/cluster/convert/swarm.go index 84ed73c0c1..56cd0ed666 100644 --- a/daemon/cluster/convert/swarm.go +++ b/daemon/cluster/convert/swarm.go @@ -93,6 +93,8 @@ func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) ( // SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy. func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error { spec.AcceptancePolicy.Policies = nil + hashs := make(map[string][]byte) + for _, p := range acceptancePolicy.Policies { role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))] if !ok { @@ -108,7 +110,11 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic if *p.Secret == "" { // if provided secret is empty, it means erase previous secret. policy.Secret = nil } else { // if provided secret is not empty, we generate a new one. - hashPwd, _ := bcrypt.GenerateFromPassword([]byte(*p.Secret), 0) + hashPwd, ok := hashs[*p.Secret] + if !ok { + hashPwd, _ = bcrypt.GenerateFromPassword([]byte(*p.Secret), 0) + hashs[*p.Secret] = hashPwd + } policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{ Data: hashPwd, Alg: "bcrypt", From 01d26abd5c08f570654fee1e043e28f1b2dc568f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 16 Jun 2016 12:12:38 -0700 Subject: [PATCH 29/71] Add api tests for secret update Signed-off-by: Tonis Tiigi (cherry picked from commit aed7667bee243b0e0d1aa480e3bb52bae894cfe5) --- integration-cli/daemon_swarm.go | 17 ++++++ integration-cli/docker_api_swarm_test.go | 68 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/integration-cli/daemon_swarm.go b/integration-cli/daemon_swarm.go index 526ac21e27..ac54cbfb86 100644 --- a/integration-cli/daemon_swarm.go +++ b/integration-cli/daemon_swarm.go @@ -114,6 +114,7 @@ func (d *SwarmDaemon) info() (swarm.Info, error) { type serviceConstructor func(*swarm.Service) type nodeConstructor func(*swarm.Node) +type specConstructor func(*swarm.Spec) func (d *SwarmDaemon) createService(c *check.C, f ...serviceConstructor) string { var service swarm.Service @@ -185,3 +186,19 @@ func (d *SwarmDaemon) listNodes(c *check.C) []swarm.Node { c.Assert(json.Unmarshal(out, &nodes), checker.IsNil) return nodes } + +func (d *SwarmDaemon) updateSwarm(c *check.C, f ...specConstructor) { + var sw swarm.Swarm + status, out, err := d.SockRequest("GET", "/swarm", nil) + c.Assert(err, checker.IsNil) + c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) + c.Assert(json.Unmarshal(out, &sw), checker.IsNil) + + for _, fn := range f { + fn(&sw.Spec) + } + url := fmt.Sprintf("/swarm/update?version=%d", sw.Version.Index) + status, out, err = d.SockRequest("POST", url, sw.Spec) + c.Assert(err, checker.IsNil) + c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) +} diff --git a/integration-cli/docker_api_swarm_test.go b/integration-cli/docker_api_swarm_test.go index 9b6ba6c685..d76c6ac0e4 100644 --- a/integration-cli/docker_api_swarm_test.go +++ b/integration-cli/docker_api_swarm_test.go @@ -145,6 +145,74 @@ func (s *DockerSwarmSuite) TestApiSwarmSecretAcceptance(c *check.C) { info, err = d2.info() c.Assert(err, checker.IsNil) c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + + // change secret + d1.updateSwarm(c, func(s *swarm.Spec) { + for i := range s.AcceptancePolicy.Policies { + p := "foobaz" + s.AcceptancePolicy.Policies[i].Secret = &p + } + }) + + err = d2.Join(d1.listenAddr, "foobar", "", false) + c.Assert(err, checker.NotNil) + c.Assert(err.Error(), checker.Contains, "secret token is necessary") + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + + c.Assert(d2.Join(d1.listenAddr, "foobaz", "", false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) + c.Assert(d2.Leave(false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + + // change policy, don't change secret + d1.updateSwarm(c, func(s *swarm.Spec) { + for i, p := range s.AcceptancePolicy.Policies { + if p.Role == swarm.NodeRoleManager { + s.AcceptancePolicy.Policies[i].Autoaccept = false + } + s.AcceptancePolicy.Policies[i].Secret = nil + } + }) + + err = d2.Join(d1.listenAddr, "", "", false) + c.Assert(err, checker.NotNil) + c.Assert(err.Error(), checker.Contains, "secret token is necessary") + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + + c.Assert(d2.Join(d1.listenAddr, "foobaz", "", false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) + c.Assert(d2.Leave(false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + + // clear secret + d1.updateSwarm(c, func(s *swarm.Spec) { + for i := range s.AcceptancePolicy.Policies { + p := "" + s.AcceptancePolicy.Policies[i].Secret = &p + } + }) + + c.Assert(d2.Join(d1.listenAddr, "", "", false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) + c.Assert(d2.Leave(false), checker.IsNil) + info, err = d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) + } func (s *DockerSwarmSuite) TestApiSwarmCAHash(c *check.C) { From c3015a22cb532a15178a6f08d87930b65a7cc11e Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 15 Jun 2016 20:58:36 -0700 Subject: [PATCH 30/71] api/client/service: list running services over replicas To provide users a view of service status, list the number of running task over the requested number of replicas. Signed-off-by: Stephen J Day (cherry picked from commit b86cb293ecc67e76ec90ec60766f52dad6903eac) --- api/client/service/list.go | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/api/client/service/list.go b/api/client/service/list.go index aba1a53cdb..c9a20872bd 100644 --- a/api/client/service/list.go +++ b/api/client/service/list.go @@ -6,15 +6,15 @@ import ( "strings" "text/tabwriter" - "golang.org/x/net/context" - "github.com/docker/docker/api/client" "github.com/docker/docker/cli" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/stringid" "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/swarm" "github.com/spf13/cobra" + "golang.org/x/net/context" ) const ( @@ -47,11 +47,10 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command { } func runList(dockerCli *client.DockerCli, opts listOptions) error { + ctx := context.Background() client := dockerCli.Client() - services, err := client.ServiceList( - context.Background(), - types.ServiceListOptions{Filter: opts.filter.Value()}) + services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: opts.filter.Value()}) if err != nil { return err } @@ -60,12 +59,29 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error { if opts.quiet { printQuiet(out, services) } else { - printTable(out, services) + taskFilter := filters.NewArgs() + for _, service := range services { + taskFilter.Add("service", service.ID) + } + + tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter}) + if err != nil { + return err + } + + running := map[string]int{} + for _, task := range tasks { + if task.Status.State == "running" { + running[task.ServiceID]++ + } + } + + printTable(out, services, running) } return nil } -func printTable(out io.Writer, services []swarm.Service) { +func printTable(out io.Writer, services []swarm.Service, running map[string]int) { writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0) // Ignore flushing errors @@ -73,18 +89,18 @@ func printTable(out io.Writer, services []swarm.Service) { fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "REPLICAS", "IMAGE", "COMMAND") for _, service := range services { - scale := "" + replicas := "" if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil { - scale = fmt.Sprintf("%d", *service.Spec.Mode.Replicated.Replicas) + replicas = fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas) } else if service.Spec.Mode.Global != nil { - scale = "global" + replicas = "global" } fmt.Fprintf( writer, listItemFmt, stringid.TruncateID(service.ID), service.Spec.Name, - scale, + replicas, service.Spec.TaskTemplate.ContainerSpec.Image, strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " ")) } From 1e5ee1dd375b3c1d292cd73feb7a98e9e50ce713 Mon Sep 17 00:00:00 2001 From: Sainath Grandhi Date: Thu, 16 Jun 2016 12:31:23 -0700 Subject: [PATCH 31/71] Fixes #23596-returns empty string for NetworkID in response to /containers/json Signed-off-by: Sainath Grandhi (cherry picked from commit ad85d299066e87cd40afc43ffee8cb73dcc79eb4) --- daemon/list.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/list.go b/daemon/list.go index 5d45de6964..128d9eda9c 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -465,6 +465,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li GlobalIPv6Address: network.GlobalIPv6Address, GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, MacAddress: network.MacAddress, + NetworkID: network.NetworkID, } if network.IPAMConfig != nil { networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{ From c73e56fd7ffdfc0b1404ac66039124b7644d7a66 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 16 Jun 2016 11:52:34 -0700 Subject: [PATCH 32/71] docs: move "advisory" to general metadata the advisory option should not be below "menu" Signed-off-by: Sebastiaan van Stijn (cherry picked from commit aadd88c306d43b401a18a29efafa714f64f87894) --- docs/reference/commandline/plugin_disable.md | 2 +- docs/reference/commandline/plugin_enable.md | 2 +- docs/reference/commandline/plugin_inspect.md | 2 +- docs/reference/commandline/plugin_install.md | 2 +- docs/reference/commandline/plugin_ls.md | 2 +- docs/reference/commandline/plugin_rm.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/commandline/plugin_disable.md b/docs/reference/commandline/plugin_disable.md index 44f78af8c4..dd5998e89f 100644 --- a/docs/reference/commandline/plugin_disable.md +++ b/docs/reference/commandline/plugin_disable.md @@ -3,9 +3,9 @@ title = "plugin disable" description = "the plugin disable command description and usage" keywords = ["plugin, disable"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ diff --git a/docs/reference/commandline/plugin_enable.md b/docs/reference/commandline/plugin_enable.md index 2bd577fd04..323882325a 100644 --- a/docs/reference/commandline/plugin_enable.md +++ b/docs/reference/commandline/plugin_enable.md @@ -3,9 +3,9 @@ title = "plugin enable" description = "the plugin enable command description and usage" keywords = ["plugin, enable"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ diff --git a/docs/reference/commandline/plugin_inspect.md b/docs/reference/commandline/plugin_inspect.md index e0282ea294..67d63c236b 100644 --- a/docs/reference/commandline/plugin_inspect.md +++ b/docs/reference/commandline/plugin_inspect.md @@ -3,9 +3,9 @@ title = "plugin inspect" description = "The plugin inspect command description and usage" keywords = ["plugin, inspect"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ diff --git a/docs/reference/commandline/plugin_install.md b/docs/reference/commandline/plugin_install.md index 276af5a09f..03d089584f 100644 --- a/docs/reference/commandline/plugin_install.md +++ b/docs/reference/commandline/plugin_install.md @@ -3,9 +3,9 @@ title = "plugin install" description = "the plugin install command description and usage" keywords = ["plugin, install"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ diff --git a/docs/reference/commandline/plugin_ls.md b/docs/reference/commandline/plugin_ls.md index ea36368ed9..624807aaad 100644 --- a/docs/reference/commandline/plugin_ls.md +++ b/docs/reference/commandline/plugin_ls.md @@ -3,9 +3,9 @@ title = "plugin ls" description = "The plugin ls command description and usage" keywords = ["plugin, list"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ diff --git a/docs/reference/commandline/plugin_rm.md b/docs/reference/commandline/plugin_rm.md index a5dcf9e7cc..5f73b00fee 100644 --- a/docs/reference/commandline/plugin_rm.md +++ b/docs/reference/commandline/plugin_rm.md @@ -3,9 +3,9 @@ title = "plugin rm" description = "the plugin rm command description and usage" keywords = ["plugin, rm"] +advisory = "experimental" [menu.main] parent = "smn_cli" -advisory = "experimental" +++ From 82453c84baa44212d5b54d79931fc45c5f89dbc5 Mon Sep 17 00:00:00 2001 From: "Kai Qiang Wu(Kennan)" Date: Tue, 14 Jun 2016 14:01:41 +0000 Subject: [PATCH 33/71] Add ecryptfs check for overlay2 We added docs about ecryptfs check but not in code side. Also refactor code to make it clean. Signed-off-by: Kai Qiang Wu(Kennan) (cherry picked from commit 136323b04315eceffbed61d680878ed23cecc015) --- daemon/graphdriver/overlay2/overlay.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/daemon/graphdriver/overlay2/overlay.go b/daemon/graphdriver/overlay2/overlay.go index af0a536120..a1343e7b81 100644 --- a/daemon/graphdriver/overlay2/overlay.go +++ b/daemon/graphdriver/overlay2/overlay.go @@ -116,17 +116,8 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap // check if they are running over btrfs, aufs, zfs or overlay switch fsMagic { - case graphdriver.FsMagicBtrfs: - logrus.Error("'overlay' is not supported over btrfs.") - return nil, graphdriver.ErrIncompatibleFS - case graphdriver.FsMagicAufs: - logrus.Error("'overlay' is not supported over aufs.") - return nil, graphdriver.ErrIncompatibleFS - case graphdriver.FsMagicZfs: - logrus.Error("'overlay' is not supported over zfs.") - return nil, graphdriver.ErrIncompatibleFS - case graphdriver.FsMagicOverlay: - logrus.Error("'overlay' is not supported over overlay.") + case graphdriver.FsMagicBtrfs, graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs: + logrus.Errorf("'overlay' is not supported over %s", backingFs) return nil, graphdriver.ErrIncompatibleFS } From 6c3d080e83cd8d1fe2c4e6699c9eccaa56149817 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 16 Jun 2016 14:54:41 -0700 Subject: [PATCH 34/71] api/client/service: fix minor spelling error in service inspect Signed-off-by: Stephen J Day (cherry picked from commit 920e65ccbca0423c43579c9ce3b9331efe14c97b) --- api/client/service/inspect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/service/inspect.go b/api/client/service/inspect.go index a75e4e7cc1..bd36d3ed06 100644 --- a/api/client/service/inspect.go +++ b/api/client/service/inspect.go @@ -102,7 +102,7 @@ func printService(out io.Writer, service swarm.Service) { } fmt.Fprintln(out, "Placement:") fmt.Fprintln(out, " Strategy:\tSPREAD") - fmt.Fprintf(out, "UpateConfig:\n") + fmt.Fprintf(out, "UpdateConfig:\n") fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism) if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 { fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay) From 1ad7b517fa4623a07998a932bcf80e0424e61e7c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 14 Jun 2016 07:43:25 -0700 Subject: [PATCH 35/71] Add security info to `docker info` The security infomation has already been added to `GET /info` in #21172. However, it is not part of the output of `docker info` yet. This fix adds the security information to `docker info`. Additional tests has been added to cover changes. This fix fixes #23500. This fix is related to #20909, #21172. Signed-off-by: Yong Tang (cherry picked from commit eee20b564ffae0b8c8043115c959f0f9d1869fed) --- api/client/info.go | 4 ++++ integration-cli/docker_cli_info_test.go | 1 + integration-cli/docker_cli_info_unix_test.go | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 integration-cli/docker_cli_info_unix_test.go diff --git a/api/client/info.go b/api/client/info.go index df1415c9cb..cf4d4fff0b 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -94,6 +94,10 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Default Runtime: %s\n", info.DefaultRuntime) } + fmt.Fprintf(cli.out, "Security Options:") + ioutils.FprintfIfNotEmpty(cli.out, " %s", strings.Join(info.SecurityOptions, " ")) + fmt.Fprintf(cli.out, "\n") + ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion) ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem) ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType) diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index 816cbfdde2..ba358112f9 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -32,6 +32,7 @@ func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) { "Storage Driver:", "Volume:", "Network:", + "Security Options:", } if DaemonIsLinux.Condition() { diff --git a/integration-cli/docker_cli_info_unix_test.go b/integration-cli/docker_cli_info_unix_test.go new file mode 100644 index 0000000000..900534d68d --- /dev/null +++ b/integration-cli/docker_cli_info_unix_test.go @@ -0,0 +1,15 @@ +// +build !windows + +package main + +import ( + "github.com/docker/docker/pkg/integration/checker" + "github.com/go-check/check" +) + +func (s *DockerSuite) TestInfoSecurityOptions(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled, Apparmor, DaemonIsLinux) + + out, _ := dockerCmd(c, "info") + c.Assert(out, checker.Contains, "Security Options: apparmor seccomp") +} From 83fbaa3cb4042728082de130a7c6659224b327b2 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Thu, 16 Jun 2016 07:54:36 -0400 Subject: [PATCH 36/71] Fix restore active sandbox we store the active sandbox after daemon.containerd.Restore, but there is a chance the `Restore` will set the container to exit see (https://github.com/docker/docker/blob/master/libcontainerd/client_linux.go#L469). so we should check if the container is really running before add it to activesandbox. Signed-off-by: Lei Jitang (cherry picked from commit 78f3094518b1f3f498bd2629667877f9a87d452f) --- daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 66b088ab59..7ec71a1305 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -179,7 +179,7 @@ func (daemon *Daemon) restore() error { logrus.Errorf("Failed to restore with containerd: %q", err) return } - if !c.HostConfig.NetworkMode.IsContainer() { + if !c.HostConfig.NetworkMode.IsContainer() && c.IsRunning() { options, err := daemon.buildSandboxOptions(c) if err != nil { logrus.Warnf("Failed build sandbox option to restore container %s: %v", c.ID, err) From 213adac2ea5d0b92e7a1e095a9fb9137491c65da Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Thu, 16 Jun 2016 23:21:08 +0100 Subject: [PATCH 37/71] Add the seccomp build tag for s390x now runc updated This was waiting for runc bump see https://github.com/docker/docker/issues/23171 runc was bumped in https://github.com/docker/docker/pull/23603 Fixes #23171 Signed-off-by: Justin Cormack (cherry picked from commit f8d970169a7ae6dcf46beb69c0367d35d019294e) --- Dockerfile.s390x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.s390x b/Dockerfile.s390x index c75b321db4..eab490c881 100644 --- a/Dockerfile.s390x +++ b/Dockerfile.s390x @@ -161,7 +161,7 @@ RUN useradd --create-home --gid docker unprivilegeduser VOLUME /var/lib/docker WORKDIR /go/src/github.com/docker/docker -ENV DOCKER_BUILDTAGS apparmor selinux +ENV DOCKER_BUILDTAGS apparmor selinux seccomp # Let us use a .bashrc file RUN ln -sfv $PWD/.bashrc ~/.bashrc From ed01cfc6db892a899b7f79888fb55400c159215f Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Thu, 16 Jun 2016 14:32:12 -0700 Subject: [PATCH 38/71] Vendoring Libnetwork caf22bd9a6a53dfe91b0266274155bc69235e8ed * fixes https://github.com/docker/docker/issues/23622 * fixes a memory leak issue with bulk sync * fixes external DNS resolution issue after live restore Signed-off-by: Madhu Venugopal (cherry picked from commit 7f2f6ed0d6f19bb6cedb6e59d410d0704636ce54) --- hack/vendor.sh | 2 +- .../docker/libnetwork/controller.go | 13 ++--- .../libnetwork/drivers/bridge/bridge.go | 25 +++++---- .../docker/libnetwork/networkdb/cluster.go | 10 +++- .../github.com/docker/libnetwork/sandbox.go | 53 ++++++++++++------- .../docker/libnetwork/sandbox_dns_unix.go | 16 ++++-- .../docker/libnetwork/sandbox_dns_windows.go | 2 +- .../docker/libnetwork/sandbox_store.go | 22 +++++--- .../docker/libnetwork/service_linux.go | 37 +++++++++---- 9 files changed, 120 insertions(+), 60 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 3d4490dd90..dc7913981e 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -65,7 +65,7 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 #get libnetwork packages -clone git github.com/docker/libnetwork 96d45528599c32354230480a1ebc0657cd4d077f +clone git github.com/docker/libnetwork caf22bd9a6a53dfe91b0266274155bc69235e8ed clone git github.com/docker/go-events 39718a26497694185f8fb58a7d6f31947f3dc42d clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec diff --git a/vendor/src/github.com/docker/libnetwork/controller.go b/vendor/src/github.com/docker/libnetwork/controller.go index b19f51d707..1646568f27 100644 --- a/vendor/src/github.com/docker/libnetwork/controller.go +++ b/vendor/src/github.com/docker/libnetwork/controller.go @@ -810,12 +810,13 @@ func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (s // Create sandbox and process options first. Key generation depends on an option if sb == nil { sb = &sandbox{ - id: stringid.GenerateRandomID(), - containerID: containerID, - endpoints: epHeap{}, - epPriority: map[string]int{}, - config: containerConfig{}, - controller: c, + id: stringid.GenerateRandomID(), + containerID: containerID, + endpoints: epHeap{}, + epPriority: map[string]int{}, + populatedEndpoints: map[string]struct{}{}, + config: containerConfig{}, + controller: c, } } sBox = sb diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go index 6d761a0e26..ce0aec8485 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go @@ -588,13 +588,26 @@ func (d *driver) createNetwork(config *networkConfiguration) error { defer osl.InitOSContext()() networkList := d.getNetworks() - for _, nw := range networkList { + for i, nw := range networkList { nw.Lock() nwConfig := nw.config nw.Unlock() if err := nwConfig.Conflicts(config); err != nil { - return types.ForbiddenErrorf("cannot create network %s (%s): conflicts with network %s (%s): %s", - config.ID, config.BridgeName, nwConfig.ID, nwConfig.BridgeName, err.Error()) + if config.DefaultBridge { + // We encountered and identified a stale default network + // We must delete it as libnetwork is the source of thruth + // The default network being created must be the only one + // This can happen only from docker 1.12 on ward + logrus.Infof("Removing stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName) + if err := d.DeleteNetwork(nwConfig.ID); err != nil { + logrus.Warnf("Failed to remove stale default network: %s (%s): %v. Will remove from store.", nwConfig.ID, nwConfig.BridgeName, err) + d.storeDelete(nwConfig) + } + networkList = append(networkList[:i], networkList[i+1:]...) + } else { + return types.ForbiddenErrorf("cannot create network %s (%s): conflicts with network %s (%s): %s", + config.ID, config.BridgeName, nwConfig.ID, nwConfig.BridgeName, err.Error()) + } } } @@ -762,12 +775,6 @@ func (d *driver) DeleteNetwork(nid string) error { return err } - // Cannot remove network if endpoints are still present - if len(n.endpoints) != 0 { - err = ActiveEndpointsError(n.id) - return err - } - // We only delete the bridge when it's not the default bridge. This is keep the backward compatible behavior. if !config.DefaultBridge { if err := d.nlh.LinkDel(n.bridge.Link); err != nil { diff --git a/vendor/src/github.com/docker/libnetwork/networkdb/cluster.go b/vendor/src/github.com/docker/libnetwork/networkdb/cluster.go index e11aae9e2f..165ba6e856 100644 --- a/vendor/src/github.com/docker/libnetwork/networkdb/cluster.go +++ b/vendor/src/github.com/docker/libnetwork/networkdb/cluster.go @@ -330,11 +330,15 @@ func (nDB *NetworkDB) bulkSyncTables() { // successfully completed bulk sync in this iteration. updatedNetworks := make([]string, 0, len(networks)) for _, nid := range networks { + var found bool for _, completedNid := range completed { if nid == completedNid { - continue + found = true + break } + } + if !found { updatedNetworks = append(updatedNetworks, nid) } } @@ -449,8 +453,9 @@ func (nDB *NetworkDB) bulkSyncNode(networks []string, node string, unsolicited b // Wait on a response only if it is unsolicited. if unsolicited { startTime := time.Now() + t := time.NewTimer(30 * time.Second) select { - case <-time.After(30 * time.Second): + case <-t.C: logrus.Errorf("Bulk sync to node %s timed out", node) case <-ch: nDB.Lock() @@ -459,6 +464,7 @@ func (nDB *NetworkDB) bulkSyncNode(networks []string, node string, unsolicited b logrus.Debugf("%s: Bulk sync to node %s took %s", nDB.config.NodeName, node, time.Now().Sub(startTime)) } + t.Stop() } return nil diff --git a/vendor/src/github.com/docker/libnetwork/sandbox.go b/vendor/src/github.com/docker/libnetwork/sandbox.go index dce169bd04..6bb2766a71 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox.go @@ -68,23 +68,24 @@ func (sb *sandbox) processOptions(options ...SandboxOption) { type epHeap []*endpoint type sandbox struct { - id string - containerID string - config containerConfig - extDNS []string - osSbox osl.Sandbox - controller *controller - resolver Resolver - resolverOnce sync.Once - refCnt int - endpoints epHeap - epPriority map[string]int - joinLeaveDone chan struct{} - dbIndex uint64 - dbExists bool - isStub bool - inDelete bool - ingress bool + id string + containerID string + config containerConfig + extDNS []string + osSbox osl.Sandbox + controller *controller + resolver Resolver + resolverOnce sync.Once + refCnt int + endpoints epHeap + epPriority map[string]int + populatedEndpoints map[string]struct{} + joinLeaveDone chan struct{} + dbIndex uint64 + dbExists bool + isStub bool + inDelete bool + ingress bool sync.Mutex } @@ -728,7 +729,7 @@ func (sb *sandbox) restoreOslSandbox() error { } } if ep.needResolver() { - sb.startResolver() + sb.startResolver(true) } } @@ -761,7 +762,7 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { ep.Unlock() if ep.needResolver() { - sb.startResolver() + sb.startResolver(false) } if i != nil && i.srcName != "" { @@ -798,6 +799,12 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { } } + // Make sure to add the endpoint to the populated endpoint set + // before populating loadbalancers. + sb.Lock() + sb.populatedEndpoints[ep.ID()] = struct{}{} + sb.Unlock() + // Populate load balancer only after updating all the other // information including gateway and other routes so that // loadbalancers are populated all the network state is in @@ -830,6 +837,7 @@ func (sb *sandbox) clearNetworkResources(origEp *endpoint) error { releaseOSSboxResources(osSbox, ep) } + delete(sb.populatedEndpoints, ep.ID()) sb.Lock() if len(sb.endpoints) == 0 { // sb.endpoints should never be empty and this is unexpected error condition @@ -879,6 +887,13 @@ func (sb *sandbox) clearNetworkResources(origEp *endpoint) error { return nil } +func (sb *sandbox) isEndpointPopulated(ep *endpoint) bool { + sb.Lock() + _, ok := sb.populatedEndpoints[ep.ID()] + sb.Unlock() + return ok +} + // joinLeaveStart waits to ensure there are no joins or leaves in progress and // marks this join/leave in progress without race func (sb *sandbox) joinLeaveStart() { diff --git a/vendor/src/github.com/docker/libnetwork/sandbox_dns_unix.go b/vendor/src/github.com/docker/libnetwork/sandbox_dns_unix.go index 3f531beb99..735708563f 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox_dns_unix.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox_dns_unix.go @@ -21,7 +21,7 @@ const ( filePerm = 0644 ) -func (sb *sandbox) startResolver() { +func (sb *sandbox) startResolver(restore bool) { sb.resolverOnce.Do(func() { var err error sb.resolver = NewResolver(sb) @@ -31,10 +31,16 @@ func (sb *sandbox) startResolver() { } }() - err = sb.rebuildDNS() - if err != nil { - log.Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err) - return + // In the case of live restore container is already running with + // right resolv.conf contents created before. Just update the + // external DNS servers from the restored sandbox for embedded + // server to use. + if !restore { + err = sb.rebuildDNS() + if err != nil { + log.Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err) + return + } } sb.resolver.SetExtServers(sb.extDNS) diff --git a/vendor/src/github.com/docker/libnetwork/sandbox_dns_windows.go b/vendor/src/github.com/docker/libnetwork/sandbox_dns_windows.go index f2f58d5b98..e1ca73edef 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox_dns_windows.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox_dns_windows.go @@ -8,7 +8,7 @@ import ( // Stub implementations for DNS related functions -func (sb *sandbox) startResolver() { +func (sb *sandbox) startResolver(bool) { } func (sb *sandbox) setupResolutionFiles() error { diff --git a/vendor/src/github.com/docker/libnetwork/sandbox_store.go b/vendor/src/github.com/docker/libnetwork/sandbox_store.go index 5aa4839406..5b963e71c3 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox_store.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox_store.go @@ -27,6 +27,7 @@ type sbState struct { dbExists bool Eps []epState EpPriority map[string]int + ExtDNS []string } func (sbs *sbState) Key() []string { @@ -113,6 +114,10 @@ func (sbs *sbState) CopyTo(o datastore.KVObject) error { dstSbs.Eps = append(dstSbs.Eps, eps) } + for _, dns := range sbs.ExtDNS { + dstSbs.ExtDNS = append(dstSbs.ExtDNS, dns) + } + return nil } @@ -126,6 +131,7 @@ func (sb *sandbox) storeUpdate() error { ID: sb.id, Cid: sb.containerID, EpPriority: sb.epPriority, + ExtDNS: sb.extDNS, } retry: @@ -191,13 +197,15 @@ func (c *controller) sandboxCleanup(activeSandboxes map[string]interface{}) { sbs := kvo.(*sbState) sb := &sandbox{ - id: sbs.ID, - controller: sbs.c, - containerID: sbs.Cid, - endpoints: epHeap{}, - dbIndex: sbs.dbIndex, - isStub: true, - dbExists: true, + id: sbs.ID, + controller: sbs.c, + containerID: sbs.Cid, + endpoints: epHeap{}, + populatedEndpoints: map[string]struct{}{}, + dbIndex: sbs.dbIndex, + isStub: true, + dbExists: true, + extDNS: sbs.ExtDNS, } msg := " for cleanup" diff --git a/vendor/src/github.com/docker/libnetwork/service_linux.go b/vendor/src/github.com/docker/libnetwork/service_linux.go index 1d36c4538d..780889f0bc 100644 --- a/vendor/src/github.com/docker/libnetwork/service_linux.go +++ b/vendor/src/github.com/docker/libnetwork/service_linux.go @@ -184,14 +184,20 @@ func (c *controller) rmServiceBinding(name, sid, nid, eid string, vip net.IP, in func (n *network) connectedLoadbalancers() []*loadBalancer { c := n.getController() + serviceBindings := make([]*service, 0, len(c.serviceBindings)) c.Lock() - defer c.Unlock() + for _, s := range c.serviceBindings { + serviceBindings = append(serviceBindings, s) + } + c.Unlock() var lbs []*loadBalancer - for _, s := range c.serviceBindings { + for _, s := range serviceBindings { + s.Lock() if lb, ok := s.loadBalancers[n.ID()]; ok { lbs = append(lbs, lb) } + s.Unlock() } return lbs @@ -229,12 +235,14 @@ func (sb *sandbox) populateLoadbalancers(ep *endpoint) { continue } + lb.service.Lock() addService := true for _, ip := range lb.backEnds { sb.addLBBackend(ip, lb.vip, lb.fwMark, lb.service.ingressPorts, eIP, gwIP, addService) addService = false } + lb.service.Unlock() } } @@ -245,6 +253,10 @@ func (n *network) addLBBackend(ip, vip net.IP, fwMark uint32, ingressPorts []*Po n.WalkEndpoints(func(e Endpoint) bool { ep := e.(*endpoint) if sb, ok := ep.getSandbox(); ok { + if !sb.isEndpointPopulated(ep) { + return false + } + var gwIP net.IP if ep := sb.getGatewayEndpoint(); ep != nil { gwIP = ep.Iface().Address().IP @@ -264,6 +276,10 @@ func (n *network) rmLBBackend(ip, vip net.IP, fwMark uint32, ingressPorts []*Por n.WalkEndpoints(func(e Endpoint) bool { ep := e.(*endpoint) if sb, ok := ep.getSandbox(); ok { + if !sb.isEndpointPopulated(ep) { + return false + } + var gwIP net.IP if ep := sb.getGatewayEndpoint(); ep != nil { gwIP = ep.Iface().Address().IP @@ -356,15 +372,13 @@ func (sb *sandbox) rmLBBackend(ip, vip net.IP, fwMark uint32, ingressPorts []*Po } if err := i.DelDestination(s, d); err != nil { - logrus.Errorf("Failed to delete real server %s for vip %s fwmark %d: %v", ip, vip, fwMark, err) - return + logrus.Infof("Failed to delete real server %s for vip %s fwmark %d: %v", ip, vip, fwMark, err) } if rmService { s.SchedName = ipvs.RoundRobin if err := i.DelService(s); err != nil { - logrus.Errorf("Failed to create a new service for vip %s fwmark %d: %v", vip, fwMark, err) - return + logrus.Errorf("Failed to delete a new service for vip %s fwmark %d: %v", vip, fwMark, err) } var iPorts []*PortConfig @@ -372,13 +386,11 @@ func (sb *sandbox) rmLBBackend(ip, vip net.IP, fwMark uint32, ingressPorts []*Po iPorts = ingressPorts if err := programIngress(gwIP, iPorts, true); err != nil { logrus.Errorf("Failed to delete ingress: %v", err) - return } } if err := invokeFWMarker(sb.Key(), vip, fwMark, iPorts, eIP, true); err != nil { logrus.Errorf("Failed to add firewall mark rule in sbox %s: %v", sb.Key(), err) - return } } } @@ -454,12 +466,17 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro rule := strings.Fields(fmt.Sprintf("-t nat %s %s -p %s --dport %d -j DNAT --to-destination %s:%d", addDelOpt, ingressChain, strings.ToLower(PortConfig_Protocol_name[int32(iPort.Protocol)]), iPort.PublishedPort, gwIP, iPort.PublishedPort)) if err := iptables.RawCombinedOutput(rule...); err != nil { - return fmt.Errorf("setting up rule failed, %v: %v", rule, err) + errStr := fmt.Sprintf("setting up rule failed, %v: %v", rule, err) + if !isDelete { + return fmt.Errorf("%s", errStr) + } + + logrus.Infof("%s", errStr) } } if err := plumbProxy(iPort, isDelete); err != nil { - return fmt.Errorf("failed to create proxy for port %d: %v", iPort.PublishedPort, err) + logrus.Warnf("failed to create proxy for port %d: %v", iPort.PublishedPort, err) } } From fdb5324595bfcbd9046a9bdeb5ef23ae8781f451 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 16 Jun 2016 16:29:23 -0700 Subject: [PATCH 39/71] Update docker info output example Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 07e1c62bf4a24e8a075e87b38be3bbe7c8282e42) --- docs/reference/commandline/info.md | 7 +++++-- man/docker-info.1.md | 12 ++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/reference/commandline/info.md b/docs/reference/commandline/info.md index 1303aa0435..21f483c282 100644 --- a/docs/reference/commandline/info.md +++ b/docs/reference/commandline/info.md @@ -45,19 +45,22 @@ storage driver and a node that is part of a 2 node Swarm cluster: Paused: 1 Stopped: 10 Images: 52 - Server Version: 1.11.1 + Server Version: 1.12.0-dev Storage Driver: overlay Backing Filesystem: extfs Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local - Network: bridge null host + Network: bridge null host overlay Swarm: NodeID: 0gac67oclbxq7 IsManager: YES Managers: 2 Nodes: 2 + Runtimes: default + Default Runtime: default + Security Options: apparmor seccomp Kernel Version: 4.4.0-21-generic Operating System: Ubuntu 16.04 LTS OSType: linux diff --git a/man/docker-info.1.md b/man/docker-info.1.md index d777f259f9..1d96b562eb 100644 --- a/man/docker-info.1.md +++ b/man/docker-info.1.md @@ -41,14 +41,22 @@ storage driver: Paused: 1 Stopped: 10 Images: 52 - Server Version: 1.11.1 + Server Version: 1.12.0-dev Storage Driver: overlay Backing Filesystem: extfs Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local - Network: bridge null host + Network: bridge null host overlay + Swarm: + NodeID: 0gac67oclbxq7 + IsManager: YES + Managers: 2 + Nodes: 2 + Runtimes: default + Default Runtime: default + Security Options: apparmor seccomp Kernel Version: 4.4.0-21-generic Operating System: Ubuntu 16.04 LTS OSType: linux From 7444bb2c250d067b0b3a3a764454f024bef42164 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 17 Jun 2016 00:32:58 +0200 Subject: [PATCH 40/71] Bump engine-api to c57d0447ea1ae71f6dad83c8d8a1215a89869a0c Signed-off-by: Vincent Demeester (cherry picked from commit 58529a15535ee2accafdde740214c0d5b595d6e1) --- hack/vendor.sh | 2 +- .../docker/engine-api/client/interface.go | 2 +- .../client/interface_experimental.go | 4 +- .../engine-api/client/plugin_install.go | 42 ++++++++++--------- .../engine-api/client/service_inspect.go | 22 ++++++---- .../docker/engine-api/types/plugin.go | 9 ++++ 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index dc7913981e..93cc52cd6c 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -60,7 +60,7 @@ clone git golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://gith clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d -clone git github.com/docker/engine-api f50fbe5f9c4c8eeed591549d2c8187f4076f3717 +clone git github.com/docker/engine-api c57d0447ea1ae71f6dad83c8d8a1215a89869a0c clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 diff --git a/vendor/src/github.com/docker/engine-api/client/interface.go b/vendor/src/github.com/docker/engine-api/client/interface.go index 7fb04aced3..929a6bc72a 100644 --- a/vendor/src/github.com/docker/engine-api/client/interface.go +++ b/vendor/src/github.com/docker/engine-api/client/interface.go @@ -101,7 +101,7 @@ type NodeAPIClient interface { // ServiceAPIClient defines API client methods for the services type ServiceAPIClient interface { ServiceCreate(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error) - ServiceInspect(ctx context.Context, serviceID string) (swarm.Service, error) + ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) ServiceRemove(ctx context.Context, serviceID string) error ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec) error diff --git a/vendor/src/github.com/docker/engine-api/client/interface_experimental.go b/vendor/src/github.com/docker/engine-api/client/interface_experimental.go index a63ae70bb6..eb0cd7bf14 100644 --- a/vendor/src/github.com/docker/engine-api/client/interface_experimental.go +++ b/vendor/src/github.com/docker/engine-api/client/interface_experimental.go @@ -3,8 +3,6 @@ package client import ( - "io" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) @@ -29,7 +27,7 @@ type PluginAPIClient interface { PluginRemove(ctx context.Context, name string) error PluginEnable(ctx context.Context, name string) error PluginDisable(ctx context.Context, name string) error - PluginInstall(ctx context.Context, name, registryAuth string, acceptAllPermissions, noEnable bool, in io.ReadCloser, out io.Writer) error + PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error PluginPush(ctx context.Context, name string, registryAuth string) error PluginSet(ctx context.Context, name string, args []string) error PluginInspect(ctx context.Context, name string) (*types.Plugin, error) diff --git a/vendor/src/github.com/docker/engine-api/client/plugin_install.go b/vendor/src/github.com/docker/engine-api/client/plugin_install.go index 9005e81a56..914376f512 100644 --- a/vendor/src/github.com/docker/engine-api/client/plugin_install.go +++ b/vendor/src/github.com/docker/engine-api/client/plugin_install.go @@ -3,21 +3,28 @@ package client import ( - "bufio" "encoding/json" - "fmt" - "io" + "net/http" "net/url" - "strings" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // PluginInstall installs a plugin -func (cli *Client) PluginInstall(ctx context.Context, name, registryAuth string, acceptAllPermissions, noEnable bool, in io.ReadCloser, out io.Writer) error { - headers := map[string][]string{"X-Registry-Auth": {registryAuth}} - resp, err := cli.post(ctx, "/plugins/pull", url.Values{"name": []string{name}}, nil, headers) +func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error { + // FIXME(vdemeester) name is a ref, we might want to parse/validate it here. + query := url.Values{} + query.Set("name", name) + resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { + newAuthHeader, privilegeErr := options.PrivilegeFunc() + if privilegeErr != nil { + ensureReaderClosed(resp) + return privilegeErr + } + resp, err = cli.tryPluginPull(ctx, query, newAuthHeader) + } if err != nil { ensureReaderClosed(resp) return err @@ -28,27 +35,24 @@ func (cli *Client) PluginInstall(ctx context.Context, name, registryAuth string, } ensureReaderClosed(resp) - if !acceptAllPermissions && len(privileges) > 0 { - - fmt.Fprintf(out, "Plugin %q requested the following privileges:\n", name) - for _, privilege := range privileges { - fmt.Fprintf(out, " - %s: %v\n", privilege.Name, privilege.Value) - } - - fmt.Fprint(out, "Do you grant the above permissions? [y/N] ") - reader := bufio.NewReader(in) - line, _, err := reader.ReadLine() + if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { + accept, err := options.AcceptPermissionsFunc(privileges) if err != nil { return err } - if strings.ToLower(string(line)) != "y" { + if !accept { resp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) ensureReaderClosed(resp) return pluginPermissionDenied{name} } } - if noEnable { + if options.Disabled { return nil } return cli.PluginEnable(ctx, name) } + +func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (*serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.post(ctx, "/plugins/pull", query, nil, headers) +} diff --git a/vendor/src/github.com/docker/engine-api/client/service_inspect.go b/vendor/src/github.com/docker/engine-api/client/service_inspect.go index 3dbb8cd560..958cd662ea 100644 --- a/vendor/src/github.com/docker/engine-api/client/service_inspect.go +++ b/vendor/src/github.com/docker/engine-api/client/service_inspect.go @@ -1,25 +1,33 @@ package client import ( + "bytes" "encoding/json" + "io/ioutil" "net/http" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) -// ServiceInspect returns the service information. -func (cli *Client) ServiceInspect(ctx context.Context, serviceID string) (swarm.Service, error) { +// ServiceInspectWithRaw returns the service information and the raw data. +func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { serverResp, err := cli.get(ctx, "/services/"+serviceID, nil, nil) if err != nil { if serverResp.statusCode == http.StatusNotFound { - return swarm.Service{}, serviceNotFoundError{serviceID} + return swarm.Service{}, nil, serviceNotFoundError{serviceID} } - return swarm.Service{}, err + return swarm.Service{}, nil, err + } + defer ensureReaderClosed(serverResp) + + body, err := ioutil.ReadAll(serverResp.body) + if err != nil { + return swarm.Service{}, nil, err } var response swarm.Service - err = json.NewDecoder(serverResp.body).Decode(&response) - ensureReaderClosed(serverResp) - return response, err + rdr := bytes.NewReader(body) + err = json.NewDecoder(rdr).Decode(&response) + return response, body, err } diff --git a/vendor/src/github.com/docker/engine-api/types/plugin.go b/vendor/src/github.com/docker/engine-api/types/plugin.go index f9df9efe05..05030ff3de 100644 --- a/vendor/src/github.com/docker/engine-api/types/plugin.go +++ b/vendor/src/github.com/docker/engine-api/types/plugin.go @@ -7,6 +7,15 @@ import ( "fmt" ) +// PluginInstallOptions holds parameters to install a plugin. +type PluginInstallOptions struct { + Disabled bool + AcceptAllPermissions bool + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + PrivilegeFunc RequestPrivilegeFunc + AcceptPermissionsFunc func(PluginPrivileges) (bool, error) +} + // PluginConfig represents the values of settings potentially modifiable by a user type PluginConfig struct { Mounts []PluginMount From d9d24b6605218b34b0e3c4051625e1e1e0cda472 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 17 Jun 2016 00:33:21 +0200 Subject: [PATCH 41/71] =?UTF-8?q?Update=20docker=20plugin=20install=20code?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … for more consistency (api side). Signed-off-by: Vincent Demeester (cherry picked from commit 2c82337b0490dc0a5b8329d1f23c6bad00d8551b) --- api/client/plugin/install.go | 39 ++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index ef890b1f90..486dc4f622 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -3,12 +3,15 @@ package plugin import ( + "bufio" "fmt" + "strings" "github.com/docker/docker/api/client" "github.com/docker/docker/cli" "github.com/docker/docker/reference" "github.com/docker/docker/registry" + "github.com/docker/engine-api/types" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -36,8 +39,8 @@ func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { return cmd } -func runInstall(dockerCli *client.DockerCli, options pluginOptions) error { - named, err := reference.ParseNamed(options.name) // FIXME: validate +func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error { + named, err := reference.ParseNamed(opts.name) // FIXME: validate if err != nil { return err } @@ -56,6 +59,34 @@ func runInstall(dockerCli *client.DockerCli, options pluginOptions) error { if err != nil { return err } - // TODO: pass noEnable flag - return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, options.grantPerms, false, dockerCli.In(), dockerCli.Out()) + + requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install") + + // TODO: pass acceptAllPermissions and noEnable flag + options := types.PluginInstallOptions{ + RegistryAuth: encodedAuth, + Disabled: false, + AcceptAllPermissions: opts.grantPerms, + AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name), + PrivilegeFunc: requestPrivilege, + } + + return dockerCli.Client().PluginInstall(ctx, ref.String(), options) +} + +func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) { + return func(privileges types.PluginPrivileges) (bool, error) { + fmt.Fprintf(dockerCli.Out(), "Plugin %q requested the following privileges:\n", name) + for _, privilege := range privileges { + fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value) + } + + fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ") + reader := bufio.NewReader(dockerCli.In()) + line, _, err := reader.ReadLine() + if err != nil { + return false, err + } + return strings.ToLower(string(line)) == "y", nil + } } From 42f445565daaa086e1031d7cf765ff56fbe26824 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 17 Jun 2016 00:47:22 +0200 Subject: [PATCH 42/71] Update ServiceInspectWithRaw Signed-off-by: Vincent Demeester (cherry picked from commit 4a70cb55710a76ee7dfa059c440c34fd6cb84f16) --- api/client/idresolver/idresolver.go | 2 +- api/client/service/inspect.go | 2 +- api/client/service/scale.go | 3 ++- api/client/service/tasks.go | 2 +- api/client/service/update.go | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/client/idresolver/idresolver.go b/api/client/idresolver/idresolver.go index 05c4c9c366..b958bd0672 100644 --- a/api/client/idresolver/idresolver.go +++ b/api/client/idresolver/idresolver.go @@ -40,7 +40,7 @@ func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, } return id, nil case swarm.Service: - service, err := r.client.ServiceInspect(ctx, id) + service, _, err := r.client.ServiceInspectWithRaw(ctx, id) if err != nil { return id, nil } diff --git a/api/client/service/inspect.go b/api/client/service/inspect.go index bd36d3ed06..1c2edb051a 100644 --- a/api/client/service/inspect.go +++ b/api/client/service/inspect.go @@ -50,7 +50,7 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error { ctx := context.Background() getRef := func(ref string) (interface{}, []byte, error) { - service, err := client.ServiceInspect(ctx, ref) + service, _, err := client.ServiceInspectWithRaw(ctx, ref) if err == nil || !apiclient.IsErrServiceNotFound(err) { return service, nil, err } diff --git a/api/client/service/scale.go b/api/client/service/scale.go index 2962f29091..e313948aca 100644 --- a/api/client/service/scale.go +++ b/api/client/service/scale.go @@ -61,7 +61,8 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string client := dockerCli.Client() ctx := context.Background() - service, err := client.ServiceInspect(ctx, serviceID) + service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) + if err != nil { return err } diff --git a/api/client/service/tasks.go b/api/client/service/tasks.go index 6169d8bdb7..7520b1f16d 100644 --- a/api/client/service/tasks.go +++ b/api/client/service/tasks.go @@ -44,7 +44,7 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error { client := dockerCli.Client() ctx := context.Background() - service, err := client.ServiceInspect(ctx, opts.serviceID) + service, _, err := client.ServiceInspectWithRaw(ctx, opts.serviceID) if err != nil { return err } diff --git a/api/client/service/update.go b/api/client/service/update.go index 20bc386ba3..c8e3a6d7a5 100644 --- a/api/client/service/update.go +++ b/api/client/service/update.go @@ -41,7 +41,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID stri client := dockerCli.Client() ctx := context.Background() - service, err := client.ServiceInspect(ctx, serviceID) + service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) if err != nil { return err } From b3387e96ed307cb8967c7ee165574dc342ff0ec3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 16 Jun 2016 16:42:40 -0700 Subject: [PATCH 43/71] fix "overlay" -> "overlay2" in error message Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2d4b285a75621604f31058a43cb5ef1d4618533e) --- daemon/graphdriver/overlay2/overlay.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/overlay2/overlay.go b/daemon/graphdriver/overlay2/overlay.go index a1343e7b81..b48bcb90de 100644 --- a/daemon/graphdriver/overlay2/overlay.go +++ b/daemon/graphdriver/overlay2/overlay.go @@ -114,10 +114,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap backingFs = fsName } - // check if they are running over btrfs, aufs, zfs or overlay + // check if they are running over btrfs, aufs, zfs, overlay, or ecryptfs switch fsMagic { case graphdriver.FsMagicBtrfs, graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs: - logrus.Errorf("'overlay' is not supported over %s", backingFs) + logrus.Errorf("'overlay2' is not supported over %s", backingFs) return nil, graphdriver.ErrIncompatibleFS } From 56b253fb3cc8bcdf99ff09e96de60b91e1a794d4 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 14 Jun 2016 22:44:49 -0700 Subject: [PATCH 44/71] Do not show empty tags for digest references in output When a repository has a tag and digests, show tag for each digest value. Do not duplicate rows for the same image name with both tag and digest. Signed-off-by: Derek McGowan (github: dmcgowan) (cherry picked from commit 79eada38141dca71f5195df59882f1cb46657640) --- api/client/formatter/formatter.go | 118 ++++++++++++++----- api/client/formatter/formatter_test.go | 35 ++---- integration-cli/docker_cli_by_digest_test.go | 14 +-- 3 files changed, 103 insertions(+), 64 deletions(-) diff --git a/api/client/formatter/formatter.go b/api/client/formatter/formatter.go index a52ec8eb10..1e250a2522 100644 --- a/api/client/formatter/formatter.go +++ b/api/client/formatter/formatter.go @@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() { ctx.postformat(tmpl, &containerContext{}) } +func isDangling(image types.Image) bool { + return len(image.RepoTags) == 1 && image.RepoTags[0] == ":" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "@" +} + func (ctx ImageContext) Write() { switch ctx.Format { case tableFormatKey: @@ -200,42 +204,98 @@ virtual_size: {{.Size}} } for _, image := range ctx.Images { + images := []*imageContext{} + if isDangling(image) { + images = append(images, &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: "", + tag: "", + digest: "", + }) + } else { + repoTags := map[string][]string{} + repoDigests := map[string][]string{} - repoTags := image.RepoTags - repoDigests := image.RepoDigests - - if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { - // dangling image - clear out either repoTags or repoDigests so we only show it once below - repoDigests = []string{} - } - // combine the tags and digests lists - tagsAndDigests := append(repoTags, repoDigests...) - for _, repoAndRef := range tagsAndDigests { - repo := "" - tag := "" - digest := "" - - if !strings.HasPrefix(repoAndRef, "") { - ref, err := reference.ParseNamed(repoAndRef) + for _, refString := range append(image.RepoTags) { + ref, err := reference.ParseNamed(refString) if err != nil { continue } - repo = ref.Name() - - switch x := ref.(type) { - case reference.Canonical: - digest = x.Digest().String() - case reference.NamedTagged: - tag = x.Tag() + if nt, ok := ref.(reference.NamedTagged); ok { + repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag()) } } - imageCtx := &imageContext{ - trunc: ctx.Trunc, - i: image, - repo: repo, - tag: tag, - digest: digest, + for _, refString := range append(image.RepoDigests) { + ref, err := reference.ParseNamed(refString) + if err != nil { + continue + } + if c, ok := ref.(reference.Canonical); ok { + repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String()) + } } + + for repo, tags := range repoTags { + digests := repoDigests[repo] + + // Do not display digests as their own row + delete(repoDigests, repo) + + if !ctx.Digest { + // Ignore digest references, just show tag once + digests = nil + } + + for _, tag := range tags { + if len(digests) == 0 { + images = append(images, &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: repo, + tag: tag, + digest: "", + }) + continue + } + // Display the digests for each tag + for _, dgst := range digests { + images = append(images, &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: repo, + tag: tag, + digest: dgst, + }) + } + + } + } + + // Show rows for remaining digest only references + for repo, digests := range repoDigests { + // If digests are displayed, show row per digest + if ctx.Digest { + for _, dgst := range digests { + images = append(images, &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: repo, + tag: "", + digest: dgst, + }) + } + } else { + images = append(images, &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: repo, + tag: "", + }) + } + } + } + for _, imageCtx := range images { err = ctx.contextFormat(tmpl, imageCtx) if err != nil { return diff --git a/api/client/formatter/formatter_test.go b/api/client/formatter/formatter_test.go index 7dd5a68fcd..07cde63f95 100644 --- a/api/client/formatter/formatter_test.go +++ b/api/client/formatter/formatter_test.go @@ -301,7 +301,6 @@ func TestImageContextWrite(t *testing.T) { }, `REPOSITORY TAG IMAGE ID CREATED SIZE image tag1 imageID1 24 hours ago 0 B -image imageID1 24 hours ago 0 B image tag2 imageID2 24 hours ago 0 B imageID3 24 hours ago 0 B `, @@ -312,7 +311,7 @@ image tag2 imageID2 24 hours ago Format: "table {{.Repository}}", }, }, - "REPOSITORY\nimage\nimage\nimage\n\n", + "REPOSITORY\nimage\nimage\n\n", }, { ImageContext{ @@ -322,7 +321,6 @@ image tag2 imageID2 24 hours ago Digest: true, }, `REPOSITORY DIGEST -image image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf image @@ -335,7 +333,7 @@ image Quiet: true, }, }, - "REPOSITORY\nimage\nimage\nimage\n\n", + "REPOSITORY\nimage\nimage\n\n", }, { ImageContext{ @@ -344,7 +342,7 @@ image Quiet: true, }, }, - "imageID1\nimageID1\nimageID2\nimageID3\n", + "imageID1\nimageID2\nimageID3\n", }, { ImageContext{ @@ -355,8 +353,7 @@ image Digest: true, }, `REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE -image tag1 imageID1 24 hours ago 0 B -image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B +image tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B image tag2 imageID2 24 hours ago 0 B imageID3 24 hours ago 0 B `, @@ -369,7 +366,7 @@ image tag2 }, Digest: true, }, - "imageID1\nimageID1\nimageID2\nimageID3\n", + "imageID1\nimageID2\nimageID3\n", }, // Raw Format { @@ -384,12 +381,6 @@ image_id: imageID1 created_at: %s virtual_size: 0 B -repository: image -tag: -image_id: imageID1 -created_at: %s -virtual_size: 0 B - repository: image tag: tag2 image_id: imageID2 @@ -402,7 +393,7 @@ image_id: imageID3 created_at: %s virtual_size: 0 B -`, expectedTime, expectedTime, expectedTime, expectedTime), +`, expectedTime, expectedTime, expectedTime), }, { ImageContext{ @@ -413,13 +404,6 @@ virtual_size: 0 B }, fmt.Sprintf(`repository: image tag: tag1 -digest: -image_id: imageID1 -created_at: %s -virtual_size: 0 B - -repository: image -tag: digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf image_id: imageID1 created_at: %s @@ -439,7 +423,7 @@ image_id: imageID3 created_at: %s virtual_size: 0 B -`, expectedTime, expectedTime, expectedTime, expectedTime), +`, expectedTime, expectedTime, expectedTime), }, { ImageContext{ @@ -449,7 +433,6 @@ virtual_size: 0 B }, }, `image_id: imageID1 -image_id: imageID1 image_id: imageID2 image_id: imageID3 `, @@ -461,7 +444,7 @@ image_id: imageID3 Format: "{{.Repository}}", }, }, - "image\nimage\nimage\n\n", + "image\nimage\n\n", }, { ImageContext{ @@ -470,7 +453,7 @@ image_id: imageID3 }, Digest: true, }, - "image\nimage\nimage\n\n", + "image\nimage\n\n", }, } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 34f1f7ef88..2f71d0f103 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -284,10 +284,8 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { out, _ = dockerCmd(c, "images", "--digests") // make sure image 1 has repo, tag, AND repo, , digest - reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*\s`) - reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1.String() + `\s`) + reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*` + digest1.String() + `\s`) c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out)) - c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out)) // make sure image 2 has repo, , digest c.Assert(re2.MatchString(out), checker.True, check.Commentf("expected %q: %s", re2.String(), out)) @@ -298,21 +296,19 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { out, _ = dockerCmd(c, "images", "--digests") // make sure image 1 has repo, tag, digest - c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out)) + c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out)) // make sure image 2 has repo, tag, digest - reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*\s`) - reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2.String() + `\s`) - c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out)) + reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*` + digest2.String() + `\s`) c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out)) // list images out, _ = dockerCmd(c, "images", "--digests") // make sure image 1 has repo, tag, digest - c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out)) + c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out)) // make sure image 2 has repo, tag, digest - c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out)) + c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out)) // make sure busybox has tag, but not digest busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*\s`) c.Assert(busyboxRe.MatchString(out), checker.True, check.Commentf("expected %q: %s", busyboxRe.String(), out)) From 528f3ab668428f84be9ec27512d4d60c45bd1566 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 15 Jun 2016 22:41:30 -0700 Subject: [PATCH 45/71] Update swarmkit to 310f1119 Signed-off-by: Tonis Tiigi (cherry picked from commit 2783568284a0c84bbb53df3c40295d27a151889e) --- hack/vendor.sh | 3 +- .../docker/swarmkit/agent/exec/controller.go | 107 +++++---- .../agent/exec/controller_test.mock.go | 64 +++--- .../docker/swarmkit/agent/exec/errors.go | 36 +-- .../github.com/docker/swarmkit/agent/node.go | 95 +++++--- .../github.com/docker/swarmkit/api/ca.pb.go | 98 +++++--- .../github.com/docker/swarmkit/api/ca.proto | 2 + .../docker/swarmkit/ca/certificates.go | 93 +++++--- .../github.com/docker/swarmkit/ca/config.go | 12 +- .../github.com/docker/swarmkit/ca/server.go | 35 ++- .../swarmkit/manager/dispatcher/dispatcher.go | 28 +-- .../docker/swarmkit/manager/manager.go | 7 +- .../swarmkit/manager/scheduler/filter.go | 3 - .../swarmkit/manager/scheduler/nodeinfo.go | 20 +- .../swarmkit/manager/scheduler/scheduler.go | 19 +- .../swarmkit/manager/state/raft/raft.go | 37 ++- vendor/src/github.com/pkg/errors/.gitignore | 24 ++ vendor/src/github.com/pkg/errors/.travis.yml | 10 + vendor/src/github.com/pkg/errors/LICENSE | 24 ++ vendor/src/github.com/pkg/errors/README.md | 50 +++++ vendor/src/github.com/pkg/errors/appveyor.yml | 32 +++ vendor/src/github.com/pkg/errors/errors.go | 211 ++++++++++++++++++ vendor/src/github.com/pkg/errors/stack.go | 165 ++++++++++++++ 23 files changed, 907 insertions(+), 268 deletions(-) create mode 100644 vendor/src/github.com/pkg/errors/.gitignore create mode 100644 vendor/src/github.com/pkg/errors/.travis.yml create mode 100644 vendor/src/github.com/pkg/errors/LICENSE create mode 100644 vendor/src/github.com/pkg/errors/README.md create mode 100644 vendor/src/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/src/github.com/pkg/errors/errors.go create mode 100644 vendor/src/github.com/pkg/errors/stack.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 93cc52cd6c..db7260b602 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -139,7 +139,7 @@ clone git github.com/docker/docker-credential-helpers v0.3.0 clone git github.com/docker/containerd 860f3a94940894ac0a106eff4bd1616a67407ee2 # cluster -clone git github.com/docker/swarmkit 682e0b69be208176d6055cba855a5e9cf15c7cb4 +clone git github.com/docker/swarmkit 310f1119bc81f22e60b5670d9d4731bc12d7be87 clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 clone git github.com/gogo/protobuf 43a2e0b1c32252bfbbdf81f7faa7a88fb3fa4028 clone git github.com/cloudflare/cfssl 92f037e39eb103fb30f9151be40d9ed267fc4ae2 @@ -158,6 +158,7 @@ clone git github.com/prometheus/common ffe929a3f4c4faeaa10f2b9535c2b1be3ad15650 clone git github.com/prometheus/procfs 454a56f35412459b5e684fd5ec0f9211b94f002a clone hg bitbucket.org/ww/goautoneg 75cd24fc2f2c2a2088577d12123ddee5f54e0675 clone git github.com/matttproud/golang_protobuf_extensions fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a +clone git github.com/pkg/errors 01fa4104b9c248c8945d14d9f128454d5b28d595 # cli clone git github.com/spf13/cobra 75205f23b3ea70dc7ae5e900d074e010c23c37e9 https://github.com/dnephin/cobra.git diff --git a/vendor/src/github.com/docker/swarmkit/agent/exec/controller.go b/vendor/src/github.com/docker/swarmkit/agent/exec/controller.go index 59b54a1b3e..b691a74b3b 100644 --- a/vendor/src/github.com/docker/swarmkit/agent/exec/controller.go +++ b/vendor/src/github.com/docker/swarmkit/agent/exec/controller.go @@ -6,16 +6,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/log" + "github.com/pkg/errors" "golang.org/x/net/context" ) -// ContainerController controls execution of container tasks. -type ContainerController interface { - // ContainerStatus returns the status of the target container, if - // available. When the container is not available, the status will be nil. - ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) -} - // Controller controls execution of a task. type Controller interface { // Update the task definition seen by the controller. Will return @@ -48,6 +42,15 @@ type Controller interface { Close() error } +// ContainerStatuser reports status of a container. +// +// This can be implemented by controllers or error types. +type ContainerStatuser interface { + // ContainerStatus returns the status of the target container, if + // available. When the container is not available, the status will be nil. + ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) +} + // Resolve attempts to get a controller from the executor and reports the // correct status depending on the tasks current state according to the result. // @@ -121,6 +124,13 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus, return status, nil } + // containerStatus exitCode keeps track of whether or not we've set it in + // this particular method. Eventually, we assemble this as part of a defer. + var ( + containerStatus *api.ContainerStatus + exitCode int + ) + // returned when a fatal execution of the task is fatal. In this case, we // proceed to a terminal error state and set the appropriate fields. // @@ -131,28 +141,37 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus, panic("err must not be nil when fatal") } - if IsTemporary(err) { - switch Cause(err) { - case context.DeadlineExceeded, context.Canceled: - // no need to set these errors, since these will more common. - default: - status.Err = err.Error() + if cs, ok := err.(ContainerStatuser); ok { + var err error + containerStatus, err = cs.ContainerStatus(ctx) + if err != nil { + log.G(ctx).WithError(err).Error("error resolving container status on fatal") } + } + // make sure we've set the *correct* exit code + if ec, ok := err.(ExitCoder); ok { + exitCode = ec.ExitCode() + } + + if cause := errors.Cause(err); cause == context.DeadlineExceeded || cause == context.Canceled { return retry() } - if cause := Cause(err); cause == context.DeadlineExceeded || cause == context.Canceled { + status.Err = err.Error() // still reported on temporary + if IsTemporary(err) { return retry() } + // only at this point do we consider the error fatal to the task. log.G(ctx).WithError(err).Error("fatal task error") - status.Err = err.Error() + // NOTE(stevvooe): The following switch dictates the terminal failure + // state based on the state in which the failure was encountered. switch { case status.State < api.TaskStateStarting: status.State = api.TaskStateRejected - case status.State > api.TaskStateStarting: + case status.State >= api.TaskStateStarting: status.State = api.TaskStateFailed } @@ -172,21 +191,37 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus, return } - cctlr, ok := ctlr.(ContainerController) - if !ok { - return - } - - cstatus, err := cctlr.ContainerStatus(ctx) - if err != nil { - log.G(ctx).WithError(err).Error("container status unavailable") - return - } - - if cstatus != nil { - status.RuntimeStatus = &api.TaskStatus_Container{ - Container: cstatus, + if containerStatus == nil { + // collect this, if we haven't + cctlr, ok := ctlr.(ContainerStatuser) + if !ok { + return } + + var err error + containerStatus, err = cctlr.ContainerStatus(ctx) + if err != nil { + log.G(ctx).WithError(err).Error("container status unavailable") + } + + // at this point, things have gone fairly wrong. Remain positive + // and let's get something out the door. + if containerStatus == nil { + containerStatus = new(api.ContainerStatus) + containerStatusTask := task.Status.GetContainer() + if containerStatusTask != nil { + *containerStatus = *containerStatusTask // copy it over. + } + } + } + + // at this point, we *must* have a containerStatus. + if exitCode != 0 { + containerStatus.ExitCode = int32(exitCode) + } + + status.RuntimeStatus = &api.TaskStatus_Container{ + Container: containerStatus, } }() @@ -222,17 +257,7 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus, return transition(api.TaskStateRunning, "started") case api.TaskStateRunning: if err := ctlr.Wait(ctx); err != nil { - // Wait should only proceed to failed if there is a terminal - // error. The only two conditions when this happens are when we - // get an exit code or when the container doesn't exist. - switch err := err.(type) { - case ExitCoder: - return transition(api.TaskStateFailed, "failed") - default: - // pursuant to the above comment, report fatal, but wrap as - // temporary. - return fatal(MakeTemporary(err)) - } + return fatal(err) } return transition(api.TaskStateCompleted, "finished") diff --git a/vendor/src/github.com/docker/swarmkit/agent/exec/controller_test.mock.go b/vendor/src/github.com/docker/swarmkit/agent/exec/controller_test.mock.go index 7a45c9aa28..3b2a1a740f 100644 --- a/vendor/src/github.com/docker/swarmkit/agent/exec/controller_test.mock.go +++ b/vendor/src/github.com/docker/swarmkit/agent/exec/controller_test.mock.go @@ -9,38 +9,6 @@ import ( context "golang.org/x/net/context" ) -// Mock of ContainerController interface -type MockContainerController struct { - ctrl *gomock.Controller - recorder *_MockContainerControllerRecorder -} - -// Recorder for MockContainerController (not exported) -type _MockContainerControllerRecorder struct { - mock *MockContainerController -} - -func NewMockContainerController(ctrl *gomock.Controller) *MockContainerController { - mock := &MockContainerController{ctrl: ctrl} - mock.recorder = &_MockContainerControllerRecorder{mock} - return mock -} - -func (_m *MockContainerController) EXPECT() *_MockContainerControllerRecorder { - return _m.recorder -} - -func (_m *MockContainerController) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) { - ret := _m.ctrl.Call(_m, "ContainerStatus", ctx) - ret0, _ := ret[0].(*api.ContainerStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockContainerControllerRecorder) ContainerStatus(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "ContainerStatus", arg0) -} - // Mock of Controller interface type MockController struct { ctrl *gomock.Controller @@ -141,3 +109,35 @@ func (_m *MockController) Close() error { func (_mr *_MockControllerRecorder) Close() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Close") } + +// Mock of ContainerStatuser interface +type MockContainerStatuser struct { + ctrl *gomock.Controller + recorder *_MockContainerStatuserRecorder +} + +// Recorder for MockContainerStatuser (not exported) +type _MockContainerStatuserRecorder struct { + mock *MockContainerStatuser +} + +func NewMockContainerStatuser(ctrl *gomock.Controller) *MockContainerStatuser { + mock := &MockContainerStatuser{ctrl: ctrl} + mock.recorder = &_MockContainerStatuserRecorder{mock} + return mock +} + +func (_m *MockContainerStatuser) EXPECT() *_MockContainerStatuserRecorder { + return _m.recorder +} + +func (_m *MockContainerStatuser) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) { + ret := _m.ctrl.Call(_m, "ContainerStatus", ctx) + ret0, _ := ret[0].(*api.ContainerStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockContainerStatuserRecorder) ContainerStatus(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "ContainerStatus", arg0) +} diff --git a/vendor/src/github.com/docker/swarmkit/agent/exec/errors.go b/vendor/src/github.com/docker/swarmkit/agent/exec/errors.go index db6467c6fc..4d082b76fe 100644 --- a/vendor/src/github.com/docker/swarmkit/agent/exec/errors.go +++ b/vendor/src/github.com/docker/swarmkit/agent/exec/errors.go @@ -1,6 +1,6 @@ package exec -import "errors" +import "github.com/pkg/errors" var ( // ErrRuntimeUnsupported encountered when a task requires a runtime @@ -37,23 +37,6 @@ type ExitCoder interface { ExitCode() int } -type causal interface { - Cause() error -} - -// Cause returns the cause of the error, recursively. -func Cause(err error) error { - for err != nil { - if causal, ok := err.(causal); ok { - err = causal.Cause() - } else { - break - } - } - - return err -} - // Temporary indicates whether or not the error condition is temporary. // // If this is encountered in the controller, the failing operation will be @@ -65,15 +48,19 @@ type Temporary interface { // MakeTemporary makes the error temporary. func MakeTemporary(err error) error { - return &temporary{error: err} + if IsTemporary(err) { + return err + } + + return temporary{err} } type temporary struct { error } -func (t *temporary) Cause() error { return t.error } -func (t *temporary) Temporary() bool { return true } +func (t temporary) Cause() error { return t.error } +func (t temporary) Temporary() bool { return true } // IsTemporary returns true if the error or a recursive cause returns true for // temporary. @@ -85,11 +72,12 @@ func IsTemporary(err error) bool { } } - if causal, ok := err.(causal); !ok { + cause := errors.Cause(err) + if cause == err { break - } else { - err = causal.Cause() } + + err = cause } return false diff --git a/vendor/src/github.com/docker/swarmkit/agent/node.go b/vendor/src/github.com/docker/swarmkit/agent/node.go index 9e4018527d..1f9fca4fcf 100644 --- a/vendor/src/github.com/docker/swarmkit/agent/node.go +++ b/vendor/src/github.com/docker/swarmkit/agent/node.go @@ -78,21 +78,23 @@ type NodeConfig struct { // cluster. Node handles workloads and may also run as a manager. type Node struct { sync.RWMutex - config *NodeConfig - remotes *persistentRemotes - role string - roleCond *sync.Cond - conn *grpc.ClientConn - connCond *sync.Cond - nodeID string - started chan struct{} - stopped chan struct{} - ready chan struct{} - closed chan struct{} - err error - agent *Agent - manager *manager.Manager - roleChangeReq chan api.NodeRole + config *NodeConfig + remotes *persistentRemotes + role string + roleCond *sync.Cond + conn *grpc.ClientConn + connCond *sync.Cond + nodeID string + nodeMembership api.NodeSpec_Membership + started chan struct{} + stopped chan struct{} + ready chan struct{} // closed when agent has completed registration and manager(if enabled) is ready to receive control requests + certificateRequested chan struct{} // closed when certificate issue request has been sent by node + closed chan struct{} + err error + agent *Agent + manager *manager.Manager + roleChangeReq chan api.NodeRole // used to send role updates from the dispatcher api on promotion/demotion } // NewNode returns new Node instance. @@ -113,14 +115,15 @@ func NewNode(c *NodeConfig) (*Node, error) { } n := &Node{ - remotes: newPersistentRemotes(stateFile, p...), - role: ca.AgentRole, - config: c, - started: make(chan struct{}), - stopped: make(chan struct{}), - closed: make(chan struct{}), - ready: make(chan struct{}), - roleChangeReq: make(chan api.NodeRole, 1), + remotes: newPersistentRemotes(stateFile, p...), + role: ca.AgentRole, + config: c, + started: make(chan struct{}), + stopped: make(chan struct{}), + closed: make(chan struct{}), + ready: make(chan struct{}), + certificateRequested: make(chan struct{}), + roleChangeReq: make(chan api.NodeRole, 1), } n.roleCond = sync.NewCond(n.RLocker()) n.connCond = sync.NewCond(n.RLocker()) @@ -171,14 +174,17 @@ func (n *Node) run(ctx context.Context) (err error) { } }() - if (n.config.JoinAddr == "" && n.nodeID == "") || n.config.ForceNewCluster { + if n.config.JoinAddr == "" && n.nodeID == "" { if err := n.bootstrapCA(); err != nil { return err } } if n.config.JoinAddr != "" || n.config.ForceNewCluster { - n.remotes = newPersistentRemotes(filepath.Join(n.config.StateDir, stateFilename), api.Peer{Addr: n.config.JoinAddr}) + n.remotes = newPersistentRemotes(filepath.Join(n.config.StateDir, stateFilename)) + if n.config.JoinAddr != "" { + n.remotes.Observe(api.Peer{Addr: n.config.JoinAddr}, 1) + } } csrRole := n.role @@ -193,23 +199,22 @@ func (n *Node) run(ctx context.Context) (err error) { // - We wait for LoadOrCreateSecurityConfig to finish since we need a certificate to operate. // - Given a valid certificate, spin a renewal go-routine that will ensure that certificates stay // up to date. - nodeIDChan := make(chan string, 1) - caLoadDone := make(chan struct{}) + issueResponseChan := make(chan api.IssueNodeCertificateResponse, 1) go func() { select { case <-ctx.Done(): - case <-caLoadDone: - case nodeID := <-nodeIDChan: - logrus.Debugf("Requesting certificate for NodeID: %v", nodeID) + case resp := <-issueResponseChan: + logrus.Debugf("Requesting certificate for NodeID: %v", resp.NodeID) n.Lock() - n.nodeID = nodeID + n.nodeID = resp.NodeID + n.nodeMembership = resp.NodeMembership n.Unlock() + close(n.certificateRequested) } }() certDir := filepath.Join(n.config.StateDir, "certificates") - securityConfig, err := ca.LoadOrCreateSecurityConfig(ctx, certDir, n.config.CAHash, n.config.Secret, csrRole, picker.NewPicker(n.remotes), nodeIDChan) - close(caLoadDone) + securityConfig, err := ca.LoadOrCreateSecurityConfig(ctx, certDir, n.config.CAHash, n.config.Secret, csrRole, picker.NewPicker(n.remotes), issueResponseChan) if err != nil { return err } @@ -223,6 +228,7 @@ func (n *Node) run(ctx context.Context) (err error) { if err != nil { return err } + defer db.Close() if err := n.loadCertificates(); err != nil { return err @@ -402,10 +408,17 @@ func (n *Node) runAgent(ctx context.Context, db *bolt.DB, creds credentials.Tran // Ready returns a channel that is closed after node's initialization has // completes for the first time. -func (n *Node) Ready(ctx context.Context) <-chan struct{} { +func (n *Node) Ready() <-chan struct{} { return n.ready } +// CertificateRequested returns a channel that is closed after node has +// requested a certificate. After this call a caller can expect calls to +// NodeID() and `NodeMembership()` to succeed. +func (n *Node) CertificateRequested() <-chan struct{} { + return n.certificateRequested +} + func (n *Node) waitRole(ctx context.Context, role string) <-chan struct{} { c := make(chan struct{}) n.roleCond.L.Lock() @@ -482,6 +495,13 @@ func (n *Node) NodeID() string { return n.nodeID } +// NodeMembership returns current node's membership. May be empty if not set. +func (n *Node) NodeMembership() api.NodeSpec_Membership { + n.RLock() + defer n.RUnlock() + return n.nodeMembership +} + // Manager return manager instance started by node. May be nil. func (n *Node) Manager() *manager.Manager { n.RLock() @@ -528,6 +548,7 @@ func (n *Node) loadCertificates() error { n.Lock() n.role = clientTLSCreds.Role() n.nodeID = clientTLSCreds.NodeID() + n.nodeMembership = api.NodeMembershipAccepted n.roleCond.Broadcast() n.Unlock() @@ -623,11 +644,11 @@ func (n *Node) runManager(ctx context.Context, securityConfig *ca.SecurityConfig select { case <-ctx.Done(): m.Stop(context.Background()) // todo: this should be sync like other components - case <-n.waitRole(ctx, ca.AgentRole): + <-done + // in case of demotion manager will stop itself + case <-done: } - <-done - ready = nil // ready event happens once, even on multiple starts n.Lock() n.manager = nil diff --git a/vendor/src/github.com/docker/swarmkit/api/ca.pb.go b/vendor/src/github.com/docker/swarmkit/api/ca.pb.go index c1c58d07fe..139c43c1c9 100644 --- a/vendor/src/github.com/docker/swarmkit/api/ca.pb.go +++ b/vendor/src/github.com/docker/swarmkit/api/ca.pb.go @@ -63,7 +63,8 @@ func (*IssueNodeCertificateRequest) ProtoMessage() {} func (*IssueNodeCertificateRequest) Descriptor() ([]byte, []int) { return fileDescriptorCa, []int{2} } type IssueNodeCertificateResponse struct { - NodeID string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + NodeID string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + NodeMembership NodeSpec_Membership `protobuf:"varint,2,opt,name=node_membership,json=nodeMembership,proto3,enum=docker.swarmkit.v1.NodeSpec_Membership" json:"node_membership,omitempty"` } func (m *IssueNodeCertificateResponse) Reset() { *m = IssueNodeCertificateResponse{} } @@ -178,7 +179,8 @@ func (m *IssueNodeCertificateResponse) Copy() *IssueNodeCertificateResponse { } o := &IssueNodeCertificateResponse{ - NodeID: m.NodeID, + NodeID: m.NodeID, + NodeMembership: m.NodeMembership, } return o @@ -247,9 +249,10 @@ func (this *IssueNodeCertificateResponse) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 5) + s := make([]string, 0, 6) s = append(s, "&api.IssueNodeCertificateResponse{") s = append(s, "NodeID: "+fmt.Sprintf("%#v", this.NodeID)+",\n") + s = append(s, "NodeMembership: "+fmt.Sprintf("%#v", this.NodeMembership)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -583,6 +586,11 @@ func (m *IssueNodeCertificateResponse) MarshalTo(data []byte) (int, error) { i = encodeVarintCa(data, i, uint64(len(m.NodeID))) i += copy(data[i:], m.NodeID) } + if m.NodeMembership != 0 { + data[i] = 0x10 + i++ + i = encodeVarintCa(data, i, uint64(m.NodeMembership)) + } return i, nil } @@ -842,6 +850,9 @@ func (m *IssueNodeCertificateResponse) Size() (n int) { if l > 0 { n += 1 + l + sovCa(uint64(l)) } + if m.NodeMembership != 0 { + n += 1 + sovCa(uint64(m.NodeMembership)) + } return n } @@ -913,6 +924,7 @@ func (this *IssueNodeCertificateResponse) String() string { } s := strings.Join([]string{`&IssueNodeCertificateResponse{`, `NodeID:` + fmt.Sprintf("%v", this.NodeID) + `,`, + `NodeMembership:` + fmt.Sprintf("%v", this.NodeMembership) + `,`, `}`, }, "") return s @@ -1326,6 +1338,25 @@ func (m *IssueNodeCertificateResponse) Unmarshal(data []byte) error { } m.NodeID = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeMembership", wireType) + } + m.NodeMembership = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCa + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.NodeMembership |= (NodeSpec_Membership(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipCa(data[iNdEx:]) @@ -1584,33 +1615,36 @@ var ( ) var fileDescriptorCa = []byte{ - // 442 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x48, 0x4e, 0xd4, 0x2b, - 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xc9, 0x4f, 0xce, 0x4e, 0x2d, 0xd2, 0x2b, 0x2e, 0x4f, - 0x2c, 0xca, 0xcd, 0xce, 0x2c, 0xd1, 0x2b, 0x33, 0x94, 0xe2, 0x2e, 0xa9, 0x2c, 0x48, 0x2d, 0x86, - 0x28, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x33, 0xf5, 0x41, 0x2c, 0xa8, 0xa8, 0x70, 0x41, - 0x4e, 0x69, 0x7a, 0x66, 0x9e, 0x3e, 0x84, 0x82, 0x08, 0x2a, 0x39, 0x73, 0xc9, 0xf8, 0xe5, 0xa7, - 0xa4, 0x3a, 0xa7, 0x16, 0x95, 0x64, 0xa6, 0x65, 0x26, 0x27, 0x96, 0xa4, 0x06, 0x97, 0x24, 0x96, - 0x94, 0x16, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x29, 0x73, 0xb1, 0xe7, 0x01, 0xe5, - 0xe3, 0x33, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x9d, 0xb8, 0x1e, 0xdd, 0x93, 0x67, 0x03, - 0x69, 0xf1, 0x74, 0x09, 0x62, 0x03, 0x49, 0x79, 0xa6, 0x28, 0xcd, 0x63, 0xe4, 0x92, 0xc5, 0x61, - 0x4a, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x15, 0x17, 0x5b, 0x31, 0x58, 0x04, 0x6c, 0x0a, - 0xb7, 0x91, 0x92, 0x1e, 0xa6, 0x1f, 0xf4, 0x3c, 0x8b, 0x8b, 0x4b, 0x13, 0xf3, 0x92, 0x61, 0x7a, - 0xa1, 0x3a, 0x84, 0x1c, 0xb9, 0xb8, 0x93, 0x11, 0x06, 0x4b, 0x30, 0x81, 0x0d, 0x90, 0xc7, 0x66, - 0x00, 0x92, 0xfd, 0x41, 0xc8, 0x7a, 0x94, 0x9a, 0x18, 0xb9, 0xa4, 0x41, 0xa6, 0xa7, 0xa2, 0xb9, - 0x12, 0xe6, 0x4b, 0x03, 0x2e, 0x96, 0xa2, 0xfc, 0x9c, 0x54, 0xb0, 0xe3, 0xf8, 0x8c, 0x64, 0xb0, - 0x99, 0x0d, 0xd2, 0x19, 0x04, 0x54, 0x13, 0x04, 0x56, 0x29, 0x24, 0xc9, 0xc5, 0x9c, 0x5c, 0x5c, - 0x04, 0x76, 0x0c, 0x8f, 0x13, 0x3b, 0x30, 0x4c, 0x98, 0x9d, 0x83, 0x83, 0x82, 0x40, 0x62, 0x42, - 0x62, 0x40, 0xbf, 0xa6, 0x26, 0x17, 0xa5, 0x96, 0x48, 0x30, 0x83, 0x42, 0x2c, 0x08, 0xca, 0x03, - 0x05, 0x35, 0x76, 0x37, 0x40, 0xc3, 0x88, 0xa8, 0xa0, 0x96, 0xe5, 0x92, 0x76, 0x4f, 0x2d, 0x09, - 0xca, 0xcf, 0x2f, 0x71, 0x76, 0xc4, 0xf4, 0x88, 0x92, 0x03, 0x97, 0x0c, 0x76, 0x69, 0xa8, 0x1d, - 0x0a, 0xa8, 0x61, 0x09, 0xb2, 0x87, 0x07, 0x25, 0xa8, 0x8c, 0xba, 0x18, 0xb9, 0x98, 0x9c, 0x1d, - 0x85, 0x9a, 0x19, 0xb9, 0x44, 0xb0, 0x99, 0x24, 0xa4, 0x8f, 0x2d, 0x70, 0xf0, 0x38, 0x49, 0xca, - 0x80, 0x78, 0x0d, 0x10, 0x47, 0x2a, 0x71, 0x9c, 0x5a, 0xf7, 0x6e, 0x06, 0x13, 0x93, 0x00, 0xa3, - 0xd1, 0x74, 0x26, 0x2e, 0x70, 0x00, 0x40, 0x1d, 0x84, 0x2d, 0xf8, 0xb0, 0x3b, 0x08, 0x4f, 0x64, - 0x63, 0x77, 0x10, 0xbe, 0x98, 0x41, 0x38, 0x48, 0xa8, 0x8d, 0x91, 0x4b, 0x14, 0x6b, 0x4a, 0x17, - 0x32, 0xc0, 0x95, 0x68, 0x70, 0x65, 0x2d, 0x29, 0x43, 0x12, 0x74, 0xa0, 0x3b, 0xc4, 0x49, 0xe6, - 0xc4, 0x43, 0x39, 0x86, 0x1b, 0x40, 0xfc, 0xe1, 0xa1, 0x1c, 0x63, 0xc3, 0x23, 0x39, 0xc6, 0x13, - 0x40, 0x7c, 0x01, 0x88, 0x1f, 0x00, 0x71, 0x12, 0x1b, 0x38, 0x73, 0x1b, 0x03, 0x02, 0x00, 0x00, - 0xff, 0xff, 0x42, 0x13, 0xc9, 0x2a, 0x34, 0x04, 0x00, 0x00, + // 487 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x94, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x59, 0x07, 0xa5, 0x65, 0x52, 0x05, 0xb4, 0x14, 0x14, 0x52, 0x37, 0xad, 0xcc, 0x01, + 0x4e, 0x4e, 0x6a, 0x6e, 0x9c, 0x48, 0x8c, 0x84, 0x72, 0x00, 0xa1, 0xcd, 0x03, 0x20, 0xd7, 0x19, + 0x82, 0xd5, 0x26, 0x6b, 0x76, 0x37, 0x20, 0x6e, 0x08, 0x24, 0x0e, 0xdc, 0x11, 0x9c, 0x78, 0x04, + 0x9e, 0xa3, 0xe2, 0xc4, 0x91, 0x13, 0xa2, 0x7d, 0x00, 0xc4, 0x23, 0xb0, 0xbb, 0x71, 0x48, 0xff, + 0xac, 0xa3, 0xf6, 0x30, 0x8a, 0x77, 0x76, 0xbe, 0x2f, 0xbf, 0x9d, 0xf1, 0x1a, 0x56, 0xd3, 0x24, + 0xcc, 0x05, 0x57, 0x9c, 0xd2, 0x21, 0x4f, 0xf7, 0x50, 0x84, 0xf2, 0x75, 0x22, 0xc6, 0x7b, 0x99, + 0x0a, 0x5f, 0xed, 0x34, 0x6b, 0xea, 0x4d, 0x8e, 0x72, 0x56, 0xd0, 0xac, 0xc9, 0x1c, 0xd3, 0xf9, + 0x62, 0x7d, 0xc4, 0x47, 0xdc, 0x3e, 0xb6, 0xcd, 0x53, 0x91, 0xbd, 0x9e, 0xef, 0x4f, 0x47, 0xd9, + 0xa4, 0x3d, 0xfb, 0x99, 0x25, 0x83, 0x18, 0xfc, 0x27, 0x7c, 0x88, 0x31, 0x0a, 0x95, 0x3d, 0xcf, + 0xd2, 0x44, 0xe1, 0x40, 0x25, 0x6a, 0x2a, 0x19, 0xbe, 0x9c, 0xa2, 0x54, 0xf4, 0x36, 0xac, 0x4c, + 0xf4, 0xfe, 0xb3, 0x6c, 0xd8, 0x20, 0xdb, 0xe4, 0xee, 0x95, 0x1e, 0x1c, 0xfd, 0xda, 0xaa, 0x1a, + 0x49, 0xff, 0x21, 0xab, 0x9a, 0xad, 0xfe, 0x30, 0xf8, 0x4a, 0x60, 0xb3, 0xc4, 0x45, 0xe6, 0x7c, + 0x22, 0x91, 0xde, 0x87, 0xaa, 0xb4, 0x19, 0xeb, 0x52, 0x8b, 0x82, 0xf0, 0xec, 0x81, 0xc2, 0xbe, + 0x94, 0xd3, 0x64, 0x92, 0xce, 0xb5, 0x85, 0x82, 0x76, 0xa1, 0x96, 0x2e, 0x8c, 0x1b, 0x9e, 0x35, + 0xd8, 0x72, 0x19, 0x1c, 0xfb, 0x7f, 0x76, 0x5c, 0x13, 0xbc, 0x23, 0xb0, 0x61, 0xdc, 0xf1, 0x14, + 0xe5, 0xfc, 0x94, 0x1d, 0xb8, 0x2c, 0xf8, 0x3e, 0x5a, 0xb8, 0x7a, 0xe4, 0xbb, 0xbc, 0x8d, 0x92, + 0xe9, 0x1a, 0x66, 0x2b, 0xe9, 0x2d, 0xa8, 0xa4, 0x52, 0x58, 0x98, 0xb5, 0xde, 0x8a, 0xee, 0x49, + 0x25, 0x1e, 0x30, 0x66, 0x72, 0xf4, 0xa6, 0x3e, 0x2b, 0xa6, 0x02, 0x55, 0xa3, 0x62, 0x3a, 0xc6, + 0x8a, 0x55, 0xf0, 0x89, 0x80, 0xef, 0x86, 0x28, 0x9a, 0x74, 0x9e, 0x5e, 0xd3, 0xa7, 0x70, 0xd5, + 0x16, 0x8d, 0x71, 0xbc, 0x8b, 0x42, 0xbe, 0xc8, 0x72, 0x0b, 0x51, 0x8f, 0xee, 0x94, 0x51, 0x0f, + 0xf4, 0x9b, 0x11, 0x3e, 0xfe, 0x5f, 0xce, 0xea, 0x46, 0xbf, 0x58, 0x07, 0x9b, 0xb0, 0xf1, 0x08, + 0x15, 0xe3, 0x5c, 0xc5, 0xdd, 0xb3, 0xbd, 0x09, 0x1e, 0x80, 0xef, 0xde, 0x2e, 0xa8, 0xb7, 0x4f, + 0x8e, 0xc7, 0x90, 0xaf, 0x9d, 0xe8, 0x7e, 0xf4, 0x91, 0x80, 0x17, 0x77, 0xe9, 0x7b, 0x02, 0xeb, + 0x2e, 0x27, 0xda, 0x76, 0x91, 0x2f, 0x41, 0x6a, 0x76, 0xce, 0x2f, 0x98, 0x41, 0x06, 0xab, 0xdf, + 0xbf, 0xfd, 0xf9, 0xe2, 0x79, 0xd7, 0x48, 0xf4, 0xd9, 0x03, 0xdb, 0xd2, 0x02, 0xc8, 0x35, 0x10, + 0x37, 0xd0, 0x92, 0xf7, 0xc7, 0x0d, 0xb4, 0x6c, 0xd6, 0x0b, 0x20, 0xfa, 0x81, 0xc0, 0x0d, 0xe7, + 0xe5, 0xa1, 0x9d, 0xb2, 0x89, 0x96, 0xdd, 0xd6, 0xe6, 0xce, 0x05, 0x14, 0xa7, 0x41, 0x7a, 0xfe, + 0xc1, 0x61, 0xeb, 0xd2, 0x4f, 0x1d, 0x7f, 0x0f, 0x5b, 0xe4, 0xed, 0x51, 0x8b, 0x1c, 0xe8, 0xf8, + 0xa1, 0xe3, 0xb7, 0x8e, 0xdd, 0xaa, 0xfd, 0x5e, 0xdc, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x72, + 0xd0, 0xad, 0xdf, 0x94, 0x04, 0x00, 0x00, } diff --git a/vendor/src/github.com/docker/swarmkit/api/ca.proto b/vendor/src/github.com/docker/swarmkit/api/ca.proto index 149a8a1ac8..06250080ad 100644 --- a/vendor/src/github.com/docker/swarmkit/api/ca.proto +++ b/vendor/src/github.com/docker/swarmkit/api/ca.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package docker.swarmkit.v1; import "types.proto"; +import "specs.proto"; import "gogoproto/gogo.proto"; import "plugin/plugin.proto"; @@ -42,6 +43,7 @@ message IssueNodeCertificateRequest { message IssueNodeCertificateResponse { string node_id = 1 [(gogoproto.customname) = "NodeID"]; + NodeSpec.Membership node_membership = 2; } message GetRootCACertificateRequest {} diff --git a/vendor/src/github.com/docker/swarmkit/ca/certificates.go b/vendor/src/github.com/docker/swarmkit/ca/certificates.go index cdda5ed00f..d39813b305 100644 --- a/vendor/src/github.com/docker/swarmkit/ca/certificates.go +++ b/vendor/src/github.com/docker/swarmkit/ca/certificates.go @@ -122,13 +122,12 @@ func (rca *RootCA) IssueAndSaveNewCertificates(paths CertPaths, cn, ou, org stri return nil, err } - var signedCert []byte if !rca.CanSign() { return nil, ErrNoValidSigner } // Obtain a signed Certificate - signedCert, err = rca.ParseValidateAndSignCSR(csr, cn, ou, org) + certChain, err := rca.ParseValidateAndSignCSR(csr, cn, ou, org) if err != nil { log.Debugf("failed to sign node certificate: %v", err) return nil, err @@ -141,12 +140,12 @@ func (rca *RootCA) IssueAndSaveNewCertificates(paths CertPaths, cn, ou, org stri } // Write the chain to disk - if err := ioutils.AtomicWriteFile(paths.Cert, signedCert, 0644); err != nil { + if err := ioutils.AtomicWriteFile(paths.Cert, certChain, 0644); err != nil { return nil, err } // Create a valid TLSKeyPair out of the PEM encoded private key and certificate - tlsKeyPair, err := tls.X509KeyPair(signedCert, key) + tlsKeyPair, err := tls.X509KeyPair(certChain, key) if err != nil { return nil, err } @@ -157,7 +156,7 @@ func (rca *RootCA) IssueAndSaveNewCertificates(paths CertPaths, cn, ou, org stri // RequestAndSaveNewCertificates gets new certificates issued, either by signing them locally if a signer is // available, or by requesting them from the remote server at remoteAddr. -func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, paths CertPaths, role, secret string, picker *picker.Picker, transport credentials.TransportAuthenticator, nodeInfo chan<- string) (*tls.Certificate, error) { +func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, paths CertPaths, role, secret string, picker *picker.Picker, transport credentials.TransportAuthenticator, nodeInfo chan<- api.IssueNodeCertificateResponse) (*tls.Certificate, error) { // Create a new key/pair and CSR for the new manager // Write the new CSR and the new key to a temporary location so we can survive crashes on rotation tempPaths := genTempPaths(paths) @@ -245,31 +244,56 @@ func (rca *RootCA) ParseValidateAndSignCSR(csrBytes []byte, cn, ou, org string) return nil, err } - return cert, nil + // Append the first root CA Cert to the certificate, to create a valid chain + // Get the first Root CA Cert on the bundle + firstRootCA, _, err := helpers.ParseOneCertificateFromPEM(rca.Cert) + if err != nil { + return nil, err + } + if len(firstRootCA) < 1 { + return nil, fmt.Errorf("no valid Root CA certificates found") + } + // Convert the first root CA back to PEM + firstRootCAPEM := helpers.EncodeCertificatePEM(firstRootCA[0]) + if firstRootCAPEM == nil { + return nil, fmt.Errorf("error while encoding the Root CA certificate") + } + // Append this Root CA to the certificate to make [Cert PEM]\n[Root PEM][EOF] + certChain := append(cert, firstRootCAPEM...) + + return certChain, nil } -// NewRootCA creates a new RootCA object from unparsed cert and key byte +// NewRootCA creates a new RootCA object from unparsed PEM cert bundle and key byte // slices. key may be nil, and in this case NewRootCA will return a RootCA // without a signer. -func NewRootCA(cert, key []byte, certExpiry time.Duration) (RootCA, error) { - // Check to see if the Certificate file is a valid, self-signed Cert - parsedCA, err := helpers.ParseSelfSignedCertificatePEM(cert) +func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, error) { + // Parse all the certificates in the cert bundle + parsedCerts, err := helpers.ParseCertificatesPEM(certBytes) if err != nil { return RootCA{}, err } - - // Calculate the digest for our RootCACertificate - digest := digest.FromBytes(cert) - - // Create a Pool with our RootCACertificate - pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(cert) { - return RootCA{}, fmt.Errorf("error while adding root CA cert to Cert Pool") + // Check to see if we have at least one valid cert + if len(parsedCerts) < 1 { + return RootCA{}, fmt.Errorf("no valid Root CA certificates found") } - if len(key) == 0 { + // Create a Pool with all of the certificates found + pool := x509.NewCertPool() + for _, cert := range parsedCerts { + // Check to see if all of the certificates are valid, self-signed root CA certs + if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { + return RootCA{}, fmt.Errorf("error while validating Root CA Certificate: %v", err) + } + pool.AddCert(cert) + } + + // Calculate the digest for our Root CA bundle + digest := digest.FromBytes(certBytes) + + if len(keyBytes) == 0 { // This RootCA does not have a valid signer. - return RootCA{Cert: cert, Digest: digest, Pool: pool}, nil + return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil } var ( @@ -288,39 +312,40 @@ func NewRootCA(cert, key []byte, certExpiry time.Duration) (RootCA, error) { } // Attempt to decrypt the current private-key with the passphrases provided - priv, err = helpers.ParsePrivateKeyPEMWithPassword(key, passphrase) + priv, err = helpers.ParsePrivateKeyPEMWithPassword(keyBytes, passphrase) if err != nil { - priv, err = helpers.ParsePrivateKeyPEMWithPassword(key, passphrasePrev) + priv, err = helpers.ParsePrivateKeyPEMWithPassword(keyBytes, passphrasePrev) if err != nil { log.Debug("Malformed private key %v", err) return RootCA{}, err } } - if err := ensureCertKeyMatch(parsedCA, priv.Public()); err != nil { + // We will always use the first certificate inside of the root bundle as the active one + if err := ensureCertKeyMatch(parsedCerts[0], priv.Public()); err != nil { return RootCA{}, err } - signer, err := local.NewSigner(priv, parsedCA, cfsigner.DefaultSigAlgo(priv), SigningPolicy(certExpiry)) + signer, err := local.NewSigner(priv, parsedCerts[0], cfsigner.DefaultSigAlgo(priv), SigningPolicy(certExpiry)) if err != nil { return RootCA{}, err } // If the key was loaded from disk unencrypted, but there is a passphrase set, // ensure it is encrypted, so it doesn't hit raft in plain-text - keyBlock, _ := pem.Decode(key) + keyBlock, _ := pem.Decode(keyBytes) if keyBlock == nil { // This RootCA does not have a valid signer. - return RootCA{Cert: cert, Digest: digest, Pool: pool}, nil + return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil } if passphraseStr != "" && !x509.IsEncryptedPEMBlock(keyBlock) { - key, err = EncryptECPrivateKey(key, passphraseStr) + keyBytes, err = EncryptECPrivateKey(keyBytes, passphraseStr) if err != nil { return RootCA{}, err } } - return RootCA{Signer: signer, Key: key, Digest: digest, Cert: cert, Pool: pool}, nil + return RootCA{Signer: signer, Key: keyBytes, Digest: digest, Cert: certBytes, Pool: pool}, nil } func ensureCertKeyMatch(cert *x509.Certificate, key crypto.PublicKey) error { @@ -494,15 +519,12 @@ func GenerateAndSignNewTLSCert(rootCA RootCA, cn, ou, org string, paths CertPath } // Obtain a signed Certificate - cert, err := rootCA.ParseValidateAndSignCSR(csr, cn, ou, org) + certChain, err := rootCA.ParseValidateAndSignCSR(csr, cn, ou, org) if err != nil { log.Debugf("failed to sign node certificate: %v", err) return nil, err } - // Append the root CA Key to the certificate, to create a valid chain - certChain := append(cert, rootCA.Cert...) - // Ensure directory exists err = os.MkdirAll(filepath.Dir(paths.Cert), 0755) if err != nil { @@ -550,7 +572,7 @@ func GenerateAndWriteNewKey(paths CertPaths) (csr, key []byte, err error) { // GetRemoteSignedCertificate submits a CSR together with the intended role to a remote CA server address // available through a picker, and that is part of a CA identified by a specific certificate pool. -func GetRemoteSignedCertificate(ctx context.Context, csr []byte, role, secret string, rootCAPool *x509.CertPool, picker *picker.Picker, creds credentials.TransportAuthenticator, nodeInfo chan<- string) ([]byte, error) { +func GetRemoteSignedCertificate(ctx context.Context, csr []byte, role, secret string, rootCAPool *x509.CertPool, picker *picker.Picker, creds credentials.TransportAuthenticator, nodeInfo chan<- api.IssueNodeCertificateResponse) ([]byte, error) { if rootCAPool == nil { return nil, fmt.Errorf("valid root CA pool required") } @@ -596,13 +618,12 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, role, secret st return nil, err } - nodeID := issueResponse.NodeID // Send back the NodeID on the nodeInfo, so the caller can know what ID was assigned by the CA if nodeInfo != nil { - nodeInfo <- nodeID + nodeInfo <- *issueResponse } - statusRequest := &api.NodeCertificateStatusRequest{NodeID: nodeID} + statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} expBackoff := events.NewExponentialBackoff(events.ExponentialBackoffConfig{ Base: time.Second, Factor: time.Second, diff --git a/vendor/src/github.com/docker/swarmkit/ca/config.go b/vendor/src/github.com/docker/swarmkit/ca/config.go index 876dd24485..64eb6cf1fb 100644 --- a/vendor/src/github.com/docker/swarmkit/ca/config.go +++ b/vendor/src/github.com/docker/swarmkit/ca/config.go @@ -139,7 +139,7 @@ func NewConfigPaths(baseCertDir string) *SecurityConfigPaths { // LoadOrCreateSecurityConfig encapsulates the security logic behind joining a cluster. // Every node requires at least a set of TLS certificates with which to join the cluster with. // In the case of a manager, these certificates will be used both for client and server credentials. -func LoadOrCreateSecurityConfig(ctx context.Context, baseCertDir, caHash, secret, proposedRole string, picker *picker.Picker, nodeInfo chan<- string) (*SecurityConfig, error) { +func LoadOrCreateSecurityConfig(ctx context.Context, baseCertDir, caHash, secret, proposedRole string, picker *picker.Picker, nodeInfo chan<- api.IssueNodeCertificateResponse) (*SecurityConfig, error) { paths := NewConfigPaths(baseCertDir) var ( @@ -198,7 +198,10 @@ func LoadOrCreateSecurityConfig(ctx context.Context, baseCertDir, caHash, secret org := identity.NewID() if nodeInfo != nil { - nodeInfo <- cn + nodeInfo <- api.IssueNodeCertificateResponse{ + NodeID: cn, + NodeMembership: api.NodeMembershipAccepted, + } } tlsKeyPair, err = rootCA.IssueAndSaveNewCertificates(paths.Node, cn, proposedRole, org) } else { @@ -225,7 +228,10 @@ func LoadOrCreateSecurityConfig(ctx context.Context, baseCertDir, caHash, secret log.Debugf("new TLS credentials generated: %s.", paths.Node.Cert) } else { if nodeInfo != nil { - nodeInfo <- clientTLSCreds.NodeID() + nodeInfo <- api.IssueNodeCertificateResponse{ + NodeID: clientTLSCreds.NodeID(), + NodeMembership: api.NodeMembershipAccepted, + } } log.Debugf("loaded local TLS credentials: %s.", paths.Node.Cert) } diff --git a/vendor/src/github.com/docker/swarmkit/ca/server.go b/vendor/src/github.com/docker/swarmkit/ca/server.go index 0b2a30d850..9d118cdad8 100644 --- a/vendor/src/github.com/docker/swarmkit/ca/server.go +++ b/vendor/src/github.com/docker/swarmkit/ca/server.go @@ -33,7 +33,7 @@ type Server struct { // Started is a channel which gets closed once the server is running // and able to service RPCs. - Started chan struct{} + started chan struct{} } // DefaultAcceptancePolicy returns the default acceptance policy. @@ -64,7 +64,7 @@ func NewServer(store *store.MemoryStore, securityConfig *SecurityConfig) *Server return &Server{ store: store, securityConfig: securityConfig, - Started: make(chan struct{}), + started: make(chan struct{}), } } @@ -249,7 +249,8 @@ func (s *Server) IssueNodeCertificate(ctx context.Context, request *api.IssueNod } return &api.IssueNodeCertificateResponse{ - NodeID: nodeID, + NodeID: nodeID, + NodeMembership: nodeMembership, }, nil } @@ -289,11 +290,14 @@ func (s *Server) getRolePolicy(role api.NodeRole) *api.AcceptancePolicy_RoleAdmi // issueRenewCertificate receives a nodeID and a CSR and modifies the node's certificate entry with the new CSR // and changes the state to RENEW, so it can be picked up and signed by the signing reconciliation loop func (s *Server) issueRenewCertificate(ctx context.Context, nodeID string, csr []byte) (*api.IssueNodeCertificateResponse, error) { - var cert api.Certificate + var ( + cert api.Certificate + node *api.Node + ) err := s.store.Update(func(tx store.Tx) error { // Attempt to retrieve the node with nodeID - node := store.GetNode(tx, nodeID) + node = store.GetNode(tx, nodeID) if node == nil { log.G(ctx).WithFields(logrus.Fields{ "node.id": nodeID, @@ -325,8 +329,10 @@ func (s *Server) issueRenewCertificate(ctx context.Context, nodeID string, csr [ "cert.role": cert.Role, "method": "issueRenewCertificate", }).Debugf("node certificate updated") + return &api.IssueNodeCertificateResponse{ - NodeID: nodeID, + NodeID: nodeID, + NodeMembership: node.Spec.Membership, }, nil } @@ -358,7 +364,14 @@ func (s *Server) Run(ctx context.Context) error { s.ctx, s.cancel = context.WithCancel(ctx) s.mu.Unlock() - close(s.Started) + // Run() should never be called twice, but just in case, we're + // attempting to close the started channel in a safe way + select { + case <-s.started: + return fmt.Errorf("CA server cannot be started more than once") + default: + close(s.started) + } // Retrieve the channels to keep track of changes in the cluster // Retrieve all the currently registered nodes @@ -439,6 +452,11 @@ func (s *Server) Stop() error { return nil } +// Ready waits on the ready channel and returns when the server is ready to serve. +func (s *Server) Ready() <-chan struct{} { + return s.started +} + func (s *Server) addTask() error { s.mu.Lock() if !s.isRunning() { @@ -600,8 +618,7 @@ func (s *Server) signNodeCert(ctx context.Context, node *api.Node) { // We were able to successfully sign the new CSR. Let's try to update the nodeStore for { err = s.store.Update(func(tx store.Tx) error { - // Remote nodes are expecting a full certificate chain, not just a signed certificate - node.Certificate.Certificate = append(cert, s.securityConfig.RootCA().Cert...) + node.Certificate.Certificate = cert node.Certificate.Status = api.IssuanceStatus{ State: api.IssuanceStateIssued, } diff --git a/vendor/src/github.com/docker/swarmkit/manager/dispatcher/dispatcher.go b/vendor/src/github.com/docker/swarmkit/manager/dispatcher/dispatcher.go index e35ea92e26..9ad726b60d 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/dispatcher/dispatcher.go +++ b/vendor/src/github.com/docker/swarmkit/manager/dispatcher/dispatcher.go @@ -95,7 +95,6 @@ type Dispatcher struct { cluster Cluster ctx context.Context cancel context.CancelFunc - wg sync.WaitGroup taskUpdates map[string]*api.TaskStatus // indexed by task ID taskUpdatesLock sync.Mutex @@ -152,8 +151,6 @@ func (d *Dispatcher) Run(ctx context.Context) error { d.mu.Unlock() return fmt.Errorf("dispatcher is stopped") } - d.wg.Add(1) - defer d.wg.Done() logger := log.G(ctx).WithField("module", "dispatcher") ctx = log.WithLogger(ctx, logger) if err := d.markNodesUnknown(ctx); err != nil { @@ -236,27 +233,19 @@ func (d *Dispatcher) Stop() error { d.cancel() d.mu.Unlock() d.nodes.Clean() - // wait for all handlers to finish their raft deals, because manager will - // set raftNode to nil - d.wg.Wait() return nil } -func (d *Dispatcher) addTask() error { +func (d *Dispatcher) isRunningLocked() error { d.mu.Lock() if !d.isRunning() { d.mu.Unlock() return grpc.Errorf(codes.Aborted, "dispatcher is stopped") } - d.wg.Add(1) d.mu.Unlock() return nil } -func (d *Dispatcher) doneTask() { - d.wg.Done() -} - func (d *Dispatcher) markNodesUnknown(ctx context.Context) error { log := log.G(ctx).WithField("method", "(*Dispatcher).markNodesUnknown") var nodes []*api.Node @@ -325,10 +314,9 @@ func (d *Dispatcher) isRunning() bool { // register is used for registration of node with particular dispatcher. func (d *Dispatcher) register(ctx context.Context, nodeID string, description *api.NodeDescription) (string, string, error) { // prevent register until we're ready to accept it - if err := d.addTask(); err != nil { + if err := d.isRunningLocked(); err != nil { return "", "", err } - defer d.doneTask() // create or update node in store // TODO(stevvooe): Validate node specification. @@ -390,10 +378,9 @@ func (d *Dispatcher) UpdateTaskStatus(ctx context.Context, r *api.UpdateTaskStat } log := log.G(ctx).WithFields(fields) - if err := d.addTask(); err != nil { + if err := d.isRunningLocked(); err != nil { return nil, err } - defer d.doneTask() if _, err := d.nodes.GetWithSession(nodeID, r.SessionID); err != nil { return nil, err @@ -505,10 +492,9 @@ func (d *Dispatcher) Tasks(r *api.TasksRequest, stream api.Dispatcher_TasksServe } nodeID := nodeInfo.NodeID - if err := d.addTask(); err != nil { + if err := d.isRunningLocked(); err != nil { return err } - defer d.doneTask() fields := logrus.Fields{ "node.id": nodeID, @@ -585,10 +571,9 @@ func (d *Dispatcher) Tasks(r *api.TasksRequest, stream api.Dispatcher_TasksServe } func (d *Dispatcher) nodeRemove(id string, status api.NodeStatus) error { - if err := d.addTask(); err != nil { + if err := d.isRunningLocked(); err != nil { return err } - defer d.doneTask() // TODO(aaronl): Is it worth batching node removals? err := d.store.Update(func(tx store.Tx) error { node := store.GetNode(tx, id) @@ -640,10 +625,9 @@ func (d *Dispatcher) Session(r *api.SessionRequest, stream api.Dispatcher_Sessio } nodeID := nodeInfo.NodeID - if err := d.addTask(); err != nil { + if err := d.isRunningLocked(); err != nil { return err } - defer d.doneTask() // register the node. nodeID, sessionID, err := d.register(stream.Context(), nodeID, r.Description) diff --git a/vendor/src/github.com/docker/swarmkit/manager/manager.go b/vendor/src/github.com/docker/swarmkit/manager/manager.go index 1a89aa6c20..c22ec6cfee 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/manager.go +++ b/vendor/src/github.com/docker/swarmkit/manager/manager.go @@ -3,6 +3,7 @@ package manager import ( "crypto/x509" "encoding/pem" + "errors" "fmt" "net" "os" @@ -101,6 +102,10 @@ func New(config *Config) (*Manager, error) { tcpAddr := config.ProtoAddr["tcp"] + if tcpAddr == "" { + return nil, errors.New("no tcp listen address or listener provided") + } + listenHost, listenPort, err := net.SplitHostPort(tcpAddr) if err == nil { ip := net.ParseIP(listenHost) @@ -664,7 +669,7 @@ func (m *Manager) rotateRootCAKEK(ctx context.Context, clusterID string) error { return s.Update(func(tx store.Tx) error { cluster = store.GetCluster(tx, clusterID) if cluster == nil { - return fmt.Errorf("cluster not found") + return fmt.Errorf("cluster not found: %s", clusterID) } cluster.RootCA.CAKey = finalKey return store.UpdateCluster(tx, cluster) diff --git a/vendor/src/github.com/docker/swarmkit/manager/scheduler/filter.go b/vendor/src/github.com/docker/swarmkit/manager/scheduler/filter.go index efa6dcd241..c0d23d3c5c 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/scheduler/filter.go +++ b/vendor/src/github.com/docker/swarmkit/manager/scheduler/filter.go @@ -53,9 +53,6 @@ func (f *ResourceFilter) SetTask(t *api.Task) bool { // Check returns true if the task can be scheduled into the given node. func (f *ResourceFilter) Check(n *NodeInfo) bool { - if n.AvailableResources == nil { - return false - } if f.reservations.NanoCPUs > n.AvailableResources.NanoCPUs { return false } diff --git a/vendor/src/github.com/docker/swarmkit/manager/scheduler/nodeinfo.go b/vendor/src/github.com/docker/swarmkit/manager/scheduler/nodeinfo.go index a8be3b5b3f..a6e92fa6c3 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/scheduler/nodeinfo.go +++ b/vendor/src/github.com/docker/swarmkit/manager/scheduler/nodeinfo.go @@ -6,10 +6,10 @@ import "github.com/docker/swarmkit/api" type NodeInfo struct { *api.Node Tasks map[string]*api.Task - AvailableResources *api.Resources + AvailableResources api.Resources } -func newNodeInfo(n *api.Node, tasks map[string]*api.Task, availableResources *api.Resources) NodeInfo { +func newNodeInfo(n *api.Node, tasks map[string]*api.Task, availableResources api.Resources) NodeInfo { nodeInfo := NodeInfo{ Node: n, Tasks: make(map[string]*api.Task), @@ -31,11 +31,9 @@ func (nodeInfo *NodeInfo) removeTask(t *api.Task) bool { } delete(nodeInfo.Tasks, t.ID) - if nodeInfo.AvailableResources != nil { - reservations := taskReservations(t.Spec) - nodeInfo.AvailableResources.MemoryBytes += reservations.MemoryBytes - nodeInfo.AvailableResources.NanoCPUs += reservations.NanoCPUs - } + reservations := taskReservations(t.Spec) + nodeInfo.AvailableResources.MemoryBytes += reservations.MemoryBytes + nodeInfo.AvailableResources.NanoCPUs += reservations.NanoCPUs return true } @@ -49,11 +47,9 @@ func (nodeInfo *NodeInfo) addTask(t *api.Task) bool { } if _, ok := nodeInfo.Tasks[t.ID]; !ok { nodeInfo.Tasks[t.ID] = t - if nodeInfo.AvailableResources != nil { - reservations := taskReservations(t.Spec) - nodeInfo.AvailableResources.MemoryBytes -= reservations.MemoryBytes - nodeInfo.AvailableResources.NanoCPUs -= reservations.NanoCPUs - } + reservations := taskReservations(t.Spec) + nodeInfo.AvailableResources.MemoryBytes -= reservations.MemoryBytes + nodeInfo.AvailableResources.NanoCPUs -= reservations.NanoCPUs return true } diff --git a/vendor/src/github.com/docker/swarmkit/manager/scheduler/scheduler.go b/vendor/src/github.com/docker/swarmkit/manager/scheduler/scheduler.go index b26ba3aa5c..6a57d4898b 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/scheduler/scheduler.go +++ b/vendor/src/github.com/docker/swarmkit/manager/scheduler/scheduler.go @@ -243,16 +243,18 @@ func (s *Scheduler) deleteTask(ctx context.Context, t *api.Task) { func (s *Scheduler) createOrUpdateNode(n *api.Node) { nodeInfo := s.nodeHeap.nodeInfo(n.ID) + var resources api.Resources if n.Description != nil && n.Description.Resources != nil { - if nodeInfo.AvailableResources == nil { - // if nodeInfo.AvailableResources hasn't been initialized - // we copy resources information from node description and - // pass it to nodeInfo - resources := *n.Description.Resources - nodeInfo.AvailableResources = &resources + resources = *n.Description.Resources + // reconcile resources by looping over all tasks in this node + for _, task := range nodeInfo.Tasks { + reservations := taskReservations(task.Spec) + resources.MemoryBytes -= reservations.MemoryBytes + resources.NanoCPUs -= reservations.NanoCPUs } } nodeInfo.Node = n + nodeInfo.AvailableResources = resources s.nodeHeap.addOrUpdateNode(nodeInfo) } @@ -422,10 +424,9 @@ func (s *Scheduler) buildNodeHeap(tx store.ReadTx, tasksByNode map[string]map[st i := 0 for _, n := range nodes { - var resources *api.Resources + var resources api.Resources if n.Description != nil && n.Description.Resources != nil { - resources = &api.Resources{NanoCPUs: n.Description.Resources.NanoCPUs, - MemoryBytes: n.Description.Resources.MemoryBytes} + resources = *n.Description.Resources } s.nodeHeap.heap = append(s.nodeHeap.heap, newNodeInfo(n, tasksByNode[n.ID], resources)) s.nodeHeap.index[n.ID] = i diff --git a/vendor/src/github.com/docker/swarmkit/manager/state/raft/raft.go b/vendor/src/github.com/docker/swarmkit/manager/state/raft/raft.go index 8b49bfe99e..dbefac5cbb 100644 --- a/vendor/src/github.com/docker/swarmkit/manager/state/raft/raft.go +++ b/vendor/src/github.com/docker/swarmkit/manager/state/raft/raft.go @@ -91,6 +91,10 @@ type Node struct { removed uint32 joinAddr string + // waitProp waits for all the proposals to be terminated before + // shutting down the node. + waitProp sync.WaitGroup + // forceNewCluster is a special flag used to recover from disaster // scenario by pointing to an existing or backed up data directory. forceNewCluster bool @@ -420,6 +424,7 @@ func (n *Node) stop() { defer n.stopMu.Unlock() n.cancel() + n.waitProp.Wait() n.asyncTasks.Wait() members := n.cluster.Members() @@ -762,6 +767,17 @@ func (n *Node) mustStop() bool { return atomic.LoadUint32(&n.removed) == 1 } +// canSubmitProposal defines if any more proposals +// could be submitted and processed. +func (n *Node) canSubmitProposal() bool { + select { + case <-n.Ctx.Done(): + return false + default: + return true + } +} + // Saves a log entry to our Store func (n *Node) saveToStorage(raftConfig *api.RaftConfig, hardState raftpb.HardState, entries []raftpb.Entry, snapshot raftpb.Snapshot) (err error) { if !raft.IsEmptySnap(snapshot) { @@ -842,7 +858,7 @@ func (n *Node) sendToMember(members map[uint64]*membership.Member, m raftpb.Mess } } - if queryMember == nil { + if queryMember == nil || queryMember.RaftID == n.Config.ID { n.Config.Logger.Error("could not find cluster member to query for leader address") return } @@ -885,10 +901,19 @@ type applyResult struct { err error } -// processInternalRaftRequest sends a message through consensus -// and then waits for it to be applies to the server. It will -// block until the change is performed or there is an error +// processInternalRaftRequest sends a message to nodes participating +// in the raft to apply a log entry and then waits for it to be applied +// on the server. It will block until the update is performed, there is +// an error or until the raft node finalizes all the proposals on node +// shutdown. func (n *Node) processInternalRaftRequest(ctx context.Context, r *api.InternalRaftRequest, cb func()) (proto.Message, error) { + n.waitProp.Add(1) + defer n.waitProp.Done() + + if !n.canSubmitProposal() { + return nil, ErrStopped + } + r.ID = n.reqIDGen.Next() ch := n.wait.register(r.ID, cb) @@ -923,7 +948,7 @@ func (n *Node) processInternalRaftRequest(ctx context.Context, r *api.InternalRa return res.resp, res.err } return nil, ErrLostLeadership - case <-n.stopCh: + case <-n.Ctx.Done(): n.wait.cancel(r.ID) return nil, ErrStopped case <-ctx.Done(): @@ -956,7 +981,7 @@ func (n *Node) configure(ctx context.Context, cc raftpb.ConfChange) error { case <-ctx.Done(): n.wait.trigger(cc.ID, nil) return ctx.Err() - case <-n.stopCh: + case <-n.Ctx.Done(): return ErrStopped } } diff --git a/vendor/src/github.com/pkg/errors/.gitignore b/vendor/src/github.com/pkg/errors/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/src/github.com/pkg/errors/.travis.yml b/vendor/src/github.com/pkg/errors/.travis.yml new file mode 100644 index 0000000000..024e28466e --- /dev/null +++ b/vendor/src/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.3 + - 1.5.4 + - 1.6.2 + - tip + +script: + - go test -v ./... diff --git a/vendor/src/github.com/pkg/errors/LICENSE b/vendor/src/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000000..fafcaafdc7 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, Dave Cheney +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. + +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/vendor/src/github.com/pkg/errors/README.md b/vendor/src/github.com/pkg/errors/README.md new file mode 100644 index 0000000000..6ea6422ec7 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/README.md @@ -0,0 +1,50 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors provides simple error handling primitives. + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +BSD-2-Clause diff --git a/vendor/src/github.com/pkg/errors/appveyor.yml b/vendor/src/github.com/pkg/errors/appveyor.yml new file mode 100644 index 0000000000..a932eade02 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/src/github.com/pkg/errors/errors.go b/vendor/src/github.com/pkg/errors/errors.go new file mode 100644 index 0000000000..65bf7a0fa5 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/errors.go @@ -0,0 +1,211 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type Causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type StackTrace interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stacktrace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stacktrace of this error. For example: +// +// if err, ok := err.(StackTrace); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// _error is an error implementation returned by New and Errorf +// that implements its own fmt.Formatter. +type _error struct { + msg string + *stack +} + +func (e _error) Error() string { return e.msg } + +func (e _error) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, e.msg) + fmt.Fprintf(s, "%+v", e.StackTrace()) + return + } + fallthrough + case 's': + io.WriteString(s, e.msg) + } +} + +// New returns an error with the supplied message. +func New(message string) error { + return _error{ + message, + callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +func Errorf(format string, args ...interface{}) error { + return _error{ + fmt.Sprintf(format, args...), + callers(), + } +} + +type cause struct { + cause error + msg string +} + +func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) } +func (c cause) Cause() error { return c.cause } + +// wrapper is an error implementation returned by Wrap and Wrapf +// that implements its own fmt.Formatter. +type wrapper struct { + cause + *stack +} + +func (w wrapper) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + fmt.Fprintf(s, "%+v: %s", w.StackTrace()[0], w.msg) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + } +} + +// Wrap returns an error annotating err with message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + return wrapper{ + cause: cause{ + cause: err, + msg: message, + }, + stack: callers(), + } +} + +// Wrapf returns an error annotating err with the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return wrapper{ + cause: cause{ + cause: err, + msg: fmt.Sprintf(format, args...), + }, + stack: callers(), + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type Causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/src/github.com/pkg/errors/stack.go b/vendor/src/github.com/pkg/errors/stack.go new file mode 100644 index 0000000000..243a64a2ac --- /dev/null +++ b/vendor/src/github.com/pkg/errors/stack.go @@ -0,0 +1,165 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} From 01c5b208e8c5f003fea3ca28cc38913b370f74fc Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 15 Jun 2016 22:41:43 -0700 Subject: [PATCH 46/71] Update daemon to new swarmkit Signed-off-by: Tonis Tiigi (cherry picked from commit a83bba467a8bd24924b1a0cf55b954ac49937b5b) --- daemon/cluster/cluster.go | 8 ++-- daemon/cluster/executor/container/adapter.go | 2 +- .../cluster/executor/container/controller.go | 43 ++++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index b8e56432a3..cdfe0b41ba 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -111,7 +111,7 @@ func New(config Config) (*Cluster, error) { select { case <-time.After(swarmConnectTimeout): logrus.Errorf("swarm component could not be started before timeout was reached") - case <-n.Ready(context.Background()): + case <-n.Ready(): case <-ctx.Done(): } if ctx.Err() != nil { @@ -213,7 +213,7 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre go func() { select { - case <-node.Ready(context.Background()): + case <-node.Ready(): c.Lock() c.reconnectDelay = initialReconnectDelay c.Unlock() @@ -273,7 +273,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) { c.Unlock() select { - case <-n.Ready(context.Background()): + case <-n.Ready(): if err := initAcceptancePolicy(n, req.Spec.AcceptancePolicy); err != nil { return "", err } @@ -319,7 +319,7 @@ func (c *Cluster) Join(req types.JoinRequest) error { return fmt.Errorf("Timeout reached before node was joined. Your cluster settings may be preventing this node from automatically joining. To accept this node into cluster run `docker node accept %v` in an existing cluster manager", nodeid) } return ErrSwarmJoinTimeoutReached - case <-n.Ready(context.Background()): + case <-n.Ready(): go c.reconnectOnFailure(ctx) return nil case <-ctx.Done(): diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index 32768ace8e..c46bfab05b 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -39,7 +39,7 @@ func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapt func (c *containerAdapter) pullImage(ctx context.Context) error { // if the image needs to be pulled, the auth config will be retrieved and updated - encodedAuthConfig := c.container.task.ServiceAnnotations.Labels[fmt.Sprintf("%v.registryauth", systemLabelPrefix)] + encodedAuthConfig := c.container.spec().RegistryAuth authConfig := &types.AuthConfig{} if encodedAuthConfig != "" { diff --git a/daemon/cluster/executor/container/controller.go b/daemon/cluster/executor/container/controller.go index 7cb11132ea..efdd12ac3b 100644 --- a/daemon/cluster/executor/container/controller.go +++ b/daemon/cluster/executor/container/controller.go @@ -2,13 +2,13 @@ package container import ( "fmt" - "strings" executorpkg "github.com/docker/docker/daemon/cluster/executor" "github.com/docker/engine-api/types" "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/log" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -84,31 +84,32 @@ func (r *controller) Prepare(ctx context.Context) error { return err } - for { - if err := r.checkClosed(); err != nil { - return err - } - if err := r.adapter.create(ctx, r.backend); err != nil { - if isContainerCreateNameConflict(err) { - if _, err := r.adapter.inspect(ctx); err != nil { - return err - } + if err := r.adapter.pullImage(ctx); err != nil { + // NOTE(stevvooe): We always try to pull the image to make sure we have + // the most up to date version. This will return an error, but we only + // log it. If the image truly doesn't exist, the create below will + // error out. + // + // This gives us some nice behavior where we use up to date versions of + // mutable tags, but will still run if the old image is available but a + // registry is down. + // + // If you don't want this behavior, lock down your image to an + // immutable tag or digest. + log.G(ctx).WithError(err).Error("pulling image failed") + } - // container is already created. success! - return exec.ErrTaskPrepared - } - - if !strings.Contains(err.Error(), "No such image") { // todo: better error detection - return err - } - if err := r.adapter.pullImage(ctx); err != nil { + if err := r.adapter.create(ctx, r.backend); err != nil { + if isContainerCreateNameConflict(err) { + if _, err := r.adapter.inspect(ctx); err != nil { return err } - continue // retry to create the container + // container is already created. success! + return exec.ErrTaskPrepared } - break + return err } return nil @@ -135,7 +136,7 @@ func (r *controller) Start(ctx context.Context) error { } if err := r.adapter.start(ctx); err != nil { - return err + return errors.Wrap(err, "starting container failed") } return nil From 0cac3c4c23c71e7829af8ca15e6ebbeaf6956e51 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 16 Jun 2016 09:42:22 -0700 Subject: [PATCH 47/71] Return membership status on join without timeout Signed-off-by: Tonis Tiigi (cherry picked from commit 1973cee0cd86da3c7b7002a3fed7af2557df538c) --- daemon/cluster/cluster.go | 69 +++++++++++++++--------- integration-cli/daemon_swarm.go | 23 +++++--- integration-cli/docker_api_swarm_test.go | 48 +++++++++-------- 3 files changed, 84 insertions(+), 56 deletions(-) diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index cdfe0b41ba..7a9cbace30 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -28,25 +28,28 @@ import ( const swarmDirName = "swarm" const controlSocket = "control.sock" -const swarmConnectTimeout = 10 * time.Second +const swarmConnectTimeout = 20 * time.Second const stateFile = "docker-state.json" const ( initialReconnectDelay = 100 * time.Millisecond - maxReconnectDelay = 10 * time.Second + maxReconnectDelay = 30 * time.Second ) // ErrNoManager is returned then a manager-only function is called on non-manager -var ErrNoManager = fmt.Errorf("this node is not participating as a Swarm manager") +var ErrNoManager = fmt.Errorf("This node is not participating as a Swarm manager") // ErrNoSwarm is returned on leaving a cluster that was never initialized -var ErrNoSwarm = fmt.Errorf("this node is not part of Swarm") +var ErrNoSwarm = fmt.Errorf("This node is not part of Swarm") // ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated -var ErrSwarmExists = fmt.Errorf("this node is already part of a Swarm") +var ErrSwarmExists = fmt.Errorf("This node is already part of a Swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.") + +// ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet. +var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.") // ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached. -var ErrSwarmJoinTimeoutReached = fmt.Errorf("timeout reached before node was joined") +var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.") type state struct { ListenAddr string @@ -249,13 +252,14 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre // Init initializes new cluster from user provided request. func (c *Cluster) Init(req types.InitRequest) (string, error) { c.Lock() - if c.node != nil { + if node := c.node; node != nil { c.Unlock() if !req.ForceNewCluster { - return "", ErrSwarmExists + return "", errSwarmExists(node) } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() + c.cancelReconnect() if err := c.node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") { return "", err } @@ -297,9 +301,9 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) { // Join makes current Cluster part of an existing swarm cluster. func (c *Cluster) Join(req types.JoinRequest) error { c.Lock() - if c.node != nil { + if node := c.node; node != nil { c.Unlock() - return ErrSwarmExists + return errSwarmExists(node) } // todo: check current state existing if len(req.RemoteAddrs) == 0 { @@ -312,23 +316,29 @@ func (c *Cluster) Join(req types.JoinRequest) error { } c.Unlock() - select { - case <-time.After(swarmConnectTimeout): - go c.reconnectOnFailure(ctx) - if nodeid := n.NodeID(); nodeid != "" { - return fmt.Errorf("Timeout reached before node was joined. Your cluster settings may be preventing this node from automatically joining. To accept this node into cluster run `docker node accept %v` in an existing cluster manager", nodeid) + certificateRequested := n.CertificateRequested() + for { + select { + case <-certificateRequested: + if n.NodeMembership() == swarmapi.NodeMembershipPending { + return fmt.Errorf("Your node is in the process of joining the cluster but needs to be accepted by existing cluster member.\nTo accept this node into cluster run \"docker node accept %v\" in an existing cluster manager. Use \"docker info\" command to see the current Swarm status of your node.", n.NodeID()) + } + certificateRequested = nil + case <-time.After(swarmConnectTimeout): + // attempt to connect will continue in background, also reconnecting + go c.reconnectOnFailure(ctx) + return ErrSwarmJoinTimeoutReached + case <-n.Ready(): + go c.reconnectOnFailure(ctx) + return nil + case <-ctx.Done(): + c.RLock() + defer c.RUnlock() + if c.err != nil { + return c.err + } + return ctx.Err() } - return ErrSwarmJoinTimeoutReached - case <-n.Ready(): - go c.reconnectOnFailure(ctx) - return nil - case <-ctx.Done(): - c.RLock() - defer c.RUnlock() - if c.err != nil { - return c.err - } - return ctx.Err() } } @@ -1004,6 +1014,13 @@ func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, return } +func errSwarmExists(node *swarmagent.Node) error { + if node.NodeMembership() != swarmapi.NodeMembershipAccepted { + return ErrPendingSwarmExists + } + return ErrSwarmExists +} + func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.AcceptancePolicy) error { ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) for conn := range node.ListenControlSocket(ctx) { diff --git a/integration-cli/daemon_swarm.go b/integration-cli/daemon_swarm.go index ac54cbfb86..6c18daee19 100644 --- a/integration-cli/daemon_swarm.go +++ b/integration-cli/daemon_swarm.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strings" + "time" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/engine-api/types" @@ -167,14 +168,22 @@ func (d *SwarmDaemon) getNode(c *check.C, id string) *swarm.Node { return &node } -func (d *SwarmDaemon) updateNode(c *check.C, node *swarm.Node, f ...nodeConstructor) { - for _, fn := range f { - fn(node) +func (d *SwarmDaemon) updateNode(c *check.C, id string, f ...nodeConstructor) { + for i := 0; ; i++ { + node := d.getNode(c, id) + for _, fn := range f { + fn(node) + } + url := fmt.Sprintf("/nodes/%s/update?version=%d", node.ID, node.Version.Index) + status, out, err := d.SockRequest("POST", url, node.Spec) + if i < 10 && strings.Contains(string(out), "update out of sequence") { + time.Sleep(100 * time.Millisecond) + continue + } + c.Assert(err, checker.IsNil) + c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) + return } - url := fmt.Sprintf("/nodes/%s/update?version=%d", node.ID, node.Version.Index) - status, out, err := d.SockRequest("POST", url, node.Spec) - c.Assert(err, checker.IsNil) - c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) } func (d *SwarmDaemon) listNodes(c *check.C) []swarm.Node { diff --git a/integration-cli/docker_api_swarm_test.go b/integration-cli/docker_api_swarm_test.go index d76c6ac0e4..ca3aed235c 100644 --- a/integration-cli/docker_api_swarm_test.go +++ b/integration-cli/docker_api_swarm_test.go @@ -82,7 +82,7 @@ func (s *DockerSwarmSuite) testAPISwarmManualAcceptance(c *check.C, secret strin err := d2.Join(d1.listenAddr, "", "", false) c.Assert(err, checker.NotNil) if secret == "" { - c.Assert(err.Error(), checker.Contains, "Timeout reached") + c.Assert(err.Error(), checker.Contains, "needs to be accepted") info, err := d2.info() c.Assert(err, checker.IsNil) c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStatePending) @@ -97,23 +97,25 @@ func (s *DockerSwarmSuite) testAPISwarmManualAcceptance(c *check.C, secret strin c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive) } d3 := s.AddDaemon(c, false, false) - go func() { - for i := 0; ; i++ { - info, err := d3.info() - c.Assert(err, checker.IsNil) - if info.NodeID != "" { - d1.updateNode(c, d1.getNode(c, info.NodeID), func(n *swarm.Node) { - n.Spec.Membership = swarm.NodeMembershipAccepted - }) - return - } - if i >= 10 { - c.Errorf("could not find nodeID") - } - time.Sleep(300 * time.Millisecond) + c.Assert(d3.Join(d1.listenAddr, secret, "", false), checker.NotNil) + info, err := d3.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStatePending) + c.Assert(len(info.NodeID), checker.GreaterThan, 5) + d1.updateNode(c, info.NodeID, func(n *swarm.Node) { + n.Spec.Membership = swarm.NodeMembershipAccepted + }) + for i := 0; ; i++ { + info, err := d3.info() + c.Assert(err, checker.IsNil) + if info.LocalNodeState == swarm.LocalNodeStateActive { + break } - }() - c.Assert(d3.Join(d1.listenAddr, secret, "", false), checker.IsNil) + if i > 10 { + c.Errorf("node did not become active") + } + time.Sleep(200 * time.Millisecond) + } } func (s *DockerSwarmSuite) TestApiSwarmSecretAcceptance(c *check.C) { @@ -236,7 +238,7 @@ func (s *DockerSwarmSuite) TestApiSwarmPromoteDemote(c *check.C) { c.Assert(info.ControlAvailable, checker.Equals, false) c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) - d1.updateNode(c, d1.getNode(c, d2.NodeID), func(n *swarm.Node) { + d1.updateNode(c, d2.NodeID, func(n *swarm.Node) { n.Spec.Role = swarm.NodeRoleManager }) @@ -255,7 +257,7 @@ func (s *DockerSwarmSuite) TestApiSwarmPromoteDemote(c *check.C) { time.Sleep(100 * time.Millisecond) } - d1.updateNode(c, d1.getNode(c, d2.NodeID), func(n *swarm.Node) { + d1.updateNode(c, d2.NodeID, func(n *swarm.Node) { n.Spec.Role = swarm.NodeRoleWorker }) @@ -466,7 +468,7 @@ func (s *DockerSwarmSuite) TestApiSwarmNodeUpdate(c *check.C) { nodes := d.listNodes(c) - d.updateNode(c, d.getNode(c, nodes[0].ID), func(n *swarm.Node) { + d.updateNode(c, nodes[0].ID, func(n *swarm.Node) { n.Spec.Availability = swarm.NodeAvailabilityPause }) @@ -489,14 +491,14 @@ func (s *DockerSwarmSuite) TestApiSwarmNodeDrainPause(c *check.C) { waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.checkActiveContainerCount, d2.checkActiveContainerCount), checker.Equals, instances) // drain d2, all containers should move to d1 - d1.updateNode(c, d1.getNode(c, d2.NodeID), func(n *swarm.Node) { + d1.updateNode(c, d2.NodeID, func(n *swarm.Node) { n.Spec.Availability = swarm.NodeAvailabilityDrain }) waitAndAssert(c, defaultReconciliationTimeout, d1.checkActiveContainerCount, checker.Equals, instances) waitAndAssert(c, defaultReconciliationTimeout, d2.checkActiveContainerCount, checker.Equals, 0) // set d2 back to active - d1.updateNode(c, d1.getNode(c, d2.NodeID), func(n *swarm.Node) { + d1.updateNode(c, d2.NodeID, func(n *swarm.Node) { n.Spec.Availability = swarm.NodeAvailabilityActive }) @@ -516,7 +518,7 @@ func (s *DockerSwarmSuite) TestApiSwarmNodeDrainPause(c *check.C) { d2ContainerCount := len(d2.activeContainers()) // set d2 to paused, scale service up, only d1 gets new tasks - d1.updateNode(c, d1.getNode(c, d2.NodeID), func(n *swarm.Node) { + d1.updateNode(c, d2.NodeID, func(n *swarm.Node) { n.Spec.Availability = swarm.NodeAvailabilityPause }) From 3635938b00176a3056f09c32b53bdc66bc55ef3c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jun 2016 14:34:28 -0700 Subject: [PATCH 48/71] add some more fields in docker service inspect -p Signed-off-by: Victor Vieux (cherry picked from commit 4c9e21b674046e8a3819cfbbb96f471bd280fba5) --- api/client/service/inspect.go | 51 ++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/api/client/service/inspect.go b/api/client/service/inspect.go index 1c2edb051a..fcb1d834a4 100644 --- a/api/client/service/inspect.go +++ b/api/client/service/inspect.go @@ -13,6 +13,7 @@ import ( "github.com/docker/docker/pkg/ioutils" apiclient "github.com/docker/engine-api/client" "github.com/docker/engine-api/types/swarm" + "github.com/docker/go-units" "github.com/spf13/cobra" ) @@ -101,7 +102,10 @@ func printService(out io.Writer, service swarm.Service) { } } fmt.Fprintln(out, "Placement:") - fmt.Fprintln(out, " Strategy:\tSPREAD") + fmt.Fprintln(out, " Strategy:\tSpread") + if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 { + ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", ")) + } fmt.Fprintf(out, "UpdateConfig:\n") fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism) if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 { @@ -109,6 +113,42 @@ func printService(out io.Writer, service swarm.Service) { } fmt.Fprintf(out, "ContainerSpec:\n") printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec) + + if service.Spec.TaskTemplate.Resources != nil { + fmt.Fprintln(out, "Resources:") + printResources := func(out io.Writer, r *swarm.Resources) { + if r.NanoCPUs != 0 { + fmt.Fprintf(out, " CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9) + } + if r.MemoryBytes != 0 { + fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(r.MemoryBytes))) + } + } + if service.Spec.TaskTemplate.Resources.Reservations != nil { + fmt.Fprintln(out, "Reservations:") + printResources(out, service.Spec.TaskTemplate.Resources.Reservations) + } + if service.Spec.TaskTemplate.Resources.Limits != nil { + fmt.Fprintln(out, "Limits:") + printResources(out, service.Spec.TaskTemplate.Resources.Limits) + } + } + if len(service.Spec.Networks) > 0 { + fmt.Fprintf(out, "Networks:") + for _, n := range service.Spec.Networks { + fmt.Fprintf(out, " %s", n.Target) + } + } + + if len(service.Endpoint.Ports) > 0 { + fmt.Fprintln(out, "Ports:") + for _, port := range service.Endpoint.Ports { + fmt.Fprintf(out, " Name = %s\n", port.Name) + fmt.Fprintf(out, " Protocol = %s\n", port.Protocol) + fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort) + fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort) + } + } } func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) { @@ -124,4 +164,13 @@ func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) { } ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir) ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User) + if len(containerSpec.Mounts) > 0 { + fmt.Fprintln(out, " Mounts:") + for _, v := range containerSpec.Mounts { + fmt.Fprintf(out, " Target = %s\n", v.Target) + fmt.Fprintf(out, " Source = %s\n", v.Source) + fmt.Fprintf(out, " Writable = %v\n", v.Writable) + fmt.Fprintf(out, " Type = %v\n", v.Type) + } + } } From efc10a92efece3fa0720a45b5741f32502663f2f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 16 Jun 2016 16:08:15 -0700 Subject: [PATCH 49/71] Fix removing containers on leaving from pending state Signed-off-by: Tonis Tiigi (cherry picked from commit 826f6f07031abc4dea6f71ed69d33a4e0789ee11) --- daemon/cluster/cluster.go | 9 ++++---- integration-cli/docker_api_swarm_test.go | 26 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index 7a9cbace30..8c70526c5e 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -389,10 +389,11 @@ func (c *Cluster) Leave(force bool) error { if err := node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") { return err } - nodeID := node.NodeID() - for _, id := range c.config.Backend.ListContainersForNode(nodeID) { - if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil { - logrus.Errorf("error removing %v: %v", id, err) + if nodeID := node.NodeID(); nodeID != "" { + for _, id := range c.config.Backend.ListContainersForNode(nodeID) { + if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil { + logrus.Errorf("error removing %v: %v", id, err) + } } } c.Lock() diff --git a/integration-cli/docker_api_swarm_test.go b/integration-cli/docker_api_swarm_test.go index ca3aed235c..9f086bd930 100644 --- a/integration-cli/docker_api_swarm_test.go +++ b/integration-cli/docker_api_swarm_test.go @@ -552,6 +552,32 @@ func (s *DockerSwarmSuite) TestApiSwarmLeaveRemovesContainer(c *check.C) { c.Assert(id, checker.HasPrefix, strings.TrimSpace(id2)) } +// #23629 +func (s *DockerSwarmSuite) TestApiSwarmLeaveOnPendingJoin(c *check.C) { + s.AddDaemon(c, true, true) + d2 := s.AddDaemon(c, false, false) + + id, err := d2.Cmd("run", "-d", "busybox", "top") + c.Assert(err, checker.IsNil) + id = strings.TrimSpace(id) + + go d2.Join("nosuchhost:1234", "", "", false) // will block on pending state + + time.Sleep(1 * time.Second) + + info, err := d2.info() + c.Assert(err, checker.IsNil) + c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStatePending) + + c.Assert(d2.Leave(true), checker.IsNil) + + waitAndAssert(c, defaultReconciliationTimeout, d2.checkActiveContainerCount, checker.Equals, 1) + + id2, err := d2.Cmd("ps", "-q") + c.Assert(err, checker.IsNil) + c.Assert(id, checker.HasPrefix, strings.TrimSpace(id2)) +} + func (s *DockerSwarmSuite) TestApiSwarmManagerRestore(c *check.C) { d1 := s.AddDaemon(c, true, true) From be82ff5c7f9ac30d260c460e6f69353e59350ead Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 15 Jun 2016 11:21:31 -0400 Subject: [PATCH 50/71] Fix removing plugins Signed-off-by: Brian Goff (cherry picked from commit 5e156fd3d4b21267caca093dd0df7ed6bce85535) --- plugin/backend.go | 4 ---- plugin/manager.go | 12 +++++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/plugin/backend.go b/plugin/backend.go index 541f06c936..cf9473b8f6 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -76,10 +76,6 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A } p := pm.newPlugin(ref, pluginID) - if ref, ok := ref.(reference.NamedTagged); ok { - p.p.Tag = ref.Tag() - } - if err := pm.initPlugin(p); err != nil { return nil, err } diff --git a/plugin/manager.go b/plugin/manager.go index 8d298453a6..9c128b207c 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -58,7 +58,12 @@ func (p *plugin) Client() *plugins.Client { } func (p *plugin) Name() string { - return p.p.Name + name := p.p.Name + if len(p.p.Tag) > 0 { + // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these + name += ":" + p.p.Tag + } + return name } func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { @@ -300,12 +305,13 @@ func (pm *Manager) initPlugin(p *plugin) error { func (pm *Manager) remove(p *plugin) error { if p.p.Active { - return fmt.Errorf("plugin %s is active", p.p.Name) + return fmt.Errorf("plugin %s is active", p.Name()) } pm.Lock() // fixme: lock single record defer pm.Unlock() os.RemoveAll(p.stateSourcePath) - delete(pm.plugins, p.p.Name) + delete(pm.plugins, p.p.ID) + delete(pm.nameToID, p.Name()) pm.save() return nil } From 9647e4d6cc5150ea0fa16e56e46eecc9626e49a7 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 15 Jun 2016 13:39:33 -0400 Subject: [PATCH 51/71] Implement plugin restore after daemon restart This ensures that: - The in-memory plugin store is populated with all the plugins - Plugins which were active before daemon restart are active after. This utilizes the liverestore feature when available, otherwise it manually starts the plugin. Signed-off-by: Brian Goff (cherry picked from commit dfd91873056c172ffc061d882da0cd18204b521a) --- cmd/dockerd/daemon_plugin_support.go | 2 +- plugin/backend.go | 8 +-- plugin/manager.go | 95 +++++++++++++++++++--------- plugin/manager_linux.go | 27 ++++---- plugin/manager_windows.go | 4 ++ 5 files changed, 89 insertions(+), 47 deletions(-) diff --git a/cmd/dockerd/daemon_plugin_support.go b/cmd/dockerd/daemon_plugin_support.go index 56a4f85dfd..cbbfe67a48 100644 --- a/cmd/dockerd/daemon_plugin_support.go +++ b/cmd/dockerd/daemon_plugin_support.go @@ -10,5 +10,5 @@ import ( ) func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error { - return plugin.Init(config.Root, config.ExecRoot, remote, rs) + return plugin.Init(config.Root, config.ExecRoot, remote, rs, config.LiveRestore) } diff --git a/plugin/backend.go b/plugin/backend.go index cf9473b8f6..1b6daa4770 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -40,7 +40,7 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) { if err != nil { return tp, err } - return p.p, nil + return p.P, nil } // Pull pulls a plugin and enables it. @@ -86,14 +86,14 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A pm.save() pm.Unlock() - return computePrivileges(&p.p.Manifest), nil + return computePrivileges(&p.P.Manifest), nil } // List displays the list of plugins and associated metadata. func (pm *Manager) List() ([]types.Plugin, error) { out := make([]types.Plugin, 0, len(pm.plugins)) for _, p := range pm.plugins { - out = append(out, p.p) + out = append(out, p.P) } return out, nil } @@ -101,7 +101,7 @@ func (pm *Manager) List() ([]types.Plugin, error) { // Push pushes a plugin to the store. func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.AuthConfig) error { p, err := pm.get(name) - dest := filepath.Join(pm.libRoot, p.p.ID) + dest := filepath.Join(pm.libRoot, p.P.ID) config, err := os.Open(filepath.Join(dest, "manifest.json")) if err != nil { return err diff --git a/plugin/manager.go b/plugin/manager.go index 9c128b207c..a33248ba93 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -46,7 +46,7 @@ func (e ErrInadequateCapability) Error() string { type plugin struct { //sync.RWMutex TODO - p types.Plugin + P types.Plugin `json:"plugin"` client *plugins.Client restartManager restartmanager.RestartManager stateSourcePath string @@ -58,17 +58,17 @@ func (p *plugin) Client() *plugins.Client { } func (p *plugin) Name() string { - name := p.p.Name - if len(p.p.Tag) > 0 { + name := p.P.Name + if len(p.P.Tag) > 0 { // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these - name += ":" + p.p.Tag + name += ":" + p.P.Tag } return name } func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { p := &plugin{ - p: types.Plugin{ + P: types.Plugin{ Name: ref.Name(), ID: id, }, @@ -76,12 +76,20 @@ func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { runtimeSourcePath: filepath.Join(pm.runRoot, id), } if ref, ok := ref.(reference.NamedTagged); ok { - p.p.Tag = ref.Tag() + p.P.Tag = ref.Tag() } return p } -// TODO: figure out why save() doesn't json encode *plugin object +func (pm *Manager) restorePlugin(p *plugin) error { + p.stateSourcePath = filepath.Join(pm.libRoot, p.P.ID, "state") + p.runtimeSourcePath = filepath.Join(pm.runRoot, p.P.ID) + if p.P.Active { + return pm.restore(p) + } + return nil +} + type pluginMap map[string]*plugin // Manager controls the plugin subsystem. @@ -95,6 +103,7 @@ type Manager struct { containerdClient libcontainerd.Client registryService registry.Service handleLegacy bool + liveRestore bool } // GetManager returns the singleton plugin Manager @@ -104,7 +113,7 @@ func GetManager() *Manager { // Init (was NewManager) instantiates the singleton Manager. // TODO: revert this to NewManager once we get rid of all the singletons. -func Init(root, execRoot string, remote libcontainerd.Remote, rs registry.Service) (err error) { +func Init(root, execRoot string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool) (err error) { if manager != nil { return nil } @@ -125,17 +134,18 @@ func Init(root, execRoot string, remote libcontainerd.Remote, rs registry.Servic handlers: make(map[string]func(string, *plugins.Client)), registryService: rs, handleLegacy: true, + liveRestore: liveRestore, } if err := os.MkdirAll(manager.runRoot, 0700); err != nil { return err } - if err := manager.init(); err != nil { - return err - } manager.containerdClient, err = remote.Client(manager) if err != nil { return err } + if err := manager.init(); err != nil { + return err + } return nil } @@ -170,7 +180,7 @@ func FindWithCapability(capability string) ([]Plugin, error) { defer manager.RUnlock() pluginLoop: for _, p := range manager.plugins { - for _, typ := range p.p.Manifest.Interface.Types { + for _, typ := range p.P.Manifest.Interface.Types { if typ.Capability != capability || typ.Prefix != "docker" { continue pluginLoop } @@ -221,7 +231,7 @@ func LookupWithCapability(name, capability string) (Plugin, error) { } capability = strings.ToLower(capability) - for _, typ := range p.p.Manifest.Interface.Types { + for _, typ := range p.P.Manifest.Interface.Types { if typ.Capability == capability && typ.Prefix == "docker" { return p, nil } @@ -262,55 +272,78 @@ func (pm *Manager) init() error { } return err } - // TODO: Populate pm.plugins - if err := json.NewDecoder(dt).Decode(&pm.nameToID); err != nil { + + if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil { return err } - // FIXME: validate, restore - return nil + var group sync.WaitGroup + group.Add(len(pm.plugins)) + for _, p := range pm.plugins { + go func(p *plugin) { + defer group.Done() + if err := pm.restorePlugin(p); err != nil { + logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err) + return + } + + pm.Lock() + pm.nameToID[p.Name()] = p.P.ID + requiresManualRestore := !pm.liveRestore && p.P.Active + pm.Unlock() + + if requiresManualRestore { + // if liveRestore is not enabled, the plugin will be stopped now so we should enable it + if err := pm.enable(p); err != nil { + logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err) + } + } + }(p) + group.Wait() + } + return pm.save() } func (pm *Manager) initPlugin(p *plugin) error { - dt, err := os.Open(filepath.Join(pm.libRoot, p.p.ID, "manifest.json")) + dt, err := os.Open(filepath.Join(pm.libRoot, p.P.ID, "manifest.json")) if err != nil { return err } - err = json.NewDecoder(dt).Decode(&p.p.Manifest) + err = json.NewDecoder(dt).Decode(&p.P.Manifest) dt.Close() if err != nil { return err } - p.p.Config.Mounts = make([]types.PluginMount, len(p.p.Manifest.Mounts)) - for i, mount := range p.p.Manifest.Mounts { - p.p.Config.Mounts[i] = mount + p.P.Config.Mounts = make([]types.PluginMount, len(p.P.Manifest.Mounts)) + for i, mount := range p.P.Manifest.Mounts { + p.P.Config.Mounts[i] = mount } - p.p.Config.Env = make([]string, 0, len(p.p.Manifest.Env)) - for _, env := range p.p.Manifest.Env { + p.P.Config.Env = make([]string, 0, len(p.P.Manifest.Env)) + for _, env := range p.P.Manifest.Env { if env.Value != nil { - p.p.Config.Env = append(p.p.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) + p.P.Config.Env = append(p.P.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) } } - copy(p.p.Config.Args, p.p.Manifest.Args.Value) + copy(p.P.Config.Args, p.P.Manifest.Args.Value) - f, err := os.Create(filepath.Join(pm.libRoot, p.p.ID, "plugin-config.json")) + f, err := os.Create(filepath.Join(pm.libRoot, p.P.ID, "plugin-config.json")) if err != nil { return err } - err = json.NewEncoder(f).Encode(&p.p.Config) + err = json.NewEncoder(f).Encode(&p.P.Config) f.Close() return err } func (pm *Manager) remove(p *plugin) error { - if p.p.Active { + if p.P.Active { return fmt.Errorf("plugin %s is active", p.Name()) } pm.Lock() // fixme: lock single record defer pm.Unlock() os.RemoveAll(p.stateSourcePath) - delete(pm.plugins, p.p.ID) + delete(pm.plugins, p.P.ID) delete(pm.nameToID, p.Name()) pm.save() return nil @@ -332,7 +365,7 @@ func (pm *Manager) set(p *plugin, args []string) error { func (pm *Manager) save() error { filePath := filepath.Join(pm.libRoot, "plugins.json") - jsonData, err := json.Marshal(pm.nameToID) + jsonData, err := json.Marshal(pm.plugins) if err != nil { logrus.Debugf("Error in json.Marshal: %v", err) return err diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index b5051dad1a..75da3beefb 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -25,11 +25,11 @@ func (pm *Manager) enable(p *plugin) error { } p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0) - if err := pm.containerdClient.Create(p.p.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only + if err := pm.containerdClient.Create(p.P.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only return err } - socket := p.p.Manifest.Interface.Socket + socket := p.P.Manifest.Interface.Socket p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil) if err != nil { return err @@ -38,11 +38,11 @@ func (pm *Manager) enable(p *plugin) error { //TODO: check net.Dial pm.Lock() // fixme: lock single record - p.p.Active = true + p.P.Active = true pm.save() pm.Unlock() - for _, typ := range p.p.Manifest.Interface.Types { + for _, typ := range p.P.Manifest.Interface.Types { if handler := pm.handlers[typ.String()]; handler != nil { handler(p.Name(), p.Client()) } @@ -51,16 +51,21 @@ func (pm *Manager) enable(p *plugin) error { return nil } +func (pm *Manager) restore(p *plugin) error { + p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0) + return pm.containerdClient.Restore(p.P.ID, libcontainerd.WithRestartManager(p.restartManager)) +} + func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { s := oci.DefaultSpec() - rootfs := filepath.Join(pm.libRoot, p.p.ID, "rootfs") + rootfs := filepath.Join(pm.libRoot, p.P.ID, "rootfs") s.Root = specs.Root{ Path: rootfs, Readonly: false, // TODO: all plugins should be readonly? settable in manifest? } - mounts := append(p.p.Config.Mounts, types.PluginMount{ + mounts := append(p.P.Config.Mounts, types.PluginMount{ Source: &p.runtimeSourcePath, Destination: defaultPluginRuntimeDestination, Type: "bind", @@ -95,11 +100,11 @@ func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { s.Mounts = append(s.Mounts, m) } - envs := make([]string, 1, len(p.p.Config.Env)+1) + envs := make([]string, 1, len(p.P.Config.Env)+1) envs[0] = "PATH=" + system.DefaultPathEnv - envs = append(envs, p.p.Config.Env...) + envs = append(envs, p.P.Config.Env...) - args := append(p.p.Manifest.Entrypoint, p.p.Config.Args...) + args := append(p.P.Manifest.Entrypoint, p.P.Config.Args...) s.Process = specs.Process{ Terminal: false, Args: args, @@ -114,13 +119,13 @@ func (pm *Manager) disable(p *plugin) error { if err := p.restartManager.Cancel(); err != nil { logrus.Error(err) } - if err := pm.containerdClient.Signal(p.p.ID, int(syscall.SIGKILL)); err != nil { + if err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGKILL)); err != nil { logrus.Error(err) } os.RemoveAll(p.runtimeSourcePath) pm.Lock() // fixme: lock single record defer pm.Unlock() - p.p.Active = false + p.P.Active = false pm.save() return nil } diff --git a/plugin/manager_windows.go b/plugin/manager_windows.go index 055a732884..84d7a4c1c9 100644 --- a/plugin/manager_windows.go +++ b/plugin/manager_windows.go @@ -19,3 +19,7 @@ func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { func (pm *Manager) disable(p *plugin) error { return fmt.Errorf("Not implemented") } + +func (pm *Manager) restore(p *plugin) error { + return fmt.Errorf("Not implemented") +} From ab6abb799b3c7747d7dca2fb52544a4ec30ec0a7 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 16 Jun 2016 17:04:33 -0700 Subject: [PATCH 52/71] Add cert-expiry to swarm update Signed-off-by: Tonis Tiigi (cherry picked from commit 7d8d51aa9d0c1737ff7f97a3efac0a2ef0975b56) --- api/client/swarm/update.go | 8 ++++++++ docs/reference/commandline/swarm_update.md | 1 + 2 files changed, 9 insertions(+) diff --git a/api/client/swarm/update.go b/api/client/swarm/update.go index a26b0d59f2..c3eff5d43d 100644 --- a/api/client/swarm/update.go +++ b/api/client/swarm/update.go @@ -18,6 +18,7 @@ type updateOptions struct { secret string taskHistoryLimit int64 dispatcherHeartbeat time.Duration + nodeCertExpiry time.Duration } func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { @@ -38,6 +39,7 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster") flags.Int64Var(&opts.taskHistoryLimit, "task-history-limit", 10, "Task history retention limit") flags.DurationVar(&opts.dispatcherHeartbeat, "dispatcher-heartbeat", time.Duration(5*time.Second), "Dispatcher heartbeat period") + flags.DurationVar(&opts.nodeCertExpiry, "cert-expiry", time.Duration(90*24*time.Hour), "Validity period for node certificates") return cmd } @@ -92,5 +94,11 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error { } } + if flags.Changed("cert-expiry") { + if v, err := flags.GetDuration("cert-expiry"); err == nil { + spec.CAConfig.NodeCertExpiry = v + } + } + return nil } diff --git a/docs/reference/commandline/swarm_update.md b/docs/reference/commandline/swarm_update.md index 942a330ea1..afbcf6455e 100644 --- a/docs/reference/commandline/swarm_update.md +++ b/docs/reference/commandline/swarm_update.md @@ -22,6 +22,7 @@ parent = "smn_cli" --help Print usage --secret string Set secret value needed to accept nodes into cluster --task-history-limit int Task history retention limit (default 10) + --cert-expiry duration Validity period for node certificates (default 2160h0m0s) Updates a Swarm cluster with new parameter values. This command must target a manager node. From 0efe76c62b3d1fbd412331cf8e4474e0625dbf12 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 16 Jun 2016 17:04:56 -0700 Subject: [PATCH 53/71] Add integration test for parsing swarm update options Signed-off-by: Tonis Tiigi (cherry picked from commit a933ac3c27ac88cf69a2bc2bc16ce076486c0206) --- integration-cli/docker_cli_swarm_test.go | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 integration-cli/docker_cli_swarm_test.go diff --git a/integration-cli/docker_cli_swarm_test.go b/integration-cli/docker_cli_swarm_test.go new file mode 100644 index 0000000000..95deab5766 --- /dev/null +++ b/integration-cli/docker_cli_swarm_test.go @@ -0,0 +1,76 @@ +// +build !windows + +package main + +import ( + "encoding/json" + "time" + + "github.com/docker/docker/pkg/integration/checker" + "github.com/docker/engine-api/types/swarm" + "github.com/go-check/check" +) + +func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) { + d := s.AddDaemon(c, true, true) + + getSpec := func() swarm.Spec { + out, err := d.Cmd("swarm", "inspect") + c.Assert(err, checker.IsNil) + var sw []swarm.Swarm + c.Assert(json.Unmarshal([]byte(out), &sw), checker.IsNil) + c.Assert(len(sw), checker.Equals, 1) + return sw[0].Spec + } + + out, err := d.Cmd("swarm", "update", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", "--auto-accept", "manager", "--auto-accept", "worker", "--secret", "foo") + c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) + + spec := getSpec() + c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) + c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, uint64(11*time.Second)) + + c.Assert(spec.AcceptancePolicy.Policies, checker.HasLen, 2) + + for _, p := range spec.AcceptancePolicy.Policies { + c.Assert(p.Autoaccept, checker.Equals, true) + c.Assert(p.Secret, checker.NotNil) + c.Assert(*p.Secret, checker.Not(checker.Equals), "") + } + + out, err = d.Cmd("swarm", "update", "--auto-accept", "none") + c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) + + spec = getSpec() + c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) + c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, uint64(11*time.Second)) + + c.Assert(spec.AcceptancePolicy.Policies, checker.HasLen, 2) + + for _, p := range spec.AcceptancePolicy.Policies { + c.Assert(p.Autoaccept, checker.Equals, false) + // secret is still set + c.Assert(p.Secret, checker.NotNil) + c.Assert(*p.Secret, checker.Not(checker.Equals), "") + } + + out, err = d.Cmd("swarm", "update", "--auto-accept", "manager", "--secret", "") + c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) + + spec = getSpec() + + c.Assert(spec.AcceptancePolicy.Policies, checker.HasLen, 2) + + for _, p := range spec.AcceptancePolicy.Policies { + c.Assert(p.Autoaccept, checker.Equals, p.Role == swarm.NodeRoleManager) + // secret has been removed + c.Assert(p.Secret, checker.IsNil) + } + + // setting anything under 30m for cert-expiry is not allowed + out, err = d.Cmd("swarm", "update", "--cert-expiry", "15m") + c.Assert(err, checker.NotNil) + c.Assert(out, checker.Contains, "minimum certificate expiry time") + spec = getSpec() + c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) +} From d747fbc95c1a7fae0ec3f42d1811a37e4c66ea7a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jun 2016 16:39:27 -0700 Subject: [PATCH 54/71] improve some messages in the node subcmds Signed-off-by: Victor Vieux (cherry picked from commit eb962235fbf1579cf36bb0582347e8639ec47205) --- api/client/node/accept.go | 2 +- api/client/node/demote.go | 2 +- api/client/node/promote.go | 2 +- api/client/node/update.go | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/api/client/node/accept.go b/api/client/node/accept.go index ae672ffe90..d024cd8c64 100644 --- a/api/client/node/accept.go +++ b/api/client/node/accept.go @@ -33,7 +33,7 @@ func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) }); err != nil { return err } - fmt.Println(id, "attempting to accept a node in the swarm.") + fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.", id) } return nil diff --git a/api/client/node/demote.go b/api/client/node/demote.go index 25f2073ee6..ed794afe10 100644 --- a/api/client/node/demote.go +++ b/api/client/node/demote.go @@ -33,7 +33,7 @@ func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) }); err != nil { return err } - fmt.Println(id, "attempting to demote a manager in the swarm.") + fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.", id) } return nil diff --git a/api/client/node/promote.go b/api/client/node/promote.go index 858b36a8c7..8c69521437 100644 --- a/api/client/node/promote.go +++ b/api/client/node/promote.go @@ -33,7 +33,7 @@ func runPromote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string }); err != nil { return err } - fmt.Println(id, "attempting to promote a node to a manager in the swarm.") + fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.", id) } return nil diff --git a/api/client/node/update.go b/api/client/node/update.go index 64cde8ea56..d18a4abdd0 100644 --- a/api/client/node/update.go +++ b/api/client/node/update.go @@ -21,7 +21,11 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { Short: "Update a node", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags)) + if err := runUpdate(dockerCli, args[0], mergeNodeUpdate(flags)); err != nil { + return err + } + fmt.Fprintln(dockerCli.Out(), args[0]) + return nil }, } @@ -47,7 +51,6 @@ func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node * return err } - fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID) return nil } From fb364d86fa16f8edf428f86e00c334f3b6d9ea11 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 16 Jun 2016 17:55:50 -0400 Subject: [PATCH 55/71] Add some tests for bundlefile and improve the error messages for LoadFile Signed-off-by: Daniel Nephin (cherry picked from commit c0ea589c1b27f8b68e84cfe6692a7639da52ce6d) --- api/client/bundlefile/bundlefile.go | 27 +++++--- api/client/bundlefile/bundlefile_test.go | 79 ++++++++++++++++++++++++ api/client/service/opts_test.go | 68 +++++++------------- api/client/stack/opts.go | 7 ++- pkg/testutil/assert/assert.go | 47 ++++++++++++++ pkg/testutil/pkg.go | 1 + 6 files changed, 174 insertions(+), 55 deletions(-) create mode 100644 api/client/bundlefile/bundlefile_test.go create mode 100644 pkg/testutil/assert/assert.go create mode 100644 pkg/testutil/pkg.go diff --git a/api/client/bundlefile/bundlefile.go b/api/client/bundlefile/bundlefile.go index 0e31772148..75c2d07433 100644 --- a/api/client/bundlefile/bundlefile.go +++ b/api/client/bundlefile/bundlefile.go @@ -4,8 +4,8 @@ package bundlefile import ( "encoding/json" + "fmt" "io" - "os" ) // Bundlefile stores the contents of a bundlefile @@ -34,19 +34,28 @@ type Port struct { } // LoadFile loads a bundlefile from a path to the file -func LoadFile(path string) (*Bundlefile, error) { - reader, err := os.Open(path) - if err != nil { - return nil, err - } - +func LoadFile(reader io.Reader) (*Bundlefile, error) { bundlefile := &Bundlefile{} - if err := json.NewDecoder(reader).Decode(bundlefile); err != nil { + decoder := json.NewDecoder(reader) + if err := decoder.Decode(bundlefile); err != nil { + switch jsonErr := err.(type) { + case *json.SyntaxError: + return nil, fmt.Errorf( + "JSON syntax error at byte %v: %s", + jsonErr.Offset, + jsonErr.Error()) + case *json.UnmarshalTypeError: + return nil, fmt.Errorf( + "Unexpected type at byte %v. Expected %s but received %s.", + jsonErr.Offset, + jsonErr.Type, + jsonErr.Value) + } return nil, err } - return bundlefile, err + return bundlefile, nil } // Print writes the contents of the bundlefile to the output writer diff --git a/api/client/bundlefile/bundlefile_test.go b/api/client/bundlefile/bundlefile_test.go new file mode 100644 index 0000000000..1ff8235ff8 --- /dev/null +++ b/api/client/bundlefile/bundlefile_test.go @@ -0,0 +1,79 @@ +// +build experimental + +package bundlefile + +import ( + "bytes" + "strings" + "testing" + + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestLoadFileV01Success(t *testing.T) { + reader := strings.NewReader(`{ + "Version": "0.1", + "Services": { + "redis": { + "Image": "redis@sha256:4b24131101fa0117bcaa18ac37055fffd9176aa1a240392bb8ea85e0be50f2ce", + "Networks": ["default"] + }, + "web": { + "Image": "dockercloud/hello-world@sha256:fe79a2cfbd17eefc344fb8419420808df95a1e22d93b7f621a7399fd1e9dca1d", + "Networks": ["default"], + "User": "web" + } + } + }`) + + bundle, err := LoadFile(reader) + assert.NilError(t, err) + assert.Equal(t, bundle.Version, "0.1") + assert.Equal(t, len(bundle.Services), 2) +} + +func TestLoadFileSyntaxError(t *testing.T) { + reader := strings.NewReader(`{ + "Version": "0.1", + "Services": unquoted string + }`) + + _, err := LoadFile(reader) + assert.Error(t, err, "syntax error at byte 37: invalid character 'u'") +} + +func TestLoadFileTypeError(t *testing.T) { + reader := strings.NewReader(`{ + "Version": "0.1", + "Services": { + "web": { + "Image": "redis", + "Networks": "none" + } + } + }`) + + _, err := LoadFile(reader) + assert.Error(t, err, "Unexpected type at byte 94. Expected []string but received string") +} + +func TestPrint(t *testing.T) { + var buffer bytes.Buffer + bundle := &Bundlefile{ + Version: "0.1", + Services: map[string]Service{ + "web": { + Image: "image", + Command: []string{"echo", "something"}, + }, + }, + } + assert.NilError(t, Print(&buffer, bundle)) + output := buffer.String() + assert.Contains(t, output, "\"Image\": \"image\"") + assert.Contains(t, output, + `"Command": [ + "echo", + "something" + ]`) +} diff --git a/api/client/service/opts_test.go b/api/client/service/opts_test.go index 189e7c11cd..808ce236ef 100644 --- a/api/client/service/opts_test.go +++ b/api/client/service/opts_test.go @@ -1,82 +1,60 @@ package service import ( - "strings" "testing" "time" + "github.com/docker/docker/pkg/testutil/assert" "github.com/docker/engine-api/types/swarm" ) -func assertEqual(t *testing.T, actual, expected interface{}) { - if expected != actual { - t.Fatalf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) - } -} - -func assertNilError(t *testing.T, err error) { - if err != nil { - t.Fatalf("Expected no error, got: %s", err.Error()) - } -} - -func assertError(t *testing.T, err error, contains string) { - if err == nil { - t.Fatalf("Expected an error, but error was nil") - } - - if !strings.Contains(err.Error(), contains) { - t.Fatalf("Expected error to contain '%s', got '%s'", contains, err.Error()) - } -} - func TestMemBytesString(t *testing.T) { var mem memBytes = 1048576 - assertEqual(t, mem.String(), "1 MiB") + assert.Equal(t, mem.String(), "1 MiB") } func TestMemBytesSetAndValue(t *testing.T) { var mem memBytes - assertNilError(t, mem.Set("5kb")) - assertEqual(t, mem.Value(), int64(5120)) + assert.NilError(t, mem.Set("5kb")) + assert.Equal(t, mem.Value(), int64(5120)) } func TestNanoCPUsString(t *testing.T) { var cpus nanoCPUs = 6100000000 - assertEqual(t, cpus.String(), "6.100") + assert.Equal(t, cpus.String(), "6.100") } func TestNanoCPUsSetAndValue(t *testing.T) { var cpus nanoCPUs - assertNilError(t, cpus.Set("0.35")) - assertEqual(t, cpus.Value(), int64(350000000)) + assert.NilError(t, cpus.Set("0.35")) + assert.Equal(t, cpus.Value(), int64(350000000)) } func TestDurationOptString(t *testing.T) { dur := time.Duration(300 * 10e8) duration := DurationOpt{value: &dur} - assertEqual(t, duration.String(), "5m0s") + assert.Equal(t, duration.String(), "5m0s") } func TestDurationOptSetAndValue(t *testing.T) { var duration DurationOpt - assertNilError(t, duration.Set("300s")) - assertEqual(t, *duration.Value(), time.Duration(300*10e8)) + assert.NilError(t, duration.Set("300s")) + assert.Equal(t, *duration.Value(), time.Duration(300*10e8)) } func TestUint64OptString(t *testing.T) { value := uint64(2345678) opt := Uint64Opt{value: &value} - assertEqual(t, opt.String(), "2345678") + assert.Equal(t, opt.String(), "2345678") opt = Uint64Opt{} - assertEqual(t, opt.String(), "none") + assert.Equal(t, opt.String(), "none") } func TestUint64OptSetAndValue(t *testing.T) { var opt Uint64Opt - assertNilError(t, opt.Set("14445")) - assertEqual(t, *opt.Value(), uint64(14445)) + assert.NilError(t, opt.Set("14445")) + assert.Equal(t, *opt.Value(), uint64(14445)) } func TestMountOptString(t *testing.T) { @@ -95,16 +73,16 @@ func TestMountOptString(t *testing.T) { }, } expected := "BIND /home/path /target, VOLUME foo /target/foo" - assertEqual(t, mount.String(), expected) + assert.Equal(t, mount.String(), expected) } func TestMountOptSetNoError(t *testing.T) { var mount MountOpt - assertNilError(t, mount.Set("type=bind,target=/target,source=/foo")) + assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo")) mounts := mount.Value() - assertEqual(t, len(mounts), 1) - assertEqual(t, mounts[0], swarm.Mount{ + assert.Equal(t, len(mounts), 1) + assert.Equal(t, mounts[0], swarm.Mount{ Type: swarm.MountType("BIND"), Source: "/foo", Target: "/target", @@ -113,25 +91,25 @@ func TestMountOptSetNoError(t *testing.T) { func TestMountOptSetErrorNoType(t *testing.T) { var mount MountOpt - assertError(t, mount.Set("target=/target,source=/foo"), "type is required") + assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required") } func TestMountOptSetErrorNoTarget(t *testing.T) { var mount MountOpt - assertError(t, mount.Set("type=VOLUME,source=/foo"), "target is required") + assert.Error(t, mount.Set("type=VOLUME,source=/foo"), "target is required") } func TestMountOptSetErrorInvalidKey(t *testing.T) { var mount MountOpt - assertError(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'") + assert.Error(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'") } func TestMountOptSetErrorInvalidField(t *testing.T) { var mount MountOpt - assertError(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'") + assert.Error(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'") } func TestMountOptSetErrorInvalidWritable(t *testing.T) { var mount MountOpt - assertError(t, mount.Set("type=VOLUME,writable=yes"), "invalid value for writable: yes") + assert.Error(t, mount.Set("type=VOLUME,writable=yes"), "invalid value for writable: yes") } diff --git a/api/client/stack/opts.go b/api/client/stack/opts.go index b4e12dc260..43839430ed 100644 --- a/api/client/stack/opts.go +++ b/api/client/stack/opts.go @@ -31,7 +31,12 @@ func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefil } fmt.Fprintf(stderr, "Loading bundle from %s\n", path) - bundle, err := bundlefile.LoadFile(path) + reader, err := os.Open(path) + if err != nil { + return nil, err + } + + bundle, err := bundlefile.LoadFile(reader) if err != nil { return nil, fmt.Errorf("Error reading %s: %v\n", path, err) } diff --git a/pkg/testutil/assert/assert.go b/pkg/testutil/assert/assert.go new file mode 100644 index 0000000000..fffdfd0274 --- /dev/null +++ b/pkg/testutil/assert/assert.go @@ -0,0 +1,47 @@ +// Package assert contains functions for making assertions in unit tests +package assert + +import ( + "strings" +) + +// TestingT is an interface which defines the methods of testing.T that are +// required by this package +type TestingT interface { + Fatalf(string, ...interface{}) +} + +// Equal compare the actual value to the expected value and fails the test if +// they are not equal. +func Equal(t TestingT, actual, expected interface{}) { + if expected != actual { + t.Fatalf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) + } +} + +// NilError asserts that the error is nil, otherwise it fails the test. +func NilError(t TestingT, err error) { + if err != nil { + t.Fatalf("Expected no error, got: %s", err.Error()) + } +} + +// Error asserts that error is not nil, and contains the expected text, +// otherwise it fails the test. +func Error(t TestingT, err error, contains string) { + if err == nil { + t.Fatalf("Expected an error, but error was nil") + } + + if !strings.Contains(err.Error(), contains) { + t.Fatalf("Expected error to contain '%s', got '%s'", contains, err.Error()) + } +} + +// Contains asserts that the string contains a substring, otherwise it fails the +// test. +func Contains(t TestingT, actual, contains string) { + if !strings.Contains(actual, contains) { + t.Fatalf("Expected '%s' to contain '%s'", actual, contains) + } +} diff --git a/pkg/testutil/pkg.go b/pkg/testutil/pkg.go new file mode 100644 index 0000000000..110b2e6a79 --- /dev/null +++ b/pkg/testutil/pkg.go @@ -0,0 +1 @@ +package testutil From aa1f2418940f666f910c65422134f43b324268a1 Mon Sep 17 00:00:00 2001 From: Charles Smith Date: Thu, 16 Jun 2016 21:28:07 -0700 Subject: [PATCH 56/71] update output for node commands, minor edits Signed-off-by: Charles Smith (cherry picked from commit 3b2132c2c7f277750edfd9fc76d73dbb9142fde2) --- docs/reference/commandline/node_inspect.md | 73 +++++++++++++--------- docs/reference/commandline/node_ls.md | 27 ++++---- docs/reference/commandline/node_tasks.md | 40 ++++++------ docs/reference/commandline/node_update.md | 6 ++ 4 files changed, 84 insertions(+), 62 deletions(-) diff --git a/docs/reference/commandline/node_inspect.md b/docs/reference/commandline/node_inspect.md index 48119c1693..7bb19214c0 100644 --- a/docs/reference/commandline/node_inspect.md +++ b/docs/reference/commandline/node_inspect.md @@ -30,17 +30,17 @@ Example output: $ docker node inspect swarm-manager [ - { - "ID": "0gac67oclbxq7", + { + "ID": "e216jshn25ckzbvmwlnh5jr3g", "Version": { - "Index": 2028 + "Index": 10 }, - "CreatedAt": "2016-06-06T20:49:32.720047494Z", - "UpdatedAt": "2016-06-07T00:23:31.207632893Z", + "CreatedAt": "2016-06-16T22:52:44.9910662Z", + "UpdatedAt": "2016-06-16T22:52:45.230878043Z", "Spec": { - "Role": "MANAGER", - "Membership": "ACCEPTED", - "Availability": "ACTIVE" + "Role": "manager", + "Membership": "accepted", + "Availability": "active" }, "Description": { "Hostname": "swarm-manager", @@ -50,38 +50,55 @@ Example output: }, "Resources": { "NanoCPUs": 1000000000, - "MemoryBytes": 1044250624 + "MemoryBytes": 1039843328 }, "Engine": { "EngineVersion": "1.12.0", - "Labels": { - "provider": "virtualbox" - } + "Plugins": [ + { + "Type": "Volume", + "Name": "local" + }, + { + "Type": "Network", + "Name": "overlay" + }, + { + "Type": "Network", + "Name": "null" + }, + { + "Type": "Network", + "Name": "host" + }, + { + "Type": "Network", + "Name": "bridge" + }, + { + "Type": "Network", + "Name": "overlay" + } + ] } }, "Status": { - "State": "READY" + "State": "ready" }, - "Manager": { - "Raft": { - "RaftID": 2143745093569717375, - "Addr": "192.168.99.118:4500", - "Status": { - "Leader": true, - "Reachability": "REACHABLE" - } - } - }, - "Attachment": {}, - } + "ManagerStatus": { + "Leader": true, + "Reachability": "reachable", + "Addr": "168.0.32.137:2377" + } + } ] - $ docker node inspect --format '{{ .Manager.Raft.Status.Leader }}' self + $ docker node inspect --format '{{ .ManagerStatus.Leader }}' self false $ docker node inspect --pretty self - ID: 2otfhz83efcc7 - Hostname: ad960a848573 + ID: e216jshn25ckzbvmwlnh5jr3g + Hostname: swarm-manager Status: State: Ready Availability: Active diff --git a/docs/reference/commandline/node_ls.md b/docs/reference/commandline/node_ls.md index ce82f6b4b8..56e157a38e 100644 --- a/docs/reference/commandline/node_ls.md +++ b/docs/reference/commandline/node_ls.md @@ -29,10 +29,10 @@ Lists all the nodes that the Docker Swarm manager knows about. You can filter us Example output: $ docker node ls - ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER - 0gac67oclbxq swarm-master Ready Active Reachable Yes - 0pwvm3ve66q7 swarm-node-02 Ready Active - 15xwihgw71aw * swarm-node-01 Ready Active Reachable + ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER + 1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active + 38ciaotwjuritcdtn9npbnkuz swarm-worker1 Accepted Ready Active + e216jshn25ckzbvmwlnh5jr3g * swarm-manager1 Accepted Ready Active Reachable Yes ## Filtering @@ -49,22 +49,21 @@ The currently supported filters are: ### name -The `name` filter matches on all or part of a tasks's name. +The `name` filter matches on all or part of a node name. The following filter matches the node with a name equal to `swarm-master` string. - $ docker node ls -f name=swarm-master - ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER - 0gac67oclbxq * swarm-master Ready Active Reachable Yes + $ docker node ls -f name=swarm-manager1 + ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER + e216jshn25ckzbvmwlnh5jr3g * swarm-manager1 Accepted Ready Active Reachable Yes ### id The `id` filter matches all or part of a node's id. - $ docker node ls -f id=0 - ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER - 0gac67oclbxq * swarm-master Ready Active Reachable Yes - 0pwvm3ve66q7 swarm-node-02 Ready Active + $ docker node ls -f id=1 + ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER + 1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active #### label @@ -76,8 +75,8 @@ The following filter matches nodes with the `usage` label regardless of its valu ```bash $ docker node ls -f "label=foo" -ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER -15xwihgw71aw * swarm-node-01 Ready Active Reachable +ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER +1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active ``` diff --git a/docs/reference/commandline/node_tasks.md b/docs/reference/commandline/node_tasks.md index 5bd6832a8a..d4c258c1aa 100644 --- a/docs/reference/commandline/node_tasks.md +++ b/docs/reference/commandline/node_tasks.md @@ -26,13 +26,13 @@ Lists all the tasks on a Node that Docker knows about. You can filter using the Example output: - $ docker node tasks swarm-master - ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE - dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - 5v26yzixl3one3ptjyqqbd0ro redis.5 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - adcaphlhsfr30d47lby6walg6 redis.8 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - chancjvk9tex6768uzzacslq2 redis.9 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master + $ docker node tasks swarm-manager1 + ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE + 7q92v0nr1hcgts2amcjyqg3pq redis.1 redis redis:3.0.6 Running 5 hours Running swarm-manager1 + b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 29 seconds Running swarm-manager1 + bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 + dkkual96p4bb3s6b10r7coxxt redis.9 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 + 0tgctg8h8cech4w0k0gwrmr23 redis.10 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 ## Filtering @@ -53,22 +53,22 @@ The `name` filter matches on all or part of a task's name. The following filter matches all tasks with a name containing the `redis` string. - $ docker node tasks -f name=redis swarm-master - ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE - dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - 5v26yzixl3one3ptjyqqbd0ro redis.5 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - adcaphlhsfr30d47lby6walg6 redis.8 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master - chancjvk9tex6768uzzacslq2 redis.9 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master + $ docker node tasks -f name=redis swarm-manager1 + ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE + 7q92v0nr1hcgts2amcjyqg3pq redis.1 redis redis:3.0.6 Running 5 hours Running swarm-manager1 + b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 29 seconds Running swarm-manager1 + bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 + dkkual96p4bb3s6b10r7coxxt redis.9 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 + 0tgctg8h8cech4w0k0gwrmr23 redis.10 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 ### id The `id` filter matches a task's id. - $ docker node tasks -f id=f33pcf8lwhs4c1t4kq8szwzta swarm-master - ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE - f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master + $ docker node tasks -f id=bg8c07zzg87di2mufeq51a2qp swarm-manager1 + ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE + bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1 #### label @@ -80,9 +80,9 @@ The following filter matches tasks with the `usage` label regardless of its valu ```bash $ docker node tasks -f "label=usage" -ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE -dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master -f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master +ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE +b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 10 minutes Running swarm-manager1 +bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 9 minutes Running swarm-manager1 ``` diff --git a/docs/reference/commandline/node_update.md b/docs/reference/commandline/node_update.md index a48712d6c9..8f005430a3 100644 --- a/docs/reference/commandline/node_update.md +++ b/docs/reference/commandline/node_update.md @@ -16,6 +16,12 @@ parent = "smn_cli" Update a node + Options: + --availability string Availability of the node (active/pause/drain) + --help Print usage + --membership string Membership of the node (accepted/rejected) + --role string Role of the node (worker/manager) + ## Related information From b7a1f1a2d15f389fad6352e66c6a82e4025996ab Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Fri, 17 Jun 2016 15:27:54 +0100 Subject: [PATCH 57/71] fix invalid typo in error message Signed-off-by: Justin Cormack (cherry picked from commit 2322ccb8fff46a4d3a84f3ef214cb473816374af) --- api/client/swarm/opts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client/swarm/opts.go b/api/client/swarm/opts.go index 741f0cf8e8..15129100f0 100644 --- a/api/client/swarm/opts.go +++ b/api/client/swarm/opts.go @@ -35,12 +35,12 @@ func (a *NodeAddrOption) String() string { // Set the value for this flag func (a *NodeAddrOption) Set(value string) error { if !strings.Contains(value, ":") { - return fmt.Errorf("Invalud url, a host and port are required") + return fmt.Errorf("Invalid url, a host and port are required") } parts := strings.Split(value, ":") if len(parts) != 2 { - return fmt.Errorf("Invalud url, too many colons") + return fmt.Errorf("Invalid url, too many colons") } a.addr = value From 445f4f2f3e1942eb24e6ba954e6ac83c0a6ed00d Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Fri, 17 Jun 2016 15:49:35 +0100 Subject: [PATCH 58/71] Missing line feed after accepting node Avoids ``` root@swarmatorium:~# docker node accept 3dnh6k13o44np9fwl83e7gh80 Node 3dnh6k13o44np9fwl83e7gh80 accepted in the swarm.root@swarmatorium:~# ``` Signed-off-by: Justin Cormack (cherry picked from commit c34d752419f0e9c17aa759df1a8314974b0d17ed) --- api/client/node/accept.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/node/accept.go b/api/client/node/accept.go index d024cd8c64..df31aa2a2b 100644 --- a/api/client/node/accept.go +++ b/api/client/node/accept.go @@ -33,7 +33,7 @@ func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) }); err != nil { return err } - fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.", id) + fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", id) } return nil From fcdd4d4a520955da5ddc71f084ce7996c861deb7 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 17 Jun 2016 08:49:06 +0200 Subject: [PATCH 59/71] bash completion for `docker swarm update --cert-expiry` Signed-off-by: Harald Albers (cherry picked from commit dc2fc75d4260767937d98ea81c51fdea148162b2) --- contrib/completion/bash/docker | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index abde5ce14c..1d9ee41816 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1639,9 +1639,15 @@ _docker_swarm_join() { } _docker_swarm_update() { + case "$prev" in + --auto-accept|--cert-expiry|--dispatcher-heartbeat|--secret|--task-history-limit) + return + ;; + esac + case "$cur" in -*) - COMPREPLY=( $( compgen -W "--auto-accept --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--auto-accept --cert-expiry --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) ) ;; esac } From 82608cd4ce7e287385dba49fc74dff2db28d31a6 Mon Sep 17 00:00:00 2001 From: Anusha Ragunathan Date: Fri, 17 Jun 2016 07:44:57 -0700 Subject: [PATCH 60/71] Add disable flag for plugin Install. Signed-off-by: Anusha Ragunathan (cherry picked from commit 22e781e8e3ae1d1ab62ddcda983cabfde2e08ad4) --- api/client/plugin/install.go | 5 +++-- integration-cli/docker_cli_plugins_test.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index 486dc4f622..2baea0a347 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -19,6 +19,7 @@ import ( type pluginOptions struct { name string grantPerms bool + disable bool } func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { @@ -35,6 +36,7 @@ func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { flags := cmd.Flags() flags.BoolVar(&options.grantPerms, "grant-all-permissions", true, "grant all permissions necessary to run the plugin") + flags.BoolVar(&options.disable, "disable", false, "do not enable the plugin on install") return cmd } @@ -62,10 +64,9 @@ func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error { requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install") - // TODO: pass acceptAllPermissions and noEnable flag options := types.PluginInstallOptions{ RegistryAuth: encodedAuth, - Disabled: false, + Disabled: opts.disable, AcceptAllPermissions: opts.grantPerms, AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name), PrivilegeFunc: requestPrivilege, diff --git a/integration-cli/docker_cli_plugins_test.go b/integration-cli/docker_cli_plugins_test.go index ad031b7ff6..fdb99431d3 100644 --- a/integration-cli/docker_cli_plugins_test.go +++ b/integration-cli/docker_cli_plugins_test.go @@ -34,3 +34,21 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(out, checker.Contains, nameWithTag) } + +func (s *DockerSuite) TestPluginInstallDisable(c *check.C) { + testRequires(c, DaemonIsLinux, ExperimentalDaemon) + name := "tiborvass/no-remove" + tag := "latest" + nameWithTag := name + ":" + tag + + _, _, err := dockerCmdWithError("plugin", "install", name, "--disable") + c.Assert(err, checker.IsNil) + + out, _, err := dockerCmdWithError("plugin", "ls") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, "false") + + out, _, err = dockerCmdWithError("plugin", "remove", nameWithTag) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, nameWithTag) +} From 05c32e7f08b7537a460abddd24e0f8eb3934f5f4 Mon Sep 17 00:00:00 2001 From: Ke Xu Date: Fri, 17 Jun 2016 16:16:13 +0900 Subject: [PATCH 61/71] fix #23017, add zsh completion for dockerd Signed-off-by: Ke Xu (cherry picked from commit bd1fc1e5c26b954fb35f7adda46d764d80a1a570) --- contrib/completion/zsh/_docker | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 69e7dbd3fa..81c628ee79 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -1,4 +1,4 @@ -#compdef docker +#compdef docker dockerd # # zsh completion for docker (http://docker.com) # @@ -1410,6 +1410,13 @@ _docker() { return ret } +_dockerd() { + integer ret=1 + words[1]='daemon' + __docker_subcommand && ret=0 + return ret +} + _docker "$@" # Local Variables: From 4b2883fac67d87700261699caf8db98a6119d0ab Mon Sep 17 00:00:00 2001 From: Anil Madhavapeddy Date: Fri, 17 Jun 2016 16:26:47 +0100 Subject: [PATCH 62/71] `docker swarm`: more consistent CLI help This changes `docker swarm help` to be consistent with capitals and removes full stops. Before: ``` Commands: init Initialize a Swarm. join Join a Swarm as a node and/or manager. update update the Swarm. leave Leave a Swarm. inspect Inspect the Swarm ``` After: ``` Commands: init Initialize a Swarm join Join a Swarm as a node and/or manager update Update the Swarm leave Leave a Swarm inspect Inspect the Swarm ``` Signed-off-by: Anil Madhavapeddy (cherry picked from commit 0ec78739ac7d8ec1579d1d4fd63cd207dc56e7c8) --- api/client/swarm/init.go | 2 +- api/client/swarm/join.go | 2 +- api/client/swarm/leave.go | 2 +- api/client/swarm/update.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/client/swarm/init.go b/api/client/swarm/init.go index d16c09793b..1f403ae4a1 100644 --- a/api/client/swarm/init.go +++ b/api/client/swarm/init.go @@ -28,7 +28,7 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "init", - Short: "Initialize a Swarm.", + Short: "Initialize a Swarm", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return runInit(dockerCli, flags, opts) diff --git a/api/client/swarm/join.go b/api/client/swarm/join.go index 346445f783..1bb9ae04b3 100644 --- a/api/client/swarm/join.go +++ b/api/client/swarm/join.go @@ -25,7 +25,7 @@ func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "join [OPTIONS] HOST:PORT", - Short: "Join a Swarm as a node and/or manager.", + Short: "Join a Swarm as a node and/or manager", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.remote = args[0] diff --git a/api/client/swarm/leave.go b/api/client/swarm/leave.go index e3f8fbfa57..50f05c7a76 100644 --- a/api/client/swarm/leave.go +++ b/api/client/swarm/leave.go @@ -19,7 +19,7 @@ func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "leave", - Short: "Leave a Swarm.", + Short: "Leave a Swarm", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return runLeave(dockerCli, opts) diff --git a/api/client/swarm/update.go b/api/client/swarm/update.go index c3eff5d43d..a77112da44 100644 --- a/api/client/swarm/update.go +++ b/api/client/swarm/update.go @@ -27,7 +27,7 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "update", - Short: "update the Swarm.", + Short: "Update the Swarm", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return runUpdate(dockerCli, flags, opts) From 8fdc925338961bcf4681656d4d25a5280c9544fb Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Fri, 17 Jun 2016 09:17:03 -0700 Subject: [PATCH 63/71] Check if DriverState is valid before using in convert Signed-off-by: Jana Radhakrishnan (cherry picked from commit 4fea81154b817b09e553308a1595bfd97a8c0582) --- daemon/cluster/convert/network.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/daemon/cluster/convert/network.go b/daemon/cluster/convert/network.go index 53b952427a..6bb9a8185c 100644 --- a/daemon/cluster/convert/network.go +++ b/daemon/cluster/convert/network.go @@ -148,17 +148,22 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource { } } - return basictypes.NetworkResource{ + nr := basictypes.NetworkResource{ ID: n.ID, Name: n.Spec.Annotations.Name, Scope: "swarm", - Driver: n.DriverState.Name, EnableIPv6: spec.Ipv6Enabled, IPAM: ipam, Internal: spec.Internal, - Options: n.DriverState.Options, Labels: n.Spec.Annotations.Labels, } + + if n.DriverState != nil { + nr.Driver = n.DriverState.Name + nr.Options = n.DriverState.Options + } + + return nr } // BasicNetworkCreateToGRPC converts a NetworkCreateRequest to a grpc NetworkSpec. From bbc214fa9bedbde32c38f9703cbca5e01dd18ce4 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 17 Jun 2016 10:58:11 -0700 Subject: [PATCH 64/71] Add newline to promote/demote message Signed-off-by: Tonis Tiigi (cherry picked from commit 3386e3570a1251bbea5302d8b8e639aaf68436c3) --- api/client/node/demote.go | 2 +- api/client/node/promote.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client/node/demote.go b/api/client/node/demote.go index ed794afe10..e9d70e989f 100644 --- a/api/client/node/demote.go +++ b/api/client/node/demote.go @@ -33,7 +33,7 @@ func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) }); err != nil { return err } - fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.", id) + fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", id) } return nil diff --git a/api/client/node/promote.go b/api/client/node/promote.go index 8c69521437..87fe42e361 100644 --- a/api/client/node/promote.go +++ b/api/client/node/promote.go @@ -33,7 +33,7 @@ func runPromote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string }); err != nil { return err } - fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.", id) + fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", id) } return nil From a7c925cba4dd867b8c43aa8238ac90b6dd1d4cf7 Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Fri, 17 Jun 2016 10:27:34 -0700 Subject: [PATCH 65/71] Vendoring libnetwork @13be89d1cf79 Signed-off-by: Jana Radhakrishnan (cherry picked from commit 70e2585d545e18757222b8ccfb21514fde7c42f4) --- hack/vendor.sh | 2 +- vendor/src/github.com/docker/libnetwork/service_linux.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index db7260b602..bc0822fe5c 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -65,7 +65,7 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 #get libnetwork packages -clone git github.com/docker/libnetwork caf22bd9a6a53dfe91b0266274155bc69235e8ed +clone git github.com/docker/libnetwork 13be89d1cf79760acae842a32ad8531567220286 clone git github.com/docker/go-events 39718a26497694185f8fb58a7d6f31947f3dc42d clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec diff --git a/vendor/src/github.com/docker/libnetwork/service_linux.go b/vendor/src/github.com/docker/libnetwork/service_linux.go index 780889f0bc..d6b4b4cd02 100644 --- a/vendor/src/github.com/docker/libnetwork/service_linux.go +++ b/vendor/src/github.com/docker/libnetwork/service_linux.go @@ -436,8 +436,8 @@ func programIngress(gwIP net.IP, ingressPorts []*PortConfig, isDelete bool) erro } for _, chain := range []string{"OUTPUT", "PREROUTING"} { - if !iptables.Exists(iptables.Nat, chain, "-j", ingressChain) { - if err := iptables.RawCombinedOutput("-t", "nat", "-I", chain, "-j", ingressChain); err != nil { + if !iptables.Exists(iptables.Nat, chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain) { + if err := iptables.RawCombinedOutput("-t", "nat", "-I", chain, "-m", "addrtype", "--dst-type", "LOCAL", "-j", ingressChain); err != nil { return fmt.Errorf("failed to add jump rule in %s to ingress chain: %v", chain, err) } } From f24e5d79bc986e111f13568dc8860a44b1eb4cf3 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Fri, 17 Jun 2016 09:12:37 -0700 Subject: [PATCH 66/71] plugins: install should not automatically accept all permissions Signed-off-by: Tibor Vass (cherry picked from commit 4b70d4561e4a9b57d80b70cfebc50831e230735a) --- api/client/plugin/install.go | 11 ++++++----- integration-cli/docker_cli_plugins_test.go | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index 2baea0a347..2841388884 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -25,7 +25,7 @@ type pluginOptions struct { func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { var options pluginOptions cmd := &cobra.Command{ - Use: "install", + Use: "install PLUGIN", Short: "Install a plugin", Args: cli.RequiresMinArgs(1), // TODO: allow for set args RunE: func(cmd *cobra.Command, args []string) error { @@ -35,7 +35,7 @@ func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVar(&options.grantPerms, "grant-all-permissions", true, "grant all permissions necessary to run the plugin") + flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "grant all permissions necessary to run the plugin") flags.BoolVar(&options.disable, "disable", false, "do not enable the plugin on install") return cmd @@ -62,14 +62,15 @@ func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error { return err } - requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install") + registryAuthFunc := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install") options := types.PluginInstallOptions{ RegistryAuth: encodedAuth, Disabled: opts.disable, AcceptAllPermissions: opts.grantPerms, AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name), - PrivilegeFunc: requestPrivilege, + // TODO: Rename PrivilegeFunc, it has nothing to do with privileges + PrivilegeFunc: registryAuthFunc, } return dockerCli.Client().PluginInstall(ctx, ref.String(), options) @@ -77,7 +78,7 @@ func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error { func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) { return func(privileges types.PluginPrivileges) (bool, error) { - fmt.Fprintf(dockerCli.Out(), "Plugin %q requested the following privileges:\n", name) + fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name) for _, privilege := range privileges { fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value) } diff --git a/integration-cli/docker_cli_plugins_test.go b/integration-cli/docker_cli_plugins_test.go index fdb99431d3..4846744d62 100644 --- a/integration-cli/docker_cli_plugins_test.go +++ b/integration-cli/docker_cli_plugins_test.go @@ -11,7 +11,7 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) { tag := "latest" nameWithTag := name + ":" + tag - _, _, err := dockerCmdWithError("plugin", "install", name) + _, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", name) c.Assert(err, checker.IsNil) out, _, err := dockerCmdWithError("plugin", "ls") @@ -41,7 +41,7 @@ func (s *DockerSuite) TestPluginInstallDisable(c *check.C) { tag := "latest" nameWithTag := name + ":" + tag - _, _, err := dockerCmdWithError("plugin", "install", name, "--disable") + _, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", "--disable", name) c.Assert(err, checker.IsNil) out, _, err := dockerCmdWithError("plugin", "ls") From 1cfd620124821e35767d2894024f87bba58c413a Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Fri, 17 Jun 2016 09:13:35 -0700 Subject: [PATCH 67/71] plugins: remove automatic mounting of a state dir Signed-off-by: Tibor Vass (cherry picked from commit 15ff9de65801178a522c445ff9fb8964e21d12a0) --- plugin/manager.go | 9 +-------- plugin/manager_linux.go | 11 +++++------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/plugin/manager.go b/plugin/manager.go index a33248ba93..006905764f 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -22,10 +22,7 @@ import ( "github.com/docker/engine-api/types" ) -const ( - defaultPluginRuntimeDestination = "/run/docker/plugins" - defaultPluginStateDestination = "/state" -) +const defaultPluginRuntimeDestination = "/run/docker/plugins" var manager *Manager @@ -49,7 +46,6 @@ type plugin struct { P types.Plugin `json:"plugin"` client *plugins.Client restartManager restartmanager.RestartManager - stateSourcePath string runtimeSourcePath string } @@ -72,7 +68,6 @@ func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { Name: ref.Name(), ID: id, }, - stateSourcePath: filepath.Join(pm.libRoot, id, "state"), runtimeSourcePath: filepath.Join(pm.runRoot, id), } if ref, ok := ref.(reference.NamedTagged); ok { @@ -82,7 +77,6 @@ func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { } func (pm *Manager) restorePlugin(p *plugin) error { - p.stateSourcePath = filepath.Join(pm.libRoot, p.P.ID, "state") p.runtimeSourcePath = filepath.Join(pm.runRoot, p.P.ID) if p.P.Active { return pm.restore(p) @@ -342,7 +336,6 @@ func (pm *Manager) remove(p *plugin) error { } pm.Lock() // fixme: lock single record defer pm.Unlock() - os.RemoveAll(p.stateSourcePath) delete(pm.plugins, p.P.ID) delete(pm.nameToID, p.Name()) pm.save() diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 75da3beefb..277fa3c48e 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -70,11 +70,6 @@ func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { Destination: defaultPluginRuntimeDestination, Type: "bind", Options: []string{"rbind", "rshared"}, - }, types.PluginMount{ - Source: &p.stateSourcePath, - Destination: defaultPluginStateDestination, - Type: "bind", - Options: []string{"rbind", "rshared"}, }) for _, mount := range mounts { m := specs.Mount{ @@ -105,10 +100,14 @@ func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { envs = append(envs, p.P.Config.Env...) args := append(p.P.Manifest.Entrypoint, p.P.Config.Args...) + cwd := p.P.Manifest.Workdir + if len(cwd) == 0 { + cwd = "/" + } s.Process = specs.Process{ Terminal: false, Args: args, - Cwd: "/", // TODO: add in manifest? + Cwd: cwd, Env: envs, } From 2ae733014091b11802f3a738e4d3c7f3d21c4af3 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Fri, 17 Jun 2016 09:14:23 -0700 Subject: [PATCH 68/71] plugins: fix usage for plugin commands Signed-off-by: Tibor Vass (cherry picked from commit 65ed9daf70ccf0027f4eac8049667130deb249ed) --- api/client/plugin/disable.go | 2 +- api/client/plugin/enable.go | 2 +- api/client/plugin/inspect.go | 2 +- api/client/plugin/push.go | 2 +- api/client/plugin/remove.go | 2 +- api/client/plugin/set.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client/plugin/disable.go b/api/client/plugin/disable.go index 6d0ff696c6..0382db24d2 100644 --- a/api/client/plugin/disable.go +++ b/api/client/plugin/disable.go @@ -11,7 +11,7 @@ import ( func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "disable", + Use: "disable PLUGIN", Short: "Disable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/api/client/plugin/enable.go b/api/client/plugin/enable.go index 2a05b4a943..703ad37d19 100644 --- a/api/client/plugin/enable.go +++ b/api/client/plugin/enable.go @@ -11,7 +11,7 @@ import ( func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "enable", + Use: "enable PLUGIN", Short: "Enable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/api/client/plugin/inspect.go b/api/client/plugin/inspect.go index f68ad40da7..a091df4756 100644 --- a/api/client/plugin/inspect.go +++ b/api/client/plugin/inspect.go @@ -13,7 +13,7 @@ import ( func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "inspect", + Use: "inspect PLUGIN", Short: "Inspect a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/api/client/plugin/push.go b/api/client/plugin/push.go index c650eb6f89..e34b9679e9 100644 --- a/api/client/plugin/push.go +++ b/api/client/plugin/push.go @@ -16,7 +16,7 @@ import ( func newPushCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "push", + Use: "push PLUGIN", Short: "Push a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/api/client/plugin/remove.go b/api/client/plugin/remove.go index 9acd79e4a2..54c904a18a 100644 --- a/api/client/plugin/remove.go +++ b/api/client/plugin/remove.go @@ -13,7 +13,7 @@ import ( func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "rm", + Use: "rm PLUGIN", Short: "Remove a plugin", Aliases: []string{"remove"}, Args: cli.RequiresMinArgs(1), diff --git a/api/client/plugin/set.go b/api/client/plugin/set.go index 5bb7bfe165..017f2b5325 100644 --- a/api/client/plugin/set.go +++ b/api/client/plugin/set.go @@ -12,7 +12,7 @@ import ( func newSetCommand(dockerCli *client.DockerCli) *cobra.Command { cmd := &cobra.Command{ - Use: "set", + Use: "set PLUGIN key1=value1 [key2=value2...]", Short: "Change settings for a plugin", Args: cli.RequiresMinArgs(2), RunE: func(cmd *cobra.Command, args []string) error { From 6eaac7be89a161eaf9e7ee03005954a7bf0925d4 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 17 Jun 2016 18:13:24 +0200 Subject: [PATCH 69/71] Update plugin command with defaulttag This way, you don't have to specify the ":latest" tag for some command and not for others Signed-off-by: Vincent Demeester (cherry picked from commit cb321e82db662f5190a6d83a90677a9dd9fdcd31) --- api/client/plugin/disable.go | 20 +++++++++++++++++++- api/client/plugin/enable.go | 20 +++++++++++++++++++- api/client/plugin/inspect.go | 15 ++++++++++++++- api/client/plugin/install.go | 4 +++- api/client/plugin/push.go | 4 +++- api/client/plugin/remove.go | 14 +++++++++++++- api/client/plugin/set.go | 16 +++++++++++++++- plugin/manager.go | 10 ++++++++++ 8 files changed, 96 insertions(+), 7 deletions(-) diff --git a/api/client/plugin/disable.go b/api/client/plugin/disable.go index 0382db24d2..058c6889cd 100644 --- a/api/client/plugin/disable.go +++ b/api/client/plugin/disable.go @@ -3,8 +3,11 @@ package plugin import ( + "fmt" + "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -15,9 +18,24 @@ func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command { Short: "Disable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return dockerCli.Client().PluginDisable(context.Background(), args[0]) + return runDisable(dockerCli, args[0]) }, } return cmd } + +func runDisable(dockerCli *client.DockerCli, name string) error { + named, err := reference.ParseNamed(name) // FIXME: validate + if err != nil { + return err + } + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return fmt.Errorf("invalid name: %s", named.String()) + } + return dockerCli.Client().PluginDisable(context.Background(), ref.String()) +} diff --git a/api/client/plugin/enable.go b/api/client/plugin/enable.go index 703ad37d19..cc2488bcec 100644 --- a/api/client/plugin/enable.go +++ b/api/client/plugin/enable.go @@ -3,8 +3,11 @@ package plugin import ( + "fmt" + "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -15,9 +18,24 @@ func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command { Short: "Enable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return dockerCli.Client().PluginEnable(context.Background(), args[0]) + return runEnable(dockerCli, args[0]) }, } return cmd } + +func runEnable(dockerCli *client.DockerCli, name string) error { + named, err := reference.ParseNamed(name) // FIXME: validate + if err != nil { + return err + } + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return fmt.Errorf("invalid name: %s", named.String()) + } + return dockerCli.Client().PluginEnable(context.Background(), ref.String()) +} diff --git a/api/client/plugin/inspect.go b/api/client/plugin/inspect.go index a091df4756..8f7e98d441 100644 --- a/api/client/plugin/inspect.go +++ b/api/client/plugin/inspect.go @@ -4,9 +4,11 @@ package plugin import ( "encoding/json" + "fmt" "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -25,7 +27,18 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command { } func runInspect(dockerCli *client.DockerCli, name string) error { - p, err := dockerCli.Client().PluginInspect(context.Background(), name) + named, err := reference.ParseNamed(name) // FIXME: validate + if err != nil { + return err + } + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return fmt.Errorf("invalid name: %s", named.String()) + } + p, err := dockerCli.Client().PluginInspect(context.Background(), ref.String()) if err != nil { return err } diff --git a/api/client/plugin/install.go b/api/client/plugin/install.go index 2841388884..ea73498c26 100644 --- a/api/client/plugin/install.go +++ b/api/client/plugin/install.go @@ -46,7 +46,9 @@ func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error { if err != nil { return err } - named = reference.WithDefaultTag(named) + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } ref, ok := named.(reference.NamedTagged) if !ok { return fmt.Errorf("invalid name: %s", named.String()) diff --git a/api/client/plugin/push.go b/api/client/plugin/push.go index e34b9679e9..d26fab8dfc 100644 --- a/api/client/plugin/push.go +++ b/api/client/plugin/push.go @@ -31,7 +31,9 @@ func runPush(dockerCli *client.DockerCli, name string) error { if err != nil { return err } - named = reference.WithDefaultTag(named) + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } ref, ok := named.(reference.NamedTagged) if !ok { return fmt.Errorf("invalid name: %s", named.String()) diff --git a/api/client/plugin/remove.go b/api/client/plugin/remove.go index 54c904a18a..a8e52a4f26 100644 --- a/api/client/plugin/remove.go +++ b/api/client/plugin/remove.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -28,8 +29,19 @@ func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command { func runRemove(dockerCli *client.DockerCli, names []string) error { var errs cli.Errors for _, name := range names { + named, err := reference.ParseNamed(name) // FIXME: validate + if err != nil { + return err + } + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return fmt.Errorf("invalid name: %s", named.String()) + } // TODO: pass names to api instead of making multiple api calls - if err := dockerCli.Client().PluginRemove(context.Background(), name); err != nil { + if err := dockerCli.Client().PluginRemove(context.Background(), ref.String()); err != nil { errs = append(errs, err) continue } diff --git a/api/client/plugin/set.go b/api/client/plugin/set.go index 017f2b5325..188bd63cc4 100644 --- a/api/client/plugin/set.go +++ b/api/client/plugin/set.go @@ -3,10 +3,13 @@ package plugin import ( + "fmt" + "golang.org/x/net/context" "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + "github.com/docker/docker/reference" "github.com/spf13/cobra" ) @@ -24,5 +27,16 @@ func newSetCommand(dockerCli *client.DockerCli) *cobra.Command { } func runSet(dockerCli *client.DockerCli, name string, args []string) error { - return dockerCli.Client().PluginSet(context.Background(), name, args) + named, err := reference.ParseNamed(name) // FIXME: validate + if err != nil { + return err + } + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return fmt.Errorf("invalid name: %s", named.String()) + } + return dockerCli.Client().PluginSet(context.Background(), ref.String(), args) } diff --git a/plugin/manager.go b/plugin/manager.go index 006905764f..885e92e2c4 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -204,6 +204,16 @@ func LookupWithCapability(name, capability string) (Plugin, error) { ) handleLegacy := true if manager != nil { + if named, err := reference.ParseNamed(name); err == nil { // FIXME: validate + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return nil, fmt.Errorf("invalid name: %s", named.String()) + } + name = ref.String() + } p, err = manager.get(name) if err != nil { if _, ok := err.(ErrNotFound); !ok { From 70bd46293abcd79c6c6a51199974509d5c69a166 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Fri, 17 Jun 2016 12:02:05 -0700 Subject: [PATCH 70/71] plugins: fix name handling for legacy plugins Signed-off-by: Tibor Vass (cherry picked from commit 8ba17b4823f9a1a40a405905613667d97bb24546) --- plugin/manager.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/manager.go b/plugin/manager.go index 885e92e2c4..33887ce7de 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -204,7 +204,8 @@ func LookupWithCapability(name, capability string) (Plugin, error) { ) handleLegacy := true if manager != nil { - if named, err := reference.ParseNamed(name); err == nil { // FIXME: validate + fullName := name + if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate if reference.IsNameOnly(named) { named = reference.WithDefaultTag(named) } @@ -212,9 +213,9 @@ func LookupWithCapability(name, capability string) (Plugin, error) { if !ok { return nil, fmt.Errorf("invalid name: %s", named.String()) } - name = ref.String() + fullName = ref.String() } - p, err = manager.get(name) + p, err = manager.get(fullName) if err != nil { if _, ok := err.(ErrNotFound); !ok { return nil, err From 944a8f16c7fa25af655ddcafad2be4b9bfc836b2 Mon Sep 17 00:00:00 2001 From: "Arnaud Porterie (icecrime)" Date: Fri, 17 Jun 2016 11:38:19 -0700 Subject: [PATCH 71/71] Improve `docker service inspect --pretty` Remove capitalization in placement, and remove spurious `\t`. Signed-off-by: Arnaud Porterie (icecrime) (cherry picked from commit 3c60b7b9843276082c0ba4b868f354b9b44e9627) --- api/client/service/inspect.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/client/service/inspect.go b/api/client/service/inspect.go index fcb1d834a4..36dfa89afa 100644 --- a/api/client/service/inspect.go +++ b/api/client/service/inspect.go @@ -94,11 +94,11 @@ func printService(out io.Writer, service swarm.Service) { } if service.Spec.Mode.Global != nil { - fmt.Fprintln(out, "Mode:\t\tGLOBAL") + fmt.Fprintln(out, "Mode:\t\tGlobal") } else { - fmt.Fprintln(out, "Mode:\t\tREPLICATED") + fmt.Fprintln(out, "Mode:\t\tReplicated") if service.Spec.Mode.Replicated.Replicas != nil { - fmt.Fprintf(out, " Replicas:\t\t%d\n", *service.Spec.Mode.Replicated.Replicas) + fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas) } } fmt.Fprintln(out, "Placement:") @@ -157,7 +157,7 @@ func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) { fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " ")) } if len(containerSpec.Args) > 0 { - fmt.Fprintf(out, " Args:\t%s\n", strings.Join(containerSpec.Args, " ")) + fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " ")) } if len(containerSpec.Env) > 0 { fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))