Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -1,3 +0,0 @@
bundles
.gopath
vendor/pkg

View File

@@ -1,42 +0,0 @@
# Docker project generated files to ignore
# if you want to ignore files created by your editor/tools,
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
*.exe
*.exe~
*.orig
*.test
.*.swp
.DS_Store
.bashrc
.dotcloud
.flymake*
.git/
.gopath/
.hg/
.vagrant*
Vagrantfile
a.out
autogen/
bin
build_src
bundles/
docker/docker
hyper/hyper
dockerversion/version_autogen.go
docs/AWS_S3_BUCKET
docs/GITCOMMIT
docs/GIT_BRANCH
docs/VERSION
docs/_build
docs/_static
docs/_templates
docs/changed-files
# generated by man/md2man-all.sh
man/man1
man/man5
man/man8
pyenv
vendor/pkg/
*.yml
.idea
integration-cli/util.conf

View File

@@ -1,171 +0,0 @@
# Generate AUTHORS: hack/generate-authors.sh
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
# duplicates that aren't also email duplicates): scan the output of:
# git log --format='%aE - %aN' | sort -uf
#
# For explanation on this file format: man git-shortlog
Patrick Stapleton <github@gdi2290.com>
Shishir Mahajan <shishir.mahajan@redhat.com> <smahajan@redhat.com>
Erwin van der Koogh <info@erronis.nl>
Ahmed Kamal <email.ahmedkamal@googlemail.com>
Tejesh Mehta <tejesh.mehta@gmail.com> <tj@init.me>
Cristian Staretu <cristian.staretu@gmail.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
Marcus Linke <marcus.linke@gmx.de>
Aleksandrs Fadins <aleks@s-ko.net>
Christopher Latham <sudosurootdev@gmail.com>
Hu Keping <hukeping@huawei.com>
Wayne Chang <wayne@neverfear.org>
Chen Chao <cc272309126@gmail.com>
Daehyeok Mun <daehyeok@gmail.com>
<daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
<jt@yadutaf.fr> <admin@jtlebi.fr>
<jeff@docker.com> <jefferya@programmerq.net>
<charles.hooper@dotcloud.com> <chooper@plumata.com>
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
<guillaume.charmes@docker.com> <guillaume@dotcloud.com>
<guillaume.charmes@docker.com> <guillaume@docker.com>
<guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
<guillaume.charmes@docker.com> <guillaume@charmes.net>
<kencochrane@gmail.com> <KenCochrane@gmail.com>
Thatcher Peskens <thatcher@docker.com>
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
Thatcher Peskens <thatcher@docker.com> dhrp <thatcher@gmx.net>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
Joffrey F <joffrey@docker.com>
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
Tim Terhorst <mynamewastaken+git@gmail.com>
Andy Smith <github@anarkystic.com>
<kalessin@kalessin.fr> <louis@dotcloud.com>
<victor.vieux@docker.com> <victor.vieux@dotcloud.com>
<victor.vieux@docker.com> <victor@dotcloud.com>
<victor.vieux@docker.com> <dev@vvieux.com>
<victor.vieux@docker.com> <victor@docker.com>
<victor.vieux@docker.com> <vieux@docker.com>
<victor.vieux@docker.com> <victorvieux@gmail.com>
<dominik@honnef.co> <dominikh@fork-bomb.org>
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
Walter Stanish <walter@pratyeka.org>
<daniel@gasienica.ch> <dgasienica@zynga.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>
Konstantin Pelykh <kpelykh@zettaset.com>
David Sissitka <me@dsissitka.com>
Nolan Darilek <nolan@thewordnerd.info>
<mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
Benoit Chesneau <bchesneau@gmail.com>
Jordan Arentsen <blissdev@gmail.com>
Daniel Garcia <daniel@danielgarcia.info>
Miguel Angel Fernández <elmendalerenda@gmail.com>
Bhiraj Butala <abhiraj.butala@gmail.com>
Faiz Khan <faizkhan00@gmail.com>
Victor Lyuboslavsky <victor@victoreda.com>
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
Matthew Mueller <mattmuelle@gmail.com>
<mosoni@ebay.com> <mohitsoni1989@gmail.com>
Shih-Yuan Lee <fourdollars@gmail.com>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> root <root@vagrant-ubuntu-12.10.vagrantup.com>
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
<proppy@google.com> <proppy@aminche.com>
<michael@docker.com> <michael@crosbymichael.com>
<michael@docker.com> <crosby.michael@gmail.com>
<michael@docker.com> <crosbymichael@gmail.com>
<github@developersupport.net> <github@metaliveblog.com>
<brandon@ifup.org> <brandon@ifup.co>
<dano@spotify.com> <daniel.norberg@gmail.com>
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
<gurjeet@singh.im> <singh.gurjeet@gmail.com>
<shawn@churchofgit.com> <shawnlandden@gmail.com>
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
<solomon@docker.com> <solomon.hykes@dotcloud.com>
<solomon@docker.com> <solomon@dotcloud.com>
<solomon@docker.com> <s@docker.com>
Sven Dowideit <SvenDowideit@home.org.au>
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
<alexl@redhat.com> <alexander.larsson@gmail.com>
Alexandr Morozov <lk4d4math@gmail.com>
<git.nivoc@neverbox.com> <kuehnle@online.de>
O.S. Tezer <ostezer@gmail.com>
<ostezer@gmail.com> <ostezer@users.noreply.github.com>
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
<justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
<taim@bosboot.org> <maztaim@users.noreply.github.com>
<viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
<vbatts@redhat.com> <vbatts@hashbangbash.com>
<altsysrq@gmail.com> <iamironbob@gmail.com>
Sridhar Ratnakumar <sridharr@activestate.com>
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
Liang-Chi Hsieh <viirya@gmail.com>
Aleksa Sarai <cyphar@cyphar.com>
Will Weaver <monkey@buildingbananas.com>
Timothy Hobbs <timothyhobbs@seznam.cz>
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
<github@hollensbe.org> <erik+github@hollensbe.org>
<github@albersweb.de> <albers@users.noreply.github.com>
<lsm5@fedoraproject.org> <lsm5@redhat.com>
<marc@marc-abramowitz.com> <msabramo@gmail.com>
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
<bernat@luffy.cx> <vincent@bernat.im>
<p@pwaller.net> <peter@scraperwiki.com>
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
Francisco Carriedo <fcarriedo@gmail.com>
<julienbordellier@gmail.com> <git@julienbordellier.com>
<ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
<lk4d4@docker.com> <lk4d4math@gmail.com>
<arnaud.porterie@docker.com> <icecrime@gmail.com>
<baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
Brian Goff <cpuguy83@gmail.com>
<cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
<ewindisch@docker.com> <eric@windisch.us>
<frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
Hollie Teal <hollie@docker.com>
<hollie@docker.com> <hollie.teal@docker.com>
<hollie@docker.com> <hollietealok@users.noreply.github.com>
<huu@prismskylabs.com> <whoshuu@gmail.com>
Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.github.com>
<jess@docker.com> <jfrazelle@users.noreply.github.com>
<konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
<tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
<estesp@linux.vnet.ibm.com> <estesp@gmail.com>
<github@gone.nl> <thaJeztah@users.noreply.github.com>
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
<oi@truffles.me.uk> <timruffles@googlemail.com>
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
<jess@docker.com> <princess@docker.com>
John Howard (VM) <John.Howard@microsoft.com> John Howard <jhoward@microsoft.com>
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
mattyw <mattyw@me.com> <gh@mattyw.net>
resouer <resouer@163.com> <resouer@gmail.com>
AJ Bowen <aj@gandi.net> soulshake <amy@gandi.net>
AJ Bowen <aj@gandi.net> soulshake <aj@gandi.net>
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
bin liu <liubin0329@users.noreply.github.com> <liubin0329@gmail.com>
John Howard (VM) <John.Howard@microsoft.com> jhowardmsft <jhoward@microsoft.com>
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
Tangi COLIN <tangicolin@gmail.com> tangicolin <tangicolin@gmail.com>

File diff suppressed because it is too large Load Diff

View File

@@ -1,434 +0,0 @@
# Contributing to Docker
Want to hack on Docker? Awesome! We have a contributor's guide that explains
[setting up a Docker development environment and the contribution
process](https://docs.docker.com/opensource/project/who-written-for/).
![Contributors guide](docs/static_files/contributors.png)
This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#docker-community-guidelines) before you
start participating.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#docker-community-guidelines)
## Reporting security issues
The Docker maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated and we will publicly thank you for it.
We also like to send gifts&mdash;if you're into Docker schwag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [our issue database](https://github.com/docker/docker/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, you can use the "subscribe" button to get notified on
updates. Do *not* leave random "+1" or "I have this too" comments, as they
only clutter the discussion, and don't help resolving it. However, if you
have ways to reproduce the issue or have additional information that may help
resolving the issue, please leave a comment.
When reporting issues, always include:
* The output of `docker version`.
* The output of `docker info`.
Also include the steps required to reproduce the problem if possible and
applicable. This information will help us review and fix your issue faster.
When sending lengthy log-files, consider posting them as a gist (https://gist.github.com).
Don't forget to remove sensitive data from your logfiles before posting (you can
replace those parts with "REDACTED").
**Issue Report Template**:
```
Description of problem:
`docker version`:
`docker info`:
`uname -a`:
Environment details (AWS, VirtualBox, physical, etc.):
How reproducible:
Steps to Reproduce:
1.
2.
3.
Actual Results:
Expected Results:
Additional info:
```
##Quick contribution tips and guidelines
This section gives the experienced contributor some tips and guidelines.
###Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it. Any significant improvement should be
documented as [a GitHub issue](https://github.com/docker/docker/issues) before
anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged! Our contributor's guide explains [the review process we
use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
### Design and cleanup proposals
You can propose new designs for existing Docker features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project. For information on making these types of
contributions, see [the advanced contribution
section](https://docs.docker.com/opensource/workflow/advanced-contributing/) in
the contributors guide.
We try hard to keep Docker lean and focused. Docker can't do everything for
everybody. This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* Docker.
### Talking to other Docker users and contributors
<table class="tg">
<col width="45%">
<col width="65%">
<tr>
<td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
<td>
<p>
IRC a direct line to our most knowledgeable Docker users; we have
both the <code>#docker</code> and <code>#docker-dev</code> group on
<strong>irc.freenode.net</strong>.
IRC is a rich chat protocol but it can overwhelm new users. You can search
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
</p>
Read our <a href="https://docs.docker.com/opensource/get-help/#irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
</td>
</tr>
<tr>
<td>Google Groups</td>
<td>
There are two groups.
<a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank">Docker-user</a>
is for people using Docker containers.
The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
group is for contributors and other people contributing to the Docker
project.
</td>
</tr>
<tr>
<td>Twitter</td>
<td>
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
to get updates on our products. You can also tweet us questions or just
share blogs or stories.
</td>
</tr>
<tr>
<td>Stack Overflow</td>
<td>
Stack Overflow has over 17000 Docker questions listed. We regularly
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
and so do many other knowledgeable Docker users.
</td>
</tr>
</table>
### Conventions
Fork the repository and make changes on your fork in a feature branch:
- If it's a bug fix branch, name it XXXX-something where XXXX is the number of
the issue.
- If it's a feature branch, create an enhancement issue to announce
your intentions, and name it XXXX-something where XXXX is the number of the
issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. [Run the full test
suite](https://docs.docker.com/opensource/project/test-and-docs/) on your branch before
submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build. See our contributors guide for [our style
guide](https://docs.docker.com/opensource/doc-style) and instructions on [building
the documentation](https://docs.docker.com/opensource/project/test-and-docs/#build-and-test-the-documentation).
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
Commit messages must start with a capitalized and short summary (max. 50 chars)
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
Pull requests must be cleanly rebased on top of master without multiple branches
mixed into the PR.
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
After every commit, [make sure the test suite passes]
(https://docs.docker.com/opensource/project/test-and-docs/). Include documentation
changes in the same pull request so that a revert would remove all traces of
the feature or fix.
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
close an issue. Including references automatically closes the issue on a merge.
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
from the Git history.
Please see the [Coding Style](#coding-style) for further guidelines.
### Merge approval
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance.
A change requires LGTMs from an absolute majority of the maintainers of each
component affected. For example, if a change affects `docs/` and `registry/`, it
needs an absolute majority from the maintainers of `docs/` AND, separately, an
absolute majority of the maintainers of `registry/`.
For more details, see the [MAINTAINERS](MAINTAINERS) page.
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
Note that the old-style `Docker-DCO-1.1-Signed-off-by: ...` format is still
accepted, so there is no need to update outstanding pull requests to the new
format right away, but please do adjust your processes for future contributions.
### How can I become a maintainer?
The procedures for adding new maintainers are explained in the
global [MAINTAINERS](https://github.com/docker/opensource/blob/master/MAINTAINERS)
file in the [https://github.com/docker/opensource/](https://github.com/docker/opensource/)
repository.
Don't forget: being a maintainer is a time investment. Make sure you
will have time to make yourself available. You don't have to be a
maintainer to make a difference on the project!
## Docker community guidelines
We want to keep the Docker community awesome, growing and collaborative. We need
your help to keep it that way. To help with this we've come up with some general
guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members:
no regional, racial, gender, or other abuse will be tolerated. We like
nice people way better than mean ones!
* Encourage diversity and participation: Make everyone in our community feel
welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break
the law.
* Stay on topic: Make sure that you are posting to the correct channel and
avoid off-topic discussions. Remember when you update an issue or respond
to an email you are potentially sending to a large number of people. Please
consider this before you update. Also remember that nobody likes spam.
* Don't send email to the maintainers: There's no need to send email to the
maintainers to ask them to investigate an issue or to take a look at a
pull request. Instead of sending an email, GitHub mentions should be
used to ping maintainers to review a pull request, a proposal or an
issue.
### Guideline violations — 3 strikes method
The point of this section is not to find opportunities to punish people, but we
do need a fair way to deal with people who are making our community suck.
1. First occurrence: We'll give you a friendly, but public reminder that the
behavior is inappropriate according to our guidelines.
2. Second occurrence: We will send you a private message with a warning that
any additional violations will result in removal from the community.
3. Third occurrence: Depending on the violation, we may need to delete or ban
your account.
**Notes:**
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
have spam all over the place.
* Violations are forgiven after 6 months of good behavior, and we won't hold a
grudge.
* People who commit minor infractions will get some education, rather than
hammering them in the 3 strikes process.
* The rules apply equally to everyone in the community, no matter how much
you've contributed.
* Extreme violations of a threatening, abusive, destructive or illegal nature
will be addressed immediately and are not subject to 3 strikes or forgiveness.
* Contact abuse@docker.com to report abuse or appeal violations. In the case of
appeals, we know that mistakes happen, and we'll work with you to come up with a
fair solution if there has been a misunderstanding.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to it's context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant it's own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.

View File

@@ -1,251 +0,0 @@
# This file describes the standard way to build Docker, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker .
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
#
# # Run the test suite:
# docker run --privileged docker hack/make.sh test
#
# # Publish a release:
# docker run --privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM ubuntu:trusty
# add zfs ppa
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
# add llvm repo
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421
RUN echo deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty main > /etc/apt/sources.list.d/llvm.list
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
clang-3.8 \
createrepo \
curl \
dpkg-sig \
gcc-mingw-w64 \
git \
iptables \
jq \
libapparmor-dev \
libcap-dev \
libltdl-dev \
libsqlite3-dev \
libsystemd-journal-dev \
libtool \
mercurial \
pkg-config \
python-dev \
python-mock \
python-pip \
python-websocket \
s3cmd=1.1.0* \
ubuntu-zfs \
xfsprogs \
libzfs-dev \
tar \
--no-install-recommends \
&& ln -snf /usr/bin/clang-3.8 /usr/local/bin/clang \
&& ln -snf /usr/bin/clang++-3.8 /usr/local/bin/clang++
# Get lvm2 source for compiling statically
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
&& curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
| tar -xzC /usr/local/lvm2 --strip-components=1
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure \
--build="$(gcc -print-multiarch)" \
--enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# Configure the container for OSX cross compilation
ENV OSX_SDK MacOSX10.11.sdk
RUN set -x \
&& export OSXCROSS_PATH="/osxcross" \
&& git clone --depth 1 https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
&& curl -sSL https://s3.dockerproject.org/darwin/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh
ENV PATH /osxcross/target/bin:$PATH
# install seccomp: the version shipped in trusty is too old
ENV SECCOMP_VERSION 2.2.3
RUN set -x \
&& export SECCOMP_PATH="$(mktemp -d)" \
&& curl -fsSL "https://github.com/seccomp/libseccomp/releases/download/v${SECCOMP_VERSION}/libseccomp-${SECCOMP_VERSION}.tar.gz" \
| tar -xzC "$SECCOMP_PATH" --strip-components=1 \
&& ( \
cd "$SECCOMP_PATH" \
&& ./configure --prefix=/usr/local \
&& make \
&& make install \
&& ldconfig \
) \
&& rm -rf "$SECCOMP_PATH"
# Install Go
# IMPORTANT: If the version of Go is updated, the Windows to Linux CI machines
# will need updating, to avoid errors. Ping #docker-maintainers on IRC
# with a heads-up.
ENV GO_VERSION 1.5.3
RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" \
| tar -xzC /usr/local
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# Compile Go for cross compilation
ENV DOCKER_CROSSPLATFORMS \
linux/386 linux/arm \
darwin/amd64 \
freebsd/amd64 freebsd/386 freebsd/arm \
windows/amd64 windows/386
# (set an explicit GOARM of 5 for maximum compatibility)
ENV GOARM 5
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
# ENV GOFMT_VERSION 1.3.3
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
ENV GO_TOOLS_COMMIT 823804e1ae08dbb14eb807afc7db9993bc9e3cc3
# Grab Go's cover tool for dead-simple code coverage testing
# Grab Go's vet tool for examining go code to find suspicious constructs
# and help prevent errors that the compiler might not catch
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
&& go install -v golang.org/x/tools/cmd/cover \
&& go install -v golang.org/x/tools/cmd/vet
# Grab Go's lint tool
ENV GO_LINT_COMMIT 32a87160691b3c96046c0c678fe57c5bef761456
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
&& go install -v github.com/golang/lint/golint
# Install two versions of the registry. The first is an older version that
# only supports schema1 manifests. The second is a newer version that supports
# both. This allows integration-cli tests to cover push/pull with both schema1
# and schema2 manifests.
ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
ENV REGISTRY_COMMIT 47a064d4195a9b56133891bbb13620c3ac83a827
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-5
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary github.com/docker/notary/cmd/notary \
&& rm -rf "$GOPATH"
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT \
&& pip install -r test-requirements.txt
# Setup s3cmd config
RUN { \
echo '[default]'; \
echo 'access_key=$AWS_ACCESS_KEY'; \
echo 'secret_key=$AWS_SECRET_KEY'; \
} > ~/.s3cfg
# Set user.email so crosbymichael's in-container merge commits go smoothly
RUN git config --global user.email 'docker-dummy@example.com'
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
buildpack-deps:jessie@sha256:25785f89240fbcdd8a74bdaf30dd5599a9523882c6dfc567f2e9ef7cf6f79db6 \
busybox:latest@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0 \
debian:jessie@sha256:f968f10b4b523737e253a97eac59b0d1420b5c19b69928d35801a6373ffe330e \
hello-world:latest@sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Build/install the tool for embedding resources in Windows binaries
ENV RSRC_COMMIT ba14da1f827188454a4591717fff29999010887f
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/akavel/rsrc.git "$GOPATH/src/github.com/akavel/rsrc" \
&& (cd "$GOPATH/src/github.com/akavel/rsrc" && git checkout -q "$RSRC_COMMIT") \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,203 +0,0 @@
# This file describes the standard way to build Docker on aarch64, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker -f Dockerfile.aarch64 .
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
#
# # Run the test suite:
# docker run --privileged docker hack/make.sh test
#
# # Publish a release:
# docker run --privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM aarch64/ubuntu:trusty
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
createrepo \
curl \
dpkg-sig \
g++ \
gcc \
git \
iptables \
jq \
libapparmor-dev \
libc6-dev \
libcap-dev \
libsqlite3-dev \
libsystemd-journal-dev \
mercurial \
parallel \
pkg-config \
python-dev \
python-mock \
python-pip \
python-websocket \
s3cmd=1.1.0* \
--no-install-recommends
# Install armhf loader to use armv6 binaries on armv8
RUN dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y libc6:armhf
# Get lvm2 source for compiling statically
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
&& curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
| tar -xzC /usr/local/lvm2 --strip-components=1
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# fix platform enablement in lvm2 to support aarch64 properly
RUN set -e \
&& for f in config.guess config.sub; do \
curl -fsSL -o "/usr/local/lvm2/autoconf/$f" "http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=$f;hb=HEAD"; \
done
# "arch.c:78:2: error: #error the arch code needs to know about your machine type"
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure \
--build="$(gcc -print-multiarch)" \
--enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# install seccomp: the version shipped in trusty is too old
ENV SECCOMP_VERSION 2.2.3
RUN set -x \
&& export SECCOMP_PATH="$(mktemp -d)" \
&& curl -fsSL "https://github.com/seccomp/libseccomp/releases/download/v${SECCOMP_VERSION}/libseccomp-${SECCOMP_VERSION}.tar.gz" \
| tar -xzC "$SECCOMP_PATH" --strip-components=1 \
&& ( \
cd "$SECCOMP_PATH" \
&& ./configure --prefix=/usr/local \
&& make \
&& make install \
&& ldconfig \
) \
&& rm -rf "$SECCOMP_PATH"
# Install Go
# We don't have official binary tarballs for ARM64, eigher for Go or bootstrap,
# so we use the official armv6 released binaries as a GOROOT_BOOTSTRAP, and
# build Go from source code.
ENV BOOT_STRAP_VERSION 1.6beta1
ENV GO_VERSION 1.5.3
RUN mkdir -p /usr/src/go-bootstrap \
&& curl -fsSL https://storage.googleapis.com/golang/go${BOOT_STRAP_VERSION}.linux-arm6.tar.gz | tar -v -C /usr/src/go-bootstrap -xz --strip-components=1 \
&& mkdir /usr/src/go \
&& curl -fsSL https://storage.googleapis.com/golang/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/src/go -xz --strip-components=1 \
&& cd /usr/src/go/src \
&& GOOS=linux GOARCH=arm64 GOROOT_BOOTSTRAP=/usr/src/go-bootstrap ./make.bash
ENV PATH /usr/src/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# Only install one version of the registry, because old version which support
# schema1 manifests is not working on ARM64, we should skip integration-cli
# tests for schema1 manifests on ARM64.
ENV REGISTRY_COMMIT 47a064d4195a9b56133891bbb13620c3ac83a827
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-5
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary github.com/docker/notary/cmd/notary \
&& rm -rf "$GOPATH"
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT \
&& pip install -r test-requirements.txt
# Setup s3cmd config
RUN { \
echo '[default]'; \
echo 'access_key=$AWS_ACCESS_KEY'; \
echo 'secret_key=$AWS_SECRET_KEY'; \
} > ~/.s3cfg
# Set user.email so crosbymichael's in-container merge commits go smoothly
RUN git config --global user.email 'docker-dummy@example.com'
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
aarch64/buildpack-deps:jessie@sha256:6aa1d6910791b7ac78265fd0798e5abd6cb3f27ae992f6f960f6c303ec9535f2 \
aarch64/busybox:latest@sha256:b23a6a37cf269dff6e46d2473b6e227afa42b037e6d23435f1d2bc40fc8c2828 \
aarch64/debian:jessie@sha256:4be74a41a7c70ebe887b634b11ffe516cf4fcd56864a54941e56bb49883c3170 \
aarch64/hello-world:latest@sha256:65a4a158587b307bb02db4de41b836addb0c35175bdc801367b1ac1ddeb9afda
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,220 +0,0 @@
# This file describes the standard way to build Docker on ARMv7, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker -f Dockerfile.armhf .
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
#
# # Run the test suite:
# docker run --privileged docker hack/make.sh test
#
# # Publish a release:
# docker run --privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM armhf/ubuntu:trusty
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
createrepo \
curl \
dpkg-sig \
git \
iptables \
jq \
net-tools \
libapparmor-dev \
libcap-dev \
libltdl-dev \
libsqlite3-dev \
libsystemd-journal-dev \
libtool \
mercurial \
pkg-config \
python-dev \
python-mock \
python-pip \
python-websocket \
xfsprogs \
tar \
--no-install-recommends
# Get lvm2 source for compiling statically
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
&& curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
| tar -xzC /usr/local/lvm2 --strip-components=1
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure \
--build="$(gcc -print-multiarch)" \
--enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# Install Go
#ENV GO_VERSION 1.5.3
# TODO update GO_TOOLS_COMMIT below when this updates to 1.5+
ENV GO_VERSION 1.4.3
RUN curl -fsSL "https://github.com/hypriot/golang-armbuilds/releases/download/v${GO_VERSION}/go${GO_VERSION}.linux-armv7.tar.gz" \
| tar -xzC /usr/local
# temporarily using Hypriot's tarballs while we wait for official 1.6+
#RUN curl -fsSL https://golang.org/dl/go${GO_VERSION}.linux-arm6.tar.gz \
# | tar -xzC /usr/local
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# we're building for armhf, which is ARMv7, so let's be explicit about that
ENV GOARCH arm
ENV GOARM 7
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
# ENV GOFMT_VERSION 1.3.3
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
#ENV GO_TOOLS_COMMIT 823804e1ae08dbb14eb807afc7db9993bc9e3cc3
# TODO update this sha when we upgrade to Go 1.5+
ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
# Grab Go's cover tool for dead-simple code coverage testing
# Grab Go's vet tool for examining go code to find suspicious constructs
# and help prevent errors that the compiler might not catch
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
&& go install -v golang.org/x/tools/cmd/cover \
&& go install -v golang.org/x/tools/cmd/vet
# Grab Go's lint tool
#ENV GO_LINT_COMMIT 32a87160691b3c96046c0c678fe57c5bef761456
# TODO update this sha when we upgrade to Go 1.5+
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
&& go install -v github.com/golang/lint/golint
# install seccomp: the version shipped in trusty is too old
ENV SECCOMP_VERSION 2.2.3
RUN set -x \
&& export SECCOMP_PATH="$(mktemp -d)" \
&& curl -fsSL "https://github.com/seccomp/libseccomp/releases/download/v${SECCOMP_VERSION}/libseccomp-${SECCOMP_VERSION}.tar.gz" \
| tar -xzC "$SECCOMP_PATH" --strip-components=1 \
&& ( \
cd "$SECCOMP_PATH" \
&& ./configure --prefix=/usr/local \
&& make \
&& make install \
&& ldconfig \
) \
&& rm -rf "$SECCOMP_PATH"
# Install two versions of the registry. The first is an older version that
# only supports schema1 manifests. The second is a newer version that supports
# both. This allows integration-cli tests to cover push/pull with both schema1
# and schema2 manifests.
ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
ENV REGISTRY_COMMIT cb08de17d74bef86ce6c5abe8b240e282f5750be
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-5
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary github.com/docker/notary/cmd/notary \
&& rm -rf "$GOPATH"
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT \
&& pip install -r test-requirements.txt
# Set user.email so crosbymichael's in-container merge commits go smoothly
RUN git config --global user.email 'docker-dummy@example.com'
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
armhf/buildpack-deps:jessie@sha256:ca6cce8e5bf5c952129889b5cc15cd6aa8d995d77e55e3749bbaadae50e476cb \
armhf/busybox:latest@sha256:d98a7343ac750ffe387e3d514f8521ba69846c216778919b01414b8617cfb3d4 \
armhf/debian:jessie@sha256:4a2187483f04a84f9830910fe3581d69b3c985cc045d9f01d8e2f3795b28107b \
armhf/hello-world:latest@sha256:161dcecea0225975b2ad5f768058212c1e0d39e8211098666ffa1ac74cfb7791
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Build/install the tool for embedding resources in Windows binaries
ENV RSRC_VERSION v2
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b "$RSRC_VERSION" https://github.com/akavel/rsrc.git "$GOPATH/src/github.com/akavel/rsrc" \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,69 +0,0 @@
FROM centos:7.3.1611
#This Dockerfile is used for dev hypercli
#REF: integration-cli/README.md
##########################################################################
RUN yum install -y\
automake\
gcc\
wget\
time\
git
## Install Go
ENV GO_VERSION 1.8.3
RUN wget http://golangtc.com/static/go/${GO_VERSION}/go${GO_VERSION}.linux-amd64.tar.gz
#RUN wget http://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz
RUN tar -xzf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local
## Env
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/hyperhq/hypercli/vendor
ENV HYPER_CONFIG=/root/.hyper
ENV DOCKER_REMOTE_DAEMON=1
ENV DOCKER_CERT_PATH=fixtures/hyper_ssl
ENV DOCKER_TLS_VERIFY=
ENV DOCKER_HOST=
ENV ACCESS_KEY=
ENV SECRET_KEY=
ENV REGION=
## Ensure /usr/bin/hyper
RUN ln -s /go/src/github.com/hyperhq/hypercli/hyper/hyper /usr/bin/hyper
RUN echo alias hypercli=\"hyper --region \${DOCKER_HOST}\" >> /root/.bashrc
## Ensure /go/src/github.com/docker/docker
RUN mkdir -p /go/src/github.com/docker
RUN ln -s /go/src/github.com/hyperhq/hypercli /go/src/github.com/docker/docker
WORKDIR /go/src/github.com/hyperhq/hypercli
VOLUME ["/go/src/github.com/hyperhq/hypercli"]
ENTRYPOINT ["hack/generate-hyper-conf-dev.sh"]
##########################################################################
# install on-my-zsh
RUN yum install -y zsh
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
RUN sed -i "s/^ZSH_THEME=.*/ZSH_THEME=\"gianu\"/g" /root/.zshrc
RUN echo alias hypercli=\"hyper --region \${DOCKER_HOST}\" >> /root/.zshrc
# config git
RUN git config --global color.ui true; \
git config --global color.status auto; \
git config --global color.diff auto; \
git config --global color.branch auto; \
git config --global color.interactive auto; \
git config --global alias.st 'status'; \
git config --global alias.ci 'commit'; \
git config --global alias.co 'checkout'; \
git config --global alias.br 'branch'; \
git config --global alias.sr 'show-ref'; \
git config --global alias.cm '!sh -c "br_name=`git symbolic-ref HEAD|sed s#refs/heads/##`; git commit -em \"[\${br_name}] \""'; \
git config --global alias.lg "log --graph --pretty=format:'[%ci] %Cgreen(%cr) %Cred%h%Creset -%x09%C(yellow)%Creset %C(cyan)[%an]%Creset %x09 %s%Creset' --abbrev-commit --date=short"; \
git config --global push.default current

View File

@@ -1,79 +0,0 @@
# This file describes the standard way to build Docker, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker -f Dockerfile.gccgo .
#
FROM gcc:5.3
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
btrfs-tools \
build-essential \
curl \
git \
iptables \
jq \
net-tools \
libapparmor-dev \
libcap-dev \
libsqlite3-dev \
mercurial \
parallel \
python-dev \
python-mock \
python-pip \
python-websocket \
--no-install-recommends
# Get lvm2 source for compiling statically
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure --enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# install seccomp: the version shipped in jessie is too old
ENV SECCOMP_VERSION v2.2.3
RUN set -x \
&& export SECCOMP_PATH=$(mktemp -d) \
&& git clone https://github.com/seccomp/libseccomp.git "$SECCOMP_PATH" \
&& ( \
cd "$SECCOMP_PATH" \
&& git checkout "$SECCOMP_VERSION" \
&& ./autogen.sh \
&& ./configure --prefix=/usr \
&& make \
&& make install \
) \
&& rm -rf "$SECCOMP_PATH"
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,198 +0,0 @@
# This file describes the standard way to build Docker on ppc64le, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker -f Dockerfile.ppc64le .
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
#
# # Run the test suite:
# docker run --privileged docker hack/make.sh test
#
# # Publish a release:
# docker run --privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM ppc64le/gcc:5.3
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
createrepo \
curl \
dpkg-sig \
git \
iptables \
jq \
net-tools \
libapparmor-dev \
libcap-dev \
libltdl-dev \
libsqlite3-dev \
libsystemd-journal-dev \
libtool \
mercurial \
pkg-config \
python-dev \
python-mock \
python-pip \
python-websocket \
xfsprogs \
tar \
--no-install-recommends
# Get lvm2 source for compiling statically
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
&& curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
| tar -xzC /usr/local/lvm2 --strip-components=1
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# fix platform enablement in lvm2 to support ppc64le properly
RUN set -e \
&& for f in config.guess config.sub; do \
curl -fsSL -o "/usr/local/lvm2/autoconf/$f" "http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=$f;hb=HEAD"; \
done
# "arch.c:78:2: error: #error the arch code needs to know about your machine type"
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure \
--build="$(gcc -print-multiarch)" \
--enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# TODO install Go, using gccgo as GOROOT_BOOTSTRAP (Go 1.5+ supports ppc64le properly)
# possibly a ppc64le/golang image?
ENV PATH /go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
# ENV GOFMT_VERSION 1.3.3
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
# TODO update this sha when we upgrade to Go 1.5+
ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
# Grab Go's cover tool for dead-simple code coverage testing
# Grab Go's vet tool for examining go code to find suspicious constructs
# and help prevent errors that the compiler might not catch
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
&& go install -v golang.org/x/tools/cmd/cover \
&& go install -v golang.org/x/tools/cmd/vet
# Grab Go's lint tool
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
&& go install -v github.com/golang/lint/golint
# Install two versions of the registry. The first is an older version that
# only supports schema1 manifests. The second is a newer version that supports
# both. This allows integration-cli tests to cover push/pull with both schema1
# and schema2 manifests.
ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
ENV REGISTRY_COMMIT cb08de17d74bef86ce6c5abe8b240e282f5750be
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# TODO update this when we upgrade to Go 1.5.1+
# Install notary server
#ENV NOTARY_VERSION docker-v1.10-5
#RUN set -x \
# && export GOPATH="$(mktemp -d)" \
# && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
# && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
# && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
# go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
# && rm -rf "$GOPATH"
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT \
&& pip install -r test-requirements.txt
# Set user.email so crosbymichael's in-container merge commits go smoothly
RUN git config --global user.email 'docker-dummy@example.com'
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor selinux
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
ppc64le/buildpack-deps:jessie@sha256:902bfe4ef1389f94d143d64516dd50a2de75bca2e66d4a44b1d73f63ddf05dda \
ppc64le/busybox:latest@sha256:38bb82085248d5a3c24bd7a5dc146f2f2c191e189da0441f1c2ca560e3fc6f1b \
ppc64le/debian:jessie@sha256:412845f51b6ab662afba71bc7a716e20fdb9b84f185d180d4c7504f8a75c4f91 \
ppc64le/hello-world:latest@sha256:186a40a9a02ca26df0b6c8acdfb8ac2f3ae6678996a838f977e57fac9d963974
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Build/install the tool for embedding resources in Windows binaries
ENV RSRC_VERSION v2
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b "$RSRC_VERSION" https://github.com/akavel/rsrc.git "$GOPATH/src/github.com/akavel/rsrc" \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,42 +0,0 @@
FROM centos:7.3.1611
#This Dockerfile is used for autotest hypercli
#REF: integration-cli/README.md
##########################################################################
RUN yum install -y\
automake\
gcc\
wget\
time\
git
## Install Go
ENV GO_VERSION 1.8.3
RUN wget http://golangtc.com/static/go/${GO_VERSION}/go${GO_VERSION}.linux-amd64.tar.gz
#RUN wget http://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz
RUN tar -xzf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local
## Env
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/hyperhq/hypercli/integration-cli/vendor:/go/src/github.com/hyperhq/hypercli/vendor
ENV HYPER_CONFIG=/root/.hyper
ENV DOCKER_REMOTE_DAEMON=1
ENV DOCKER_CERT_PATH=fixtures/hyper_ssl
ENV DOCKER_TLS_VERIFY=
ENV DOCKER_HOST="tcp://us-west-1.hyper.sh:443"
## if BRANCH start with '#', then it means PR number, otherwise it means branch name
ENV BRANCH="master"
ENV ACCESS_KEY=
ENV SECRET_KEY=
ENV REGION=
RUN mkdir -p /go/src/github.com/hyperhq
WORKDIR /go/src/github.com/hyperhq
ADD hack/generate-hyper-conf-qa.sh /generate-hyper-conf-qa.sh
ENTRYPOINT ["/generate-hyper-conf-qa.sh"]

View File

@@ -1,191 +0,0 @@
# This file describes the standard way to build Docker on s390x, using docker
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker -f Dockerfile.s390x .
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
#
# # Run the test suite:
# docker run --privileged docker hack/make.sh test
#
# # Publish a release:
# docker run --privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM s390x/gcc:5.3
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
createrepo \
curl \
dpkg-sig \
git \
iptables \
jq \
net-tools \
libapparmor-dev \
libcap-dev \
libltdl-dev \
libsqlite3-dev \
libsystemd-journal-dev \
libtool \
mercurial \
pkg-config \
python-dev \
python-mock \
python-pip \
python-websocket \
xfsprogs \
tar \
--no-install-recommends
# Get lvm2 source for compiling statically
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
&& curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
| tar -xzC /usr/local/lvm2 --strip-components=1
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# fix platform enablement in lvm2 to support s390x properly
RUN set -e \
&& for f in config.guess config.sub; do \
curl -fsSL -o "/usr/local/lvm2/autoconf/$f" "http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=$f;hb=HEAD"; \
done
# "arch.c:78:2: error: #error the arch code needs to know about your machine type"
# Compile and install lvm2
RUN cd /usr/local/lvm2 \
&& ./configure \
--build="$(gcc -print-multiarch)" \
--enable-static_link \
&& make device-mapper \
&& make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# Note: Go comes from the base image (gccgo, specifically)
# We can't compile Go proper because s390x isn't an officially supported architecture yet.
ENV PATH /go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
# ENV GOFMT_VERSION 1.3.3
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
# TODO update this sha when we upgrade to Go 1.5+
ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
# Grab Go's cover tool for dead-simple code coverage testing
# Grab Go's vet tool for examining go code to find suspicious constructs
# and help prevent errors that the compiler might not catch
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
&& go install -v golang.org/x/tools/cmd/cover \
&& go install -v golang.org/x/tools/cmd/vet
# Grab Go's lint tool
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
&& go install -v github.com/golang/lint/golint
# Install registry
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-5
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -gccgoflags=-lpthread -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH"
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT \
&& pip install -r test-requirements.txt
# Set user.email so crosbymichael's in-container merge commits go smoothly
RUN git config --global user.email 'docker-dummy@example.com'
# Add an unprivileged user to be used for tests which need it
RUN groupadd -r docker
RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor selinux
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
s390x/buildpack-deps:jessie@sha256:4d1381224acaca6c4bfe3604de3af6972083a8558a99672cb6989c7541780099 \
s390x/busybox:latest@sha256:dd61522c983884a66ed72d60301925889028c6d2d5e0220a8fe1d9b4c6a4f01b \
s390x/debian:jessie@sha256:b74c863400909eff3c5e196cac9bfd1f6333ce47aae6a38398d87d5875da170a \
s390x/hello-world:latest@sha256:780d80b3a7677c3788c0d5cd9168281320c8d4a6d9183892d8ee5cdd610f5699
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Build/install the tool for embedding resources in Windows binaries
ENV RSRC_VERSION v2
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone --depth 1 -b "$RSRC_VERSION" https://github.com/akavel/rsrc.git "$GOPATH/src/github.com/akavel/rsrc" \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
# Upload docker source
COPY . /go/src/github.com/docker/docker

View File

@@ -1,34 +0,0 @@
# docker build -t docker:simple -f Dockerfile.simple .
# docker run --rm docker:simple hack/make.sh dynbinary
# docker run --rm --privileged docker:simple hack/dind hack/make.sh test-unit
# docker run --rm --privileged -v /var/lib/docker docker:simple hack/dind hack/make.sh dynbinary test-integration-cli
# This represents the bare minimum required to build and test Docker.
FROM debian:jessie
# compile and runtime deps
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#build-dependencies
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
btrfs-tools \
curl \
gcc \
git \
golang \
libdevmapper-dev \
libsqlite3-dev \
\
ca-certificates \
e2fsprogs \
iptables \
procps \
xfsprogs \
xz-utils \
\
aufs-tools \
&& rm -rf /var/lib/apt/lists/*
ENV AUTO_GOPATH 1
WORKDIR /usr/src/docker
COPY . /usr/src/docker

View File

@@ -1,93 +0,0 @@
# This file describes the standard way to build Docker, using a docker container on Windows
# Server 2016
#
# Usage:
#
# # Assemble the full dev environment. This is slow the first time. Run this from
# # a directory containing the sources you are validating. For example from
# # c:\go\src\github.com\docker\docker
#
# docker build -t docker -f Dockerfile.windows .
#
#
# # Build docker in a container. Run the following from a Windows cmd command prommpt,
# # replacing c:\built with the directory you want the binaries to be placed on the
# # host system.
#
# docker run --rm -v "c:\built:c:\target" docker sh -c 'cd /c/go/src/github.com/docker/docker; hack/make.sh binary; ec=$?; if [ $ec -eq 0 ]; then robocopy /c/go/src/github.com/docker/docker/bundles/$(cat VERSION)/binary /c/target/binary; fi; exit $ec'
#
# Important notes:
# ---------------
#
# Multiple commands in a single powershell RUN command are deliberately not done. This is
# because PS doesn't have a concept quite like set -e in bash. It would be possible to use
# try-catch script blocks, but that would make this file unreadable. The problem is that
# if there are two commands eg "RUN powershell -command fail; succeed", as far as docker
# would be concerned, the return code from the overall RUN is succeed. This doesn't apply to
# RUN which uses cmd as the command interpreter such as "RUN fail; succeed".
#
# 'sleep 5' is a deliberate workaround for a current problem on containers in Windows
# Server 2016. It ensures that the network is up and available for when the command is
# network related. This bug is being tracked internally at Microsoft and exists in TP4.
# Generally sleep 1 or 2 is probably enough, but making it 5 to make the build file
# as bullet proof as possible. This isn't a big deal as this only runs the first time.
#
# The cygwin posix utilities from GIT aren't usable interactively as at January 2016. This
# is because they require a console window which isn't present in a container in Windows.
# See the example at the top of this file. Do NOT use -it in that docker run!!!
#
# Don't try to use a volume for passing the source through. The cygwin posix utilities will
# balk at reparse points. Again, see the example at the top of this file on how use a volume
# to get the built binary out of the container.
FROM windowsservercore
# Environment variable notes:
# - GOLANG_VERSION should be updated to be consistent with the Linux dockerfile.
# - FROM_DOCKERFILE is used for detection of building within a container.
ENV GOLANG_VERSION=1.5.3 \
GIT_VERSION=2.7.0 \
RSRC_COMMIT=ba14da1f827188454a4591717fff29999010887f \
GOPATH=C:/go;C:/go/src/github.com/docker/docker/vendor \
FROM_DOCKERFILE=1
# Make sure we're in temp for the downloads
WORKDIR c:/windows/temp
# Download everything else we need to install
# We want a 64-bit make.exe, not 16 or 32-bit. This was hard to find, so documenting the links
# - http://sourceforge.net/p/mingw-w64/wiki2/Make/ -->
# - http://sourceforge.net/projects/mingw-w64/files/External%20binary%20packages%20%28Win64%20hosted%29/ -->
# - http://sourceforge.net/projects/mingw-w64/files/External binary packages %28Win64 hosted%29/make/
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile make.zip http://downloads.sourceforge.net/project/mingw-w64/External%20binary%20packages%20%28Win64%20hosted%29/make/make-3.82.90-20111115.zip
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile gcc.zip http://downloads.sourceforge.net/project/tdm-gcc/TDM-GCC%205%20series/5.1.0-tdm64-1/gcc-5.1.0-tdm64-1-core.zip
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile runtime.zip http://downloads.sourceforge.net/project/tdm-gcc/MinGW-w64%20runtime/GCC%205%20series/mingw64runtime-v4-git20150618-gcc5-tdm64-1.zip
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile binutils.zip http://downloads.sourceforge.net/project/tdm-gcc/GNU%20binutils/binutils-2.25-tdm64-1.zip
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile 7zsetup.exe http://www.7-zip.org/a/7z1514-x64.exe
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile lzma.7z http://www.7-zip.org/a/lzma1514.7z
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile gitsetup.exe https://github.com/git-for-windows/git/releases/download/v%GIT_VERSION%.windows.1/Git-%GIT_VERSION%-64-bit.exe
RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile go.msi https://storage.googleapis.com/golang/go%GOLANG_VERSION%.windows-amd64.msi
# Path
RUN setx /M Path "c:\git\cmd;c:\git\bin;c:\git\usr\bin;%Path%;c:\gcc\bin;c:\7zip"
# Install and expand the bits we downloaded.
# Note: The git, 7z and go.msi installers execute asynchronously.
RUN powershell -command start-process .\gitsetup.exe -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES /CLOSEAPPLICATIONS /DIR=c:\git' -Wait
RUN powershell -command start-process .\7zsetup -ArgumentList '/S /D=c:/7zip' -Wait
RUN powershell -command start-process .\go.msi -ArgumentList '/quiet' -Wait
RUN powershell -command Expand-Archive gcc.zip \gcc -Force
RUN powershell -command Expand-Archive runtime.zip \gcc -Force
RUN powershell -command Expand-Archive binutils.zip \gcc -Force
RUN powershell -command 7z e lzma.7z bin/lzma.exe
RUN powershell -command 7z x make.zip make-3.82.90-20111115/bin_amd64/make.exe
RUN powershell -command mv make-3.82.90-20111115/bin_amd64/make.exe /gcc/bin/
# RSRC for manifest and icon
RUN powershell -command sleep 5 ; git clone https://github.com/akavel/rsrc.git c:\go\src\github.com\akavel\rsrc
RUN cd c:/go/src/github.com/akavel/rsrc && git checkout -q %RSRC_COMMIT% && go install -v
# Prepare for building
WORKDIR c:/
COPY . /go/src/github.com/docker/docker

View File

@@ -1,242 +0,0 @@
# Docker maintainers file
#
# This file describes who runs the docker/docker project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant
# parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
# The Core maintainers are the ghostbusters of the project: when there's a problem others
# can't solve, they show up and fix it with bizarre devices and weaponry.
# They have final say on technical implementation and coding style.
# They are ultimately responsible for quality in all its forms: usability polish,
# bugfixes, performance, stability, etc. When ownership can cleanly be passed to
# a subsystem, they are responsible for doing so and holding the
# subsystem maintainers accountable. If ownership is unclear, they are the de facto owners.
# For each release (including minor releases), a "release captain" is assigned from the
# pool of core maintainers. Rotation is encouraged across all maintainers, to ensure
# the release process is clear and up-to-date.
people = [
"calavera",
"coolljt0725",
"cpuguy83",
"crosbymichael",
"duglin",
"estesp",
"icecrime",
"jfrazelle",
"lk4d4",
"mhbauer",
"runcom",
"tianon",
"tibor",
"tonistiigi",
"unclejack",
"vbatts",
"vdemeester"
]
[Org."Docs maintainers"]
# TODO Describe the docs maintainers role.
people = [
"jamtur01",
"moxiegirl",
"sven",
"thajeztah"
]
[Org.Curators]
# The curators help ensure that incoming issues and pull requests are properly triaged and
# that our various contribution and reviewing processes are respected. With their knowledge of
# the repository activity, they can also guide contributors to relevant material or
# discussions.
#
# They are neither code nor docs reviewers, so they are never expected to merge. They can
# however:
# - close an issue or pull request when it's an exact duplicate
# - close an issue or pull request when it's inappropriate or off-topic
people = [
"thajeztah"
]
[Org.Alumni]
# This list contains maintainers that are no longer active on the project.
# It is thanks to these people that the project has become what it is today.
# Thank you!
people = [
# As a maintainer, Erik was responsible for the "builder", and
# started the first designs for the new networking model in
# Docker. Erik is now working on all kinds of plugins for Docker
# (https://github.com/contiv) and various open source projects
# in his own repository https://github.com/erikh. You may
# still stumble into him in our issue tracker, or on IRC.
"erikh",
# Victor is one of the earliest contributors to Docker, having worked on the
# project when it was still "dotCloud" in April 2013. He's been responsible
# for multiple releases (https://github.com/docker/docker/pulls?q=is%3Apr+bump+in%3Atitle+author%3Avieux),
# and up until today (2015), our number 2 contributor. Although he's no longer
# a maintainer for the Docker "Engine", he's still actively involved in other
# Docker projects, and most likely can be found in the Docker Swarm repository,
# for which he's a core maintainer.
"vieux",
# Vishnu became a maintainer to help out on the daemon codebase and
# libcontainer integration. He's currently involved in the
# Open Containers Initiative, working on the specifications,
# besides his work on cAdvisor and Kubernetes for Google.
"vishh"
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[people.coolljt0725]
Name = "Lei Jitang"
Email = "leijitang@huawei.com"
GitHub = "coolljt0725"
[people.cpuguy83]
Name = "Brian Goff"
Email = "cpuguy83@gmail.com"
Github = "cpuguy83"
[people.crosbymichael]
Name = "Michael Crosby"
Email = "crosbymichael@gmail.com"
GitHub = "crosbymichael"
[people.duglin]
Name = "Doug Davis"
Email = "dug@us.ibm.com"
GitHub = "duglin"
[people.erikh]
Name = "Erik Hollensbe"
Email = "erik@docker.com"
GitHub = "erikh"
[people.estesp]
Name = "Phil Estes"
Email = "estesp@linux.vnet.ibm.com"
GitHub = "estesp"
[people.icecrime]
Name = "Arnaud Porterie"
Email = "arnaud@docker.com"
GitHub = "icecrime"
[people.jamtur01]
Name = "James Turnbull"
Email = "james@lovedthanlost.net"
GitHub = "jamtur01"
[people.jfrazelle]
Name = "Jessie Frazelle"
Email = "j@docker.com"
GitHub = "jfrazelle"
[people.lk4d4]
Name = "Alexander Morozov"
Email = "lk4d4@docker.com"
GitHub = "lk4d4"
[people.mhbauer]
Name = "Morgan Bauer"
Email = "mbauer@us.ibm.com"
GitHub = "mhbauer"
[people.moxiegirl]
Name = "Mary Anthony"
Email = "mary.anthony@docker.com"
GitHub = "moxiegirl"
[people.runcom]
Name = "Antonio Murdaca"
Email = "runcom@redhat.com"
GitHub = "runcom"
[people.shykes]
Name = "Solomon Hykes"
Email = "solomon@docker.com"
GitHub = "shykes"
[people.sven]
Name = "Sven Dowideit"
Email = "SvenDowideit@home.org.au"
GitHub = "SvenDowideit"
[people.thajeztah]
Name = "Sebastiaan van Stijn"
Email = "github@gone.nl"
GitHub = "thaJeztah"
[people.theadactyl]
Name = "Thea Lamkin"
Email = "thea@docker.com"
GitHub = "theadactyl"
[people.tianon]
Name = "Tianon Gravi"
Email = "admwiggin@gmail.com"
GitHub = "tianon"
[people.tibor]
Name = "Tibor Vass"
Email = "tibor@docker.com"
GitHub = "tiborvass"
[people.tonistiigi]
Name = "Tõnis Tiigi"
Email = "tonis@docker.com"
GitHub = "tonistiigi"
[people.unclejack]
Name = "Cristian Staretu"
Email = "cristian.staretu@gmail.com"
GitHub = "unclejack"
[people.vbatts]
Name = "Vincent Batts"
Email = "vbatts@redhat.com"
GitHub = "vbatts"
[people.vdemeester]
Name = "Vincent Demeester"
Email = "vincent@sbr.pm"
GitHub = "vdemeester"
[people.vieux]
Name = "Victor Vieux"
Email = "vieux@docker.com"
GitHub = "vieux"
[people.vishh]
Name = "Vishnu Kannan"
Email = "vishnuk@google.com"
GitHub = "vishh"

View File

@@ -1,119 +0,0 @@
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-docker-py test-integration-cli test-unit validate
# get OS/Arch of docker engine
DOCKER_OSARCH := $(shell bash -c 'source hack/make/.detect-daemon-osarch && echo $${DOCKER_ENGINE_OSARCH:+$$DOCKER_CLIENT_OSARCH}')
# default for linux/amd64 and others
DOCKERFILE := Dockerfile
# switch to different Dockerfile for linux/arm
ifeq ($(DOCKER_OSARCH), linux/arm)
DOCKERFILE := Dockerfile.armhf
else
ifeq ($(DOCKER_OSARCH), linux/arm64)
DOCKERFILE := Dockerfile.aarch64
else
ifeq ($(DOCKER_OSARCH), linux/ppc64le)
DOCKERFILE := Dockerfile.ppc64le
else
ifeq ($(DOCKER_OSARCH), linux/s390x)
DOCKERFILE := Dockerfile.s390x
else
ifeq ($(DOCKER_OSARCH), windows/amd64)
DOCKERFILE := Dockerfile.windows
endif
endif
endif
endif
endif
export DOCKERFILE
# env vars passed through directly to Docker's build scripts
# to allow things like `make DOCKER_CLIENTONLY=1 binary` easily
# `docs/sources/contributing/devenvironment.md ` and `project/PACKAGERS.md` have some limited documentation of some of these
DOCKER_ENVS := \
-e BUILDFLAGS \
-e KEEPBUNDLE \
-e DOCKER_BUILD_GOGC \
-e DOCKER_BUILD_PKGS \
-e DOCKER_CLIENTONLY \
-e DOCKER_DEBUG \
-e DOCKER_EXPERIMENTAL \
-e DOCKERFILE \
-e DOCKER_GRAPHDRIVER \
-e DOCKER_INCREMENTAL_BINARY \
-e DOCKER_REMAP_ROOT \
-e DOCKER_STORAGE_OPTS \
-e DOCKER_USERLANDPROXY \
-e TESTDIRS \
-e TESTFLAGS \
-e TIMEOUT
# note: we _cannot_ add "-e DOCKER_BUILDTAGS" here because even if it's unset in the shell, that would shadow the "ENV DOCKER_BUILDTAGS" set in our Dockerfile, which is very important for our official builds
# to allow `make BIND_DIR=. shell` or `make BIND_DIR= test`
# (default to no bind mount if DOCKER_HOST is set)
# note: BINDDIR is supported for backwards-compatibility here
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_FLAGS := docker run --rm -i --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT)
# if this session isn't interactive, then we don't want to allocate a
# TTY, which would fail, but if it is interactive, we do want to attach
# so that the user can send e.g. ^C through.
INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0)
ifeq ($(INTERACTIVE), 1)
DOCKER_FLAGS += -t
endif
DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
default: binary
all: build
$(DOCKER_RUN_DOCKER) hack/make.sh
binary: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary
build: bundles
docker build ${DOCKER_BUILD_ARGS} -t "$(DOCKER_IMAGE)" -f "$(DOCKERFILE)" .
bundles:
mkdir bundles
cross: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary binary cross
deb: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary build-deb
docs:
$(MAKE) -C docs docs
gccgo: build
$(DOCKER_RUN_DOCKER) hack/make.sh gccgo
rpm: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary build-rpm
shell: build
$(DOCKER_RUN_DOCKER) bash
test: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary cross test-unit test-integration-cli test-docker-py
test-docker-py: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-docker-py
test-integration-cli: build
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration-cli
test-unit: build
$(DOCKER_RUN_DOCKER) hack/make.sh test-unit
validate: build
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet validate-vendor

View File

@@ -1,52 +0,0 @@
# HyperCLI [![Build Status](https://travis-ci.org/hyperhq/hypercli.svg?branch=master)](https://travis-ci.org/hyperhq/hypercli)
Go version of Hyper.sh client command line tools.
## Install
### Quick and Easy (Recommended)
Grab the latest version for your system on the [Releases](https://github.com/hyperhq/hypercli/releases) page or build it by yourself as the [instruction](#how-to-build).
You can either run the binary directly or add somewhere in your $PATH.
## Getting Started
#### Before Getting Started
Before you can use Hyper.sh, be sure you've [created a free account with Hyper.sh](http://www.hyper.sh) and [generate your credentials on Hyper.sh](https://docs.hyper.sh/GettingStarted/generate_api_credential.html).
Once the installation and setup completes, enter `hyper config` in your terminal. The CLI will prompt to ask for your API credential:
![](https://trello-attachments.s3.amazonaws.com/56daae9b816ec930c8d98197/720x143/9fdd9a68694376d4ec62a3d93409e67c/upload_3_18_2016_at_6_11_19_PM.png)
The credential is stored in a local configuration file `$HOME/.hyper/config.json`. The configuration file is similar to Docker's, with an extra section `clouds`.
![](https://trello-attachments.s3.amazonaws.com/56daae9b816ec930c8d98197/635x160/c9caa016982d5884eb06578292c154bf/config.png)
Or you can use environmental vairables `HYPER_ACCESS` and `HYPER_SECRET` to pass the access key and secret key (CLI will search for these envs before loading the configuration file).
You only need to do that once for your machine. If you've done that, then you can continue.
[See the official docs](http://docs.hyper.sh/) for more detailed info on using Hyper.sh.
#### Actually Getting Started
The easiest way to get started is by digging around.
`$ hyper --help` for example usage and a list of commands
## How to build
```
$ mkdir $GOPATH/src/github.com/hyperhq/
$ cd $GOPATH/src/github.com/hyperhq/
$ git clone https://github.com/hyperhq/hypercli hypercli
$ cd hypercli
$ ./build.sh
```
## Contributing
Give us a pull request! File a bug!

View File

@@ -1,183 +0,0 @@
Docker Engine Roadmap
=====================
### How should I use this document?
This document provides description of items that the project decided to prioritize. This should
serve as a reference point for Docker contributors to understand where the project is going, and
help determine if a contribution could be conflicting with some longer terms plans.
The fact that a feature isn't listed here doesn't mean that a patch for it will automatically be
refused (except for those mentioned as "frozen features" below)! We are always happy to receive
patches for new cool features we haven't thought about, or didn't judge priority. Please however
understand that such patches might take longer for us to review.
### How can I help?
Short term objectives are listed in the [wiki](https://github.com/docker/docker/wiki) and described
in [Issues](https://github.com/docker/docker/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). Our
goal is to split down the workload in such way that anybody can jump in and help. Please comment on
issues if you want to take it to avoid duplicating effort! Similarly, if a maintainer is already
assigned on an issue you'd like to participate in, pinging him on IRC or GitHub to offer your help is
the best way to go.
### How can I add something to the roadmap?
The roadmap process is new to the Docker Engine: we are only beginning to structure and document the
project objectives. Our immediate goal is to be more transparent, and work with our community to
focus our efforts on fewer prioritized topics.
We hope to offer in the near future a process allowing anyone to propose a topic to the roadmap, but
we are not quite there yet. For the time being, the BDFL remains the keeper of the roadmap, and we
won't be accepting pull requests adding or removing items from this file.
# 1. Features and refactoring
## 1.1 Security
Security is a top objective for the Docker Engine. The most notable items we intend to provide in
the near future are:
- Trusted distribution of images: the effort is driven by the [distribution](https://github.com/docker/distribution)
group but will have significant impact on the Engine
- [User namespaces](https://github.com/docker/docker/pull/12648)
- [Seccomp support](https://github.com/docker/libcontainer/pull/613)
## 1.2 Plumbing project
We define a plumbing tool as a standalone piece of software usable and meaningful on its own. In
the current state of the Docker Engine, most subsystems provide independent functionalities (such
the builder, pushing and pulling images, running applications in a containerized environment, etc)
but all are coupled in a single binary. We want to offer the users to flexibility to use only the
pieces they need, and we will also gain in maintainability by splitting the project among multiple
repositories.
As it currently stands, the rough design outlines is to have:
- Low level plumbing tools, each dealing with one responsibility (e.g., [runC](https://runc.io))
- Docker subsystems services, each exposing an elementary concept over an API, and relying on one or
multiple lower level plumbing tools for their implementation (e.g., network management)
- Docker Engine to expose higher level actions (e.g., create a container with volume `V` and network
`N`), while still providing pass-through access to the individual subsystems.
The architectural details are still being worked on, but one thing we know for sure is that we need
to technically decouple the pieces.
### 1.2.1 Runtime
A Runtime tool already exists today in the form of [runC](https://github.com/opencontainers/runc).
We intend to modify the Engine to directly call out to a binary implementing the Open Containers
Specification such as runC rather than relying on libcontainer to set the container runtime up.
This plan will deprecate the existing [`execdriver`](https://github.com/docker/docker/tree/master/daemon/execdriver)
as different runtime backends will be implemented as separated binaries instead of being compiled
into the Engine.
### 1.2.2 Builder
The Builder (i.e., the ability to build an image from a Dockerfile) is already nicely decoupled,
but would benefit from being entirely separated from the Engine, and rely on the standard Engine
API for its operations.
### 1.2.3 Distribution
Distribution already has a [dedicated repository](https://github.com/docker/distribution) which
holds the implementation for Registry v2 and client libraries. We could imagine going further by
having the Engine call out to a binary providing image distribution related functionalities.
There are two short term goals related to image distribution. The first is stabilize and simplify
the push/pull code. Following that is the conversion to the more secure Registry V2 protocol.
### 1.2.4 Networking
Most of networking related code was already decoupled today in [libnetwork](https://github.com/docker/libnetwork).
As with other ingredients, we might want to take it a step further and make it a meaningful utility
that the Engine would call out to instead of a library.
## 1.3 Plugins
An initiative around plugins started with Docker 1.7.0, with the goal of allowing for out of
process extensibility of some Docker functionalities, starting with volumes and networking. The
approach is to provide specific extension points rather than generic hooking facilities. We also
deliberately keep the extensions API the simplest possible, expanding as we discover valid use
cases that cannot be implemented.
At the time of writing:
- Plugin support is merged as an experimental feature: real world use cases and user feedback will
help us refine the UX to make the feature more user friendly.
- There are no immediate plans to expand on the number of pluggable subsystems.
- Golang 1.5 might add language support for [plugins](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ)
which we consider supporting as an alternative to JSON/HTTP.
## 1.4 Volume management
Volumes are not a first class citizen in the Engine today: we would like better volume management,
similar to the way network are managed in the new [CNM](https://github.com/docker/docker/issues/9983).
## 1.5 Better API implementation
The current Engine API is insufficiently typed, versioned, and ultimately hard to maintain. We
also suffer from the lack of a common implementation with [Swarm](https://github.com/docker/swarm).
## 1.6 Checkpoint/restore
Support for checkpoint/restore was [merged](https://github.com/docker/libcontainer/pull/479) in
[libcontainer](https://github.com/docker/libcontainer) and made available through [runC](https://runc.io):
we intend to take advantage of it in the Engine.
# 2 Frozen features
## 2.1 Docker exec
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
*debugging* feature, as well as being strongly dependent on the Runtime ingredient effort.
## 2.2 Dockerfile syntax
The Dockerfile syntax as we know it is simple, and has proven successful in supporting all our
[official images](https://github.com/docker-library/official-images). Although this is *not* a
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
reasons:
- Long term impact of syntax changes is a sensitive matter that require an amount of attention
the volume of Engine codebase and activity today doesn't allow us to provide.
- Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
languages without cluttering the Engine's codebase.
- A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
to own the Dockerfile syntax and decide collectively on the direction to give it.
- Our experience with official images tend to show that no new instruction or syntax expansion is
*strictly* necessary for the majority of use cases, and although we are aware many things are still
lacking for many, we cannot make it a priority yet for the above reasons.
Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
what we want to do first!
## 2.3 Remote Registry Operations
A large amount of work is ongoing in the area of image distribution and
provenance. This includes moving to the V2 Registry API and heavily
refactoring the code that powers these features. The desired result is more
secure, reliable and easier to use image distribution.
Part of the problem with this part of the code base is the lack of a stable
and flexible interface. If new features are added that access the registry
without solidifying these interfaces, achieving feature parity will continue
to be elusive. While we get a handle on this situation, we are imposing a
moratorium on new code that accesses the Registry API in commands that don't
already make remote calls.
Currently, only the following commands cause interaction with a remote
registry:
- push
- pull
- run
- build
- search
- login
In the interest of stabilizing the registry access model during this ongoing
work, we are not accepting additions to other commands that will cause remote
interaction with the Registry API. This moratorium will lift when the goals of
the distribution project have been met.

View File

@@ -1,45 +0,0 @@
# Vendoring policies
This document outlines recommended Vendoring policies for Docker repositories.
(Example, libnetwork is a Docker repo and logrus is not.)
## Vendoring using tags
Commit ID based vendoring provides little/no information about the updates
vendored. To fix this, vendors will now require that repositories use annotated
tags along with commit ids to snapshot commits. Annotated tags by themselves
are not sufficient, since the same tag can be force updated to reference
different commits.
Each tag should:
- Follow Semantic Versioning rules (refer to section on "Semantic Versioning")
- Have a corresponding entry in the change tracking document.
Each repo should:
- Have a change tracking document between tags/releases. Ex: CHANGELOG.md,
github releases file.
The goal here is for consuming repos to be able to use the tag version and
changelog updates to determine whether the vendoring will cause any breaking or
backward incompatible changes. This also means that repos can specify having
dependency on a package of a specific version or greater up to the next major
release, without encountering breaking changes.
## Semantic Versioning
Annotated version tags should follow Schema Versioning policies.
According to http://semver.org:
"Given a version number MAJOR.MINOR.PATCH, increment the:
MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.
Additional labels for pre-release and build metadata are available as extensions
to the MAJOR.MINOR.PATCH format."
## Vendoring cadence
In order to avoid huge vendoring changes, it is recommended to have a regular
cadence for vendoring updates. eg. monthly.
## Pre-merge vendoring tests
All related repos will be vendored into docker/docker.
CI on docker/docker should catch any breaking changes involving multiple repos.

View File

@@ -1 +0,0 @@
1.10.16

View File

@@ -1,5 +0,0 @@
This directory contains code pertaining to the Docker API:
- Used by the docker client when communicating with the docker daemon
- Used by third party tools wishing to interface with the docker daemon

View File

@@ -1,101 +0,0 @@
package client
import (
"fmt"
"io"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
)
// CmdAttach attaches to a running container.
//
// Usage: docker attach [OPTIONS] CONTAINER
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
proxy := cmd.Bool([]string{}, true, "Proxy all received signals to the process")
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
containerID := cmd.Arg(0)
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if !c.State.Running {
return fmt.Errorf("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return fmt.Errorf("You cannot attach to a paused container, unpause it first")
}
if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil {
return err
}
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, cmd.Arg(0), false); err != nil {
logrus.Debugf("Error monitoring TTY size: %s", err)
}
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: !*noStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
}
var in io.ReadCloser
if options.Stdin {
in = cli.in
}
if *proxy && !c.Config.Tty {
sigc := cli.forwardAllSignals(ctx, containerID)
defer signal.StopCatch(sigc)
}
resp, err := cli.client.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
if in != nil && c.Config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
return err
}
_, status, err := getExitCode(ctx, cli, containerID)
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}

View File

@@ -1,651 +0,0 @@
package client
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/docker/go-units"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/builder/dockerignore"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/fileutils"
"github.com/hyperhq/hypercli/pkg/gitutils"
"github.com/hyperhq/hypercli/pkg/httputils"
"github.com/hyperhq/hypercli/pkg/ioutils"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/progress"
"github.com/hyperhq/hypercli/pkg/streamformatter"
"github.com/hyperhq/hypercli/pkg/urlutil"
"github.com/hyperhq/hypercli/reference"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
// CmdBuild builds a new image from the source code at a given path.
//
// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
//
// Usage: docker build [OPTIONS] PATH | URL | -
func (cli *DockerCli) Build(args ...string) error {
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
flTags := opts.NewListOpts(validateTag)
cmd.Var(&flTags, []string{"t", "-tag"}, "Name and optionally a tag in the 'name:tag' format")
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the build output and print image ID on success")
noCache := cmd.Bool([]string{"-no-cache"}, false, "Do not use cache when building the image")
rm := cmd.Bool([]string{"-rm"}, true, "Remove intermediate containers after a successful build")
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flShmSize := cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
flBuildArg := opts.NewListOpts(runconfigopts.ValidateEnv)
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
ulimits := make(map[string]*units.Ulimit)
flUlimits := runconfigopts.NewUlimitOpt(&ulimits)
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
cmd.Require(flag.Exact, 1)
// For trusted pull on "FROM <image>" instruction.
addTrustedFlags(cmd, true)
cmd.ParseFlags(args, true)
var (
buildCtx io.ReadCloser
err error
)
specifiedContext := cmd.Arg(0)
var (
contextDir string
tempDir string
relDockerfile string
progBuff io.Writer
buildBuff io.Writer
)
progBuff = cli.out
buildBuff = cli.out
if *suppressOutput {
progBuff = bytes.NewBuffer(nil)
buildBuff = bytes.NewBuffer(nil)
}
switch {
case specifiedContext == "-":
buildCtx, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
case urlutil.IsURL(specifiedContext):
buildCtx, relDockerfile, err = getContextFromURL(progBuff, specifiedContext, *dockerfileName)
default:
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
}
if err != nil {
if *suppressOutput && urlutil.IsURL(specifiedContext) {
fmt.Fprintln(cli.err, progBuff)
}
return fmt.Errorf("unable to prepare context: %s", err)
}
if tempDir != "" {
defer os.RemoveAll(tempDir)
contextDir = tempDir
}
if buildCtx == nil {
// And canonicalize dockerfile name to a platform-independent one
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
if err != nil {
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
}
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
if err != nil && !os.IsNotExist(err) {
return err
}
var excludes []string
if err == nil {
excludes, err = dockerignore.ReadAll(f)
if err != nil {
return err
}
}
if err := validateContextDirectory(contextDir, excludes); err != nil {
return fmt.Errorf("Error checking context: '%s'.", err)
}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The daemon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above.
var includes = []string{"."}
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", relDockerfile)
}
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: archive.Uncompressed,
ExcludePatterns: excludes,
IncludeFiles: includes,
})
if err != nil {
return err
}
}
ctx := context.Background()
var resolvedTags []*resolvedTag
if isTrusted() {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, cli.trustedReference, &resolvedTags)
}
// Setup an upload progress bar
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
var memory int64
if *flMemoryString != "" {
parsedMemory, err := units.RAMInBytes(*flMemoryString)
if err != nil {
return err
}
memory = parsedMemory
}
var memorySwap int64
if *flMemorySwap != "" {
if *flMemorySwap == "-1" {
memorySwap = -1
} else {
parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
if err != nil {
return err
}
memorySwap = parsedMemorySwap
}
}
var shmSize int64
if *flShmSize != "" {
shmSize, err = units.RAMInBytes(*flShmSize)
if err != nil {
return err
}
}
options := types.ImageBuildOptions{
Memory: memory,
MemorySwap: memorySwap,
Tags: flTags.GetAll(),
SuppressOutput: *suppressOutput,
NoCache: *noCache,
Remove: *rm,
ForceRemove: *forceRm,
PullParent: *pull,
CPUSetCPUs: *flCPUSetCpus,
CPUSetMems: *flCPUSetMems,
CPUShares: *flCPUShares,
CPUQuota: *flCPUQuota,
CPUPeriod: *flCPUPeriod,
CgroupParent: *flCgroupParent,
Dockerfile: relDockerfile,
ShmSize: shmSize,
Ulimits: flUlimits.GetList(),
BuildArgs: runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()),
AuthConfigs: cli.configFile.AuthConfigs,
}
response, err := cli.client.ImageBuild(ctx, body, options)
if err != nil {
return err
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, cli.outFd, cli.isTerminalOut, nil)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
if *suppressOutput {
fmt.Fprintf(cli.err, "%s%s", progBuff, buildBuff)
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
}
// Windows: show error message about modified file permissions if the
// daemon isn't running Windows.
if response.OSType != "windows" && runtime.GOOS == "windows" {
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
}
// Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout.
if *suppressOutput {
fmt.Fprintf(cli.out, "%s", buildBuff)
}
if isTrusted() {
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags {
if err := cli.tagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
}
return nil
}
// validateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read
// symlinks which point to non-existing files don't trigger an error
func validateContextDirectory(srcPath string, excludes []string) error {
contextRoot, err := getContextRoot(srcPath)
if err != nil {
return err
}
return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
return err
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
return err
} else if skip {
if f.IsDir() {
return filepath.SkipDir
}
return nil
}
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("can't stat '%s'", filePath)
}
if os.IsNotExist(err) {
return nil
}
return err
}
// skip checking if symlinks point to non-existing files, such symlinks can be useful
// also skip named pipes, because they hanging on open
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
return nil
}
if !f.IsDir() {
currentFile, err := os.Open(filePath)
if err != nil && os.IsPermission(err) {
return fmt.Errorf("no permission to read from '%s'", filePath)
}
currentFile.Close()
}
return nil
})
}
// validateTag checks if the given image name can be resolved.
func validateTag(rawRepo string) (string, error) {
_, err := reference.ParseNamed(rawRepo)
if err != nil {
return "", err
}
return rawRepo, nil
}
// isUNC returns true if the path is UNC (one starting \\). It always returns
// false on Linux.
func isUNC(path string) bool {
return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
}
// getDockerfileRelPath uses the given context directory for a `docker build`
// and returns the absolute path to the context directory, the relative path of
// the dockerfile in that context directory, and a non-nil error on success.
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
}
// The context dir might be a symbolic link, so follow it to the actual
// target directory.
//
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
// paths (those starting with \\). This hack means that when using links
// on UNC paths, they will not be followed.
if !isUNC(absContextDir) {
absContextDir, err = filepath.EvalSymlinks(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
}
}
stat, err := os.Lstat(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
}
if !stat.IsDir() {
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
}
absDockerfile := givenDockerfile
if absDockerfile == "" {
// No -f/--file was specified so use the default relative to the
// context directory.
absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
if _, err = os.Lstat(altPath); err == nil {
absDockerfile = altPath
}
}
}
// If not already an absolute path, the Dockerfile path should be joined to
// the base directory.
if !filepath.IsAbs(absDockerfile) {
absDockerfile = filepath.Join(absContextDir, absDockerfile)
}
// Evaluate symlinks in the path to the Dockerfile too.
//
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
// paths (those starting with \\). This hack means that when using links
// on UNC paths, they will not be followed.
if !isUNC(absDockerfile) {
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
}
}
if _, err := os.Lstat(absDockerfile); err != nil {
if os.IsNotExist(err) {
return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
}
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
}
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
}
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
}
return absContextDir, relDockerfile, nil
}
// writeToFile copies from the given reader and writes it to a file with the
// given filename.
func writeToFile(r io.Reader, filename string) error {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to create file: %v", err)
}
defer file.Close()
if _, err := io.Copy(file, r); err != nil {
return fmt.Errorf("unable to write file: %v", err)
}
return nil
}
// getContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func getContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(archive.HeaderSize)
if err != nil && err != io.EOF {
return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
}
if archive.IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
}
// Input should be read as a Dockerfile.
tmpDir, err := ioutil.TempDir("", "docker-build-context-")
if err != nil {
return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
}
f, err := os.Create(filepath.Join(tmpDir, api.DefaultDockerfileName))
if err != nil {
return nil, "", err
}
_, err = io.Copy(f, buf)
if err != nil {
f.Close()
return nil, "", err
}
if err := f.Close(); err != nil {
return nil, "", err
}
if err := r.Close(); err != nil {
return nil, "", err
}
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
if err != nil {
return nil, "", err
}
return ioutils.NewReadCloserWrapper(tar, func() error {
err := tar.Close()
os.RemoveAll(tmpDir)
return err
}), api.DefaultDockerfileName, nil
}
// getContextFromGitURL uses a Git URL as context for a `docker build`. The
// git repo is cloned into a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
// path of the dockerfile in that context directory, and a non-nil error on
// success.
func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
if _, err := exec.LookPath("git"); err != nil {
return "", "", fmt.Errorf("unable to find 'git': %v", err)
}
if absContextDir, err = gitutils.Clone(gitURL); err != nil {
return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
}
return getDockerfileRelPath(absContextDir, dockerfileName)
}
// getContextFromURL uses a remote URL as context for a `docker build`. The
// remote resource is downloaded as either a Dockerfile or a tar archive.
// Returns the tar archive used for the context and a path of the
// dockerfile inside the tar.
func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
response, err := httputils.Download(remoteURL)
if err != nil {
return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
}
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
// Pass the response body through a progress reader.
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
return getContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
}
// getContextFromLocalDir uses the given local directory as context for a
// `docker build`. Returns the absolute path to the local context directory,
// the relative path of the dockerfile in that context directory, and a non-nil
// error on success.
func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
// When using a local context directory, when the Dockerfile is specified
// with the `-f/--file` option then it is considered relative to the
// current directory and not the context directory.
if dockerfileName != "" {
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
}
}
return getDockerfileRelPath(localDir, dockerfileName)
}
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
digestRef reference.Canonical
tagRef reference.NamedTagged
}
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a
// trusted digest reference.
func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
scanner := bufio.NewScanner(dockerfile)
buf := bytes.NewBuffer(nil)
// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner.Scan() {
line := scanner.Text()
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
if matches != nil && matches[1] != api.NoBaseImageSpecifier {
// Replace the line with a resolved "FROM repo@digest"
ref, err := reference.ParseNamed(matches[1])
if err != nil {
return nil, nil, err
}
ref = reference.WithDefaultTag(ref)
if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
trustedRef, err := translator(ctx, ref)
if err != nil {
return nil, nil, err
}
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
resolvedTags = append(resolvedTags, &resolvedTag{
digestRef: trustedRef,
tagRef: ref,
})
}
}
_, err := fmt.Fprintln(buf, line)
if err != nil {
return nil, nil, err
}
}
return buf.Bytes(), resolvedTags, scanner.Err()
}
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
// replaces the entry with the given Dockerfile name with the contents of the
// new Dockerfile. Returns a new tar archive stream with the replaced
// Dockerfile.
func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
pipeReader, pipeWriter := io.Pipe()
go func() {
tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter)
defer inputTarStream.Close()
for {
hdr, err := tarReader.Next()
if err == io.EOF {
// Signals end of archive.
tarWriter.Close()
pipeWriter.Close()
return
}
if err != nil {
pipeWriter.CloseWithError(err)
return
}
var content io.Reader = tarReader
if hdr.Name == dockerfileName {
// This entry is the Dockerfile. Since the tar archive was
// generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive.
var newDockerfile []byte
newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator)
if err != nil {
pipeWriter.CloseWithError(err)
return
}
hdr.Size = int64(len(newDockerfile))
content = bytes.NewBuffer(newDockerfile)
}
if err := tarWriter.WriteHeader(hdr); err != nil {
pipeWriter.CloseWithError(err)
return
}
if _, err := io.Copy(tarWriter, content); err != nil {
pipeWriter.CloseWithError(err)
return
}
}
}()
return pipeReader
}

View File

@@ -1,247 +0,0 @@
package client
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"runtime"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cliconfig"
"github.com/hyperhq/hypercli/dockerversion"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/term"
)
// DockerCli represents the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
// initializing closure
init func() error
// configFile has the client configuration file
configFile *cliconfig.ConfigFile
// in holds the input stream and closer (io.ReadCloser) for the client.
in io.ReadCloser
// out holds the output stream (io.Writer) for the client.
out io.Writer
// err holds the error stream (io.Writer) for the client.
err io.Writer
// keyFile holds the key file as a string.
keyFile string
// inFd holds the file descriptor of the client's STDIN (if valid).
inFd uintptr
// outFd holds file descriptor of the client's STDOUT (if valid).
outFd uintptr
// isTerminalIn indicates whether the client's STDIN is a TTY
isTerminalIn bool
// isTerminalOut indicates whether the client's STDOUT is a TTY
isTerminalOut bool
// client is the http client that performs all API operations
client client.APIClient
// state holds the terminal state
state *term.State
region string
host string
}
// Initialize calls the init function that will setup the configuration for the client
// such as the TLS, tcp and other parameters used to run the client.
func (cli *DockerCli) Initialize() error {
if cli.init == nil {
return nil
}
return cli.init()
}
// CheckTtyInput checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error.
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
// In order to attach to a container tty, input stream for the client must
// be a tty itself: redirecting or piping the client standard input is
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
if ttyMode && attachStdin && !cli.isTerminalIn {
return errors.New("cannot enable tty mode on non tty input")
}
return nil
}
// PsFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) PsFormat() string {
return cli.configFile.PsFormat
}
// ImagesFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) ImagesFormat() string {
return cli.configFile.ImagesFormat
}
// VolumesFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) VolumesFormat() string {
return cli.configFile.VolumesFormat
}
func (cli *DockerCli) setRawTerminal() error {
if cli.isTerminalIn && os.Getenv("NORAW") == "" {
state, err := term.SetRawTerminal(cli.inFd)
if err != nil {
return err
}
cli.state = state
}
return nil
}
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
if cli.state != nil {
term.RestoreTerminal(cli.inFd, cli.state)
}
// WARNING: DO NOT REMOVE THE OS CHECK !!!
// For some reason this Close call blocks on darwin..
// As the client exists right after, simply discard the close
// until we find a better solution.
if in != nil && runtime.GOOS != "darwin" {
return in.Close()
}
return nil
}
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
// is set the client scheme will be set to https.
// The client will be given a 32-second timeout (see https://github.com/hyperhq/hypercli/pull/8035).
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
}
cli.init = func() error {
clientFlags.PostParse()
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
host, dft, err := cli.getServerHost(clientFlags.Common.Region, clientFlags.Common.TLSOptions)
if err != nil {
return err
}
customHeaders := cli.configFile.HTTPHeaders
if customHeaders == nil {
customHeaders = map[string]string{}
}
customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
verStr := api.DefaultVersion.String()
if tmpStr := os.Getenv("HYPER_API_VERSION"); tmpStr != "" {
verStr = tmpStr
}
httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
if err != nil {
return err
}
var cloudConfig cliconfig.CloudConfig
cc, ok := configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if ok && dft {
cloudConfig.AccessKey = cc.AccessKey
cloudConfig.SecretKey = cc.SecretKey
} else {
cc, ok = configFile.CloudConfig[host]
if ok {
cloudConfig.AccessKey = cc.AccessKey
cloudConfig.SecretKey = cc.SecretKey
} else {
cloudConfig.AccessKey = os.Getenv("HYPER_ACCESS")
cloudConfig.SecretKey = os.Getenv("HYPER_SECRET")
}
}
if cloudConfig.AccessKey == "" || cloudConfig.SecretKey == "" {
fmt.Fprintf(cli.err, "WARNING: null cloud config\n")
}
cli.region = clientFlags.Common.Region
if cli.region == "" {
if cli.region = cc.Region; cli.region == "" {
cli.region = cli.getDefaultRegion()
}
}
if !dft {
if cli.region = cc.Region; cli.region == "" {
cli.region = cliconfig.DefaultHyperRegion
}
}
client, err := client.NewClient(host, verStr, httpClient, customHeaders, cloudConfig.AccessKey, cloudConfig.SecretKey, cli.region)
if err != nil {
return err
}
cli.client = client
cli.host = host
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
return nil
}
return cli
}
func (cli *DockerCli) getServerHost(region string, tlsOptions *tlsconfig.Options) (host string, dft bool, err error) {
dft = false
host = region
if host == "" {
host = os.Getenv("HYPER_DEFAULT_REGION")
region = cli.getDefaultRegion()
}
if _, err := url.ParseRequestURI(host); err != nil {
host = "tcp://" + region + "." + cliconfig.DefaultHyperEndpoint
dft = true
}
host, err = opts.ParseHost(tlsOptions != nil, host)
return
}
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
if tlsOptions == nil {
// let the api client configure the default transport.
return nil, nil
}
config, err := tlsconfig.Client(*tlsOptions)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: config,
}
proto, addr, _, err := client.ParseHost(host)
if err != nil {
return nil, err
}
sockets.ConfigureTransport(tr, proto, addr)
return &http.Client{
Transport: tr,
}, nil
}

View File

@@ -1,5 +0,0 @@
// Package client provides a command-line interface for Docker.
//
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
// See https://docs.docker.com/installation/ for instructions on installing Docker.
package client

View File

@@ -1,61 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/container"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
)
// CmdCommit creates a new image from a container's changes.
//
// Usage: hyper commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, Cli.DockerCommands["commit"].Description, true)
flPause := cmd.Bool([]string{}, true, "Pause container during commit")
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
flAuthor := cmd.String([]string{"a", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
flConfig := cmd.String([]string{}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
cmd.Require(flag.Max, 2)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
name = cmd.Arg(0)
reference = cmd.Arg(1)
)
var config *container.Config
if *flConfig != "" {
config = &container.Config{}
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
return err
}
}
options := types.ContainerCommitOptions{
Reference: reference,
Comment: *flComment,
Author: *flAuthor,
Changes: flChanges.GetAll(),
Pause: *flPause,
Config: config,
}
response, err := cli.client.ContainerCommit(context.Background(), name, options)
if err != nil {
return err
}
fmt.Fprintln(cli.out, response.ID)
return nil
}

View File

@@ -1,519 +0,0 @@
package client
import (
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/libcompose/docker"
"github.com/hyperhq/libcompose/logger"
"github.com/hyperhq/libcompose/project"
"github.com/hyperhq/libcompose/project/options"
"golang.org/x/net/context"
)
const ComposeFipAuto = "auto"
// CmdCompose is the parent subcommand for all compose commands
//
// Usage: hyper compose <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdCompose(args ...string) error {
cmd := Cli.Subcmd("compose", []string{"<COMMAND>"}, composeUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdComposeRun
//
// Usage: hyper compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
func (cli *DockerCli) CmdComposeRun(args ...string) error {
cmd := Cli.Subcmd("compose run", []string{"SERVICE [COMMAND] [ARGS...]"}, "Run a one-off command on a service", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
rm := cmd.Bool([]string{"-rm"}, false, "Remove container after run, ignored in detached mode")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
Autoremove: *rm,
},
ClientFactory: cli,
})
if err != nil {
return err
}
service := cmd.Args()[0]
status, err := project.Run(context.Background(), service, cmd.Args()[1:])
if err != nil {
return err
}
if *rm {
opts := options.Delete{RemoveVolume: true}
if err = project.Delete(opts, service); err != nil {
return err
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdComposeDown
//
// Usage: hyper compose down [OPTIONS]
func (cli *DockerCli) CmdComposeDown(args ...string) error {
cmd := Cli.Subcmd("compose down", []string{}, "Stop and remove containers, images, and volumes\ncreated by `up`. Only containers and networks are removed by default.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
rmi := cmd.String([]string{"-rmi"}, "", "Remove images, type may be one of: 'all' to remove\nall images, or 'local' to remove only images that\ndon't have an custom name set by the `image` field")
vol := cmd.Bool([]string{"v", "-volumes"}, false, "Remove data volumes")
rmorphans := cmd.Bool([]string{"-remove-orphans"}, false, "Remove containers for services not defined in the Compose file")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
imageType := options.ImageType(*rmi)
if !imageType.Valid() {
return fmt.Errorf("rmi with %s is not valid", *rmi)
}
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeDown(*projectName, cmd.Args(), *rmi, *vol, *rmorphans)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeUp
//
// Usage: hyper compose up [OPTIONS]
func (cli *DockerCli) CmdComposeUp(args ...string) error {
cmd := Cli.Subcmd("compose up", []string{"[SERVICE...]"}, "Builds, (re)creates, starts, and attaches to containers for a service.\n\nUnless they are already running, this command also starts any linked services.\n\n"+
"The `hyper compose up` command aggregates the output of each container. When\n"+
"the command exits, all containers are stopped. Running `hyper compose up -d`\n"+
"starts the containers in the background and leaves them running.\n\n"+
"If there are existing containers for a service, and the service's configuration\n"+
"or image was changed after the container's creation, `hyper compose up` picks\n"+
"up the changes by stopping and recreating the containers (preserving mounted\n"+
"volumes). To prevent Compose from picking up changes, use the `--no-recreate`\n"+
"flag.\n\n"+
"If you want to force Compose to stop and recreate all containers, use the\n"+
"`--force-recreate` flag.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
detach := cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run containers in the background,\nprint new container names.\nIncompatible with --abort-on-container-exit.")
forcerecreate := cmd.Bool([]string{"-force-recreate"}, false, "Recreate containers even if their configuration\nand image haven't changed.\nIncompatible with --no-recreate.")
norecreate := cmd.Bool([]string{"-no-recreate"}, false, "If containers already exist, don't recreate them.\nIncompatible with --force-recreate.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
LoggerFactory: logger.NewColorLoggerFactory(),
},
ClientFactory: cli,
})
if err != nil {
return err
}
services := cmd.Args()
c, vc, nc := project.GetConfig()
if *projectName == "" {
*projectName = getBaseDir()
}
var fips = []string{}
var newFipNum = 0
for _, svconfig := range c.M {
if svconfig.Fip == ComposeFipAuto {
newFipNum++
}
}
fipFilterArgs, _ := filters.FromParam("dangling=true")
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fipList, err := cli.client.FipList(context.Background(), options)
if err == nil {
for _, fip := range fipList {
if fip["container"] == "" && fip["service"] == "" {
fips = append(fips, fip["fip"])
}
}
}
if newFipNum > len(fips) {
if askForConfirmation(warnMessage) == true {
newFips, err := cli.client.FipAllocate(context.Background(), fmt.Sprintf("%d", newFipNum-len(fips)))
if err != nil {
return err
}
fips = append(fips, newFips...)
}
}
i := 0
for _, svconfig := range c.M {
if svconfig.Fip == ComposeFipAuto {
if i >= newFipNum {
svconfig.Fip = ""
} else {
svconfig.Fip = fips[i]
i++
}
}
}
body, err := cli.client.ComposeUp(*projectName, services, c, vc, nc, cli.configFile.AuthConfigs, *forcerecreate, *norecreate)
if err != nil {
return err
}
defer body.Close()
err = jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
if err != nil {
return err
}
if !*detach {
signalChan := make(chan os.Signal, 1)
cleanupDone := make(chan bool)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
errChan := make(chan error)
go func() {
errChan <- project.Log(true, services...)
}()
go func() {
select {
case <-signalChan:
fmt.Printf("\nGracefully stopping...\n")
project.Stop(0, services...)
cleanupDone <- true
case err := <-errChan:
if err != nil {
logrus.Fatal(err)
}
cleanupDone <- true
}
}()
<-cleanupDone
return nil
}
return nil
}
// CmdComposeStart
//
// Usage: hyper compose start [OPTIONS] [SERVICE]
func (cli *DockerCli) CmdComposeStart(args ...string) error {
cmd := Cli.Subcmd("compose start", []string{"[SERVICE...]"}, "Start existing containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeStart(*projectName, services)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeStop
//
// Usage: hyper compose stop [OPTIONS]
func (cli *DockerCli) CmdComposeStop(args ...string) error {
cmd := Cli.Subcmd("compose stop", []string{"[SERVICE...]"}, "Stop running containers without removing them.\n\nThey can be started again with `hyper compose start`.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
nSeconds := cmd.Int([]string{"t", "-timeout"}, 10, "Specify a shutdown timeout in seconds.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeStop(*projectName, services, *nSeconds)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeCreate
//
// Usage: hyper compose create [OPTIONS]
func (cli *DockerCli) CmdComposeCreate(args ...string) error {
cmd := Cli.Subcmd("compose create", []string{"[SERVICE...]"}, "Creates containers for a service.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
forcerecreate := cmd.Bool([]string{"-force-recreate"}, false, "Recreate containers even if their configuration\nand image haven't changed.\nIncompatible with --no-recreate.")
norecreate := cmd.Bool([]string{"-no-recreate"}, false, "If containers already exist, don't recreate them.\nIncompatible with --force-recreate.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
LoggerFactory: logger.NewColorLoggerFactory(),
},
ClientFactory: cli,
})
if err != nil {
return err
}
services := cmd.Args()
c, vc, nc := project.GetConfig()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeCreate(*projectName, services, c, vc, nc, cli.configFile.AuthConfigs, *forcerecreate, *norecreate)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposePs
//
// Usage: hyper compose ps [OPTIONS]
func (cli *DockerCli) CmdComposePs(args ...string) error {
cmd := Cli.Subcmd("compose ps", []string{"[SERVICE...]"}, "List containers.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display IDs")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
},
ClientFactory: cli,
})
if err != nil {
return err
}
ps, err := project.Ps(*quiet, cmd.Args()...)
if err != nil {
return err
}
fmt.Printf(ps.String(true))
return nil
}
// CmdComposeKill
//
// Usage: hyper compose kill [OPTIONS]
func (cli *DockerCli) CmdComposeKill(args ...string) error {
cmd := Cli.Subcmd("compose kill", []string{"[SERVICE...]"}, "Force stop service containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
signal := cmd.String([]string{}, "KILL", "Signal to send to the container")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeKill(*projectName, services, *signal)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeRm
//
// Usage: hyper compose rm [OPTIONS] [SERVICE]
func (cli *DockerCli) CmdComposeRm(args ...string) error {
cmd := Cli.Subcmd("compose rm", []string{"[SERVICE...]"}, "Remove stopped service containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
removeVol := cmd.Bool([]string{"v"}, false, "Remove volumes associated with containers")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeRm(*projectName, services, *removeVol)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeScale
//
// Usage: hyper compose scale [OPTIONS] [SERVICE=NUM...]
func (cli *DockerCli) CmdComposeScale(args ...string) error {
cmd := Cli.Subcmd("compose scale", []string{"[SERVICE=NUM...]"}, "Set number of containers to run for a service.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
timeout := cmd.Int([]string{"t", "-timeout"}, 10, "Specify a shutdown timeout in seconds")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
},
ClientFactory: cli,
})
if err != nil {
return err
}
servicesScale := map[string]int{}
for _, ss := range cmd.Args() {
fields := strings.SplitN(ss, "=", 2)
if len(fields) != 2 {
continue
}
num, err := strconv.Atoi(fields[1])
if err != nil {
return err
}
servicesScale[fields[0]] = num
}
err = project.Scale(*timeout, servicesScale)
if err != nil {
return err
}
return nil
}
// CmdComposePull
//
// Usage: hyper compose pull [OPTIONS]
func (cli *DockerCli) CmdComposePull(args ...string) error {
cmd := Cli.Subcmd("compose pull", []string{"[SERVICE...]"}, "Pull images of services.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
},
ClientFactory: cli,
})
if err != nil {
return err
}
err = project.Pull(cmd.Args()...)
if err != nil {
return err
}
return nil
}
func composeUsage() string {
composeCommands := [][]string{
{"create", "Creates containers for a service"},
{"down", "Stop and remove containers, images, and volumes"},
{"kill", "Force stop service containers"},
{"ps", "List containers"},
{"pull", "Pull images of services"},
{"rm", "Remove stopped service containers"},
{"run", "Run a one-off command"},
{"scale", "Set number of containers for a service"},
{"start", "Start services"},
{"stop", "Stop services"},
{"up", "Create and start containers"},
}
help := "Commands:\n"
for _, cmd := range composeCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper compose COMMAND --help' for more information on a command.")
return help
}
func (cli *DockerCli) Create(s project.Service) client.APIClient {
return cli.client
}
func getBaseDir() string {
file, err := os.Getwd()
if err != nil {
return ""
}
return filepath.Base(file)
}

View File

@@ -1,123 +0,0 @@
package client
import (
"fmt"
"os"
"runtime"
"strings"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cliconfig"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdConfig
//
// Usage: hyper config
func (cli *DockerCli) CmdConfig(args ...string) error {
cmd := Cli.Subcmd("config", []string{"[REGION]"}, Cli.DockerCommands["config"].Description+".\nIf no region is specified, the default is defined as "+cliconfig.DefaultHyperRegion, true)
cmd.Require(flag.Max, 1)
flAccesskey := cmd.String([]string{"-accesskey"}, "", "Access Key")
flSecretkey := cmd.String([]string{"-secretkey"}, "", "Secret Key")
flDefaultRegion := cmd.String([]string{"-default-region"}, "", "Default Region Endpoint")
cmd.ParseFlags(args, true)
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" {
cli.in = os.Stdin
}
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cliconfig.DefaultHyperFormat
}
_, err := cli.configureCloud(serverAddress, *flDefaultRegion, *flAccesskey, *flSecretkey)
if err != nil {
return err
}
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Error saving config file: %v", err)
}
fmt.Fprintf(cli.out, "WARNING: Your login credentials has been saved in %s\n", cli.configFile.Filename())
return nil
}
func (cli *DockerCli) configureCloud(serverAddress, flRegion, flAccesskey, flSecretkey string) (cliconfig.CloudConfig, error) {
cloudConfig := cliconfig.CloudConfig{}
if serverAddress != "" {
if cc, ok := cli.configFile.CloudConfig[serverAddress]; ok {
cloudConfig = cc
} else {
// for legacy format
defaultHost := "tcp://" + cliconfig.DefaultHyperRegion + "." + cliconfig.DefaultHyperEndpoint
cloudConfig, ok = cli.configFile.CloudConfig[defaultHost]
if ok {
delete(cli.configFile.CloudConfig, defaultHost)
}
}
}
defaultRegion := cli.getDefaultRegion()
if cloudConfig.Region != "" {
defaultRegion = cloudConfig.Region
}
if flAccesskey = strings.TrimSpace(flAccesskey); flAccesskey == "" {
cli.promptWithDefault("Enter Access Key", cloudConfig.AccessKey)
flAccesskey = readInput(cli.in, cli.out)
flAccesskey = strings.TrimSpace(flAccesskey)
if flAccesskey == "" {
flAccesskey = cloudConfig.AccessKey
}
}
if flSecretkey = strings.TrimSpace(flSecretkey); flSecretkey == "" {
cli.promptWithDefault("Enter Secret Key", cloudConfig.SecretKey)
flSecretkey = readInput(cli.in, cli.out)
flSecretkey = strings.TrimSpace(flSecretkey)
if flSecretkey == "" {
flSecretkey = cloudConfig.SecretKey
}
}
if flRegion = strings.TrimSpace(flRegion); flRegion == "" {
cli.promptWithDefault("Enter Default Region", defaultRegion)
flRegion = readInput(cli.in, cli.out)
flRegion = strings.TrimSpace(flRegion)
if flRegion == "" {
flRegion = defaultRegion
}
}
cloudConfig.AccessKey = flAccesskey
cloudConfig.SecretKey = flSecretkey
cloudConfig.Region = flRegion
if serverAddress != "" {
cli.configFile.CloudConfig[serverAddress] = cloudConfig
}
return cloudConfig, nil
}
func (cli *DockerCli) checkCloudConfig() error {
_, ok := cli.configFile.CloudConfig[cli.host]
if !ok {
_, ok = cli.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if !ok {
return fmt.Errorf("Config info for the host is not found, please run 'hyper config %s' first.", cli.host)
}
}
return nil
}
func (cli *DockerCli) getDefaultRegion() string {
cc, ok := cli.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if ok && cc.Region != "" {
return cc.Region
}
return cliconfig.DefaultHyperRegion
}

View File

@@ -1,297 +0,0 @@
package client
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/archive"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/system"
)
type copyDirection int
const (
fromContainer copyDirection = (1 << iota)
toContainer
acrossContainers = fromContainer | toContainer
)
type cpConfig struct {
followLink bool
}
// CmdCp copies files/folders to or from a path in a container.
//
// When copying from a container, if DEST_PATH is '-' the data is written as a
// tar archive file to STDOUT.
//
// When copying to a container, if SRC_PATH is '-' the data is read as a tar
// archive file from STDIN, and the destination CONTAINER:DEST_PATH, must specify
// a directory.
//
// Usage:
// docker cp CONTAINER:SRC_PATH DEST_PATH|-
// docker cp SRC_PATH|- CONTAINER:DEST_PATH
func (cli *DockerCli) Cp(args ...string) error {
cmd := Cli.Subcmd(
"cp",
[]string{"CONTAINER:SRC_PATH DEST_PATH|-", "SRC_PATH|- CONTAINER:DEST_PATH"},
strings.Join([]string{
Cli.DockerCommands["cp"].Description,
"\nUse '-' as the source to read a tar archive from stdin\n",
"and extract it to a directory destination in a container.\n",
"Use '-' as the destination to stream a tar archive of a\n",
"container source to stdout.",
}, ""),
true,
)
followLink := cmd.Bool([]string{"L", "-follow-link"}, false, "Always follow symbol link in SRC_PATH")
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("source can not be empty")
}
if cmd.Arg(1) == "" {
return fmt.Errorf("destination can not be empty")
}
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
var direction copyDirection
if srcContainer != "" {
direction |= fromContainer
}
if dstContainer != "" {
direction |= toContainer
}
cpParam := &cpConfig{
followLink: *followLink,
}
ctx := context.Background()
switch direction {
case fromContainer:
return cli.copyFromContainer(ctx, srcContainer, srcPath, dstPath, cpParam)
case toContainer:
return cli.copyToContainer(ctx, srcPath, dstContainer, dstPath, cpParam)
case acrossContainers:
// Copying between containers isn't supported.
return fmt.Errorf("copying between containers is not supported")
default:
// User didn't specify any container.
return fmt.Errorf("must specify at least one container source")
}
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
// absolute path:
// `/path/to/file:name.txt` or `./file:name.txt`
//
// This is apparently how `scp` handles this as well:
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
//
// We can't simply check for a filepath separator because container names may
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
// client, a `:` could be part of an absolute Windows path, in which case it
// is immediately proceeded by a backslash.
func splitCpArg(arg string) (container, path string) {
if system.IsAbs(arg) {
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
return "", arg
}
parts := strings.SplitN(arg, ":", 2)
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
// Either there's no `:` in the arg
// OR it's an explicit local relative path like `./file:name.txt`.
return "", arg
}
return parts[0], parts[1]
}
func (cli *DockerCli) statContainerPath(ctx context.Context, containerName, path string) (types.ContainerPathStat, error) {
return cli.client.ContainerStatPath(ctx, containerName, path)
}
func resolveLocalPath(localPath string) (absPath string, err error) {
if absPath, err = filepath.Abs(localPath); err != nil {
return
}
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
}
func (cli *DockerCli) copyFromContainer(ctx context.Context, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
if dstPath != "-" {
// Get an absolute destination path.
dstPath, err = resolveLocalPath(dstPath)
if err != nil {
return err
}
}
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if cpParam.followLink {
srcStat, err := cli.statContainerPath(ctx, srcContainer, srcPath)
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget
if !system.IsAbs(linkTarget) {
// Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath)
linkTarget = filepath.Join(srcParent, linkTarget)
}
linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
srcPath = linkTarget
}
}
content, stat, err := cli.client.CopyFromContainer(ctx, srcContainer, srcPath)
if err != nil {
return err
}
defer content.Close()
if dstPath == "-" {
// Send the response to STDOUT.
_, err = io.Copy(os.Stdout, content)
return err
}
// Prepare source copy info.
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
RebaseName: rebaseName,
}
preArchive := content
if len(srcInfo.RebaseName) != 0 {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
}
// See comments in the implementation of `archive.CopyTo` for exactly what
// goes into deciding how and whether the source archive needs to be
// altered for the correct copy behavior.
return archive.CopyTo(preArchive, srcInfo, dstPath)
}
func (cli *DockerCli) copyToContainer(ctx context.Context, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
if srcPath != "-" {
// Get an absolute source path.
srcPath, err = resolveLocalPath(srcPath)
if err != nil {
return err
}
}
// In order to get the copy behavior right, we need to know information
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := cli.statContainerPath(ctx, dstContainer, dstPath)
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if !system.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
}
dstInfo.Path = linkTarget
dstStat, err = cli.statContainerPath(ctx, dstContainer, linkTarget)
}
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versa) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
if err == nil {
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
}
var (
content io.Reader
resolvedDstPath string
)
if srcPath == "-" {
// Use STDIN.
content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
}
} else {
// Prepare source copy info.
srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
if err != nil {
return err
}
srcArchive, err := archive.TarResource(srcInfo)
if err != nil {
return err
}
defer srcArchive.Close()
// With the stat info about the local source as well as the
// destination, we have enough information to know whether we need to
// alter the archive that we upload so that when the server extracts
// it to the specified directory in the container we get the desired
// copy behavior.
// See comments in the implementation of `archive.PrepareArchiveCopy`
// for exactly what goes into deciding how and whether the source
// archive needs to be altered for the correct copy behavior when it is
// extracted. This function also infers from the source and destination
// info which directory to extract to, which may be the parent of the
// destination that the user specified.
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
if err != nil {
return err
}
defer preparedArchive.Close()
resolvedDstPath = dstDir
content = preparedArchive
}
options := types.CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
}
return cli.client.CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
}

View File

@@ -1,233 +0,0 @@
package client
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/container"
networktypes "github.com/hyperhq/hyper-api/types/network"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
)
func (cli *DockerCli) pullImage(ctx context.Context, image string) error {
return cli.pullImageCustomOut(ctx, image, cli.out)
}
func (cli *DockerCli) pullImageCustomOut(ctx context.Context, image string, out io.Writer) error {
ref, err := reference.ParseNamed(image)
if err != nil {
return err
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImageCreateOptions{
RegistryAuth: encodedAuth,
}
responseBody, err := cli.client.ImageCreate(ctx, image, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, out, cli.outFd, cli.isTerminalOut, nil)
}
type cidFile struct {
path string
file *os.File
written bool
}
func newCIDFile(path string) (*cidFile, error) {
if _, err := os.Stat(path); err == nil {
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
}
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
}
return &cidFile{path: path, file: f}, nil
}
func parseProtoAndLocalBind(bind string) (string, string, bool) {
switch {
case strings.HasPrefix(bind, "git://"):
fallthrough
case strings.HasPrefix(bind, "http://"):
fallthrough
case strings.HasPrefix(bind, "https://"):
if strings.Count(bind, ":") < 2 {
return "", "", false
}
case strings.HasPrefix(bind, "/"):
if strings.Count(bind, ":") < 1 {
return "", "", false
}
case filepath.VolumeName(bind) != "":
// Windows local path
default:
return "", "", false
}
pos := strings.LastIndex(bind, ":")
if pos < 0 || pos >= len(bind)-1 {
return "", "", false
}
return bind[:pos], bind[pos+1:], true
}
func (cli *DockerCli) createContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
var containerIDFile *cidFile
var initvols, volumeList []string
if cidfile != "" {
var err error
if containerIDFile, err = newCIDFile(cidfile); err != nil {
return nil, err
}
defer containerIDFile.Close()
}
// Check/create protocol and local volume
defer func() {
for _, vol := range volumeList {
cli.client.VolumeRemove(ctx, vol)
}
}()
for idx, bind := range hostConfig.Binds {
if source, dest, ok := parseProtoAndLocalBind(bind); ok {
volReq := types.VolumeCreateRequest{
Driver: "hyper",
Labels: map[string]string{
"autoremove": "true",
}}
if vol, err := cli.client.VolumeCreate(ctx, volReq); err != nil {
return nil, err
} else {
initvols = append(initvols, source+":"+vol.Name)
volumeList = append(volumeList, vol.Name)
hostConfig.Binds[idx] = vol.Name + ":" + dest
}
}
}
// initialize special volumes
if len(initvols) > 0 {
err := cli.initVolumes(initvols, false)
if err != nil {
return nil, err
}
}
ref, err := reference.ParseNamed(config.Image)
if err != nil {
return nil, err
}
ref = reference.WithDefaultTag(ref)
var trustedRef reference.Canonical
if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
var err error
trustedRef, err = cli.trustedReference(ctx, ref)
if err != nil {
return nil, err
}
config.Image = trustedRef.String()
}
//create the container
response, err := cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
//if image not found try to pull it
if err != nil {
if client.IsErrImageNotFound(err) {
fmt.Fprintf(cli.err, "Unable to find image '%s' in the current region\n", ref.String())
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(ctx, config.Image, cli.err); err != nil {
return nil, err
}
if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
if err := cli.tagTrusted(ctx, trustedRef, ref); err != nil {
return nil, err
}
}
// Retry
var retryErr error
response, retryErr = cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
if retryErr != nil {
return nil, retryErr
}
} else {
return nil, err
}
}
volumeList = nil
for _, warning := range response.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if containerIDFile != nil {
if err = containerIDFile.Write(response.ID); err != nil {
return nil, err
}
}
return &response, nil
}
// CmdCreate creates a new container from a given image.
//
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdCreate(args ...string) error {
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(1)
}
if config.Image == "" {
cmd.Usage()
return nil
}
response, err := cli.createContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", response.ID)
return nil
}

View File

@@ -1,398 +0,0 @@
package client
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/docker/go-connections/nat"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/container"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hyper-api/types/network"
"github.com/hyperhq/hyper-api/types/strslice"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdCron is the parent subcommand for all cron commands
//
// Usage: hyper cron <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdCron(args ...string) error {
cmd := Cli.Subcmd("cron", []string{"COMMAND [OPTIONS]"}, cronUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdCronCreate creates a new cron with a given name
//
// Usage: hyper cron create [OPTIONS]
func (cli *DockerCli) CmdCronCreate(args ...string) error {
cmd := Cli.Subcmd("cron create", []string{"IMAGE"}, "Create a cron job", false)
var (
flSecurityGroups = ropts.NewListOpts(nil)
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flVolumes = ropts.NewListOpts(nil)
flLinks = ropts.NewListOpts(opts.ValidateLink)
flLabelsFile = ropts.NewListOpts(nil)
flPublish = ropts.NewListOpts(nil)
flExpose = ropts.NewListOpts(nil)
flName = cmd.String([]string{"-name"}, "", "Cron name")
flContainerName = cmd.String([]string{"-container-name"}, "", "Cron container name")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of cron containers (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
flNoAutoVolume = cmd.Bool([]string{"-noauto-volume"}, false, "Do not create volumes specified in image")
flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
flMailTo = cmd.String([]string{"-mailto"}, "", "Mail to while the cron has something")
flMailPolicy = cmd.String([]string{"-mail"}, "on-failure", "Mail policy to apply when to send email")
flAccessKey = cmd.String([]string{"-access-key"}, "", "Access key to run the cron job")
flSecretKey = cmd.String([]string{"-secret-key"}, "", "Secret key to run the cron job")
flMinute = cmd.String([]string{"-minute"}, "0", "The minutes of cron expression")
flHour = cmd.String([]string{"-hour"}, "0", "The hour of cron expression")
flDom = cmd.String([]string{"-dom"}, "*", "The day of month of cron expression")
flDow = cmd.String([]string{"-week"}, "*", "The day of week of cron expression")
flMonth = cmd.String([]string{"-month"}, "*", "The month of cron expression")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flVolumes, []string{"v", "--volume"}, "Volume for each container")
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if (*flAccessKey == "" && *flSecretKey != "") || (*flAccessKey != "" && *flSecretKey == "") {
return fmt.Errorf("You must specify access key and secret key at the same time")
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
if _, _, err = cli.client.ImageInspectWithRaw(context.Background(), image, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(context.Background(), image); err != nil {
return err
}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
labels = append(labels, fmt.Sprintf("sh_hyper_instancetype=%s", *flContainerSize))
for _, sg := range flSecurityGroups.GetAll() {
if sg == "" {
continue
}
labels = append(labels, fmt.Sprintf("sh_hyper_sg_%s=yes", sg))
}
if *flNoAutoVolume {
labels = append(labels, "sh_hyper_noauto_volume=true")
}
var (
domainname string
hostname = *flHostname
parts = strings.SplitN(hostname, ".", 2)
)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil {
return err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") {
return fmt.Errorf("Invalid port format for --expose: %s", e)
}
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes.GetMap() {
if arr := opts.VolumeSplitN(bind, 2); len(arr) > 1 {
// after creating the bind mount we want to delete it from the flVolumes values because
// we do not want bind mounts being committed to image configs
binds = append(binds, bind)
flVolumes.Delete(bind)
}
}
restartPolicy, err := opts.ParseRestartPolicy(*flRestartPolicy)
if err != nil {
return err
}
config := &container.Config{
Hostname: hostname,
Domainname: domainname,
Tty: true,
ExposedPorts: ports,
Env: envVariables,
Cmd: runCmd,
Image: image,
Volumes: flVolumes.GetMap(),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Labels: opts.ConvertKVStringsToMap(labels),
StopSignal: *flStopSignal,
}
hostConfig := &container.HostConfig{
Binds: binds,
PortBindings: portBindings,
Links: flLinks.GetAll(),
PublishAllPorts: *flPublishAll,
NetworkMode: container.NetworkMode(*flNetMode),
RestartPolicy: restartPolicy,
}
networkingConfig := &network.NetworkingConfig{
EndpointsConfig: make(map[string]*network.EndpointSettings),
}
if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
if epConfig == nil {
epConfig = &network.EndpointSettings{}
}
epConfig.Links = make([]string, len(hostConfig.Links))
copy(epConfig.Links, hostConfig.Links)
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
}
if *flMinute == "0" && *flHour == "0" && *flDom == "*" && *flDow == "*" && *flMonth == "*" {
return fmt.Errorf("must specify at least one schedule")
}
sv := types.Cron{
ContainerName: *flContainerName,
Schedule: *flMinute + " " + *flHour + " " + *flDom + " " + *flMonth + " " + *flDow,
OwnerEmail: *flMailTo,
Config: config,
HostConfig: hostConfig,
NetConfig: networkingConfig,
AccessKey: *flAccessKey,
SecretKey: *flSecretKey,
MailPolicy: *flMailPolicy,
}
_, err = cli.client.CronCreate(context.Background(), *flName, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Cron %s is created.\n", *flName)
return nil
}
// CmdCronDelete deletes one or more crons
//
// Usage: hyper cron rm cron [cron...]
func (cli *DockerCli) CmdCronRm(args ...string) error {
cmd := Cli.Subcmd("cron rm", []string{"cron [cron...]"}, "Remove one or more cron job", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, sn := range cmd.Args() {
if err := cli.client.CronDelete(context.Background(), sn); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", sn)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdCronLs lists all the crons
//
// Usage: hyper cron ls [OPTIONS]
func (cli *DockerCli) CmdCronLs(args ...string) error {
cmd := Cli.Subcmd("cron ls", nil, "Lists all crons", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
cronFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if cronFilterArgs, err = filters.ParseFlag(f, cronFilterArgs); err != nil {
return err
}
}
options := types.CronListOptions{
Filters: cronFilterArgs,
}
crons, err := cli.client.CronList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tSchedule\tImage\tCommand\n")
for _, cron := range crons {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cron.Name, cron.Schedule, cron.Config.Image, strings.Join([]string(cron.Config.Cmd), " "))
}
w.Flush()
return nil
}
// CmdCronInspect
//
// Usage: hyper cron inspect [OPTIONS] CRON [CRON...]
func (cli *DockerCli) CmdCronInspect(args ...string) error {
cmd := Cli.Subcmd("cron inspect", []string{"cron [cron...]"}, "Display detailed information on the given cron", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return err
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.CronInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdCronHistory
//
// Usage: hyper cron history [OPTIONS] CRON
func (cli *DockerCli) CmdCronHistory(args ...string) error {
cmd := Cli.Subcmd("cron history", []string{"cron"}, "Show the execution history (last 100) of a cron job", true)
flSince := cmd.String([]string{"-since"}, "", "Show history since timestamp")
flTail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the history")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return err
}
ctx := context.Background()
name := cmd.Args()[0]
cronHistory, err := cli.client.CronHistory(ctx, name, *flSince, *flTail)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Container\tStart\tEnd\tStatus\tMessage\n")
for _, h := range cronHistory {
status := h.Status
if status == "success" {
status = "done"
} else if status == "error" {
status = "failed"
} else {
status = "-"
}
if h.FinishedAt == 0 {
fmt.Fprintf(w, "%s\t%v\t-\t%s\t%s\n", h.Container, time.Unix(h.StartedAt, 0).UTC(), status, h.Message)
} else {
fmt.Fprintf(w, "%s\t%v\t%v\t%s\t%s\n", h.Container, time.Unix(h.StartedAt, 0).UTC(), time.Unix(h.FinishedAt, 0).UTC(), status, h.Message)
}
}
w.Flush()
return nil
}
func cronUsage() string {
cronCommands := [][]string{
{"create", "Create a cron job"},
{"inspect", "Display detailed information on the given cron"},
{"ls", "List all crons"},
{"history", "Show execution history of a cron job"},
{"rm", "Remove one or more cron job"},
}
help := "Commands:\n"
for _, cmd := range cronCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper cron COMMAND --help' for more information on a command.")
return help
}

View File

@@ -1,49 +0,0 @@
package client
import (
"fmt"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/archive"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdDiff shows changes on a container's filesystem.
//
// Each changed file is printed on a separate line, prefixed with a single
// character that indicates the status of the file: C (modified), A (added),
// or D (deleted).
//
// Usage: docker diff CONTAINER
func (cli *DockerCli) Diff(args ...string) error {
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, Cli.DockerCommands["diff"].Description, true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("Container name cannot be empty")
}
changes, err := cli.client.ContainerDiff(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
for _, change := range changes {
var kind string
switch change.Kind {
case archive.ChangeModify:
kind = "C"
case archive.ChangeAdd:
kind = "A"
case archive.ChangeDelete:
kind = "D"
}
fmt.Fprintf(cli.out, "%s %s\n", kind, change.Path)
}
return nil
}

View File

@@ -1,102 +0,0 @@
package client
import (
"crypto/tls"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"github.com/gorilla/websocket"
"github.com/hyperhq/hyper-api/types/events"
signutil "github.com/hyperhq/websocket-client/go/util"
"golang.org/x/net/context"
)
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an io.EOF error will
// be sent over the error channel. If an error is sent all processing will be stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *DockerCli) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
messages := make(chan events.Message)
errs := make(chan error, 1)
go func() {
hostUrl, err := url.Parse(cli.host)
if err != nil {
errs <- err
return
}
cloudConfig, existed := cli.configFile.CloudConfig[cli.host]
if !existed {
errs <- errors.New("Please specify 'accessKey' and 'secretKey'!")
return
}
// TODO: add filter when we have other type event.
var u = url.URL{Scheme: "wss", Host: hostUrl.Host, Path: "/events/ws"}
// add sign to header
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
errs <- err
return
}
req.URL = &u
req = signutil.Sign4(cloudConfig.AccessKey, cloudConfig.SecretKey, req)
// connect to websocket server
config := &tls.Config{
InsecureSkipVerify: true,
}
dialer := websocket.Dialer{
TLSClientConfig: config,
}
ws, resp, err := dialer.Dial(u.String(), req.Header)
if err != nil {
errs <- err
return
}
defer ws.Close()
if resp.ContentLength > 0 {
defer resp.Body.Close()
ioutil.ReadAll(resp.Body)
}
// process websocket message
for {
_, message, err := ws.ReadMessage()
if err != nil {
errs <- err
return
}
select {
case <-ctx.Done():
errs <- ctx.Err()
return
default:
var event events.Message
err := json.Unmarshal([]byte(message), &event)
if err != nil {
errs <- err
return
}
select {
case messages <- event:
case <-ctx.Done():
errs <- ctx.Err()
return
}
}
}
}()
return messages, errs
}

View File

@@ -1,209 +0,0 @@
package client
import (
"fmt"
"io"
"strings"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/promise"
)
// CmdExec runs a command in a running container.
//
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
func (cli *DockerCli) CmdExec(args ...string) error {
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
execConfig, err := ParseExec(cmd, args)
container := cmd.Arg(0)
// just in case the ParseExec does not exit
if container == "" || err != nil {
return Cli.StatusError{StatusCode: 1}
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
// Send client escape keys
execConfig.DetachKeys = cli.configFile.DetachKeys
ctx := context.Background()
response, err := cli.client.ContainerExecCreate(ctx, container, *execConfig)
if err != nil {
return err
}
execID := response.ID
if execID == "" {
fmt.Fprintf(cli.out, "exec ID empty")
return nil
}
//Temp struct for execStart so that we don't need to transfer all the execConfig
if !execConfig.Detach {
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
return err
}
} else {
execStartCheck := types.ExecStartCheck{
Detach: execConfig.Detach,
Tty: execConfig.Tty,
}
if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
// fmt.Fprintf(cli.out, "%s\n", execID)
return nil
}
// Interactive exec requested.
var (
out, stderr io.Writer
in io.ReadCloser
errCh chan error
)
if execConfig.AttachStdin {
in = cli.in
}
if execConfig.AttachStdout {
out = cli.out
}
if execConfig.AttachStderr {
if execConfig.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
resp, err := cli.client.ContainerExecAttach(ctx, execID, *execConfig)
if err != nil {
return err
}
defer resp.Close()
if in != nil && execConfig.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
})
if execConfig.Tty && cli.isTerminalIn {
if err := cli.monitorTtySize(ctx, execID, true); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
var status int
if _, status, err = getExecExitCode(ctx, cli, execID); err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// ParseExec parses the specified args for the specified command and generates
// an ExecConfig from it.
// If the minimal number of specified args is not right or if specified args are
// not valid, it will return an error.
func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
var (
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
flUser = cmd.String([]string{}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
flPrivileged = cmd.Bool([]string{}, false, "Give extended privileges to the command")
execCmd []string
)
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return nil, err
}
parsedArgs := cmd.Args()
execCmd = parsedArgs[1:]
execConfig := &types.ExecConfig{
User: *flUser,
Privileged: *flPrivileged,
Tty: *flTty,
Cmd: execCmd,
Detach: *flDetach,
}
// If -d is not set, attach to everything by default
if !*flDetach {
execConfig.AttachStdout = true
execConfig.AttachStderr = true
if *flStdin {
execConfig.AttachStdin = true
}
}
return execConfig, nil
}
func (cli *DockerCli) ExecCmd(ctx context.Context, user, contID string, cmd []string) (string, error) {
execConfig := &types.ExecConfig{
User: user,
Detach: true,
Cmd: cmd,
}
execCreateResponse, err := cli.client.ContainerExecCreate(ctx, contID, *execConfig)
if err != nil {
return "", err
}
execID := execCreateResponse.ID
if execID == "" {
err = fmt.Errorf("Failed to exec %s: Empty exec ID", strings.Join(cmd, " "))
return "", err
}
execStartCheck := types.ExecStartCheck{Detach: execConfig.Detach}
if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return "", err
}
return execID, nil
}
func (cli *DockerCli) WaitExec(ctx context.Context, execID string) error {
for {
running, status, err := getExecExitCode(ctx, cli, execID)
switch {
case err != nil:
return err
case running:
time.Sleep(100 * time.Millisecond)
case status != 0:
err = fmt.Errorf("Failed to exec cmd: %d", status)
return err
case status == 0:
return nil
}
}
}

View File

@@ -1,130 +0,0 @@
package client
import (
"fmt"
"io/ioutil"
"testing"
"github.com/hyperhq/hyper-api/types"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
type arguments struct {
args []string
}
func TestParseExec(t *testing.T) {
invalids := map[*arguments]error{
{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
{[]string{"-u"}}: fmt.Errorf("flag needs an argument: -u"),
{[]string{"--user"}}: fmt.Errorf("flag needs an argument: --user"),
}
valids := map[*arguments]*types.ExecConfig{
{
[]string{"container", "command"},
}: {
Container: "container",
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
},
{
[]string{"container", "command1", "command2"},
}: {
Container: "container",
Cmd: []string{"command1", "command2"},
AttachStdout: true,
AttachStderr: true,
},
{
[]string{"-i", "-t", "-u", "uid", "container", "command"},
}: {
User: "uid",
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Container: "container",
Cmd: []string{"command"},
},
{
[]string{"-d", "container", "command"},
}: {
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
Detach: true,
Container: "container",
Cmd: []string{"command"},
},
{
[]string{"-t", "-i", "-d", "container", "command"},
}: {
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
Detach: true,
Tty: true,
Container: "container",
Cmd: []string{"command"},
},
}
for invalid, expectedError := range invalids {
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
cmd.ShortUsage = func() {}
cmd.SetOutput(ioutil.Discard)
_, err := ParseExec(cmd, invalid.args)
if err == nil || err.Error() != expectedError.Error() {
t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
}
}
for valid, expectedExecConfig := range valids {
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
cmd.ShortUsage = func() {}
cmd.SetOutput(ioutil.Discard)
execConfig, err := ParseExec(cmd, valid.args)
if err != nil {
t.Fatal(err)
}
if !compareExecConfig(expectedExecConfig, execConfig) {
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
}
}
}
func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool {
if config1.AttachStderr != config2.AttachStderr {
return false
}
if config1.AttachStdin != config2.AttachStdin {
return false
}
if config1.AttachStdout != config2.AttachStdout {
return false
}
if config1.Container != config2.Container {
return false
}
if config1.Detach != config2.Detach {
return false
}
if config1.Privileged != config2.Privileged {
return false
}
if config1.Tty != config2.Tty {
return false
}
if config1.User != config2.User {
return false
}
if len(config1.Cmd) != len(config2.Cmd) {
return false
}
for index, value := range config1.Cmd {
if value != config2.Cmd[index] {
return false
}
}
return true
}

View File

@@ -1,42 +0,0 @@
package client
import (
"errors"
"io"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdExport exports a filesystem as a tar archive.
//
// The tar archive is streamed to STDOUT by default or written to a file.
//
// Usage: docker export [OPTIONS] CONTAINER
func (cli *DockerCli) Export(args ...string) error {
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, Cli.DockerCommands["export"].Description, true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if *outfile == "" && cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
responseBody, err := cli.client.ContainerExport(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
defer responseBody.Close()
if *outfile == "" {
_, err := io.Copy(cli.out, responseBody)
return err
}
return copyToFile(*outfile, responseBody)
}

View File

@@ -1,295 +0,0 @@
package client
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
var warnMessage = "Please note that Floating IP (FIP) is billed monthly. The billing begins when a new IP is allocated, ends when it is released. Partial month is treated as a entire month. Do you want to continue?"
// CmdFip is the parent subcommand for all fip commands
//
// Usage: docker fip <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdFip(args ...string) error {
cmd := Cli.Subcmd("fip", []string{"COMMAND [OPTIONS]"}, fipUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdNetworkCreate creates a new fip with a given name
//
// Usage: docker fip create [OPTIONS] COUNT
func (cli *DockerCli) CmdFipAllocate(args ...string) error {
cmd := Cli.Subcmd("fip allocate", []string{"COUNT"}, "Creates some new floating IPs by the user", false)
flAvailable := cmd.Bool([]string{"-pick"}, false, "Pick an available floating IP if have")
flForce := cmd.Bool([]string{"y", "-yes"}, false, "Agree to allocate floating IP, will not show prompt")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *flAvailable == true {
fipFilterArgs, _ := filters.FromParam("dangling=true")
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fips, err := cli.client.FipList(context.Background(), options)
if err == nil {
for _, fip := range fips {
if fip["container"] == "" && fip["service"] == "" {
fmt.Fprintf(cli.out, "%s\n", fip["fip"])
return nil
}
}
}
}
if *flForce == false {
if askForConfirmation(warnMessage) == false {
return nil
}
}
fips, err := cli.client.FipAllocate(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
for _, ip := range fips {
fmt.Fprintf(cli.out, "%s\n", ip)
}
return nil
}
// CmdFipRelease deletes one or more fips
//
// Usage: docker fip release FIP [FIP...]
func (cli *DockerCli) CmdFipRelease(args ...string) error {
cmd := Cli.Subcmd("fip release", []string{"FIP [FIP...]"}, "Release one or more fips", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, ip := range cmd.Args() {
if err := cli.client.FipRelease(context.Background(), ip); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdFipAttach connects a container to a floating IP
//
// Usage: docker fip attach [OPTIONS] <FIP> <CONTAINER>
func (cli *DockerCli) CmdFipAttach(args ...string) error {
cmd := Cli.Subcmd("fip attach", []string{"FIP CONTAINER"}, "Connects a container to a floating IP", false)
flForce := cmd.Bool([]string{"f", "-force"}, false, "Deattach that FIP and attach it to this container")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
if *flForce {
filter, _ := filters.FromParam("dangling=false")
options := types.NetworkListOptions{
Filters: filter,
}
fips, err := cli.client.FipList(context.Background(), options)
if err != nil {
return err
}
for _, fip := range fips {
if ip := fip["fip"]; ip == cmd.Arg(0) {
if fip["container"] != "" {
cli.client.FipDetach(context.Background(), fip["container"])
} else if fip["service"] != "" {
ip = ""
sv := types.ServiceUpdate{
FIP: &ip,
}
cli.client.ServiceUpdate(context.Background(), fip["service"], sv)
}
break
}
}
}
return cli.client.FipAttach(context.Background(), cmd.Arg(0), cmd.Arg(1))
}
// CmdFipDetach disconnects a container from a floating IP
//
// Usage: docker fip detach <CONTAINER>
func (cli *DockerCli) CmdFipDetach(args ...string) error {
cmd := Cli.Subcmd("fip detach", []string{"CONTAINER"}, "Disconnects container from a floating IP", false)
//force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
ip, err := cli.client.FipDetach(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", ip)
return nil
}
// CmdFipLs lists all the fips
//
// Usage: docker fip ls [OPTIONS]
func (cli *DockerCli) CmdFipLs(args ...string) error {
cmd := Cli.Subcmd("fip ls", nil, "Lists fips", true)
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
fipFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if fipFilterArgs, err = filters.ParseFlag(f, fipFilterArgs); err != nil {
return err
}
}
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fips, err := cli.client.FipList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Floating IP\tName\tContainer\tService\n")
for _, fip := range fips {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", fip["fip"], fip["name"], fip["container"], fip["service"])
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdFipName(args ...string) error {
cmd := Cli.Subcmd("fip name", []string{"FIP [NAME]"}, "Set a name for a floating IP", false)
//force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP")
cmd.Require(flag.Min, 1)
cmd.Require(flag.Max, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
err := cli.client.FipName(context.Background(), cmd.Arg(0), cmd.Arg(1))
if err != nil {
return err
}
return nil
}
func fipUsage() string {
fipCommands := [][]string{
{"allocate", "Allocate a or some IPs"},
{"attach", "Attach floating IP to container"},
{"detach", "Detach floating IP from container"},
{"ls", "List all floating IPs"},
{"release", "Release a floating IP"},
{"name", "Name a floating IP"},
}
help := "Commands:\n"
for _, cmd := range fipCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper fip COMMAND --help' for more information on a command.")
return help
}
// Allocate and attach a fip
func (cli *DockerCli) associateNewFip(ctx context.Context, contID string) (string, error) {
fips, err := cli.client.FipAllocate(ctx, "1")
if err != nil {
return "", err
}
for _, ip := range fips {
err = cli.client.FipAttach(ctx, ip, contID)
if err != nil {
go func() {
cli.client.FipRelease(ctx, ip)
}()
return "", err
}
return ip, nil
}
return "", fmt.Errorf("Server failed to create new fip")
}
// Release a fip
func (cli *DockerCli) releaseFip(ctx context.Context, ip string) error {
return cli.client.FipRelease(ctx, ip)
}
// Detach and release a fip
func (cli *DockerCli) releaseContainerFip(ctx context.Context, contID string) error {
ip, err := cli.client.FipDetach(ctx, contID)
if err != nil {
return err
}
return cli.client.FipRelease(ctx, ip)
}
// askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
// confirmations. If the input is not recognized, it will ask again. The function does not return
// until it gets a valid response from the user.
func askForConfirmation(s string) bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s [y/n]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
}
}

View File

@@ -1,264 +0,0 @@
package formatter
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/docker/go-units"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/hyperhq/hypercli/pkg/stringutils"
)
const (
tableKey = "table"
fipLabel = "sh.hyper.fip"
containerIDHeader = "CONTAINER ID"
imageHeader = "IMAGE"
namesHeader = "NAMES"
commandHeader = "COMMAND"
createdSinceHeader = "CREATED"
createdAtHeader = "CREATED AT"
runningForHeader = "CREATED"
statusHeader = "STATUS"
portsHeader = "PORTS"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
imageIDHeader = "IMAGE ID"
repositoryHeader = "REPOSITORY"
tagHeader = "TAG"
digestHeader = "DIGEST"
fipHeader = "PUBLIC IP"
volumeNameHeader = "NAME"
volumeSizeHeader = "SIZE"
volumeDriverHeader = "DRIVER"
volumeContainerHeader = "CONTAINER"
)
type containerContext struct {
baseSubContext
trunc bool
c types.Container
}
func (c *containerContext) ID() string {
c.addHeader(containerIDHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
return c.c.ID
}
func (c *containerContext) Names() string {
c.addHeader(namesHeader)
names := stripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}
return strings.Join(names, ",")
}
func (c *containerContext) Image() string {
c.addHeader(imageHeader)
if c.c.Image == "" {
return "<no image>"
}
if c.trunc {
if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
return trunc
}
}
return c.c.Image
}
func (c *containerContext) Command() string {
c.addHeader(commandHeader)
command := c.c.Command
if c.trunc {
command = stringutils.Truncate(command, 20)
}
return strconv.Quote(command)
}
func (c *containerContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.c.Created), 0).String()
}
func (c *containerContext) RunningFor() string {
c.addHeader(runningForHeader)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}
func (c *containerContext) Ports() string {
c.addHeader(portsHeader)
return api.DisplayablePorts(c.c.Ports)
}
func (c *containerContext) Status() string {
c.addHeader(statusHeader)
return c.c.Status
}
func (c *containerContext) Size() string {
c.addHeader(sizeHeader)
srw := units.HumanSize(float64(c.c.SizeRw))
sv := units.HumanSize(float64(c.c.SizeRootFs))
sf := srw
if c.c.SizeRootFs > 0 {
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
}
return sf
}
func (c *containerContext) Labels() string {
c.addHeader(labelsHeader)
if c.c.Labels == nil {
return ""
}
var joinLabels []string
for k, v := range c.c.Labels {
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}
func (c *containerContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
c.addHeader(h)
if c.c.Labels == nil {
return ""
}
return c.c.Labels[name]
}
func (c *containerContext) PublicIP() string {
c.addHeader(fipHeader)
if c.c.Labels == nil {
return ""
}
return c.c.Labels[fipLabel]
}
type imageContext struct {
baseSubContext
trunc bool
i types.Image
repo string
tag string
digest string
}
func (c *imageContext) ID() string {
c.addHeader(imageIDHeader)
if c.trunc {
return stringid.TruncateID(c.i.ID)
}
return c.i.ID
}
func (c *imageContext) Repository() string {
c.addHeader(repositoryHeader)
return c.repo
}
func (c *imageContext) Tag() string {
c.addHeader(tagHeader)
return c.tag
}
func (c *imageContext) Digest() string {
c.addHeader(digestHeader)
return c.digest
}
func (c *imageContext) CreatedSince() string {
c.addHeader(createdSinceHeader)
createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}
func (c *imageContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.i.Created), 0).String()
}
func (c *imageContext) Size() string {
c.addHeader(sizeHeader)
return units.HumanSize(float64(c.i.Size))
}
type volumeContext struct {
baseSubContext
i types.Volume
}
func (c *volumeContext) Name() string {
c.addHeader(volumeNameHeader)
return c.i.Name
}
func (c *volumeContext) Size() string {
c.addHeader(volumeSizeHeader)
size := c.i.Labels["size"]
return size + " GB"
}
func (c *volumeContext) Driver() string {
c.addHeader(volumeDriverHeader)
return c.i.Driver
}
func (c *volumeContext) Container() string {
c.addHeader(volumeContainerHeader)
container := c.i.Labels["container"]
return container
}
type subContext interface {
fullHeader() string
addHeader(header string)
}
type baseSubContext struct {
header []string
}
func (c *baseSubContext) fullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}
func (c *baseSubContext) addHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
}
func stripNamePrefix(ss []string) []string {
for i, s := range ss {
ss[i] = s[1:]
}
return ss
}

View File

@@ -1,192 +0,0 @@
package formatter
import (
"reflect"
"strings"
"testing"
"time"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/pkg/stringid"
)
func TestContainerPsContext(t *testing.T) {
containerID := stringid.GenerateRandomID()
unix := time.Now().Unix()
var ctx containerContext
cases := []struct {
container types.Container
trunc bool
expValue string
expHeader string
call func() string
}{
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), containerIDHeader, ctx.ID},
{types.Container{ID: containerID}, false, containerID, containerIDHeader, ctx.ID},
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
{types.Container{
Image: "a5a665ff33eced1e0803148700880edab4",
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
},
true,
"a5a665ff33ec",
imageHeader,
ctx.Image,
},
{types.Container{
Image: "a5a665ff33eced1e0803148700880edab4",
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
},
false,
"a5a665ff33eced1e0803148700880edab4",
imageHeader,
ctx.Image,
},
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
{types.Container{}, true, "", labelsHeader, ctx.Labels},
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
}
for _, c := range cases {
ctx = containerContext{c: c.container, trunc: c.trunc}
v := c.call()
if strings.Contains(v, ",") {
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
ctx = containerContext{c: c1, trunc: true}
sid := ctx.Label("com.docker.swarm.swarm-id")
node := ctx.Label("com.docker.swarm.node_name")
if sid != "33" {
t.Fatalf("Expected 33, was %s\n", sid)
}
if node != "ubuntu" {
t.Fatalf("Expected ubuntu, was %s\n", node)
}
h := ctx.fullHeader()
if h != "SWARM ID\tNODE NAME" {
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
}
c2 := types.Container{}
ctx = containerContext{c: c2, trunc: true}
label := ctx.Label("anything.really")
if label != "" {
t.Fatalf("Expected an empty string, was %s", label)
}
ctx = containerContext{c: c2, trunc: true}
fullHeader := ctx.fullHeader()
if fullHeader != "" {
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
}
}
func TestImagesContext(t *testing.T) {
imageID := stringid.GenerateRandomID()
unix := time.Now().Unix()
var ctx imageContext
cases := []struct {
imageCtx imageContext
expValue string
expHeader string
call func() string
}{
{imageContext{
i: types.Image{ID: imageID},
trunc: true,
}, stringid.TruncateID(imageID), imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{ID: imageID},
trunc: false,
}, imageID, imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{Size: 10},
trunc: true,
}, "10 B", sizeHeader, ctx.Size},
{imageContext{
i: types.Image{Created: unix},
trunc: true,
}, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
// FIXME
// {imageContext{
// i: types.Image{Created: unix},
// trunc: true,
// }, units.HumanDuration(time.Unix(unix, 0)), createdSinceHeader, ctx.CreatedSince},
{imageContext{
i: types.Image{},
repo: "busybox",
}, "busybox", repositoryHeader, ctx.Repository},
{imageContext{
i: types.Image{},
tag: "latest",
}, "latest", tagHeader, ctx.Tag},
{imageContext{
i: types.Image{},
digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", digestHeader, ctx.Digest},
}
for _, c := range cases {
ctx = c.imageCtx
v := c.call()
if strings.Contains(v, ",") {
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
}
func compareMultipleValues(t *testing.T, value, expected string) {
// comma-separated values means probably a map input, which won't
// be guaranteed to have the same order as our expected value
// We'll create maps and use reflect.DeepEquals to check instead:
entriesMap := make(map[string]string)
expMap := make(map[string]string)
entries := strings.Split(value, ",")
expectedEntries := strings.Split(expected, ",")
for _, entry := range entries {
keyval := strings.Split(entry, "=")
entriesMap[keyval[0]] = keyval[1]
}
for _, expected := range expectedEntries {
keyval := strings.Split(expected, "=")
expMap[keyval[0]] = keyval[1]
}
if !reflect.DeepEqual(expMap, entriesMap) {
t.Fatalf("Expected entries: %v, got: %v", expected, value)
}
}

View File

@@ -1,302 +0,0 @@
package formatter
import (
"bytes"
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/reference"
)
const (
tableFormatKey = "table"
rawFormatKey = "raw"
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t{{.PublicIP}}"
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultVolumeTableFormat = "table {{.Driver}}\t{{.Name}}\t{{.Size}}\t{{.Container}}"
defaultQuietFormat = "{{.ID}}"
)
// Context contains information required by the formatter to print the output as desired.
type Context struct {
// Output is the output stream to which the formatted string is written.
Output io.Writer
// Format is used to choose raw, table or custom format for the output.
Format string
// Quiet when set to true will simply print minimal information.
Quiet bool
// Trunc when set to true will truncate the output of certain fields such as Container ID.
Trunc bool
// internal element
table bool
finalFormat string
header string
buffer *bytes.Buffer
}
func (c *Context) preformat() {
c.finalFormat = c.Format
if strings.HasPrefix(c.Format, tableKey) {
c.table = true
c.finalFormat = c.finalFormat[len(tableKey):]
}
c.finalFormat = strings.Trim(c.finalFormat, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
c.finalFormat = r.Replace(c.finalFormat)
}
func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := template.New("").Parse(c.finalFormat)
if err != nil {
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
c.buffer.WriteTo(c.Output)
}
return tmpl, err
}
func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
if c.table {
if len(c.header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
tmpl.Execute(bytes.NewBufferString(""), subContext)
c.header = subContext.fullHeader()
}
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(c.header))
t.Write([]byte("\n"))
c.buffer.WriteTo(t)
t.Flush()
} else {
c.buffer.WriteTo(c.Output)
}
}
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
if err := tmpl.Execute(c.buffer, subContext); err != nil {
c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
c.buffer.WriteTo(c.Output)
return err
}
if c.table && len(c.header) == 0 {
c.header = subContext.fullHeader()
}
c.buffer.WriteString("\n")
return nil
}
// ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
type ContainerContext struct {
Context
// Size when set to true will display the size of the output.
Size bool
// Containers
Containers []types.Container
}
// ImageContext contains image specific information required by the formater, encapsulate a Context struct.
type ImageContext struct {
Context
Digest bool
// Images
Images []types.Image
}
type VolumeContext struct {
Context
// Volumes
Volumes []*types.Volume
}
func (ctx ContainerContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultContainerTableFormat
if ctx.Quiet {
ctx.Format = defaultQuietFormat
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `container_id: {{.ID}}`
} else {
ctx.Format = `container_id: {{.ID}}
image: {{.Image}}
command: {{.Command}}
created_at: {{.CreatedAt}}
status: {{.Status}}
names: {{.Names}}
public_ip: {{.PublicIP}}
labels: {{.Labels}}
ports: {{.Ports}}
`
if ctx.Size {
ctx.Format += `size: {{.Size}}
`
}
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
if ctx.table && ctx.Size {
ctx.finalFormat += "\t{{.Size}}"
}
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, container := range ctx.Containers {
containerCtx := &containerContext{
trunc: ctx.Trunc,
c: container,
}
err = ctx.contextFormat(tmpl, containerCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &containerContext{})
}
func (ctx ImageContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultImageTableFormat
if ctx.Digest {
ctx.Format = defaultImageTableFormatWithDigest
}
if ctx.Quiet {
ctx.Format = defaultQuietFormat
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `image_id: {{.ID}}`
} else {
if ctx.Digest {
ctx.Format = `repository: {{ .Repository }}
tag: {{.Tag}}
digest: {{.Digest}}
image_id: {{.ID}}
created_at: {{.CreatedAt}}
virtual_size: {{.Size}}
`
} else {
ctx.Format = `repository: {{ .Repository }}
tag: {{.Tag}}
image_id: {{.ID}}
created_at: {{.CreatedAt}}
virtual_size: {{.Size}}
`
}
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
ctx.finalFormat += "\t{{.Digest}}"
}
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, image := range ctx.Images {
repoTags := image.RepoTags
repoDigests := image.RepoDigests
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
// 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 := "<none>"
tag := "<none>"
digest := "<none>"
if !strings.HasPrefix(repoAndRef, "<none>") {
ref, err := reference.ParseNamed(repoAndRef)
if err != nil {
continue
}
repo = ref.Name()
switch x := ref.(type) {
case reference.Canonical:
digest = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
}
imageCtx := &imageContext{
trunc: ctx.Trunc,
i: image,
repo: repo,
tag: tag,
digest: digest,
}
err = ctx.contextFormat(tmpl, imageCtx)
if err != nil {
return
}
}
}
ctx.postformat(tmpl, &imageContext{})
}
func (ctx VolumeContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultVolumeTableFormat
if ctx.Quiet {
ctx.Format = "{{.Name}}"
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `name: {{.Name}}`
} else {
ctx.Format = `name: {{.Name}}
driver: {{.Driver}}
size: {{.Size}}
container: {{.Container}}
`
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, vol := range ctx.Volumes {
volCtx := &volumeContext{
i: *vol,
}
err = ctx.contextFormat(tmpl, volCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &volumeContext{})
}

View File

@@ -1,535 +0,0 @@
package formatter
import (
"bytes"
"fmt"
"testing"
"time"
"github.com/hyperhq/hyper-api/types"
)
func TestContainerContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct {
context ContainerContext
expected string
}{
// Errors
{
ContainerContext{
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
ContainerContext{
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
ContainerContext{
Context: Context{
Format: "table",
},
},
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
containerID1 ubuntu "" 24 hours ago foobar_baz
containerID2 ubuntu "" 24 hours ago foobar_bar
`,
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
},
},
"IMAGE\nubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
},
Size: true,
},
"IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Quiet: true,
},
},
"IMAGE\nubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "table",
Quiet: true,
},
},
"containerID1\ncontainerID2\n",
},
// Raw Format
{
ContainerContext{
Context: Context{
Format: "raw",
},
},
fmt.Sprintf(`container_id: containerID1
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_baz
labels:
ports:
container_id: containerID2
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_bar
labels:
ports:
`, expectedTime, expectedTime),
},
{
ContainerContext{
Context: Context{
Format: "raw",
},
Size: true,
},
fmt.Sprintf(`container_id: containerID1
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_baz
labels:
ports:
size: 0 B
container_id: containerID2
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_bar
labels:
ports:
size: 0 B
`, expectedTime, expectedTime),
},
{
ContainerContext{
Context: Context{
Format: "raw",
Quiet: true,
},
},
"container_id: containerID1\ncontainer_id: containerID2\n",
},
// Custom Format
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
},
},
"ubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
},
Size: true,
},
"ubuntu\nubuntu\n",
},
}
for _, context := range contexts {
containers := []types.Container{
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
}
out := bytes.NewBufferString("")
context.context.Output = out
context.context.Containers = containers
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestContainerContextWriteWithNoContainers(t *testing.T) {
out := bytes.NewBufferString("")
containers := []types.Container{}
contexts := []struct {
context ContainerContext
expected string
}{
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
Output: out,
},
},
"",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Output: out,
},
},
"IMAGE\n",
},
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
Output: out,
},
Size: true,
},
"",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Output: out,
},
Size: true,
},
"IMAGE SIZE\n",
},
}
for _, context := range contexts {
context.context.Containers = containers
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestImageContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct {
context ImageContext
expected string
}{
// Errors
{
ImageContext{
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
ImageContext{
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
ImageContext{
Context: Context{
Format: "table",
},
},
`REPOSITORY TAG IMAGE ID CREATED SIZE
image tag1 imageID1 24 hours ago 0 B
image <none> imageID1 24 hours ago 0 B
image tag2 imageID2 24 hours ago 0 B
<none> <none> imageID3 24 hours ago 0 B
`,
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
},
},
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
},
Digest: true,
},
`REPOSITORY DIGEST
image <none>
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
image <none>
<none> <none>
`,
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Quiet: true,
},
},
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: true,
},
},
"imageID1\nimageID1\nimageID2\nimageID3\n",
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: false,
},
Digest: true,
},
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
image tag1 <none> imageID1 24 hours ago 0 B
image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
image tag2 <none> imageID2 24 hours ago 0 B
<none> <none> <none> imageID3 24 hours ago 0 B
`,
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: true,
},
Digest: true,
},
"imageID1\nimageID1\nimageID2\nimageID3\n",
},
// Raw Format
{
ImageContext{
Context: Context{
Format: "raw",
},
},
fmt.Sprintf(`repository: image
tag: tag1
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: <none>
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: tag2
image_id: imageID2
created_at: %s
virtual_size: 0 B
repository: <none>
tag: <none>
image_id: imageID3
created_at: %s
virtual_size: 0 B
`, expectedTime, expectedTime, expectedTime, expectedTime),
},
{
ImageContext{
Context: Context{
Format: "raw",
},
Digest: true,
},
fmt.Sprintf(`repository: image
tag: tag1
digest: <none>
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: <none>
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: tag2
digest: <none>
image_id: imageID2
created_at: %s
virtual_size: 0 B
repository: <none>
tag: <none>
digest: <none>
image_id: imageID3
created_at: %s
virtual_size: 0 B
`, expectedTime, expectedTime, expectedTime, expectedTime),
},
{
ImageContext{
Context: Context{
Format: "raw",
Quiet: true,
},
},
`image_id: imageID1
image_id: imageID1
image_id: imageID2
image_id: imageID3
`,
},
// Custom Format
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
},
},
"image\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
},
Digest: true,
},
"image\nimage\nimage\n<none>\n",
},
}
for _, context := range contexts {
images := []types.Image{
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
}
out := bytes.NewBufferString("")
context.context.Output = out
context.context.Images = images
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestImageContextWriteWithNoImage(t *testing.T) {
out := bytes.NewBufferString("")
images := []types.Image{}
contexts := []struct {
context ImageContext
expected string
}{
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
Output: out,
},
},
"",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Output: out,
},
},
"REPOSITORY\n",
},
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
Output: out,
},
Digest: true,
},
"",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Output: out,
},
Digest: true,
},
"REPOSITORY DIGEST\n",
},
}
for _, context := range contexts {
context.context.Images = images
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}

View File

@@ -1,533 +0,0 @@
package client
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/container"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hyper-api/types/network"
"github.com/hyperhq/hyper-api/types/strslice"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdFunc is the parent subcommand for all func commands
//
// Usage: docker func <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdFunc(args ...string) error {
cmd := Cli.Subcmd("func", []string{"COMMAND [OPTIONS]"}, funcUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
func funcUsage() string {
funcCommands := [][]string{
{"create", "Create a new function"},
{"update", "Update a function"},
{"ls", "Lists all functions"},
{"rm", "Remove one or more function"},
{"inspect", "Display detailed information on the given function"},
{"call", "Call a function"},
{"get", "Get the return of a function call"},
{"logs", "Retrieve the logs of a function"},
{"status", "Retrieve the status of a function"},
}
help := "Commands:\n"
for _, cmd := range funcCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper func COMMAND --help' for more information on a command.")
return help
}
// CmdFuncCreate creates a new func with a given name
//
// Usage: hyper func create [OPTIONS] IMAGE [COMMAND]
func (cli *DockerCli) CmdFuncCreate(args ...string) error {
cmd := Cli.Subcmd("func create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new function", false)
var (
flName = cmd.String([]string{"-name"}, "", "Function name")
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of function containers to run the function (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flTimeout = cmd.Int([]string{"-timeout"}, 300, "The maximum execution duration of function call")
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flLabelsFile = ropts.NewListOpts(nil)
flVolumesFrom = ropts.NewListOpts(nil)
flNoAutoVolume = cmd.Bool([]string{"-noauto-volume"}, false, "Do not create volumes specified in image")
flPublish = ropts.NewListOpts(nil)
flExpose = ropts.NewListOpts(nil)
flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flLinks = ropts.NewListOpts(opts.ValidateLink)
flSecurityGroups = ropts.NewListOpts(nil)
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount shared volumes from the specified container(s)")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
for _, sg := range flSecurityGroups.GetAll() {
if sg == "" {
continue
}
labels = append(labels, fmt.Sprintf("sh_hyper_sg_%s=yes", sg))
}
if *flNoAutoVolume {
labels = append(labels, "sh_hyper_noauto_volume=true")
}
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil {
return err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") {
return fmt.Errorf("Invalid port format for --expose: %s", e)
}
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
config := types.FuncConfig{
Tty: *flTty,
ExposedPorts: ports,
Env: &envVariables,
Cmd: runCmd,
Image: image,
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Labels: opts.ConvertKVStringsToMap(labels),
StopSignal: *flStopSignal,
}
hostConfig := types.FuncHostConfig{
VolumesFrom: flVolumesFrom.GetAll(),
PortBindings: portBindings,
Links: flLinks.GetAll(),
PublishAllPorts: *flPublishAll,
NetworkMode: container.NetworkMode(*flNetMode),
}
networkingConfig := network.NetworkingConfig{
EndpointsConfig: make(map[string]*network.EndpointSettings),
}
if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
if epConfig == nil {
epConfig = &network.EndpointSettings{}
}
epConfig.Links = make([]string, len(hostConfig.Links))
copy(epConfig.Links, hostConfig.Links)
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
}
fnOpts := types.Func{
Name: *flName,
ContainerSize: *flContainerSize,
Timeout: *flTimeout,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
fn, err := cli.client.FuncCreate(context.Background(), fnOpts)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s is created with the address of https://%s.hyperfunc.io/call/%s/%s\n", fn.Name, cli.region, fn.Name, fn.UUID)
return nil
}
// CmdFuncUpdate updates a func with a given name
//
// Usage: hyper func update [OPTIONS] NAME
func (cli *DockerCli) CmdFuncUpdate(args ...string) error {
cmd := Cli.Subcmd("func update", []string{"NAME"}, "Update a function", false)
var (
flContainerSize = cmd.String([]string{"-size"}, "", "The size of function containers to run the funciton (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flRefresh = cmd.Bool([]string{"-refresh"}, false, "Whether to regenerate the uuid of function")
flTimeout = cmd.String([]string{"-timeout"}, "", "The maximum execution duration of function call")
)
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
for _, env := range envVariables {
if env == "" {
envVariables = []string{}
break
}
}
env := &envVariables
if !cmd.IsSet("-env") && !cmd.IsSet("e") && !cmd.IsSet("-env-file") {
env = nil
}
timeout, _ := strconv.Atoi(*flTimeout)
fnOpts := types.Func{
Name: name,
ContainerSize: *flContainerSize,
Refresh: *flRefresh,
Timeout: timeout,
Config: types.FuncConfig{
Env: env,
},
}
fn, err := cli.client.FuncUpdate(context.Background(), name, fnOpts)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", fn.Name)
return nil
}
// CmdFuncDelete deletes one or more funcs
//
// Usage: hyper func rm NAME [NAME...]
func (cli *DockerCli) CmdFuncRm(args ...string) error {
cmd := Cli.Subcmd("func rm", []string{"NAME [NAME...]"}, "Remove one or more function", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, name := range cmd.Args() {
name = strings.Replace(name, "/", "", -1)
if err := cli.client.FuncDelete(context.Background(), name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", name)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdFuncLs lists all the funcs
//
// Usage: hyper func ls [OPTIONS]
func (cli *DockerCli) CmdFuncLs(args ...string) error {
cmd := Cli.Subcmd("func ls", nil, "Lists all functions", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
funcFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if funcFilterArgs, err = filters.ParseFlag(f, funcFilterArgs); err != nil {
return err
}
}
options := types.FuncListOptions{
Filters: funcFilterArgs,
}
fns, err := cli.client.FuncList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tSIZE\tIMAGE\tCOMMAND\tCREATED\tUUID\n")
for _, fn := range fns {
created := units.HumanDuration(time.Now().UTC().Sub(fn.Created)) + " ago"
command := strings.Join([]string(fn.Config.Cmd), " ")
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", fn.Name, fn.ContainerSize, fn.Config.Image, command, created, fn.UUID)
}
w.Flush()
return nil
}
// CmdFuncInspect
//
// Usage: docker func inspect [OPTIONS] NAME [NAME...]
func (cli *DockerCli) CmdFuncInspect(args ...string) error {
cmd := Cli.Subcmd("func inspect", []string{"NAME [NAME...]"}, "Display detailed information on the given function", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
name = strings.Replace(name, "/", "", -1)
i, err := cli.client.FuncInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdFuncCall call a func
//
// Usage: hyper func call NAME
func (cli *DockerCli) CmdFuncCall(args ...string) error {
cmd := Cli.Subcmd("func call", []string{"NAME"}, "Call a function", false)
sync := cmd.Bool([]string{"-sync"}, false, "Block until the call is completed")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
var stdin io.Reader
if fi, err := os.Stdin.Stat(); err == nil {
if fi.Mode()&os.ModeNamedPipe != 0 {
stdin = bufio.NewReader(os.Stdin)
}
}
body, err := cli.client.FuncCall(context.Background(), cli.region, name, stdin, *sync)
if err != nil {
return err
}
defer body.Close()
if *sync {
_, err = io.Copy(cli.out, body)
return err
}
var ret types.FuncCallResponse
err = json.NewDecoder(body).Decode(&ret)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "CallId: %s\n", ret.CallId)
return nil
}
// CmdFuncGet Get the return of a func call
//
// Usage: hyper func get [OPTIONS] CALL_ID
func (cli *DockerCli) CmdFuncGet(args ...string) error {
cmd := Cli.Subcmd("func get", []string{"CALL_ID"}, "Get the return of a function call", false)
wait := cmd.Bool([]string{"-wait"}, false, "Block until the call is completed")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
callId := cmd.Arg(0)
body, err := cli.client.FuncGet(context.Background(), cli.region, callId, *wait)
if err != nil {
return err
}
defer body.Close()
_, err = io.Copy(cli.out, body)
return err
}
// CmdFuncLogs Get the return of a func call
//
// Usage: hyper func get [OPTIONS] NAME
func (cli *DockerCli) CmdFuncLogs(args ...string) error {
cmd := Cli.Subcmd("func logs", []string{"NAME"}, "Retrieve the logs of a function", false)
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
callId := cmd.String([]string{"-callid"}, "", "Only retrieve specific logs of CallId")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
reader, err := cli.client.FuncLogs(context.Background(), cli.region, name, *callId, *follow, *tail)
if err != nil {
return err
}
defer reader.Close()
dec := json.NewDecoder(reader)
for {
var log types.FuncLogsResponse
err := dec.Decode(&log)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
if log.Event != "" {
logTime := log.Time.Local().Format("2006-01-02T15:04:05Z")
if log.Event == "CALL" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, ShortStdin: %s\n",
logTime, log.Event, log.CallId, log.ShortStdin,
)
} else if log.Event == "FINISHED" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, ShortStdout: %s, ShortStderr: %s\n",
logTime, log.Event, log.CallId, log.ShortStdout, log.ShortStderr,
)
} else if log.Event == "FAILED" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, Message: %s\n",
logTime, log.Event, log.CallId, log.Message,
)
} else {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s\n",
logTime, log.Event, log.CallId,
)
}
}
}
}
// CmdFuncStatus Status the return of a func call
//
// Usage: hyper func status [OPTIONS] NAME
func (cli *DockerCli) CmdFuncStatus(args ...string) error {
cmd := Cli.Subcmd("func status", []string{"NAME"}, "Retrieve the status of a function", false)
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
status, err := cli.client.FuncStatus(context.Background(), cli.region, name)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "TOTAL\tPENDING\tRUNNING\tFINISHED\tFAILED\n")
fmt.Fprintf(w, "%d\t%d\t%d\t%d\t%d\n", status.Total, status.Pending, status.Running, status.Finished, status.Failed)
w.Flush()
return nil
}

View File

@@ -1,56 +0,0 @@
package client
import (
"io"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/pkg/stdcopy"
)
func (cli *DockerCli) holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
var err error
receiveStdout := make(chan error, 1)
if outputStream != nil || errorStream != nil {
go func() {
// When TTY is ON, use regular copy
if tty && outputStream != nil {
_, err = io.Copy(outputStream, resp.Reader)
} else {
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
}
logrus.Debugf("[hijack] End of stdout")
receiveStdout <- err
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
logrus.Debugf("[hijack] End of stdin")
}
if err := resp.CloseWrite(); err != nil {
logrus.Debugf("Couldn't send EOF: %s", err)
}
close(stdinDone)
}()
select {
case err := <-receiveStdout:
if err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
return err
}
case <-stdinDone:
if outputStream != nil || errorStream != nil {
if err := <-receiveStdout; err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
return err
}
}
}
return nil
}

View File

@@ -1,76 +0,0 @@
package client
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"golang.org/x/net/context"
"github.com/docker/go-units"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/hyperhq/hypercli/pkg/stringutils"
)
// CmdHistory shows the history of an image.
//
// Usage: docker history [OPTIONS] IMAGE
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Cli.Subcmd("history", []string{"IMAGE"}, Cli.DockerCommands["history"].Description, true)
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
history, err := cli.client.ImageHistory(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if *quiet {
for _, entry := range history {
if *noTrunc {
fmt.Fprintf(w, "%s\n", entry.ID)
} else {
fmt.Fprintf(w, "%s\n", stringid.TruncateID(entry.ID))
}
}
w.Flush()
return nil
}
var imageID string
var createdBy string
var created string
var size string
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT")
for _, entry := range history {
imageID = entry.ID
createdBy = strings.Replace(entry.CreatedBy, "\t", " ", -1)
if *noTrunc == false {
createdBy = stringutils.Truncate(createdBy, 45)
imageID = stringid.TruncateID(entry.ID)
}
if *human {
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0))) + " ago"
size = units.HumanSize(float64(entry.Size))
} else {
created = time.Unix(entry.Created, 0).Format(time.RFC3339)
size = strconv.FormatInt(entry.Size, 10)
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", imageID, created, createdBy, size, entry.Comment)
}
w.Flush()
return nil
}

View File

@@ -1,80 +0,0 @@
package client
import (
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hypercli/api/client/formatter"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
)
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified.
//
// Usage: docker images [OPTIONS] [REPOSITORY]
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, Cli.DockerCommands["images"].Description, true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
format := cmd.String([]string{"-format"}, "", "Pretty-print images using a Go template")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
imageFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
var err error
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
if err != nil {
return err
}
}
var matchName string
if cmd.NArg() == 1 {
matchName = cmd.Arg(0)
}
options := types.ImageListOptions{
MatchName: matchName,
All: *all,
Filters: imageFilterArgs,
}
images, err := cli.client.ImageList(context.Background(), options)
if err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.ImagesFormat()) > 0 && !*quiet {
f = cli.ImagesFormat()
} else {
f = "table"
}
}
imagesCtx := formatter.ImageContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Trunc: !*noTrunc,
},
Digest: *showDigests,
Images: images,
}
imagesCtx.Write()
return nil
}

View File

@@ -1,75 +0,0 @@
package client
import (
"fmt"
"io"
"os"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/urlutil"
)
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
//
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file or a path to local file relative to docker client. If the URL is '-', then the tar file is read from STDIN.
//
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
func (cli *DockerCli) Import(args ...string) error {
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, Cli.DockerCommands["import"].Description, true)
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
in io.Reader
tag string
src = cmd.Arg(0)
srcName = src
ref = cmd.Arg(1)
changes = flChanges.GetAll()
)
if cmd.NArg() == 3 {
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
tag = cmd.Arg(2)
}
if src == "-" {
in = cli.in
} else if !urlutil.IsURL(src) {
srcName = "-"
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
in = file
}
source := types.ImageImportSource{
Source: in,
SourceName: srcName,
}
options := types.ImageImportOptions{
Message: *message,
Tag: tag,
Changes: changes,
}
responseBody, err := cli.client.ImageImport(context.Background(), source, ref, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}

View File

@@ -1,153 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/go-units"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/ioutils"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/utils"
)
// CmdInfo displays system-wide information.
//
// Usage: docker info
func (cli *DockerCli) CmdInfo(args ...string) error {
cmd := Cli.Subcmd("info", nil, Cli.DockerCommands["info"].Description, true)
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
info, err := cli.client.Info(context.Background())
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
fmt.Fprintf(cli.out, " Running: %d\n", info.ContainersRunning)
fmt.Fprintf(cli.out, " Paused: %d\n", info.ContainersPaused)
fmt.Fprintf(cli.out, " Stopped: %d\n", info.ContainersStopped)
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
if info.DriverStatus != nil {
for _, pair := range info.DriverStatus {
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
// print a warning if devicemapper is using a loopback file
if pair[0] == "Data loop file" {
fmt.Fprintln(cli.err, " WARNING: Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.")
}
}
}
if info.SystemStatus != nil {
for _, pair := range info.SystemStatus {
fmt.Fprintf(cli.out, "%s: %s\n", pair[0], pair[1])
}
}
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
fmt.Fprintf(cli.out, "Plugins: \n")
fmt.Fprintf(cli.out, " Volume:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Volume, " "))
fmt.Fprintf(cli.out, "\n")
fmt.Fprintf(cli.out, " Network:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Network, " "))
fmt.Fprintf(cli.out, "\n")
if len(info.Plugins.Authorization) != 0 {
fmt.Fprintf(cli.out, " Authorization:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Authorization, " "))
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)
ioutils.FprintfIfNotEmpty(cli.out, "Architecture: %s\n", info.Architecture)
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", utils.IsDebugEnabled())
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
if info.Debug {
fmt.Fprintf(cli.out, " File Descriptors: %d\n", info.NFd)
fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
fmt.Fprintf(cli.out, " Docker Root Dir: %s\n", info.DockerRootDir)
}
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HTTPSProxy)
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
if info.IndexServerAddress != "" {
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
if len(u) > 0 {
fmt.Fprintf(cli.out, "Username: %v\n", u)
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
}
}
// Only output these warnings if the server does not support these features
// and the client is in debug mode
if utils.IsDebugEnabled() && info.OSType != "windows" {
if !info.MemoryLimit {
fmt.Fprintln(cli.err, "WARNING: No memory limit support")
}
if !info.SwapLimit {
fmt.Fprintln(cli.err, "WARNING: No swap limit support")
}
if !info.OomKillDisable {
fmt.Fprintln(cli.err, "WARNING: No oom kill disable support")
}
if !info.CPUCfsQuota {
fmt.Fprintln(cli.err, "WARNING: No cpu cfs quota support")
}
if !info.CPUCfsPeriod {
fmt.Fprintln(cli.err, "WARNING: No cpu cfs period support")
}
if !info.CPUShares {
fmt.Fprintln(cli.err, "WARNING: No cpu shares support")
}
if !info.CPUSet {
fmt.Fprintln(cli.err, "WARNING: No cpuset support")
}
if !info.IPv4Forwarding {
fmt.Fprintln(cli.err, "WARNING: IPv4 forwarding is disabled")
}
if !info.BridgeNfIptables {
fmt.Fprintln(cli.err, "WARNING: bridge-nf-call-iptables is disabled")
}
if !info.BridgeNfIP6tables {
fmt.Fprintln(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled")
}
}
if info.Labels != nil {
fmt.Fprintln(cli.out, "Labels:")
for _, attribute := range info.Labels {
fmt.Fprintf(cli.out, " %s\n", attribute)
}
}
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
if info.ClusterStore != "" {
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
}
if info.ClusterAdvertise != "" {
fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise)
}
return nil
}

View File

@@ -1,137 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"text/template"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hypercli/api/client/inspect"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
// CmdInspect displays low-level information on one or more containers or images.
//
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
}
ctx := context.Background()
var elementSearcher inspectSearcher
switch *inspectType {
case "container":
elementSearcher = cli.inspectContainers(ctx, *size)
case "image":
elementSearcher = cli.inspectImages(ctx, *size)
default:
elementSearcher = cli.inspectAll(ctx, *size)
}
return cli.inspectElements(*tmplStr, cmd.Args(), elementSearcher)
}
func (cli *DockerCli) inspectContainers(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
}
}
func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ImageInspectWithRaw(ctx, ref, getSize)
}
}
func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
if err != nil {
// Search for image with that id if a container doesn't exist.
if client.IsErrContainerNotFound(err) {
i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
if err != nil {
if client.IsErrImageNotFound(err) {
return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
}
return nil, nil, err
}
return i, rawImage, err
}
return nil, nil, err
}
return c, rawContainer, err
}
}
type inspectSearcher func(ref string) (interface{}, []byte, error)
func (cli *DockerCli) inspectElements(tmplStr string, references []string, searchByReference inspectSearcher) error {
elementInspector, err := cli.newInspectorWithTemplate(tmplStr)
if err != nil {
return Cli.StatusError{StatusCode: 64, Status: err.Error()}
}
var inspectErr error
for _, ref := range references {
element, raw, err := searchByReference(ref)
if err != nil {
inspectErr = err
break
}
if err := elementInspector.Inspect(element, raw); err != nil {
inspectErr = err
break
}
}
if err := elementInspector.Flush(); err != nil {
cli.inspectErrorStatus(err)
}
if status := cli.inspectErrorStatus(inspectErr); status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
}
return
}
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
elementInspector := inspect.NewIndentedInspector(cli.out)
if tmplStr != "" {
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
if err != nil {
return nil, fmt.Errorf("Template parsing error: %s", err)
}
elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
}
return elementInspector, nil
}

View File

@@ -1,119 +0,0 @@
package inspect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"text/template"
)
// Inspector defines an interface to implement to process elements
type Inspector interface {
Inspect(typedElement interface{}, rawElement []byte) error
Flush() error
}
// TemplateInspector uses a text template to inspect elements.
type TemplateInspector struct {
outputStream io.Writer
buffer *bytes.Buffer
tmpl *template.Template
}
// NewTemplateInspector creates a new inspector with a template.
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
return &TemplateInspector{
outputStream: outputStream,
buffer: new(bytes.Buffer),
tmpl: tmpl,
}
}
// Inspect executes the inspect template.
// It decodes the raw element into a map if the initial execution fails.
// This allows docker cli to parse inspect structs injected with Swarm fields.
func (i *TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
buffer := new(bytes.Buffer)
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
if rawElement == nil {
return fmt.Errorf("Template parsing error: %v", err)
}
return i.tryRawInspectFallback(rawElement, err)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *TemplateInspector) Flush() error {
if i.buffer.Len() == 0 {
_, err := io.WriteString(i.outputStream, "\n")
return err
}
_, err := io.Copy(i.outputStream, i.buffer)
return err
}
// IndentedInspector uses a buffer to stop the indented representation of an element.
type IndentedInspector struct {
outputStream io.Writer
elements []interface{}
rawElements [][]byte
}
// NewIndentedInspector generates a new IndentedInspector.
func NewIndentedInspector(outputStream io.Writer) Inspector {
return &IndentedInspector{
outputStream: outputStream,
}
}
// Inspect writes the raw element with an indented json format.
func (i *IndentedInspector) Inspect(typedElement interface{}, rawElement []byte) error {
if rawElement != nil {
i.rawElements = append(i.rawElements, rawElement)
} else {
i.elements = append(i.elements, typedElement)
}
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *IndentedInspector) Flush() error {
if len(i.elements) == 0 && len(i.rawElements) == 0 {
_, err := io.WriteString(i.outputStream, "[]\n")
return err
}
var buffer io.Reader
if len(i.rawElements) > 0 {
bytesBuffer := new(bytes.Buffer)
bytesBuffer.WriteString("[")
for idx, r := range i.rawElements {
bytesBuffer.Write(r)
if idx < len(i.rawElements)-1 {
bytesBuffer.WriteString(",")
}
}
bytesBuffer.WriteString("]")
indented := new(bytes.Buffer)
if err := json.Indent(indented, bytesBuffer.Bytes(), "", " "); err != nil {
return err
}
buffer = indented
} else {
b, err := json.MarshalIndent(i.elements, "", " ")
if err != nil {
return err
}
buffer = bytes.NewReader(b)
}
if _, err := io.Copy(i.outputStream, buffer); err != nil {
return err
}
_, err := io.WriteString(i.outputStream, "\n")
return err
}

View File

@@ -1,40 +0,0 @@
// +build !go1.5
package inspect
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
// tryeRawInspectFallback executes the inspect template with a raw interface.
// This allows docker cli to parse inspect structs injected with Swarm fields.
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
// We assume that the template is invalid when there is a <no value>, if the template was valid
// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
// template with the typed input.
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte, originalErr error) error {
var raw interface{}
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
if rawErr := dec.Decode(&raw); rawErr != nil {
return fmt.Errorf("unable to read inspect data: %v", rawErr)
}
if rawErr := i.tmpl.Execute(buffer, raw); rawErr != nil {
return fmt.Errorf("Template parsing error: %v", rawErr)
}
if strings.Contains(buffer.String(), "<no value>") {
return fmt.Errorf("Template parsing error: %v", originalErr)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}

View File

@@ -1,29 +0,0 @@
// +build go1.5
package inspect
import (
"bytes"
"encoding/json"
"fmt"
)
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte, _ error) error {
var raw interface{}
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
if rawErr := dec.Decode(&raw); rawErr != nil {
return fmt.Errorf("unable to read inspect data: %v", rawErr)
}
tmplMissingKey := i.tmpl.Option("missingkey=error")
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
return fmt.Errorf("Template parsing error: %v", rawErr)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}

View File

@@ -1,220 +0,0 @@
package inspect
import (
"bytes"
"strings"
"testing"
"text/template"
)
type testElement struct {
DNS string `json:"Dns"`
}
func TestTemplateInspectorDefault(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n" {
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorEmpty(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "\n" {
t.Fatalf("Expected `\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorTemplateError(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Foo}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
err = i.Inspect(testElement{"0.0.0.0"}, nil)
if err == nil {
t.Fatal("Expected error got nil")
}
if !strings.HasPrefix(err.Error(), "Template parsing error") {
t.Fatalf("Expected template error, got %v", err)
}
}
func TestTemplateInspectorRawFallback(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Dns}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0"}`)); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n" {
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorRawFallbackError(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Dns}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
err = i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Foo": "0.0.0.0"}`))
if err == nil {
t.Fatal("Expected error got nil")
}
if !strings.HasPrefix(err.Error(), "Template parsing error") {
t.Fatalf("Expected template error, got %v", err)
}
}
func TestTemplateInspectorMultiple(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n1.1.1.1\n" {
t.Fatalf("Expected `0.0.0.0\\n1.1.1.1\\n`, got `%s`", b.String())
}
}
func TestIndentedInspectorDefault(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorMultiple(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0"
},
{
"Dns": "1.1.1.1"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorEmpty(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := "[]\n"
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorRawElements(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0", "Node": "0"}`)); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, []byte(`{"Dns": "1.1.1.1", "Node": "1"}`)); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0",
"Node": "0"
},
{
"Dns": "1.1.1.1",
"Node": "1"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}

View File

@@ -1,35 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdKill kills one or more running container using SIGKILL or a specified signal.
//
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdKill(args ...string) error {
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["kill"].Description, true)
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerKill(context.Background(), name, *signal); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,323 +0,0 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/image"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/progress"
"github.com/hyperhq/hypercli/pkg/streamformatter"
"github.com/hyperhq/hypercli/pkg/symlink"
"golang.org/x/net/context"
)
const (
unsupported = "Unsupported image version"
)
type manifestItem struct {
Config string
RepoTags []string
Layers []string
}
type readCloser struct {
io.Reader
NeedClose io.ReadCloser
}
func (rc readCloser) Close() error {
return rc.NeedClose.Close()
}
func safePath(base, path string) (string, error) {
return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
}
func removeExistLayers(tmpDir string, existLayers []string, layerPaths []string) error {
keepLayerPaths := make(map[string]bool)
for _, _layerPath := range layerPaths[len(existLayers):] {
layerPath := filepath.Join(tmpDir, _layerPath)
info, err := os.Lstat(layerPath)
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if _realPath, err := filepath.EvalSymlinks(layerPath); err == nil {
realPath := filepath.Join(filepath.Base(filepath.Dir(_realPath)), "layer.tar")
keepLayerPaths[realPath] = true
}
}
}
for idx := range existLayers {
layerPath := layerPaths[idx]
if _, ok := keepLayerPaths[layerPath]; !ok {
if err := os.Remove(filepath.Join(tmpDir, layerPath)); err != nil {
continue
}
}
}
return nil
}
func (cli *DockerCli) getExistLayers(ctx context.Context, tmpDir string) ([]string, []string, error) {
manifestPath, err := safePath(tmpDir, "manifest.json")
if err != nil {
return nil, nil, err
}
manifestFile, err := os.Open(manifestPath)
if err != nil {
return nil, nil, err
}
defer manifestFile.Close()
var manifest []manifestItem
if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {
return nil, nil, err
}
allLayers := make([][]string, 0)
repoTags := make([][]string, 0)
layerPaths := make([]string, 0)
for _, m := range manifest {
configPath, err := safePath(tmpDir, m.Config)
if err != nil {
return nil, nil, err
}
config, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, nil, err
}
img, err := image.NewFromJSON(config)
if err != nil {
return nil, nil, err
}
if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual {
return nil, nil, errors.New(unsupported)
}
layerPaths = append(layerPaths, m.Layers...)
layers := make([]string, 0)
for _, diffID := range img.RootFS.DiffIDs {
layers = append(layers, string(diffID))
}
allLayers = append(allLayers, layers)
repoTags = append(repoTags, m.RepoTags)
}
diffRet, err := cli.client.ImageDiff(ctx, allLayers, repoTags)
if err != nil {
return nil, nil, err
}
return diffRet.ExistLayers, layerPaths, nil
}
func (cli *DockerCli) ImageLoadFromTar(ctx context.Context, tr io.Reader, quiet bool) (*types.ImageLoadResponse, error) {
tmpDir, err := ioutil.TempDir("", "hyper-pull-local-")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpDir)
if err := archive.Untar(tr, tmpDir, &archive.TarOptions{NoLchown: true}); err != nil {
return nil, err
}
if !quiet {
fmt.Fprintln(cli.out, "Diffing local image with remote image...")
}
existLayers, layerPaths, err := cli.getExistLayers(ctx, tmpDir)
if err != nil {
return nil, err
}
if err := removeExistLayers(tmpDir, existLayers, layerPaths); err != nil {
return nil, err
}
fs, err := archive.Tar(tmpDir, archive.Gzip)
if err != nil {
return nil, err
}
defer fs.Close()
hasNewLayers := len(existLayers) != len(layerPaths)
if hasNewLayers {
if !quiet {
fmt.Fprintln(cli.out, "Preparing to upload image...")
}
}
tarTmpDir, err := ioutil.TempDir("", "hyper-pull-local-")
if err != nil {
return nil, err
}
defer os.RemoveAll(tarTmpDir)
tarPath := filepath.Join(tarTmpDir, "image.tar")
tf, err := os.Create(tarPath)
if err != nil {
return nil, err
}
defer tf.Close()
_, err = io.Copy(tf, fs)
if err != nil {
return nil, err
}
os.RemoveAll(tmpDir)
info, err := tf.Stat()
if err != nil {
return nil, err
}
tf, err = os.Open(tarPath)
if err != nil {
return nil, err
}
resp, err := cli.client.ImageLoadLocal(ctx, quiet, info.Size())
if err != nil {
return nil, err
}
if !hasNewLayers || quiet {
go func() {
_, err := io.Copy(resp.Conn, tf)
if err != nil {
fmt.Fprintln(cli.out, err.Error())
resp.Conn.Close()
return
}
tf.Close()
}()
return &types.ImageLoadResponse{
Body: resp.Conn,
JSON: true,
}, nil
}
pr, pw := io.Pipe()
progressOutput := streamformatter.NewJSONStreamFormatter().NewProgressOutput(pw, false)
progressReader := progress.NewProgressReader(tf, progressOutput, info.Size(), "", "Uploading image")
go func() {
_, err := io.Copy(resp.Conn, progressReader)
if err != nil {
fmt.Fprintln(cli.out, err.Error())
resp.Conn.Close()
return
}
pw.CloseWithError(io.EOF)
}()
return &types.ImageLoadResponse{
Body: readCloser{io.MultiReader(pr, resp.Conn), resp.Conn},
JSON: true,
}, nil
}
// ImageDiff diff an image layers with local and imaged
func (cli *DockerCli) ImageLoadFromDaemon(ctx context.Context, name string, quiet bool) (*types.ImageLoadResponse, error) {
if !quiet {
fmt.Fprintln(cli.out, "Loading image from local docker daemon...")
}
tr, err := cli.client.ImageSaveTarFromDaemon(ctx, []string{name})
if err != nil {
return nil, err
}
defer tr.Close()
return cli.ImageLoadFromTar(ctx, tr, quiet)
}
// CmdLoad load a local image or a tar file
//
// The tar archive is read from STDIN by default, or from a tar archive file.
//
// Usage: docker load [OPTIONS]
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := Cli.Subcmd("load", nil, "Load a local image or a tar file", true)
local := cmd.String([]string{"l", "-local"}, "", "Read from a local image")
infile := cmd.String([]string{"i", "-input"}, "", "Read from a local or remote archive file compressed with gzip, bzip, or xz, instead of STDIN")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Do not show load process")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
*infile = strings.TrimSpace(*infile)
*local = strings.TrimSpace(*local)
var stdin io.Reader = cli.in
if *infile == "" && *local == "" && stdin == nil {
return errors.New("source image must be specified via --input, --local or STDIN")
}
var response *types.ImageLoadResponse
var err error
if *local != "" {
// Load from local docker daemon
response, err = cli.ImageLoadFromDaemon(context.Background(), *local, *quiet)
} else if *infile != "" {
if strings.HasPrefix(*infile, "http://") ||
strings.HasPrefix(*infile, "https://") ||
strings.HasPrefix(*infile, "ftp://") {
var input struct {
FromSrc string `json:"fromSrc"`
Quiet bool `json:"quiet"`
}
input.FromSrc = *infile
input.Quiet = *quiet
// Load from remote URL
response, err = cli.client.ImageLoad(context.Background(), input)
} else {
// Load from local tar
var af *os.File
af, err = os.Open(*infile)
if err != nil {
return err
}
defer af.Close()
response, err = cli.ImageLoadFromTar(context.Background(), af, *quiet)
}
} else if stdin != nil {
// Load from STDIN
response, err = cli.ImageLoadFromTar(context.Background(), stdin, *quiet)
}
if err != nil {
return err
}
if response == nil {
return nil
}
defer response.Body.Close()
if response.JSON {
return jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
_, err = io.Copy(cli.out, response.Body)
return err
}

View File

@@ -1,156 +0,0 @@
package client
import (
"bufio"
"fmt"
"io"
"os"
"runtime"
"strings"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/term"
)
// CmdLogin logs in or registers a user to a Docker registry service.
//
// If no server is specified, the user will be logged into or registered to the registry's index server.
//
// Usage: docker login SERVER
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
cmd.Require(flag.Max, 1)
flUser := cmd.String([]string{"u", "-username"}, "", "Username")
flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
flEmail := cmd.String([]string{"e", "-email"}, "", "Email")
cmd.ParseFlags(args, true)
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" {
cli.in = os.Stdin
}
ctx := context.Background()
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cli.electAuthServer(ctx)
}
authConfig, err := cli.configureAuth(*flUser, *flPassword, *flEmail, serverAddress)
if err != nil {
return err
}
response, err := cli.client.RegistryLogin(ctx, authConfig)
if err != nil {
if client.IsErrUnauthorized(err) {
delete(cli.configFile.AuthConfigs, serverAddress)
if err2 := cli.configFile.Save(); err2 != nil {
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
}
}
return err
}
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Error saving config file: %v", err)
}
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
if response.Status != "" {
fmt.Fprintf(cli.out, "%s\n", response.Status)
}
return nil
}
func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
if configDefault == "" {
fmt.Fprintf(cli.out, "%s: ", prompt)
} else {
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
}
}
func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress string) (types.AuthConfig, error) {
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
if !ok {
authconfig = types.AuthConfig{}
}
authconfig.Username = strings.TrimSpace(authconfig.Username)
if flUser = strings.TrimSpace(flUser); flUser == "" {
cli.promptWithDefault("Username", authconfig.Username)
flUser = readInput(cli.in, cli.out)
flUser = strings.TrimSpace(flUser)
if flUser == "" {
flUser = authconfig.Username
}
}
if flUser == "" {
return authconfig, fmt.Errorf("Error: Non-null Username Required")
}
if flPassword == "" {
oldState, err := term.SaveState(cli.inFd)
if err != nil {
return authconfig, err
}
fmt.Fprintf(cli.out, "Password: ")
term.DisableEcho(cli.inFd, oldState)
flPassword = readInput(cli.in, cli.out)
fmt.Fprint(cli.out, "\n")
term.RestoreTerminal(cli.inFd, oldState)
if flPassword == "" {
return authconfig, fmt.Errorf("Error: Password Required")
}
}
// Assume that a different username means they may not want to use
// the email from the config file, so prompt it
if flUser != authconfig.Username {
if flEmail == "" {
cli.promptWithDefault("Email", authconfig.Email)
flEmail = readInput(cli.in, cli.out)
if flEmail == "" {
flEmail = authconfig.Email
}
}
} else {
// However, if they don't override the username use the
// email from the cmd line if specified. IOW, allow
// then to change/override them. And if not specified, just
// use what's in the config file
if flEmail == "" {
flEmail = authconfig.Email
}
}
authconfig.Username = flUser
authconfig.Password = flPassword
authconfig.Email = flEmail
authconfig.ServerAddress = serverAddress
cli.configFile.AuthConfigs[serverAddress] = authconfig
return authconfig, nil
}
func readInput(in io.Reader, out io.Writer) string {
reader := bufio.NewReader(in)
line, _, err := reader.ReadLine()
if err != nil {
fmt.Fprintln(out, err.Error())
os.Exit(1)
}
return string(line)
}

View File

@@ -1,42 +0,0 @@
package client
import (
"fmt"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdLogout logs a user out from a Docker registry.
//
// If no server is specified, the user will be logged out from the registry's index server.
//
// Usage: docker logout [SERVER]
func (cli *DockerCli) CmdLogout(args ...string) error {
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cli.electAuthServer(context.Background())
}
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
return nil
}
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
delete(cli.configFile.AuthConfigs, serverAddress)
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Failed to save docker config: %v", err)
}
return nil
}

View File

@@ -1,65 +0,0 @@
package client
import (
"fmt"
"io"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stdcopy"
)
var validDrivers = map[string]bool{
"json-file": true,
"journald": true,
}
// CmdLogs fetches the logs of a given container.
//
// docker logs [OPTIONS] CONTAINER
func (cli *DockerCli) CmdLogs(args ...string) error {
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true)
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
ctx := context.Background()
c, err := cli.client.ContainerInspect(ctx, name)
if err != nil {
return err
}
if !validDrivers[c.HostConfig.LogConfig.Type] {
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: *since,
Timestamps: *times,
Follow: *follow,
Tail: *tail,
}
responseBody, err := cli.client.ContainerLogs(ctx, name, options)
if err != nil {
return err
}
defer responseBody.Close()
if c.Config.Tty {
_, err = io.Copy(cli.out, responseBody)
} else {
_, err = stdcopy.StdCopy(cli.out, cli.err, responseBody)
}
return err
}

View File

@@ -1,379 +0,0 @@
package client
import (
"fmt"
"net"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hyper-api/types/network"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringid"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
)
// CmdNetwork is the parent subcommand for all network commands
//
// Usage: docker network <COMMAND> [OPTIONS]
func (cli *DockerCli) Network(args ...string) error {
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdNetworkCreate creates a new network with a given name
//
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
func (cli *DockerCli) NetworkCreate(args ...string) error {
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
flOpts := opts.NewMapOpts(nil, nil)
flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
flIpamSubnet := opts.NewListOpts(nil)
flIpamIPRange := opts.NewListOpts(nil)
flIpamGateway := opts.NewListOpts(nil)
flIpamAux := opts.NewMapOpts(nil, nil)
flIpamOpt := opts.NewMapOpts(nil, nil)
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Set the default driver to "" if the user didn't set the value.
// That way we can know whether it was user input or not.
driver := *flDriver
if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
driver = ""
}
ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
if err != nil {
return err
}
// Construct network create request body
nc := types.NetworkCreate{
Driver: driver,
IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg, Options: flIpamOpt.GetAll()},
Options: flOpts.GetAll(),
CheckDuplicate: true,
Internal: *flInternal,
}
resp, err := cli.client.NetworkCreate(context.Background(), cmd.Arg(0), nc)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", resp.ID)
return nil
}
// CmdNetworkRm deletes one or more networks
//
// Usage: docker network rm NETWORK-NAME|NETWORK-ID [NETWORK-NAME|NETWORK-ID...]
func (cli *DockerCli) NetworkRm(args ...string) error {
cmd := Cli.Subcmd("network rm", []string{"NETWORK [NETWORK...]"}, "Deletes one or more networks", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, net := range cmd.Args() {
if err := cli.client.NetworkRemove(context.Background(), net); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdNetworkConnect connects a container to a network
//
// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
func (cli *DockerCli) NetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
flAliases := opts.NewListOpts(nil)
cmd.Var(&flAliases, []string{"-alias"}, "Add network-scoped alias for the container")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
epConfig := &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: *flIPAddress,
IPv6Address: *flIPv6Address,
},
Links: flLinks.GetAll(),
Aliases: flAliases.GetAll(),
}
return cli.client.NetworkConnect(context.Background(), cmd.Arg(0), cmd.Arg(1), epConfig)
}
// CmdNetworkDisconnect disconnects a container from a network
//
// Usage: docker network disconnect <NETWORK> <CONTAINER>
func (cli *DockerCli) NetworkDisconnect(args ...string) error {
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a network")
cmd.Require(flag.Exact, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
return cli.client.NetworkDisconnect(context.Background(), cmd.Arg(0), cmd.Arg(1), *force)
}
// CmdNetworkLs lists all the networks managed by docker daemon
//
// Usage: docker network ls [OPTIONS]
func (cli *DockerCli) NetworkLs(args ...string) error {
cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
netFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if netFilterArgs, err = filters.ParseFlag(f, netFilterArgs); err != nil {
return err
}
}
options := types.NetworkListOptions{
Filters: netFilterArgs,
}
networkResources, err := cli.client.NetworkList(context.Background(), options)
if err != nil {
return err
}
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
// unless quiet (-q) is specified, print field titles
if !*quiet {
fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
}
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
driver := networkResource.Driver
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
driver)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}
// CmdNetworkInspect inspects the network object for more details
//
// Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
func (cli *DockerCli) NetworkInspect(args ...string) error {
cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on one or more networks", false)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.NetworkInspect(context.Background(), name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// Consolidates the ipam configuration as a group from different related configurations
// user can configure network with multiple non-overlapping subnets and hence it is
// possible to correlate the various related parameters and consolidate them.
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
// structured ipam data.
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
}
iData := map[string]*network.IPAMConfig{}
// Populate non-overlapping subnets into consolidation map
for _, s := range subnets {
for k := range iData {
ok1, err := subnetMatches(s, k)
if err != nil {
return nil, err
}
ok2, err := subnetMatches(k, s)
if err != nil {
return nil, err
}
if ok1 || ok2 {
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
}
}
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
}
// Validate and add valid ip ranges
for _, r := range ranges {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, r)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].IPRange != "" {
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
}
d := iData[s]
d.IPRange = r
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for range %s", r)
}
}
// Validate and add valid gateways
for _, g := range gateways {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, g)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].Gateway != "" {
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
}
d := iData[s]
d.Gateway = g
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
}
}
// Validate and add aux-addresses
for key, aa := range auxaddrs {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, aa)
if err != nil {
return nil, err
}
if !ok {
continue
}
iData[s].AuxAddress[key] = aa
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
}
}
idl := []network.IPAMConfig{}
for _, v := range iData {
idl = append(idl, *v)
}
return idl, nil
}
func subnetMatches(subnet, data string) (bool, error) {
var (
ip net.IP
)
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
}
} else {
ip = net.ParseIP(data)
}
return s.Contains(ip), nil
}
func networkUsage() string {
networkCommands := map[string]string{
"create": "Create a network",
"connect": "Connect container to a network",
"disconnect": "Disconnect container from a network",
"inspect": "Display detailed network information",
"ls": "List all networks",
"rm": "Remove a network",
}
help := "Commands:\n"
for cmd, description := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
}
help += fmt.Sprintf("\nRun 'hyper network COMMAND --help' for more information on a command.")
return help
}

View File

@@ -1,36 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdPause pauses all processes within one or more containers.
//
// Usage: docker pause CONTAINER [CONTAINER...]
func (cli *DockerCli) Pause(args ...string) error {
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["pause"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerPause(ctx, name); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,63 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/go-connections/nat"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdPort lists port mappings for a container.
// If a private port is specified, it also shows the public-facing port that is NATed to the private port.
//
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
func (cli *DockerCli) CmdPort(args ...string) error {
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, Cli.DockerCommands["port"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
c, err := cli.client.ContainerInspect(ctx, cmd.Arg(0))
if err != nil {
return err
}
if cmd.NArg() == 2 {
var (
port = cmd.Arg(1)
proto = "tcp"
parts = strings.SplitN(port, "/", 2)
)
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = parts[1]
}
natPort := port + "/" + proto
newP, err := nat.NewPort(proto, port)
if err != nil {
return err
}
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
}
return nil
}
return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
}
for from, frontends := range c.NetworkSettings.Ports {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
}
}
return nil
}

View File

@@ -1,88 +0,0 @@
package client
import (
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hypercli/api/client/formatter"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
)
// CmdPs outputs a list of Docker containers.
//
// Usage: docker ps [OPTIONS]
func (cli *DockerCli) CmdPs(args ...string) error {
var (
err error
psFilterArgs = filters.NewArgs()
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
noTrunc = cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container (includes all states)")
since = cmd.String([]string{"#-since"}, "", "Show containers created since Id or Name (includes all states)")
before = cmd.String([]string{"#-before"}, "", "Only show containers created before Id or Name")
last = cmd.Int([]string{"n"}, -1, "Show n last created containers (includes all states)")
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter = opts.NewListOpts(nil)
)
cmd.Require(flag.Exact, 0)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.ParseFlags(args, true)
if *last == -1 && *nLatest {
*last = 1
}
// Consolidate all filter flags, and sanity check them.
// They'll get processed in the daemon/server.
for _, f := range flFilter.GetAll() {
if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
return err
}
}
options := types.ContainerListOptions{
All: *all,
Limit: *last,
Since: *since,
Before: *before,
Size: *size,
Filter: psFilterArgs,
}
containers, err := cli.client.ContainerList(context.Background(), options)
if err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 && !*quiet {
f = cli.PsFormat()
} else {
f = "table"
}
}
psCtx := formatter.ContainerContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Trunc: !*noTrunc,
},
Size: *size,
Containers: containers,
}
psCtx.Write()
return nil
}

View File

@@ -1,94 +0,0 @@
package client
import (
"errors"
"fmt"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
)
// CmdPull pulls an image or a repository from the registry.
//
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
func (cli *DockerCli) CmdPull(args ...string) error {
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true)
allTags := cmd.Bool([]string{}, false, "Download all tagged images in the repository")
addTrustedFlags(cmd, true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
remote := cmd.Arg(0)
distributionRef, err := reference.ParseNamed(remote)
if err != nil {
return err
}
if *allTags && !reference.IsNameOnly(distributionRef) {
return errors.New("tag can't be used with --all-tags/-a")
}
if err = cli.checkCloudConfig(); err != nil {
return err
}
if !*allTags && reference.IsNameOnly(distributionRef) {
distributionRef = reference.WithDefaultTag(distributionRef)
fmt.Fprintf(cli.out, "Using default tag: %s\n", reference.DefaultTag)
}
var tag string
switch x := distributionRef.(type) {
case reference.Canonical:
tag = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
ref := registry.ParseReference(tag)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
if err != nil {
return err
}
ctx := context.Background()
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
if isTrusted() && !ref.HasDigest() {
// Check if tag is digest
return cli.trustedPull(ctx, repoInfo, ref, authConfig, requestPrivilege)
}
return cli.imagePullPrivileged(ctx, authConfig, distributionRef.String(), requestPrivilege, *allTags)
}
func (cli *DockerCli) imagePullPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImagePullOptions{
PrivilegeFunc: requestPrivilege,
RegistryAuth: encodedAuth,
All: all,
}
responseBody, err := cli.client.ImagePull(context.Background(), ref, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}

View File

@@ -1,66 +0,0 @@
package client
import (
"io"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
"golang.org/x/net/context"
)
// CmdPush pushes an image or repository to the registry.
//
// Usage: hyper push NAME[:TAG]
func (cli *DockerCli) CmdPush(args ...string) error {
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, Cli.DockerCommands["push"].Description, true)
addTrustedFlags(cmd, false)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
ref, err := reference.ParseNamed(cmd.Arg(0))
if err != nil {
return err
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
ctx := context.Background()
// Resolve the Auth config relevant for this server
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
if isTrusted() {
return cli.trustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
}
responseBody, err := cli.imagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
func (cli *DockerCli) imagePushPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return nil, err
}
options := types.ImagePushOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
}
return cli.client.ImagePush(ctx, ref, options)
}

View File

@@ -1,34 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRename renames a container.
//
// Usage: docker rename OLD_NAME NEW_NAME
func (cli *DockerCli) CmdRename(args ...string) error {
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, Cli.DockerCommands["rename"].Description, true)
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
oldName := strings.TrimSpace(cmd.Arg(0))
newName := strings.TrimSpace(cmd.Arg(1))
if oldName == "" || newName == "" {
return fmt.Errorf("Error: Neither old nor new names may be empty")
}
if err := cli.client.ContainerRename(context.Background(), oldName, newName); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
return fmt.Errorf("Error: failed to rename container named %s", oldName)
}
return nil
}

View File

@@ -1,37 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRestart restarts one or more containers.
//
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRestart(args ...string) error {
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["restart"].Description, true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerRestart(ctx, name, *nSeconds); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,61 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRm removes one or more containers.
//
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["rm"].Description, true)
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
link := cmd.Bool([]string{"l", "-link"}, false, "Remove the specified link")
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if name == "" {
return fmt.Errorf("Container name cannot be empty")
}
name = strings.Trim(name, "/")
warnings, err := cli.removeContainer(ctx, name, *v, *link, *force)
if err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
for _, w := range warnings {
fmt.Fprintf(cli.out, "NOTICE : %s\n", w)
}
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}
func (cli *DockerCli) removeContainer(ctx context.Context, containerID string, removeVolumes, removeLinks, force bool) ([]string, error) {
options := types.ContainerRemoveOptions{
RemoveVolumes: removeVolumes,
RemoveLinks: removeLinks,
Force: force,
}
warnings, err := cli.client.ContainerRemove(ctx, containerID, options)
if err != nil {
return nil, err
}
return warnings, nil
}

View File

@@ -1,60 +0,0 @@
package client
import (
"fmt"
"net/url"
"strings"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRmi removes all images with the specified name(s).
//
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) CmdRmi(args ...string) error {
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["rmi"].Description, true)
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
v := url.Values{}
if *force {
v.Set("force", "1")
}
if *noprune {
v.Set("noprune", "1")
}
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
options := types.ImageRemoveOptions{
Force: *force,
PruneChildren: !*noprune,
}
dels, err := cli.client.ImageRemove(ctx, name, options)
if err != nil {
errs = append(errs, err.Error())
} else {
for _, del := range dels {
if del.Deleted != "" {
fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted)
} else {
fmt.Fprintf(cli.out, "Untagged: %s\n", del.Untagged)
}
}
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,312 +0,0 @@
package client
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/resolvconf/dns"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
derr "github.com/hyperhq/hypercli/errors"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/promise"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/pkg/stringid"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
type InitVolume struct {
Source string
Destination string
Name string
}
func (cid *cidFile) Close() error {
cid.file.Close()
if !cid.written {
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
}
}
return nil
}
func (cid *cidFile) Write(id string) error {
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
}
cid.written = true
return nil
}
// if container start fails with 'command not found' error, return 127
// if container start fails with 'command cannot be invoked' error, return 126
// return 125 for generic docker daemon failures
func runStartContainerErr(err error) error {
trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
statusError := Cli.StatusError{}
derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
switch trimmedErr {
case derrCmdNotFound:
statusError = Cli.StatusError{StatusCode: 127}
case derrCouldNotInvoke:
statusError = Cli.StatusError{StatusCode: 126}
case derrNoSuchImage, derrNoSuchImageTag:
statusError = Cli.StatusError{StatusCode: 125}
default:
statusError = Cli.StatusError{StatusCode: 125}
}
return statusError
}
// CmdRun runs a command in a new container.
//
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdRun(args ...string) error {
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
flSigProxy = cmd.Bool([]string{}, true, "Proxy received signals to the process")
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
flDetachKeys = cmd.String([]string{}, "", "Override the key sequence for detaching a container")
flAttach *opts.ListOpts
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
ErrConflictProtectionAutoRemove = fmt.Errorf("Conflicting options: --rm and --protection")
)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
// just in case the Parse does not exit
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(125)
}
if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
fmt.Fprintf(cli.err, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
}
if len(hostConfig.DNS) > 0 {
// check the DNS settings passed via --dns against
// localhost regexp to warn if they are trying to
// set a DNS to a localhost address
for _, dnsIP := range hostConfig.DNS {
if dns.IsLocalhost(dnsIP) {
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
break
}
}
}
if config.Image == "" {
cmd.Usage()
return nil
}
config.ArgsEscaped = false
if !*flDetach {
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
return err
}
} else {
if fl := cmd.Lookup("-attach"); fl != nil {
flAttach = fl.Value.(*opts.ListOpts)
if flAttach.Len() != 0 {
return ErrConflictAttachDetach
}
}
if *flAutoRemove {
return ErrConflictDetachAutoRemove
}
config.AttachStdin = false
config.AttachStdout = false
config.AttachStderr = false
config.StdinOnce = false
}
// Disable flSigProxy when in TTY mode
sigProxy := *flSigProxy
if config.Tty {
sigProxy = false
}
// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
}
ctx := context.Background()
createResponse, err := cli.createContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
cmd.ReportError(err.Error(), true)
return runStartContainerErr(err)
}
if sigProxy {
sigc := cli.forwardAllSignals(ctx, createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
)
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchronous to allow the client to write to stdin before having to read the ID
waitDisplayID = make(chan struct{})
go func() {
defer close(waitDisplayID)
fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
}()
}
if *flAutoRemove {
if hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure() {
return ErrConflictRestartPolicyAndAutoRemove
}
if _, ok := config.Labels["sh_hyper_container_protection"]; ok {
return ErrConflictProtectionAutoRemove
}
}
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
var (
out, stderr io.Writer
in io.ReadCloser
)
if config.AttachStdin {
in = cli.in
}
if config.AttachStdout {
out = cli.out
}
if config.AttachStderr {
if config.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
if *flDetachKeys != "" {
cli.configFile.DetachKeys = *flDetachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: config.AttachStdin,
Stdout: config.AttachStdout,
Stderr: config.AttachStderr,
DetachKeys: cli.configFile.DetachKeys,
}
resp, err := cli.client.ContainerAttach(ctx, createResponse.ID, options)
if err != nil {
return err
}
if in != nil && config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
})
}
if *flAutoRemove {
defer func() {
if _, err := cli.removeContainer(ctx, createResponse.ID, true, false, false); err != nil {
fmt.Fprintf(cli.err, "%v\n", err)
}
}()
}
//start the container
if err := cli.client.ContainerStart(ctx, createResponse.ID, ""); err != nil {
cmd.ReportError(err.Error(), false)
return runStartContainerErr(err)
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, createResponse.ID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
}
// Detached mode: wait for the id to be displayed and return.
if !config.AttachStdout && !config.AttachStderr {
// Detached mode
<-waitDisplayID
return nil
}
var status int
// Attached mode
if *flAutoRemove {
// Warn user if they detached us
js, err := cli.client.ContainerInspect(ctx, createResponse.ID)
if err != nil {
return runStartContainerErr(err)
}
if js.State.Running == true || js.State.Paused == true {
fmt.Fprintf(cli.err, "Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
stringid.TruncateID(createResponse.ID))
}
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
return runStartContainerErr(err)
}
if _, status, err = getExitCode(ctx, cli, createResponse.ID); err != nil {
return err
}
} else {
// No Autoremove: Simply retrieve the exit code
if !config.Tty {
// In non-TTY mode, we can't detach, so we must wait for container exit
if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
return err
}
} else {
// In TTY mode, there is a race: if the process dies too slowly, the state could
// be updated after the getExitCode call and result in the wrong exit code being reported
if _, status, err = getExitCode(ctx, cli, createResponse.ID); err != nil {
return err
}
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}

View File

@@ -1,42 +0,0 @@
package client
import (
"errors"
"io"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdSave saves one or more images to a tar archive.
//
// The tar archive is written to STDOUT by default, or written to a file.
//
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) Save(args ...string) error {
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if *outfile == "" && cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
responseBody, err := cli.client.ImageSave(context.Background(), cmd.Args())
if err != nil {
return err
}
defer responseBody.Close()
if *outfile == "" {
_, err := io.Copy(cli.out, responseBody)
return err
}
return copyToFile(*outfile, responseBody)
}

View File

@@ -1,99 +0,0 @@
package client
import (
"fmt"
"net/url"
"sort"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
registrytypes "github.com/hyperhq/hyper-api/types/registry"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringutils"
"github.com/hyperhq/hypercli/registry"
)
// CmdSearch searches the Docker Hub for images.
//
// Usage: docker search [OPTIONS] TERM
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
stars := cmd.Uint([]string{"s", "-stars"}, 0, "Only displays with at least x stars")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
v := url.Values{}
v.Set("term", name)
indexInfo, err := registry.ParseSearchIndexInfo(name)
if err != nil {
return err
}
if err = cli.checkCloudConfig(); err != nil {
return err
}
ctx := context.Background()
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, indexInfo)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImageSearchOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
}
unorderedResults, err := cli.client.ImageSearch(ctx, name, options)
if err != nil {
return err
}
results := searchResultsByStars(unorderedResults)
sort.Sort(results)
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) {
continue
}
desc := strings.Replace(res.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !*noTrunc && len(desc) > 45 {
desc = stringutils.Truncate(desc, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
if res.IsOfficial {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\t")
if res.IsAutomated {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\n")
}
w.Flush()
return nil
}
// SearchResultsByStars sorts search results in descending order by number of stars.
type searchResultsByStars []registrytypes.SearchResult
func (r searchResultsByStars) Len() int { return len(r) }
func (r searchResultsByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }

View File

@@ -1,423 +0,0 @@
package client
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"text/tabwriter"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"github.com/hyperhq/hyper-api/types/strslice"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdService is the parent subcommand for all service commands
//
// Usage: docker service <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdService(args ...string) error {
cmd := Cli.Subcmd("service", []string{"COMMAND [OPTIONS]"}, serviceUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdServiceCreate creates a new service with a given name
//
// Usage: hyper service create [OPTIONS] COUNT
func (cli *DockerCli) CmdServiceCreate(args ...string) error {
cmd := Cli.Subcmd("service create", []string{"IMAGE"}, "Create a new service", false)
var (
flSecurityGroups = ropts.NewListOpts(nil)
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flVolumes = ropts.NewListOpts(nil)
flLabelsFile = ropts.NewListOpts(nil)
flName = cmd.String([]string{"-name"}, "", "Service name")
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of service containers (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flSSLCert = cmd.String([]string{"-ssl-cert"}, "", "SSL cert file for httpsTerm service")
flServicePort = cmd.Int([]string{"-service-port"}, 0, "Publish port of the service")
flContainerPort = cmd.Int([]string{"-container-port"}, 0, "Container port of the service, default same with service port")
flReplicas = cmd.Int([]string{"-replicas"}, -1, "Number of containers belonging to this service")
flHealthCheckInterval = cmd.Int([]string{"-health-check-interval"}, 3, "Interval in seconds for health checking the containers")
flHealthCheckFall = cmd.Int([]string{"-health-check-fall"}, 3, "Number of consecutive valid health checks before considering the server as DOWN")
flHealthCheckRise = cmd.Int([]string{"-health-check-rise"}, 2, "Number of consecutive valid health checks before considering the server as UP")
flSessionAffinity = cmd.Bool([]string{"-session-affinity"}, false, "Whether the service uses sticky sessions")
flAlgorithm = cmd.String([]string{"-algorithm"}, types.LBAlgorithmRoundRobin, "Algorithm of the service (e.g. roundrobin, leastconn, source)")
flProtocol = cmd.String([]string{"-protocol"}, types.LBProtocolTCP, "Protocol of the service (e.g. http, https, tcp, httpsTerm).")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flVolumes, []string{"v", "--volume"}, "Volume for each container")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *flReplicas <= 0 {
return fmt.Errorf("replicas must be bigger than 0")
}
var binds = map[string]struct{}{}
// add any bind targets to the list of container services
for bind := range flVolumes.GetMap() {
binds[bind] = struct{}{}
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if _, _, err = cli.client.ImageInspectWithRaw(context.Background(), image, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(context.Background(), image); err != nil {
return err
}
}
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
var sgs = map[string]struct{}{}
for sg := range flSecurityGroups.GetMap() {
sgs[sg] = struct{}{}
}
sslData := []byte{}
if *flSSLCert != "" {
sslData, err = ioutil.ReadFile(*flSSLCert)
}
sv := types.Service{
Name: *flName,
Image: image,
WorkingDir: *flWorkingDir,
ContainerSize: *flContainerSize,
ServicePort: *flServicePort,
ContainerPort: *flContainerPort,
Replicas: *flReplicas,
Entrypoint: entrypoint,
Cmd: runCmd,
Env: envVariables,
Volumes: binds,
Labels: opts.ConvertKVStringsToMap(labels),
SecurityGroups: sgs,
Tty: *flTty,
Stdin: *flStdin,
NetMode: *flNetMode,
StopSignal: *flStopSignal,
HealthCheckInterval: *flHealthCheckInterval,
HealthCheckFall: *flHealthCheckFall,
HealthCheckRise: *flHealthCheckRise,
Algorithm: *flAlgorithm,
Protocol: *flProtocol,
SessionAffinity: *flSessionAffinity,
SSLCert: string(sslData),
}
service, err := cli.client.ServiceCreate(context.Background(), sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Service %s is created.\n", service.Name)
return nil
}
// CmdServiceDelete deletes one or more services
//
// Usage: hyper service rm service [service...]
func (cli *DockerCli) CmdServiceRm(args ...string) error {
cmd := Cli.Subcmd("service rm", []string{"service [service...]"}, "Remove one or more services", false)
flKeep := cmd.Bool([]string{"-keep"}, false, "Keep the service container")
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, sn := range cmd.Args() {
if err := cli.client.ServiceDelete(context.Background(), sn, *flKeep); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", sn)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdServiceLs lists all the services
//
// Usage: hyper service ls [OPTIONS]
func (cli *DockerCli) CmdServiceLs(args ...string) error {
cmd := Cli.Subcmd("service ls", nil, "Lists services", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
serviceFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if serviceFilterArgs, err = filters.ParseFlag(f, serviceFilterArgs); err != nil {
return err
}
}
options := types.ServiceListOptions{
Filters: serviceFilterArgs,
}
services, err := cli.client.ServiceList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tFIP\tContainers\tStatus\tMessage\n")
for _, service := range services {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", service.Name, service.FIP, showContainersInList(service.Containers), service.Status, service.Message)
}
w.Flush()
return nil
}
func showContainersInList(containers []string) string {
var result = []string{}
for _, c := range containers {
result = append(result, c[:12])
}
if len(result) > 2 {
return strings.Join([]string{result[0], result[1], "..."}, ", ")
}
return strings.Join(result, ", ")
}
// CmdServiceInspect
//
// Usage: docker service inspect [OPTIONS] service [service...]
func (cli *DockerCli) CmdServiceInspect(args ...string) error {
cmd := Cli.Subcmd("service inspect", []string{"service [service...]"}, "Display detailed information on the given service", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.ServiceInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdServiceScale
//
// Usage: hyper service scale [OPTIONS] SERVICE=REPLICAS [SERVICE=REPLICAS...]
func (cli *DockerCli) CmdServiceScale(args ...string) error {
cmd := Cli.Subcmd("service scale", []string{"SERVICE=REPLICAS [SERVICE=REPLICAS...]"}, "", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
for _, sr := range cmd.Args() {
fields := strings.SplitN(sr, "=", 2)
if len(fields) != 2 {
fmt.Fprintf(cli.err, "invalid argument")
continue
}
replicas, err := strconv.Atoi(fields[1])
if err != nil {
fmt.Fprintf(cli.err, "%v\n", err)
continue
}
sv := types.ServiceUpdate{
Replicas: &replicas,
}
service, err := cli.client.ServiceUpdate(ctx, fields[0], sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
}
return nil
}
// CmdServiceRolling_update
//
// Usage: hyper service rolling-update [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceRolling_update(args ...string) error {
cmd := Cli.Subcmd("service rolling-update", []string{"SERVICE [SERVICE...]"}, "Perform a rolling update of the given service", true)
flImage := cmd.String([]string{"-image"}, "", "New container image")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
if len(*flImage) == 0 {
return fmt.Errorf("image is required for rolling-update")
}
ctx := context.Background()
if _, _, err := cli.client.ImageInspectWithRaw(ctx, *flImage, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(ctx, *flImage); err != nil {
return err
}
}
for _, sr := range cmd.Args() {
sv := types.ServiceUpdate{
Image: flImage,
}
service, err := cli.client.ServiceUpdate(ctx, sr, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Rolling-update is requested for service %s.\n", service.Name)
}
return nil
}
// CmdServiceAttach_fip
//
// Usage: hyper service attach_fip [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceAttach_fip(args ...string) error {
cmd := Cli.Subcmd("service attach-fip", []string{"SERVICE"}, "Attach a fip to the service", true)
flFip := cmd.String([]string{"-fip"}, "", "Attach a fip to the service")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
if *flFip == "" {
return fmt.Errorf("Error: please provide the attached FIP via --fip")
}
ctx := context.Background()
sv := types.ServiceUpdate{
FIP: flFip,
}
service, err := cli.client.ServiceUpdate(ctx, cmd.Arg(0), sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
return nil
}
// CmdServiceDetach_fip
//
// Usage: hyper service detach_fip [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceDetach_fip(args ...string) error {
cmd := Cli.Subcmd("service detach-fip", []string{"SERVICE [SERVICE...]"}, "Detach a fip from the service", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
fip := ""
for _, sr := range cmd.Args() {
sv := types.ServiceUpdate{
FIP: &fip,
}
service, err := cli.client.ServiceUpdate(ctx, sr, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
}
return nil
}
func serviceUsage() string {
serviceCommands := [][]string{
{"create", "Create a service"},
{"inspect", "Display detailed information on the given service"},
{"ls", "List all services"},
{"scale", "Scale the service"},
{"rolling-update", "Perform a rolling update of the given service"},
{"attach-fip", "Attach a fip to the service"},
{"detach-fip", "Detach the fip from the service"},
{"rm", "Remove one or more services"},
}
help := "Commands:\n"
for _, cmd := range serviceCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper service COMMAND --help' for more information on a command.")
return help
}

View File

@@ -1,167 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
"gopkg.in/yaml.v2"
)
// CmdSg is the parent subcommand for all sg commands
//
// Usage: hyper sg <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdSg(args ...string) error {
cmd := Cli.Subcmd("sg", []string{"COMMAND [OPTIONS]"}, sgUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdSgCreate creates a new sg with a given name
//
// Usage: hyper sg create [OPTIONS] NAME
func (cli *DockerCli) CmdSgCreate(args ...string) error {
cmd := Cli.Subcmd("sg create", []string{"NAME"}, "Create a new security group", false)
file := cmd.String([]string{"f", "-file"}, "", "Yaml file to create security group")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
data, err := os.Open(*file)
if err != nil {
return err
}
err = cli.client.SgCreate(context.Background(), cmd.Arg(0), data)
if err != nil {
return err
}
return nil
}
// CmdSgRm removes a sg with a given name
//
// Usage: hyper sg rm [OPTIONS] NAME
func (cli *DockerCli) CmdSgRm(args ...string) error {
cmd := Cli.Subcmd("sg rm", []string{"NAME"}, "Remove a security group", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
err = cli.client.SgRm(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
return nil
}
// CmdSgLs list security groups
//
// Usage: hyper sg ls [OPTIONS]
func (cli *DockerCli) CmdSgLs(args ...string) error {
cmd := Cli.Subcmd("sg ls", []string{}, "List security groups", false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sgs, err := cli.client.SgLs(context.Background())
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tDescription")
fmt.Fprintf(w, "\n")
for _, sg := range sgs {
fmt.Fprintf(w, "%s\t%s\n", sg.GroupName, sg.Description)
}
w.Flush()
return nil
}
// CmdSgInspect Inspect security groups
//
// Usage: hyper sg inspect [OPTIONS] NAME
func (cli *DockerCli) CmdSgInspect(args ...string) error {
cmd := Cli.Subcmd("sg inspect", []string{"NAME"}, "Inspect the security group", false)
output := cmd.String([]string{"o", "-output"}, "json", "Output format with inspect operation (e.g. yaml or json)")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sg, err := cli.client.SgInspect(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
var data []byte
if *output == "json" {
data, err = json.MarshalIndent(sg, "", "\t")
} else {
data, err = yaml.Marshal(sg)
}
if err != nil {
return err
}
fmt.Printf("%s\n", string(data))
return nil
}
// CmdSgUpdate Update the security group
//
// Usage: hyper sg update [OPTIONS] NAME
func (cli *DockerCli) CmdSgUpdate(args ...string) error {
cmd := Cli.Subcmd("sg update", []string{"NAME"}, "Update the security group", false)
file := cmd.String([]string{"f", "-file"}, "", "Yaml file to update security group")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
data, err := os.Open(*file)
if err != nil {
return err
}
err = cli.client.SgUpdate(context.Background(), cmd.Arg(0), data)
if err != nil {
return err
}
return nil
}
func sgUsage() string {
sgCommands := [][]string{
{"create", "Create a new security group"},
{"ls", "List all security groups"},
{"rm", "Remove a security group"},
{"inspect", "Inspect the security group"},
{"update", "Update the security group"},
}
help := "Commands:\n"
for _, cmd := range sgCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper sg COMMAND --help' for more information on a command.")
return help
}

View File

@@ -1,160 +0,0 @@
package client
import (
"fmt"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdSnapshot is the parent subcommand for all snapshot commands
//
// Usage: docker snapshot <COMMAND> <OPTS>
func (cli *DockerCli) CmdSnapshot(args ...string) error {
description := Cli.DockerCommands["snapshot"].Description + "\n\nSnapshots:\n"
commands := [][]string{
{"create", "Create a snapshot"},
{"inspect", "Return low-level information on a snapshot"},
{"ls", "List snapshots"},
{"rm", "Remove a snapshot"},
}
for _, cmd := range commands {
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
description += "\nRun 'hyper snapshot COMMAND --help' for more information on a command"
cmd := Cli.Subcmd("snapshot", []string{"[COMMAND]"}, description, false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdSnapshotLs outputs a list of Docker snapshots.
//
// Usage: hyper snapshot ls [OPTIONS]
func (cli *DockerCli) CmdSnapshotLs(args ...string) error {
cmd := Cli.Subcmd("snapshot ls", nil, "List snapshots", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display snapshot names")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
var err error
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
if err != nil {
return err
}
}
snapshots, err := cli.client.SnapshotList(context.Background(), volFilterArgs)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
for _, warn := range snapshots.Warnings {
fmt.Fprintln(cli.err, warn)
}
fmt.Fprintf(w, "Snapshot Name \tVolume\tSize")
fmt.Fprintf(w, "\n")
}
for _, vol := range snapshots.Snapshots {
if *quiet {
fmt.Fprintln(w, vol.Name)
continue
}
fmt.Fprintf(w, "%s\t%s\t%d\n", vol.Name, vol.Volume, vol.Size)
}
w.Flush()
return nil
}
// CmdSnapshotInspect displays low-level information on one or more snapshots.
//
// Usage: docker snapshot inspect [OPTIONS] snapshot [snapshot...]
func (cli *DockerCli) CmdSnapshotInspect(args ...string) error {
cmd := Cli.Subcmd("snapshot inspect", []string{"snapshot [snapshot...]"}, "Return low-level information on a snapshot", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.SnapshotInspect(context.Background(), name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdSnapshotCreate creates a new snapshot.
//
// Usage: docker snapshot create [OPTIONS]
func (cli *DockerCli) CmdSnapshotCreate(args ...string) error {
cmd := Cli.Subcmd("snapshot create", []string{"-v volume"}, "Create a snapshot", true)
flForce := cmd.Bool([]string{"f", "-force"}, false, "Force to create snapshot, needed if volume is in use")
flVolume := cmd.String([]string{"v", "-volume"}, "", "Specify volume to create snapshot")
flName := cmd.String([]string{"-name"}, "", "Specify snapshot name")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volReq := types.SnapshotCreateRequest{
Name: *flName,
Volume: *flVolume,
Force: *flForce,
}
vol, err := cli.client.SnapshotCreate(context.Background(), volReq)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", vol.Name)
return nil
}
// CmdSnapshotRm removes one or more snapshots.
//
// Usage: docker snapshot rm snapshot [snapshot...]
func (cli *DockerCli) CmdSnapshotRm(args ...string) error {
cmd := Cli.Subcmd("snapshot rm", []string{"snapshot [snapshot...]"}, "Remove a snapshot", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var status = 0
for _, name := range cmd.Args() {
if err := cli.client.SnapshotRemove(context.Background(), name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", name)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}

View File

@@ -1,158 +0,0 @@
package client
import (
"fmt"
"io"
"os"
"strings"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/promise"
"github.com/hyperhq/hypercli/pkg/signal"
)
func (cli *DockerCli) forwardAllSignals(ctx context.Context, cid string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {
for s := range sigc {
if s == signal.SIGCHLD {
continue
}
var sig string
for sigStr, sigN := range signal.SignalMap {
if sigN == s {
sig = sigStr
break
}
}
if sig == "" {
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
continue
}
if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
logrus.Debugf("Error sending signal: %s", err)
}
}
}()
return sigc
}
// CmdStart starts one or more containers.
//
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
if *attach || *openStdin {
// We're going to attach to a container.
// 1. Ensure we only have one container.
if cmd.NArg() > 1 {
return fmt.Errorf("You cannot start and attach multiple containers at once.")
}
// 2. Attach to the container.
containerID := cmd.Arg(0)
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if !c.Config.Tty {
sigc := cli.forwardAllSignals(ctx, containerID)
defer signal.StopCatch(sigc)
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: *openStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
}
var in io.ReadCloser
if options.Stdin {
in = cli.in
}
resp, err := cli.client.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
if in != nil && c.Config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
cErr := promise.Go(func() error {
return cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp)
})
// 3. Start the container.
if err := cli.client.ContainerStart(ctx, containerID, ""); err != nil {
return err
}
// 4. Wait for attachment to break.
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, containerID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if attchErr := <-cErr; attchErr != nil {
return attchErr
}
_, status, err := getExitCode(ctx, cli, containerID)
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
} else {
// We're not going to attach to anything.
// Start as many containers as we want.
return cli.startContainersWithoutAttachments(ctx, cmd.Args())
}
return nil
}
func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containerIDs []string) error {
var failedContainers []string
for _, containerID := range containerIDs {
if err := cli.client.ContainerStart(ctx, containerID, ""); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
failedContainers = append(failedContainers, containerID)
} else {
fmt.Fprintf(cli.out, "%s\n", containerID)
}
}
if len(failedContainers) > 0 {
return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
}
return nil
}

View File

@@ -1,190 +0,0 @@
package client
import (
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/events"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cli/command"
"github.com/hyperhq/hypercli/cli/command/formatter"
"golang.org/x/net/context"
)
// CmdStats displays a live stream of resource usage statistics for one or more containers.
//
// This shows real-time information on CPU usage, memory usage, and network I/O.
//
// Usage: hyper stats [OPTIONS] [CONTAINER...]
func (cli *DockerCli) CmdStats(args ...string) error {
cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
cmd.ParseFlags(args, true)
names := cmd.Args()
showAll := len(names) == 0
closeChan := make(chan error)
ctx := context.Background()
eventChan := make(chan events.Message)
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
eventq, errq := cli.Events(ctx)
// Whether we successfully subscribed to eventq or not, we can now
// unblock the main goroutine.
close(started)
for {
select {
case event := <-eventq:
c <- event
case err := <-errq:
closeChan <- err
return
}
}
}
// waitFirst is a WaitGroup to wait first stat data's reach for each container
waitFirst := &sync.WaitGroup{}
cStats := stats{}
// getContainerList simulates creation event for all previously existing
// containers (only used when calling `docker stats` without arguments).
getContainerList := func() {
options := types.ContainerListOptions{
All: *all,
}
cs, err := cli.client.ContainerList(ctx, options)
if err != nil {
closeChan <- err
}
for _, container := range cs {
s := formatter.NewContainerStats(container.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, eventChan, false)
}
}
}
if showAll {
// If no names were specified, start a long running goroutine which
// monitors container events. We make sure we're subscribed before
// retrieving the list of running containers to avoid a race where we
// would "miss" a creation.
started := make(chan struct{})
eh := command.InitEventHandler()
eh.Handle("start", func(e events.Message) {
s := formatter.NewContainerStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, eventChan, false)
}
})
eh.Handle("stop", func(e events.Message) {
if !*all {
cStats.remove(e.ID[:12])
}
})
go eh.Watch(eventChan)
go monitorContainerEvents(started, eventChan)
defer close(eventChan)
<-started
// Start a short-lived goroutine to retrieve the initial list of
// containers.
getContainerList()
} else {
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range names {
s := formatter.NewContainerStats(name)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, nil, true)
}
}
// We don't expect any asynchronous errors: closeChan can be closed.
close(closeChan)
// Do a quick pause to detect any error with the provided list of
// container names.
time.Sleep(1500 * time.Millisecond)
var errs []string
cStats.mu.Lock()
for _, c := range cStats.cs {
cErr := c.GetError()
if cErr != nil {
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, cErr))
}
}
cStats.mu.Unlock()
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, ", "))
}
}
// before print to screen, make sure each container get at least one valid stat data
waitFirst.Wait()
statsCtx := formatter.Context{
Output: cli.out,
Format: formatter.NewStatsFormat(formatter.TableFormatKey),
}
cleanScreen := func() {
if !*noStream {
fmt.Fprint(cli.out, "\033[2J")
fmt.Fprint(cli.out, "\033[H")
}
}
var err error
for range time.Tick(500 * time.Millisecond) {
cleanScreen()
ccstats := []formatter.StatsEntry{}
cStats.mu.Lock()
for _, c := range cStats.cs {
ccstats = append(ccstats, c.GetStatistics())
}
cStats.mu.Unlock()
if err = formatter.ContainerStatsWrite(statsCtx, ccstats); err != nil {
break
}
if len(cStats.cs) == 0 && !showAll {
break
}
if *noStream {
break
}
select {
case err, ok := <-closeChan:
if ok {
if err != nil {
// this is suppressing "unexpected EOF" in the cli when the
// daemon restarts so it shutdowns cleanly
if err == io.ErrUnexpectedEOF {
return nil
}
return err
}
}
default:
// just skip
}
}
return err
}

View File

@@ -1,206 +0,0 @@
package client
import (
"encoding/json"
"errors"
"io"
"strings"
"sync"
"time"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/events"
"github.com/hyperhq/hypercli/cli/command/formatter"
"golang.org/x/net/context"
)
type stats struct {
mu sync.Mutex
cs []*formatter.ContainerStats
}
func (s *stats) add(cs *formatter.ContainerStats) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.isKnownContainer(cs.Container); !exists {
s.cs = append(s.cs, cs)
return true
}
return false
}
func (s *stats) remove(id string) {
s.mu.Lock()
if i, exists := s.isKnownContainer(id); exists {
s.cs = append(s.cs[:i], s.cs[i+1:]...)
}
s.mu.Unlock()
}
func (s *stats) isKnownContainer(cid string) (int, bool) {
for i, c := range s.cs {
if c.Container == cid {
return i, true
}
}
return -1, false
}
func collect(ctx context.Context, s *formatter.ContainerStats, cli *DockerCli, streamStats bool, waitFirst *sync.WaitGroup, c chan events.Message, specified bool) {
var (
getFirst bool
previousCPU uint64
previousSystem uint64
u = make(chan error, 1)
end = make(chan bool, 1)
)
defer func() {
// if error happens and we get nothing of stats, release wait group whatever
if !getFirst {
getFirst = true
waitFirst.Done()
}
}()
responseBody, err := cli.client.ContainerStats(ctx, s.Container, streamStats)
if err != nil {
s.SetError(err)
return
}
defer responseBody.Close()
dec := json.NewDecoder(responseBody)
go func() {
for {
var (
v *types.StatsJSON
memPercent = 0.0
cpuPercent = 0.0
blkRead, blkWrite uint64
mem = 0.0
memLimit = 0.0
memPerc = 0.0
)
if err := dec.Decode(&v); err != nil {
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
u <- err
// TODO: add EOF in hyper.sh
if err == io.EOF {
end <- true
break
}
time.Sleep(100 * time.Millisecond)
continue
}
// if mem usage == 0, we think this container is stopped.
if v.MemoryStats.Usage == 0 {
if !specified {
stopEvent := events.Message{
ID: s.Container,
Action: "stop",
}
c <- stopEvent
end <- true
break
} else {
u <- errors.New("This container is stopped.")
continue
}
}
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
// got any data from cgroup
if v.MemoryStats.Limit != 0 {
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
}
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
previousSystem = v.PreCPUStats.SystemUsage
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
blkRead, blkWrite = calculateBlockIO(v.BlkioStats)
mem = float64(v.MemoryStats.Usage)
memLimit = float64(v.MemoryStats.Limit)
memPerc = memPercent
netRx, netTx := calculateNetwork(v.Networks)
s.SetStatistics(formatter.StatsEntry{
CPUPercentage: cpuPercent,
Memory: mem,
MemoryPercentage: memPerc,
MemoryLimit: memLimit,
NetworkRx: netRx,
NetworkTx: netTx,
BlockRead: float64(blkRead),
BlockWrite: float64(blkWrite),
})
u <- nil
if !streamStats {
return
}
}
}()
for {
select {
case <-time.After(2 * time.Second):
// zero out the values if we have not received an update within
// the specified duration.
s.SetErrorAndReset(errors.New("timeout waiting for stats"))
// if this is the first stat you get, release WaitGroup
if !getFirst {
getFirst = true
waitFirst.Done()
}
case err := <-u:
if err != nil {
s.SetError(err)
continue
}
s.SetError(nil)
// if this is the first stat you get, release WaitGroup
if !getFirst {
getFirst = true
waitFirst.Done()
}
case <-end:
s.SetError(errors.New("This container is stopped."))
if !getFirst {
getFirst = true
waitFirst.Done()
}
break
}
if !streamStats {
return
}
}
}
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
return float64(v.CPUStats.CPUUsage.TotalUsage) / 100.0
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
case "read":
blkRead = blkRead + bioEntry.Value
case "write":
blkWrite = blkWrite + bioEntry.Value
}
}
return
}
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
var rx, tx float64
for _, v := range network {
rx += float64(v.RxBytes)
tx += float64(v.TxBytes)
}
return rx, tx
}

View File

@@ -1,39 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdStop stops one or more containers.
//
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
//
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStop(args ...string) error {
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stop"].Description+".\nSending SIGTERM and then SIGKILL after a grace period", true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerStop(ctx, name, *nSeconds); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,26 +0,0 @@
package client
import (
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdTag tags an image into a repository.
//
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
func (cli *DockerCli) Tag(args ...string) error {
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
force := cmd.Bool([]string{"#f", "#-force"}, false, "Force the tagging even if there's a conflict")
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
options := types.ImageTagOptions{
Force: *force,
}
return cli.client.ImageTag(context.Background(), cmd.Arg(0), cmd.Arg(1), options)
}

View File

@@ -1,246 +0,0 @@
package client
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"github.com/cheggaaa/pb"
)
const (
TARINFO_HEADER = iota
TARINFO_FILE
TARINFO_PAD
TARINFO_UPLOADED
TARINFO_FINISHED
)
type tarInfo struct {
info os.FileInfo
relPath string
linkName string
path string
pad int // number of zeros to pad at the end of the file entry
headerBuf *bytes.Buffer
pos int64
state int
}
type TarFile struct {
fileList []*tarInfo
blockSize int
endPad int
padding []byte
closed bool
source string
progress *pb.ProgressBar
}
func (t *TarFile) writeHeader(p []byte, info *tarInfo) (int, error) {
if info.headerBuf == nil {
header, err := tar.FileInfoHeader(info.info, info.linkName)
if err != nil {
return 0, err
}
header.Name = info.relPath
buf := &bytes.Buffer{}
tarWriter := tar.NewWriter(buf)
defer tarWriter.Close()
err = tarWriter.WriteHeader(header)
if err != nil {
return 0, err
}
info.headerBuf = &bytes.Buffer{}
io.Copy(info.headerBuf, buf)
}
size, err := info.headerBuf.Read(p)
if err != nil && err != io.EOF {
return 0, err
}
if info.headerBuf.Len() == 0 {
info.headerBuf.Truncate(0)
}
return size, nil
}
func (t *TarFile) writeFile(p []byte, info *tarInfo) (int, error) {
var f *os.File
var err error
if f, err = os.Open(info.path); err != nil {
return 0, err
}
defer f.Close()
// resuming
if info.pos != 0 {
if _, err = f.Seek(info.pos, os.SEEK_SET); err != nil {
return 0, err
}
}
if ret, err := f.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
func (t *TarFile) writePad(p []byte, info *tarInfo) (int, error) {
buf := bytes.NewBuffer(t.padding[0:info.pad])
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
// write the trailing zeros of a tar file
func (t *TarFile) writeClose(p []byte) (n int, err error) {
size := t.endPad
buf := bytes.NewBuffer(make([]byte, size))
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
t.endPad -= ret
}
if t.endPad <= 0 {
if t.progress != nil {
t.progress.Finish()
}
t.closed = true
return size, io.EOF
}
return size - t.endPad, nil
}
func (t *TarFile) AllocBar(pool *pb.Pool) *pb.ProgressBar {
if t.progress == nil {
t.progress = pb.New(len(t.fileList)).Prefix(fmt.Sprintf("Sending %s", t.source))
pool.Add(t.progress)
}
return t.progress
}
func (t *TarFile) AddFile(info os.FileInfo, relPath, linkName, path string) {
t.fileList = append(t.fileList, &tarInfo{
info: info,
relPath: relPath,
linkName: linkName,
path: path,
pad: (t.blockSize - (int(info.Size()) % t.blockSize)) % t.blockSize,
})
}
func (t *TarFile) Close() error {
if !t.closed {
if t.progress != nil {
t.progress.Finish()
}
}
t.closed = true
return nil
}
func (t *TarFile) Read(p []byte) (n int, err error) {
var (
file *tarInfo
idx int
length = len(p)
)
if length == 0 {
return
}
if t.closed {
return 0, io.EOF
}
for idx, file = range t.fileList {
if file.state != TARINFO_FINISHED {
break
}
if idx == len(t.fileList)-1 {
file = nil
break
}
}
defer func() {
if err != nil && err != io.EOF {
t.Close()
}
}()
if file == nil {
return t.writeClose(p)
}
for n < length && file.state != TARINFO_FINISHED {
switch file.state {
case TARINFO_HEADER:
if ret, err := t.writeHeader(p, file); err != nil {
return 0, err
} else {
n += ret
p = p[n:]
if file.headerBuf.Len() == 0 {
if file.info.Mode().IsRegular() {
file.state = TARINFO_FILE
} else {
file.state = TARINFO_UPLOADED
}
}
}
case TARINFO_FILE:
if ret, err := t.writeFile(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= file.info.Size() {
file.pos = 0
file.state = TARINFO_PAD
}
}
case TARINFO_PAD:
if file.pad == 0 {
file.state = TARINFO_UPLOADED
} else if ret, err := t.writePad(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= int64(file.pad) {
file.state = TARINFO_UPLOADED
}
}
case TARINFO_UPLOADED:
file.state = TARINFO_FINISHED
if t.progress != nil {
t.progress.Increment()
}
}
}
return n, nil
}
func NewTarFile(path string, blockSize int) *TarFile {
return &TarFile{
blockSize: blockSize,
endPad: blockSize * 2,
source: filepath.Base(path),
padding: make([]byte, blockSize),
}
}

View File

@@ -1,41 +0,0 @@
package client
import (
"fmt"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdTop displays the running processes of a container.
//
// Usage: docker top CONTAINER
func (cli *DockerCli) Top(args ...string) error {
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, Cli.DockerCommands["top"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var arguments []string
if cmd.NArg() > 1 {
arguments = cmd.Args()[1:]
}
procList, err := cli.client.ContainerTop(context.Background(), cmd.Arg(0), arguments)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
for _, proc := range procList.Processes {
fmt.Fprintln(w, strings.Join(proc, "\t"))
}
w.Flush()
return nil
}

View File

@@ -1,467 +0,0 @@
package client
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
"github.com/hyperhq/hyper-api/types"
registrytypes "github.com/hyperhq/hyper-api/types/registry"
"github.com/hyperhq/hypercli/cliconfig"
"github.com/hyperhq/hypercli/distribution"
"github.com/hyperhq/hypercli/dockerversion"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
)
var (
releasesRole = path.Join(data.CanonicalTargetsRole, "releases")
untrusted bool
)
func addTrustedFlags(fs *flag.FlagSet, verify bool) {
var trusted bool
if e := os.Getenv("HYPER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
trusted = true
}
}
message := "Skip image signing"
if verify {
message = "Skip image verification"
}
fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
}
func isTrusted() bool {
return !untrusted
}
type target struct {
reference registry.Reference
digest digest.Digest
size int64
}
func (cli *DockerCli) trustDirectory() string {
return filepath.Join(cliconfig.ConfigDir(), "trust")
}
// certificateDirectory returns the directory containing
// TLS certificates for the given server. An error is
// returned if there was an error parsing the server string.
func (cli *DockerCli) certificateDirectory(server string) (string, error) {
u, err := url.Parse(server)
if err != nil {
return "", err
}
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
}
func trustServer(index *registrytypes.IndexInfo) (string, error) {
if s := os.Getenv("HYPER_CONTENT_TRUST_SERVER"); s != "" {
urlObj, err := url.Parse(s)
if err != nil || urlObj.Scheme != "https" {
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
}
return s, nil
}
if index.Official {
return registry.NotaryServer, nil
}
return "https://" + index.Name, nil
}
type simpleCredentialStore struct {
auth types.AuthConfig
}
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
return scs.auth.Username, scs.auth.Password
}
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) {
server, err := trustServer(repoInfo.Index)
if err != nil {
return nil, err
}
var cfg = tlsconfig.ClientDefault()
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
// Get certificate base directory
certDir, err := cli.certificateDirectory(server)
if err != nil {
return nil, err
}
logrus.Debugf("reading certificate directory: %s", certDir)
if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
return nil, err
}
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: cfg,
DisableKeepAlives: true,
}
// Skip configuration headers since request is not going to Docker daemon
modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), http.Header{})
authTransport := transport.NewTransport(base, modifiers...)
pingClient := &http.Client{
Transport: authTransport,
Timeout: 5 * time.Second,
}
endpointStr := server + "/v2/"
req, err := http.NewRequest("GET", endpointStr, nil)
if err != nil {
return nil, err
}
challengeManager := auth.NewSimpleChallengeManager()
resp, err := pingClient.Do(req)
if err != nil {
// Ignore error on ping to operate in offline mode
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
} else {
defer resp.Body.Close()
// Add response to the challenge manager to parse out
// authentication header and register authentication method
if err := challengeManager.AddResponse(resp); err != nil {
return nil, err
}
}
creds := simpleCredentialStore{auth: authConfig}
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull")
basicHandler := auth.NewBasicHandler(creds)
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
tr := transport.NewTransport(base, modifiers...)
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever())
}
func convertTarget(t client.Target) (target, error) {
h, ok := t.Hashes["sha256"]
if !ok {
return target{}, errors.New("no valid hash, expecting sha256")
}
return target{
reference: registry.ParseReference(t.Name),
digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
size: t.Length,
}, nil
}
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
aliasMap := map[string]string{
"root": "root",
"snapshot": "repository",
"targets": "repository",
"targets/releases": "repository",
}
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
env := map[string]string{
"root": os.Getenv("HYPER_CONTENT_TRUST_ROOT_PASSPHRASE"),
"snapshot": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets/releases": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
}
// Backwards compatibility with old env names. We should remove this in 1.10
if env["root"] == "" {
if passphrase := os.Getenv("HYPER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" {
env["root"] = passphrase
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable HYPER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use HYPER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
}
}
if env["snapshot"] == "" || env["targets"] == "" || env["targets/releases"] == "" {
if passphrase := os.Getenv("HYPER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
env["snapshot"] = passphrase
env["targets"] = passphrase
env["targets/releases"] = passphrase
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable HYPER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
}
}
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
if v := env[alias]; v != "" {
return v, numAttempts > 1, nil
}
return baseRetriever(keyName, alias, createNew, numAttempts)
}
}
func (cli *DockerCli) trustedReference(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return nil, err
}
// Resolve the Auth config relevant for this server
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return nil, err
}
t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole)
if err != nil {
return nil, err
}
r, err := convertTarget(t.Target)
if err != nil {
return nil, err
}
return reference.WithDigest(ref, r.digest)
}
func (cli *DockerCli) tagTrusted(ctx context.Context, trustedRef reference.Canonical, ref reference.NamedTagged) error {
fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
options := types.ImageTagOptions{
Force: true,
}
return cli.client.ImageTag(ctx, trustedRef.String(), ref.String(), options)
}
func notaryError(repoName string, err error) error {
switch err.(type) {
case *json.SyntaxError:
logrus.Debugf("Notary syntax error: %s", err)
return fmt.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting HYPER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
case signed.ErrExpired:
return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
case trustmanager.ErrKeyNotFound:
return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
case *net.OpError:
return fmt.Errorf("Error: error contacting notary server: %v", err)
case store.ErrMetaNotFound:
return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
case signed.ErrInvalidKeyType:
return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
case signed.ErrNoKeys:
return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
case signed.ErrLowVersion:
return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
case signed.ErrRoleThreshold:
return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
case client.ErrRepositoryNotExist:
return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
case signed.ErrInsufficientSignatures:
return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err)
}
return err
}
func (cli *DockerCli) trustedPull(ctx context.Context, repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return err
}
if ref.String() == "" {
// List all targets
targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole)
if err != nil {
return notaryError(repoInfo.FullName(), err)
}
for _, tgt := range targets {
t, err := convertTarget(tgt.Target)
if err != nil {
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name())
continue
}
refs = append(refs, t)
}
} else {
t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole)
if err != nil {
return notaryError(repoInfo.FullName(), err)
}
r, err := convertTarget(t.Target)
if err != nil {
return err
}
refs = append(refs, r)
}
for i, r := range refs {
displayTag := r.reference.String()
if displayTag != "" {
displayTag = ":" + displayTag
}
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest)
ref, err := reference.WithDigest(repoInfo, r.digest)
if err != nil {
return err
}
if err := cli.imagePullPrivileged(ctx, authConfig, ref.String(), requestPrivilege, false); err != nil {
return err
}
// If reference is not trusted, tag by trusted reference
if !r.reference.HasDigest() {
tagged, err := reference.WithTag(repoInfo, r.reference.String())
if err != nil {
return err
}
trustedRef, err := reference.WithDigest(repoInfo, r.digest)
if err != nil {
return err
}
if err := cli.tagTrusted(ctx, trustedRef, tagged); err != nil {
return err
}
}
}
return nil
}
func (cli *DockerCli) trustedPush(ctx context.Context, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := cli.imagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
targets := []target{}
handleTarget := func(aux *json.RawMessage) {
var pushResult distribution.PushResult
err := json.Unmarshal(*aux, &pushResult)
if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil {
targets = append(targets, target{
reference: registry.ParseReference(pushResult.Tag),
digest: pushResult.Digest,
size: int64(pushResult.Size),
})
}
}
var tag string
switch x := ref.(type) {
case reference.Canonical:
return errors.New("cannot push a digest reference")
case reference.NamedTagged:
tag = x.Tag()
}
if tag == "" {
err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
return nil
}
err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget)
if err != nil {
return err
}
if len(targets) == 0 {
fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
return nil
}
fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
repo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
return err
}
for _, target := range targets {
h, err := hex.DecodeString(target.digest.Hex())
if err != nil {
return err
}
t := &client.Target{
Name: target.reference.String(),
Hashes: data.Hashes{
string(target.digest.Algorithm()): h,
},
Length: int64(target.size),
}
if err := repo.AddTarget(t, releasesRole); err != nil {
return err
}
}
err = repo.Publish()
if _, ok := err.(client.ErrRepoNotInitialized); !ok {
return notaryError(repoInfo.FullName(), err)
}
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
var rootKeyID string
// always select the first root key
if len(keys) > 0 {
sort.Strings(keys)
rootKeyID = keys[0]
} else {
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
if err != nil {
return err
}
rootKeyID = rootPublicKey.ID()
}
if err := repo.Initialize(rootKeyID); err != nil {
return notaryError(repoInfo.FullName(), err)
}
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
return notaryError(repoInfo.FullName(), repo.Publish())
}

View File

@@ -1,56 +0,0 @@
package client
import (
"os"
"testing"
registrytypes "github.com/hyperhq/hyper-api/types/registry"
"github.com/hyperhq/hypercli/registry"
)
func unsetENV() {
os.Unsetenv("DOCKER_CONTENT_TRUST")
os.Unsetenv("DOCKER_CONTENT_TRUST_SERVER")
}
func TestENVTrustServer(t *testing.T) {
defer unsetENV()
indexInfo := &registrytypes.IndexInfo{Name: "testserver"}
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
t.Fatal("Failed to set ENV variable")
}
output, err := trustServer(indexInfo)
expectedStr := "https://notary-test.com:5000"
if err != nil || output != expectedStr {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
}
}
func TestHTTPENVTrustServer(t *testing.T) {
defer unsetENV()
indexInfo := &registrytypes.IndexInfo{Name: "testserver"}
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
t.Fatal("Failed to set ENV variable")
}
_, err := trustServer(indexInfo)
if err == nil {
t.Fatal("Expected error with invalid scheme")
}
}
func TestOfficialTrustServer(t *testing.T) {
indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: true}
output, err := trustServer(indexInfo)
if err != nil || output != registry.NotaryServer {
t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
}
}
func TestNonOfficialTrustServer(t *testing.T) {
indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: false}
output, err := trustServer(indexInfo)
expectedStr := "https://" + indexInfo.Name
if err != nil || output != expectedStr {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
}
}

View File

@@ -1,36 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdUnpause unpauses all processes within a container, for one or more containers.
//
// Usage: docker unpause CONTAINER [CONTAINER...]
func (cli *DockerCli) Unpause(args ...string) error {
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["unpause"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerUnpause(ctx, name); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,78 +0,0 @@
package client
import (
"fmt"
"strconv"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdUpdate updates resources of one or more containers.
//
// Usage: hyper update [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdUpdate(args ...string) error {
cmd := Cli.Subcmd("update", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["update"].Description, true)
flAddSecurityGroups := opts.NewListOpts(nil)
flRmSecurityGroups := opts.NewListOpts(nil)
cmd.Var(&flAddSecurityGroups, []string{"-sg-add"}, "Add a new security group for each container")
cmd.Var(&flRmSecurityGroups, []string{"-sg-rm"}, "Remove a new security group for each container")
// make this flag string type to distinguish between 'not set', 'set to false' and 'set to true'
flContainerProtection := cmd.String([]string{"-protection"}, "", "Termination protection for container (true|false)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if cmd.NFlag() == 0 {
return fmt.Errorf("You must provide one or more flags when using this command.")
}
if *flContainerProtection != "" {
value, err := strconv.ParseBool(*flContainerProtection)
if err != nil {
return fmt.Errorf("Parse protection false failed: %v", err)
}
*flContainerProtection = strconv.FormatBool(value)
}
ctx := context.Background()
names := cmd.Args()
var errs []string
for _, name := range names {
var updateConfig struct {
AddSecurityGroups map[string]string
RemoveSecurityGroups map[string]string
SetContainerProtection string
}
sgs := map[string]string{}
for _, label := range flAddSecurityGroups.GetAll() {
if label == "" {
continue
}
sgs[label] = "yes"
}
updateConfig.AddSecurityGroups = sgs
sgs = map[string]string{}
for _, label := range flRmSecurityGroups.GetAll() {
if label == "" {
continue
}
sgs[label] = "yes"
}
updateConfig.RemoveSecurityGroups = sgs
updateConfig.SetContainerProtection = *flContainerProtection
if err := cli.client.ContainerUpdate(ctx, name, updateConfig); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,261 +0,0 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
gosignal "os/signal"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/dutchcoders/goftp"
"github.com/hyperhq/hyper-api/client"
"github.com/hyperhq/hyper-api/types"
registrytypes "github.com/hyperhq/hyper-api/types/registry"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/pkg/term"
"github.com/hyperhq/hypercli/registry"
"golang.org/x/net/context"
)
func (cli *DockerCli) electAuthServer(ctx context.Context) string {
// The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence
// the default registry URL might be Windows specific.
serverAddress := registry.IndexServer
if info, err := cli.client.Info(ctx); err != nil {
fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
} else {
serverAddress = info.IndexServerAddress
}
return serverAddress
}
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
return func() (string, error) {
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index)
authConfig, err := cli.configureAuth("", "", "", indexServer)
if err != nil {
return "", err
}
return encodeAuthToBase64(authConfig)
}
}
func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
height, width := cli.getTtySize()
if height == 0 && width == 0 {
return
}
options := types.ResizeOptions{
Height: height,
Width: width,
}
var err error
if isExec {
err = cli.client.ContainerExecResize(ctx, id, options)
} else {
err = cli.client.ContainerResize(ctx, id, options)
}
if err != nil {
logrus.Debugf("Error resize: %s", err)
}
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(ctx context.Context, cli *DockerCli, containerID string) (bool, int, error) {
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != client.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return c.State.Running, c.State.ExitCode, nil
}
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(ctx context.Context, cli *DockerCli, execID string) (bool, int, error) {
resp, err := cli.client.ContainerExecInspect(ctx, execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != client.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return resp.Running, resp.ExitCode, nil
}
func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool) error {
cli.resizeTty(ctx, id, isExec)
if runtime.GOOS == "windows" {
go func() {
prevH, prevW := cli.getTtySize()
for {
time.Sleep(time.Millisecond * 250)
h, w := cli.getTtySize()
if prevW != w || prevH != h {
cli.resizeTty(ctx, id, isExec)
}
prevH = h
prevW = w
}
}()
} else {
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
go func() {
for range sigchan {
cli.resizeTty(ctx, id, isExec)
}
}()
}
return nil
}
func (cli *DockerCli) getTtySize() (int, int) {
if !cli.isTerminalOut {
return 0, 0
}
ws, err := term.GetWinsize(cli.outFd)
if err != nil {
logrus.Debugf("Error getting size: %s", err)
if ws == nil {
return 0, 0
}
}
return int(ws.Height), int(ws.Width)
}
func copyToFile(outfile string, r io.Reader) error {
tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
if err != nil {
return err
}
tmpPath := tmpFile.Name()
_, err = io.Copy(tmpFile, r)
tmpFile.Close()
if err != nil {
os.Remove(tmpPath)
return err
}
if err = os.Rename(tmpPath, outfile); err != nil {
os.Remove(tmpPath)
return err
}
return nil
}
// resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform,
// not the client's platform.
func (cli *DockerCli) resolveAuthConfig(ctx context.Context, authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
configKey := index.Name
if index.Official {
configKey = cli.electAuthServer(ctx)
}
// First try the happy case
if c, found := authConfigs[configKey]; found || index.Official {
return c
}
convertToHostname := func(url string) string {
stripped := url
if strings.HasPrefix(url, "http://") {
stripped = strings.Replace(url, "http://", "", 1)
} else if strings.HasPrefix(url, "https://") {
stripped = strings.Replace(url, "https://", "", 1)
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
}
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
for registry, ac := range authConfigs {
if configKey == convertToHostname(registry) {
return ac
}
}
// When all else fails, return an empty auth config
return types.AuthConfig{}
}
// uploadLocalResource upload a local file/directory to a container
func (cli *DockerCli) uploadLocalResource(source, dest, serverIP, user, passwd string, isFile bool) error {
if !strings.Contains(serverIP, ":") {
serverIP += ":21"
}
ftp, err := goftp.Connect(serverIP)
if err != nil {
return err
}
defer func() {
ftp.Close()
}()
if err = ftp.Login(user, passwd); err != nil {
return err
}
if isFile {
file, err := os.Open(source)
if err != nil {
return err
}
defer func() {
file.Close()
}()
if err = ftp.Stor(dest, file); err != nil {
return err
}
} else {
if err = ftp.Cwd(dest); err != nil {
return err
}
if err = ftp.Upload(source); err != nil {
return err
}
}
return nil
}

View File

@@ -1,19 +0,0 @@
// +build !windows
package client
import (
"path/filepath"
)
func getContextRoot(srcPath string) (string, error) {
return filepath.Join(srcPath, "."), nil
}
func convertToUnixPath(path string) (int, string) {
return 0, path
}
func recoverPath(pathType int, path string) string {
return path
}

View File

@@ -1,60 +0,0 @@
// +build windows
package client
import (
"path/filepath"
"strings"
"github.com/hyperhq/hypercli/pkg/longpath"
)
const (
LOCAL_PATH_UNIX = iota
LOCAL_PATH_WIN_VOLUME
LOCAL_PATH_WIN_SHARE
)
func getContextRoot(srcPath string) (string, error) {
cr, err := filepath.Abs(srcPath)
if err != nil {
return "", err
}
return longpath.AddPrefix(cr), nil
}
// convertToUnixPath converts whatever valid path to unix format
// network path is treated as unix path unchanged
// /foo/bar -> /foo/bar
// C:\foo\bar -> /C/foo/bar
// \\host\share\foo\bar -> //host/share/foo/bar
func convertToUnixPath(path string) (int, string) {
if strings.HasPrefix(path, "git://") || strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
return LOCAL_PATH_UNIX, path
}
switch len(filepath.VolumeName(path)) {
case 0:
return LOCAL_PATH_UNIX, path
case 2:
// C:
return LOCAL_PATH_WIN_VOLUME, "/" + strings.Replace(filepath.ToSlash(path), ":", "", 1)
default:
// \\host\share
return LOCAL_PATH_WIN_SHARE, filepath.ToSlash(path)
}
}
// recoverPath recovers a file path according to path type
func recoverPath(pathType int, path string) string {
switch pathType {
case LOCAL_PATH_WIN_VOLUME:
path = filepath.FromSlash(path)
return strings.Replace(path[1:], "\\", ":\\", 1)
case LOCAL_PATH_WIN_SHARE:
return filepath.FromSlash(path)
default:
// LOCAL_PATH_UNIX
return path
}
}

View File

@@ -1,96 +0,0 @@
package client
import (
"runtime"
"text/template"
"time"
"golang.org/x/net/context"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/dockerversion"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/utils"
)
var versionTemplate = `Client:
Version: {{.Client.Version}}
API version: {{.Client.APIVersion}}
Go version: {{.Client.GoVersion}}
Git commit: {{.Client.GitCommit}}
Built: {{.Client.BuildTime}}
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
Server:
Version: {{.Server.Version}}
API version: {{.Server.APIVersion}}
Go version: {{.Server.GoVersion}}
Git commit: {{.Server.GitCommit}}
Built: {{.Server.BuildTime}}
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
// CmdVersion shows Docker version information.
//
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
//
// Usage: docker version
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
cmd := Cli.Subcmd("version", nil, Cli.DockerCommands["version"].Description, true)
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
ctx := context.Background()
templateFormat := versionTemplate
if *tmplStr != "" {
templateFormat = *tmplStr
}
var tmpl *template.Template
if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
vd := types.VersionResponse{
Client: &types.Version{
Version: dockerversion.Version,
APIVersion: cli.client.ClientVersion(),
GoVersion: runtime.Version(),
GitCommit: dockerversion.GitCommit,
BuildTime: dockerversion.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: utils.ExperimentalBuild(),
},
}
serverVersion, err := cli.client.ServerVersion(ctx)
if err == nil {
vd.Server = &serverVersion
}
// first we need to make BuildTime more human friendly
t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
if errTime == nil {
vd.Client.BuildTime = t.Format(time.ANSIC)
}
if vd.ServerOK() {
t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
if errTime == nil {
vd.Server.BuildTime = t.Format(time.ANSIC)
}
}
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
err = err2
}
cli.out.Write([]byte{'\n'})
return err
}

View File

@@ -1,411 +0,0 @@
package client
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/hyperhq/hypercli/api/client/formatter"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/cheggaaa/pb"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"golang.org/x/net/context"
)
// CmdVolume is the parent subcommand for all volume commands
//
// Usage: docker volume <COMMAND> <OPTS>
func (cli *DockerCli) CmdVolume(args ...string) error {
description := Cli.DockerCommands["volume"].Description + "\n\nCommands:\n"
commands := [][]string{
{"create", "Create a volume"},
{"inspect", "Return low-level information on a volume"},
{"ls", "List volumes"},
{"init", "Initialize volumes"},
{"rm", "Remove a volume"},
}
for _, cmd := range commands {
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
description += "\nRun 'hyper volume COMMAND --help' for more information on a command"
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdVolumeLs outputs a list of Docker volumes.
//
// Usage: docker volume ls [OPTIONS]
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
format := cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
var err error
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
if err != nil {
return err
}
}
volumes, err := cli.client.VolumeList(context.Background(), volFilterArgs)
if err != nil {
return err
}
/*
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
for _, warn := range volumes.Warnings {
fmt.Fprintln(cli.err, warn)
}
fmt.Fprintf(w, "DRIVER \tVOLUME NAME\tSIZE\tCONTAINER")
fmt.Fprintf(w, "\n")
}
for _, vol := range volumes.Volumes {
if *quiet {
fmt.Fprintln(w, vol.Name)
continue
}
var size, container string
if vol.Labels != nil {
size = vol.Labels["size"]
container = vol.Labels["container"]
if container != "" {
container = stringid.TruncateID(container)
}
}
fmt.Fprintf(w, "%s\t%s\t%s GB\t%s\n", vol.Driver, vol.Name, size, container)
}
w.Flush()
*/
f := *format
if len(f) == 0 {
if len(cli.VolumesFormat()) > 0 && !*quiet {
f = cli.VolumesFormat()
} else {
f = "table"
}
}
volCtx := formatter.VolumeContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
},
Volumes: volumes.Volumes,
}
volCtx.Write()
return nil
}
// CmdVolumeInspect displays low-level information on one or more volumes.
//
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
cmd := Cli.Subcmd("volume inspect", []string{"VOLUME [VOLUME...]"}, "Return low-level information on a volume", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.VolumeInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdVolumeCreate creates a new volume.
//
// Usage: docker volume create [OPTIONS]
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
flDriver := cmd.String([]string{}, "hyper", "Specify volume driver name")
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
flSnapshot := cmd.String([]string{"-snapshot"}, "", "Specify snapshot to create volume")
flSize := cmd.Int([]string{"-size"}, 10, "Specify volume size")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volReq := types.VolumeCreateRequest{
Driver: *flDriver,
DriverOpts: make(map[string]string),
Name: *flName,
}
volReq.DriverOpts["size"] = fmt.Sprintf("%d", *flSize)
if *flSnapshot != "" {
volReq.DriverOpts["snapshot"] = *flSnapshot
if *flSize == 10 {
volReq.DriverOpts["size"] = ""
}
}
vol, err := cli.client.VolumeCreate(context.Background(), volReq)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", vol.Name)
return nil
}
// CmdVolumeRm removes one or more volumes.
//
// Usage: docker volume rm VOLUME [VOLUME...]
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
cmd := Cli.Subcmd("volume rm", []string{"VOLUME [VOLUME...]"}, "Remove a volume", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var status = 0
ctx := context.Background()
for _, name := range cmd.Args() {
if err := cli.client.VolumeRemove(ctx, name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", name)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
func validateVolumeSource(source string) error {
switch {
case strings.HasPrefix(source, "git://"):
fallthrough
case strings.HasPrefix(source, "http://"):
fallthrough
case strings.HasPrefix(source, "https://"):
break
case filepath.VolumeName(source) != "":
fallthrough
case strings.HasPrefix(source, "/"):
info, err := os.Stat(source)
if err != nil {
return err
}
if !info.Mode().IsDir() && !info.Mode().IsRegular() {
return fmt.Errorf("Unsupported local volume source(%s): %s", source, info.Mode().String())
}
break
default:
return fmt.Errorf("%s is not supported volume source", source)
}
return nil
}
func validateVolumeInitArgs(args []string, req *types.VolumesInitializeRequest) ([]int, error) {
var sourceType []int
for _, desc := range args {
idx := strings.LastIndexByte(desc, ':')
if idx == -1 || idx >= len(desc)-1 {
return nil, fmt.Errorf("%s does not match format SOURCE:VOLUME", desc)
}
source := desc[:idx]
name := desc[idx+1:]
if err := validateVolumeSource(source); err != nil {
return nil, err
}
pathType, source := convertToUnixPath(source)
req.Volume = append(req.Volume, types.VolumeInitDesc{
Name: name,
Source: source,
})
sourceType = append(sourceType, pathType)
}
return sourceType, nil
}
// CmdVolumeInit Initializes one or more volumes.
//
// Usage: docker volume init SOURCE:VOLUME [SOURCE:VOLUME...]
func (cli *DockerCli) CmdVolumeInit(args ...string) error {
cmd := Cli.Subcmd("volume init", []string{"SOURCE:VOLUME [SOURCE:VOLUME...]"}, "Initialize a volume", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
return cli.initVolumes(cmd.Args(), false)
}
func (cli *DockerCli) initVolumes(vols []string, reload bool) error {
var req types.VolumesInitializeRequest
pathType, err := validateVolumeInitArgs(vols, &req)
if err != nil {
return err
}
ctx := context.Background()
req.Reload = reload
resp, err := cli.client.VolumeInitialize(ctx, req)
if err != nil {
return err
}
if len(resp.Session) == 0 {
return nil
}
// Upload local volumes
var wg sync.WaitGroup
var results []error
pool, err := pb.StartPool()
if err != nil {
// Ignore progress bar failures
fmt.Fprintf(cli.err, "Warning: do not show upload progress: %s\n", err.Error())
pool = nil
err = nil
}
for idx, desc := range req.Volume {
if url, ok := resp.Uploaders[desc.Name]; ok {
source := recoverPath(pathType[idx], desc.Source)
wg.Add(1)
go uploadLocalVolume(source, url, resp.Cookie, &results, &wg, pool)
}
}
wg.Wait()
if pool != nil {
pool.Stop()
}
for _, err = range results {
fmt.Fprintf(cli.err, "Upload local volume failed: %s\n", err.Error())
}
finishErr := cli.client.VolumeUploadFinish(ctx, resp.Session)
if err == nil {
err = finishErr
}
return err
}
func uploadLocalVolume(source, url, cookie string, results *[]error, wg *sync.WaitGroup, pool *pb.Pool) {
var (
resp io.ReadCloser
tar *TarFile
fullPath string
err error
)
defer func() {
if err != nil {
*results = append(*results, err)
}
wg.Done()
}()
fullPath, err = filepath.Abs(source)
if err != nil {
return
}
tar = NewTarFile(source, 512)
walkFunc := func(path string, info os.FileInfo, err error) error {
var relPath, linkName string
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
linkName, err = os.Readlink(path)
if err != nil {
return err
}
}
if path == fullPath {
if info.IsDir() {
// "." as indicator that it is a dir volume
relPath = "."
} else {
relPath = filepath.Base(path)
}
} else {
relPath, err = filepath.Rel(fullPath, path)
if err != nil {
return err
}
}
tar.AddFile(info, relPath, linkName, path)
return nil
}
err = filepath.Walk(fullPath, walkFunc)
if err != nil {
return
}
if pool != nil {
tar.AllocBar(pool)
}
resp, err = sendTarball(url, cookie, tar)
if err != nil {
return
}
defer resp.Close()
}
func sendTarball(uri, cookie string, input io.ReadCloser) (io.ReadCloser, error) {
req, err := http.NewRequest("POST", uri+"?cookie="+cookie, input)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-tar")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
if buf.Len() > 0 {
err = fmt.Errorf("%s: %s", http.StatusText(resp.StatusCode), buf.String())
} else {
err = fmt.Errorf("%s", http.StatusText(resp.StatusCode))
}
return nil, err
}
return resp.Body, nil
}

View File

@@ -1,39 +0,0 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdWait blocks until a container stops, then prints its exit code.
//
// If more than one container is specified, this will wait synchronously on each container.
//
// Usage: docker wait CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdWait(args ...string) error {
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["wait"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
status, err := cli.client.ContainerWait(ctx, name)
if err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%d\n", status)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -1,149 +0,0 @@
package api
import (
"fmt"
"mime"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libtrust"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hypercli/pkg/system"
"github.com/hyperhq/hypercli/pkg/version"
)
// Common constants for daemon and client.
const (
// Version of Current REST API
DefaultVersion version.Version = "1.23"
// MinVersion represents Minimum REST API version supported
MinVersion version.Version = "1.12"
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
DefaultDockerfileName string = "Dockerfile"
// NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used.
NoBaseImageSpecifier string = "scratch"
)
// byPortInfo is a temporary type used to sort types.Port by its fields
type byPortInfo []types.Port
func (r byPortInfo) Len() int { return len(r) }
func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byPortInfo) Less(i, j int) bool {
if r[i].PrivatePort != r[j].PrivatePort {
return r[i].PrivatePort < r[j].PrivatePort
}
if r[i].IP != r[j].IP {
return r[i].IP < r[j].IP
}
if r[i].PublicPort != r[j].PublicPort {
return r[i].PublicPort < r[j].PublicPort
}
return r[i].Type < r[j].Type
}
// DisplayablePorts returns formatted string representing open ports of container
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
// it's used by command 'docker ps'
func DisplayablePorts(ports []types.Port) string {
type portGroup struct {
first int
last int
}
groupMap := make(map[string]*portGroup)
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Sort(byPortInfo(ports))
for _, port := range ports {
current := port.PrivatePort
portKey := port.Type
if port.IP != "" {
if port.PublicPort != current {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
continue
}
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
}
group := groupMap[portKey]
if group == nil {
groupMap[portKey] = &portGroup{first: current, last: current}
// record order that groupMap keys are created
groupMapKeys = append(groupMapKeys, portKey)
continue
}
if current == (group.last + 1) {
group.last = current
continue
}
result = append(result, formGroup(portKey, group.first, group.last))
groupMap[portKey] = &portGroup{first: current, last: current}
}
for _, portKey := range groupMapKeys {
g := groupMap[portKey]
result = append(result, formGroup(portKey, g.first, g.last))
}
result = append(result, hostMappings...)
return strings.Join(result, ", ")
}
func formGroup(key string, start, last int) string {
parts := strings.Split(key, "/")
groupType := parts[0]
var ip string
if len(parts) > 1 {
ip = parts[0]
groupType = parts[1]
}
group := strconv.Itoa(start)
if start != last {
group = fmt.Sprintf("%s-%d", group, last)
}
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
}
return fmt.Sprintf("%s/%s", group, groupType)
}
// MatchesContentType validates the content type against the expected one
func MatchesContentType(contentType, expectedType string) bool {
mimetype, _, err := mime.ParseMediaType(contentType)
if err != nil {
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
}
return err == nil && mimetype == expectedType
}
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
if err != nil {
return nil, err
}
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
if err == libtrust.ErrKeyFileDoesNotExist {
trustKey, err = libtrust.GenerateECP256PrivateKey()
if err != nil {
return nil, fmt.Errorf("Error generating key: %s", err)
}
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
return nil, fmt.Errorf("Error saving key file: %s", err)
}
} else if err != nil {
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
}
return trustKey, nil
}

View File

@@ -1,341 +0,0 @@
package api
import (
"io/ioutil"
"path/filepath"
"testing"
"os"
"github.com/hyperhq/hyper-api/types"
)
type ports struct {
ports []types.Port
expected string
}
// DisplayablePorts
func TestDisplayablePorts(t *testing.T) {
cases := []ports{
{
[]types.Port{
{
PrivatePort: 9988,
Type: "tcp",
},
},
"9988/tcp"},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp",
},
{
[]types.Port{
{
IP: "0.0.0.0",
PrivatePort: 9988,
Type: "tcp",
},
},
"0.0.0.0:0->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"4.3.2.1:8899->9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 9988,
Type: "tcp",
},
},
"4.3.2.1:9988->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
}, {
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp, 9988/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 9998,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 9999,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:9998-9999->9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 8887,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 8888,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp",
},
{
[]types.Port{
{
PrivatePort: 9998,
Type: "udp",
}, {
PrivatePort: 9999,
Type: "udp",
},
},
"9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
},
},
"9988/udp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/tcp, 1.2.3.4:8899->9988/udp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
PrivatePort: 80,
Type: "tcp",
}, {
PrivatePort: 1024,
Type: "tcp",
}, {
PrivatePort: 80,
Type: "udp",
}, {
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
},
},
"80/tcp, 80/udp, 1024/tcp, 1024/udp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp",
},
}
for _, port := range cases {
actual := DisplayablePorts(port.ports)
if port.expected != actual {
t.Fatalf("Expected %s, got %s.", port.expected, actual)
}
}
}
// MatchesContentType
func TestJsonContentType(t *testing.T) {
if !MatchesContentType("application/json", "application/json") {
t.Fail()
}
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
t.Fail()
}
if MatchesContentType("dockerapplication/json", "application/json") {
t.Fail()
}
}
// LoadOrCreateTrustKey
func TestLoadOrCreateTrustKeyInvalidKeyFile(t *testing.T) {
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpKeyFolderPath)
tmpKeyFile, err := ioutil.TempFile(tmpKeyFolderPath, "keyfile")
if err != nil {
t.Fatal(err)
}
if _, err := LoadOrCreateTrustKey(tmpKeyFile.Name()); err == nil {
t.Fatalf("expected an error, got nothing.")
}
}
func TestLoadOrCreateTrustKeyCreateKey(t *testing.T) {
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpKeyFolderPath)
// Without the need to create the folder hierarchy
tmpKeyFile := filepath.Join(tmpKeyFolderPath, "keyfile")
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
t.Fatalf("expected a new key file, got : %v and %v", err, key)
}
if _, err := os.Stat(tmpKeyFile); err != nil {
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
}
// With the need to create the folder hierarchy as tmpKeyFie is in a path
// where some folder do not exists.
tmpKeyFile = filepath.Join(tmpKeyFolderPath, "folder/hierarchy/keyfile")
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
t.Fatalf("expected a new key file, got : %v and %v", err, key)
}
if _, err := os.Stat(tmpKeyFile); err != nil {
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
}
// With no path at all
defer os.Remove("keyfile")
if key, err := LoadOrCreateTrustKey("keyfile"); err != nil || key == nil {
t.Fatalf("expected a new key file, got : %v and %v", err, key)
}
if _, err := os.Stat("keyfile"); err != nil {
t.Fatalf("Expected to find a file keyfile, got %v", err)
}
}
func TestLoadOrCreateTrustKeyLoadValidKey(t *testing.T) {
tmpKeyFile := filepath.Join("fixtures", "keyfile")
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
t.Fatalf("expected a key file, got : %v and %v", err, key)
}
}

View File

@@ -1,7 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
keyID: AWX2:I27X:WQFX:IOMK:CNAK:O7PW:VYNB:ZLKC:CVAE:YJP2:SI4A:XXAY
MHcCAQEEILHTRWdcpKWsnORxSFyBnndJ4ROU41hMtr/GCiLVvwBQoAoGCCqGSM49
AwEHoUQDQgAElpVFbQ2V2UQKajqdE3fVxJ+/pE/YuEFOxWbOxF2be19BY209/iky
NzeFFK7SLpQ4CBJ7zDVXOHsMzrkY/GquGA==
-----END EC PRIVATE KEY-----

View File

@@ -1,73 +0,0 @@
package httputils
import (
"fmt"
"net/http"
"path/filepath"
"strconv"
"strings"
)
// BoolValue transforms a form value in different formats into a boolean type.
func BoolValue(r *http.Request, k string) bool {
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
}
// BoolValueOrDefault returns the default bool passed if the query param is
// missing, otherwise it's just a proxy to boolValue above
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
if _, ok := r.Form[k]; !ok {
return d
}
return BoolValue(r, k)
}
// Int64ValueOrZero parses a form value into an int64 type.
// It returns 0 if the parsing fails.
func Int64ValueOrZero(r *http.Request, k string) int64 {
val, err := Int64ValueOrDefault(r, k, 0)
if err != nil {
return 0
}
return val
}
// Int64ValueOrDefault parses a form value into an int64 type. If there is an
// error, returns the error. If there is no value returns the default value.
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
if r.Form.Get(field) != "" {
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
if err != nil {
return value, err
}
return value, nil
}
return def, nil
}
// ArchiveOptions stores archive information for different operations.
type ArchiveOptions struct {
Name string
Path string
}
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
// It fails if the archive name and path are not in the request.
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
if err := ParseForm(r); err != nil {
return ArchiveOptions{}, err
}
name := vars["name"]
path := filepath.FromSlash(r.Form.Get("path"))
switch {
case name == "":
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
case path == "":
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
}
return ArchiveOptions{name, path}, nil
}

View File

@@ -1,105 +0,0 @@
package httputils
import (
"net/http"
"net/url"
"testing"
)
func TestBoolValue(t *testing.T) {
cases := map[string]bool{
"": false,
"0": false,
"no": false,
"false": false,
"none": false,
"1": true,
"yes": true,
"true": true,
"one": true,
"100": true,
}
for c, e := range cases {
v := url.Values{}
v.Set("test", c)
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
a := BoolValue(r, "test")
if a != e {
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
}
}
}
func TestBoolValueOrDefault(t *testing.T) {
r, _ := http.NewRequest("GET", "", nil)
if !BoolValueOrDefault(r, "queryparam", true) {
t.Fatal("Expected to get true default value, got false")
}
v := url.Values{}
v.Set("param", "")
r, _ = http.NewRequest("GET", "", nil)
r.Form = v
if BoolValueOrDefault(r, "param", true) {
t.Fatal("Expected not to get true")
}
}
func TestInt64ValueOrZero(t *testing.T) {
cases := map[string]int64{
"": 0,
"asdf": 0,
"0": 0,
"1": 1,
}
for c, e := range cases {
v := url.Values{}
v.Set("test", c)
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
a := Int64ValueOrZero(r, "test")
if a != e {
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
}
}
}
func TestInt64ValueOrDefault(t *testing.T) {
cases := map[string]int64{
"": -1,
"-1": -1,
"42": 42,
}
for c, e := range cases {
v := url.Values{}
v.Set("test", c)
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
a, err := Int64ValueOrDefault(r, "test", -1)
if a != e {
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
}
if err != nil {
t.Fatalf("Error should be nil, but received: %s", err)
}
}
}
func TestInt64ValueOrDefaultWithError(t *testing.T) {
v := url.Values{}
v.Set("test", "invalid")
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
_, err := Int64ValueOrDefault(r, "test", -1)
if err == nil {
t.Fatalf("Expected an error.")
}
}

View File

@@ -1,178 +0,0 @@
package httputils
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/api/errcode"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/pkg/version"
)
// APIVersionKey is the client's requested API version.
const APIVersionKey = "api-version"
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
// HijackConnection interrupts the http response writer to get the
// underlying connection and operate with it.
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
return nil, nil, err
}
// Flush the options to make sure the client sets the raw mode
conn.Write([]byte{})
return conn, conn, nil
}
// CloseStreams ensures that a list for http streams are properly closed.
func CloseStreams(streams ...interface{}) {
for _, stream := range streams {
if tcpc, ok := stream.(interface {
CloseWrite() error
}); ok {
tcpc.CloseWrite()
} else if closer, ok := stream.(io.Closer); ok {
closer.Close()
}
}
}
// CheckForJSON makes sure that the request's Content-Type is application/json.
func CheckForJSON(r *http.Request) error {
ct := r.Header.Get("Content-Type")
// No Content-Type header is ok as long as there's no Body
if ct == "" {
if r.Body == nil || r.ContentLength == 0 {
return nil
}
}
// Otherwise it better be json
if api.MatchesContentType(ct, "application/json") {
return nil
}
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
}
// ParseForm ensures the request form is parsed even with invalid content types.
// If we don't do this, POST method without Content-type (even with empty body) will fail.
func ParseForm(r *http.Request) error {
if r == nil {
return nil
}
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
return nil
}
// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
func ParseMultipartForm(r *http.Request) error {
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
return nil
}
// WriteError decodes a specific docker error and sends it in the response.
func WriteError(w http.ResponseWriter, err error) {
if err == nil || w == nil {
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
return
}
statusCode := http.StatusInternalServerError
errMsg := err.Error()
// Based on the type of error we get we need to process things
// slightly differently to extract the error message.
// In the 'errcode.*' cases there are two different type of
// error that could be returned. errocode.ErrorCode is the base
// type of error object - it is just an 'int' that can then be
// used as the look-up key to find the message. errorcode.Error
// extends errorcode.Error by adding error-instance specific
// data, like 'details' or variable strings to be inserted into
// the message.
//
// Ideally, we should just be able to call err.Error() for all
// cases but the errcode package doesn't support that yet.
//
// Additionally, in both errcode cases, there might be an http
// status code associated with it, and if so use it.
switch err.(type) {
case errcode.ErrorCode:
daError, _ := err.(errcode.ErrorCode)
statusCode = daError.Descriptor().HTTPStatusCode
errMsg = daError.Message()
case errcode.Error:
// For reference, if you're looking for a particular error
// then you can do something like :
// import ( derr "github.com/hyperhq/hypercli/errors" )
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
daError, _ := err.(errcode.Error)
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
errMsg = daError.Message
default:
// This part of will be removed once we've
// converted everything over to use the errcode package
// FIXME: this is brittle and should not be necessary.
// If we need to differentiate between different possible error types,
// we should create appropriate error types with clearly defined meaning
errStr := strings.ToLower(err.Error())
for keyword, status := range map[string]int{
"not found": http.StatusNotFound,
"no such": http.StatusNotFound,
"bad parameter": http.StatusBadRequest,
"conflict": http.StatusConflict,
"impossible": http.StatusNotAcceptable,
"wrong login/password": http.StatusUnauthorized,
"hasn't been activated": http.StatusForbidden,
} {
if strings.Contains(errStr, keyword) {
statusCode = status
break
}
}
}
if statusCode == 0 {
statusCode = http.StatusInternalServerError
}
http.Error(w, errMsg, statusCode)
}
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return json.NewEncoder(w).Encode(v)
}
// VersionFromContext returns an API version from the context using APIVersionKey.
// It panics if the context value does not have version.Version type.
func VersionFromContext(ctx context.Context) (ver version.Version) {
if ctx == nil {
return
}
val := ctx.Value(APIVersionKey)
if val == nil {
return
}
return val.(version.Version)
}

View File

@@ -1,195 +0,0 @@
package server
import (
"bufio"
"encoding/json"
"io"
"net/http"
"runtime"
"strings"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/api/server/httputils"
"github.com/hyperhq/hypercli/dockerversion"
"github.com/hyperhq/hypercli/errors"
"github.com/hyperhq/hypercli/pkg/authorization"
"github.com/hyperhq/hypercli/pkg/ioutils"
"github.com/hyperhq/hypercli/pkg/version"
"golang.org/x/net/context"
)
// middleware is an adapter to allow the use of ordinary functions as Docker API filters.
// Any function that has the appropriate signature can be register as a middleware.
type middleware func(handler httputils.APIFunc) httputils.APIFunc
// debugRequestMiddleware dumps the request to logger
func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
logrus.Debugf("%s %s", r.Method, r.RequestURI)
if r.Method != "POST" {
return handler(ctx, w, r, vars)
}
if err := httputils.CheckForJSON(r); err != nil {
return handler(ctx, w, r, vars)
}
maxBodySize := 4096 // 4KB
if r.ContentLength > int64(maxBodySize) {
return handler(ctx, w, r, vars)
}
body := r.Body
bufReader := bufio.NewReaderSize(body, maxBodySize)
r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
b, err := bufReader.Peek(maxBodySize)
if err != io.EOF {
// either there was an error reading, or the buffer is full (in which case the request is too large)
return handler(ctx, w, r, vars)
}
var postForm map[string]interface{}
if err := json.Unmarshal(b, &postForm); err == nil {
if _, exists := postForm["password"]; exists {
postForm["password"] = "*****"
}
formStr, errMarshal := json.Marshal(postForm)
if errMarshal == nil {
logrus.Debugf("form data: %s", string(formStr))
} else {
logrus.Debugf("form data: %q", postForm)
}
}
return handler(ctx, w, r, vars)
}
}
// authorizationMiddleware perform authorization on the request.
func (s *Server) authorizationMiddleware(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// FIXME: fill when authN gets in
// User and UserAuthNMethod are taken from AuthN plugins
// Currently tracked in https://github.com/hyperhq/hypercli/pull/13994
user := ""
userAuthNMethod := ""
authCtx := authorization.NewCtx(s.authZPlugins, user, userAuthNMethod, r.Method, r.RequestURI)
if err := authCtx.AuthZRequest(w, r); err != nil {
logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
rw := authorization.NewResponseModifier(w)
if err := handler(ctx, rw, r, vars); err != nil {
logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
if err := authCtx.AuthZResponse(rw, r); err != nil {
logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
return nil
}
}
// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
dockerVersion := version.Version(s.cfg.Version)
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
// v1.20 onwards includes the GOOS of the client after the version
// such as Docker/1.7.0 (linux)
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
userAgent[1] = strings.Split(userAgent[1], " ")[0]
}
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
logrus.Warnf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
}
}
return handler(ctx, w, r, vars)
}
}
// corsMiddleware sets the CORS header expectations in the server.
func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
// otherwise, all head values will be passed to HTTP handler
corsHeaders := s.cfg.CorsHeaders
if corsHeaders == "" && s.cfg.EnableCors {
corsHeaders = "*"
}
if corsHeaders != "" {
writeCorsHeaders(w, r, corsHeaders)
}
return handler(ctx, w, r, vars)
}
}
// versionMiddleware checks the api version requirements before passing the request to the server handler.
func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
apiVersion := version.Version(vars["version"])
if apiVersion == "" {
apiVersion = api.DefaultVersion
}
if apiVersion.GreaterThan(api.DefaultVersion) {
return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.DefaultVersion)
}
if apiVersion.LessThan(api.MinVersion) {
return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.MinVersion)
}
w.Header().Set("Server", "Docker/"+dockerversion.Version+" ("+runtime.GOOS+")")
ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
return handler(ctx, w, r, vars)
}
}
// handleWithGlobalMiddlwares wraps the handler function for a request with
// the server's global middlewares. The order of the middlewares is backwards,
// meaning that the first in the list will be evaluated last.
//
// Example: handleWithGlobalMiddlewares(s.getContainersName)
//
// s.loggingMiddleware(
// s.userAgentMiddleware(
// s.corsMiddleware(
// versionMiddleware(s.getContainersName)
// )
// )
// )
// )
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
middlewares := []middleware{
versionMiddleware,
s.corsMiddleware,
s.userAgentMiddleware,
}
// Only want this on debug level
if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
middlewares = append(middlewares, debugRequestMiddleware)
}
if len(s.cfg.AuthorizationPluginNames) > 0 {
s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
middlewares = append(middlewares, s.authorizationMiddleware)
}
h := handler
for _, m := range middlewares {
h = m(h)
}
return h
}

View File

@@ -1,57 +0,0 @@
package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/docker/distribution/registry/api/errcode"
"github.com/hyperhq/hypercli/api/server/httputils"
"github.com/hyperhq/hypercli/errors"
"golang.org/x/net/context"
)
func TestVersionMiddleware(t *testing.T) {
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if httputils.VersionFromContext(ctx) == "" {
t.Fatalf("Expected version, got empty string")
}
return nil
}
h := versionMiddleware(handler)
req, _ := http.NewRequest("GET", "/containers/json", nil)
resp := httptest.NewRecorder()
ctx := context.Background()
if err := h(ctx, resp, req, map[string]string{}); err != nil {
t.Fatal(err)
}
}
func TestVersionMiddlewareWithErrors(t *testing.T) {
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if httputils.VersionFromContext(ctx) == "" {
t.Fatalf("Expected version, got empty string")
}
return nil
}
h := versionMiddleware(handler)
req, _ := http.NewRequest("GET", "/containers/json", nil)
resp := httptest.NewRecorder()
ctx := context.Background()
vars := map[string]string{"version": "0.1"}
err := h(ctx, resp, req, vars)
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion {
t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err)
}
vars["version"] = "100000"
err = h(ctx, resp, req, vars)
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion {
t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err)
}
}

View File

@@ -1,38 +0,0 @@
package server
import (
"expvar"
"fmt"
"net/http"
"net/http/pprof"
"github.com/gorilla/mux"
)
func profilerSetup(mainRouter *mux.Router, path string) {
var r = mainRouter.PathPrefix(path).Subrouter()
r.HandleFunc("/vars", expVars)
r.HandleFunc("/pprof/", pprof.Index)
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/pprof/profile", pprof.Profile)
r.HandleFunc("/pprof/symbol", pprof.Symbol)
r.HandleFunc("/pprof/block", pprof.Handler("block").ServeHTTP)
r.HandleFunc("/pprof/heap", pprof.Handler("heap").ServeHTTP)
r.HandleFunc("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
r.HandleFunc("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
}
// Replicated from expvar.go as not public.
func expVars(w http.ResponseWriter, r *http.Request) {
first := true
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}

Some files were not shown because too many files have changed in this diff Show More