Compare commits

...

549 Commits
4.0.0 ... main

Author SHA1 Message Date
Paweł Gronowski 6e6a273573
Merge pull request #3270 from Khushiyant/volume-subpath
Implement Subpath Support for Volumes in Docker-Py (#3243)
2025-06-11 09:21:35 +00:00
Sebastiaan van Stijn 526a9db743
Merge pull request #3336 from thaJeztah/fix_onbuild_assert
integration: adjust tests for omitted "OnBuild"
2025-05-22 10:44:55 +02:00
Sebastiaan van Stijn e5c3eb18b6
integration: adjust tests for omitted "OnBuild"
The Docker API may either return an empty "OnBuild" or omit the
field altogether if it's not set.

Adjust the tests to make either satisfy the test.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 01:46:53 +02:00
Khushiyant 820769e23c feat(docker/api/container): add support for subpath in volume_opts
TESTED: Yes, added unit tests to verify subpath functionality
Signed-off-by: Khushiyant <khushiyant2002@gmail.com>
2025-03-18 23:16:03 +05:30
Shaun Thompson db7f8b8bb6
Merge pull request #3296 from thaJeztah/fix_test_create_volume_invalid_driver
integration: test_create_volume_invalid_driver allow either 400 or 404
2025-01-17 12:32:14 -05:00
Shaun Thompson 747d23b9d7
Merge pull request #3307 from thaJeztah/deprecated_json_error
image load: don't depend on deprecated JSONMessage.error field
2025-01-17 12:30:54 -05:00
Sebastiaan van Stijn fad84c371a
integration: test_create_volume_invalid_driver allow either 400 or 404
The API currently returns a 404 error when trying to create a volume with
an invalid (non-existing) driver. We are considering changing this status
code to be a 400 (invalid parameter), as even though the _reason_ of the
error may be that the plugin / driver is not found, the _cause_ of the
error is that the user provided a plugin / driver that's invalid for the
engine they're connected to.

This patch updates the test to pass for either case.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-14 14:23:24 +01:00
Sebastiaan van Stijn 5a8a42466e
image load: don't depend on deprecated JSONMessage.error field
The error field  was deprecated in favor of the errorDetail struct in
[moby@3043c26], but the API continued to return both. This patch updates
docker-py to not depend on the deprecated field.

[moby@3043c26]: 3043c26419

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-14 13:23:38 +01:00
Sebastiaan van Stijn 03e43be6af
Merge pull request #3297 from thaJeztah/fix_makefile_circref
Makefile: fix circular reference for integration-dind
2024-11-18 18:03:11 +01:00
Sebastiaan van Stijn 80a584651b
Merge pull request #2442 from thaJeztah/test_service_logs_un_experimental
test_service_logs: stop testing experimental versions
2024-11-18 18:02:30 +01:00
Sebastiaan van Stijn 8ee28517c7
test_service_logs: stop testing experimental versions
Service logs are no longer experimental, so updating the tests
to only test against "stable"  implementations, and no longer
test the experimental ones.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-18 16:09:21 +01:00
Sebastiaan van Stijn d9f9b965b2
Makefile: fix circular reference for integration-dind
Noticed this warning;

    make: Circular integration-dind <- integration-dind dependency dropped.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-18 16:07:01 +01:00
Bjorn Neergaard fba6ffe297
Merge pull request #3267 from thaJeztah/add_default_version
Set a dummy-version if none set, and remove unused APT_MIRROR build-arg
2024-11-18 07:48:00 -07:00
Sebastiaan van Stijn 99ce2e6d56
Makefile: remove unused APT_MIRROR build-arg
The APT_MIRROR build-arg was removed from the Dockerfile in commit
ee2310595d, but wasn't removed from the
Makefile.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-18 15:41:24 +01:00
Sebastiaan van Stijn 504ce6193c
Set a dummy-version if none set
Make sure the Dockerfiles can be built even if no VERSION build-arg
is passed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-18 15:41:19 +01:00
Sebastiaan van Stijn bb0edd1f66
Merge pull request #3261 from thaJeztah/bump_engine_versions
Bump default API version to 1.45 (Moby 26.0/26.1)
2024-10-27 17:09:14 +01:00
Sebastiaan van Stijn e47e966e94
Bump default API version to 1.45 (Moby 26.0/26.1)
- Update API version to the latest maintained release.
0 Adjust tests for API 1.45

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-27 17:03:02 +01:00
Sebastiaan van Stijn a8bac88221
Merge pull request #3292 from yasonk/fix-exec_run-doc
fixing doc for stream param in exec_run
2024-09-30 23:22:45 +02:00
Sebastiaan van Stijn e031cf0c23
Merge pull request #3290 from laurazard/exec-no-executable-exit-code
tests/exec: expect 127 exit code for missing executable
2024-09-30 15:18:31 +02:00
Laura Brehm b1265470e6
tests/exec: add test for exit code from exec
Execs should return the exit code of the exec'd process, if it started.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-30 14:07:04 +01:00
yasonk 6bbf741c8c
fixing doc for stream param in exec_run
Signed-off-by: yasonk <yason@hey.com>
2024-09-29 18:58:38 -07:00
Laura Brehm 96ef4d3bee
tests/exec: expect 127 exit code for missing executable
Docker Engine has always returned `126` when starting an exec fails due
to a missing binary, but this was due to a bug in the daemon causing the
correct exit code to be overwritten in some cases – see: https://github.com/moby/moby/issues/45795

Change tests to expect correct exit code (`127`).

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-27 15:33:11 +01:00
Sebastiaan van Stijn a3652028b1
Merge pull request #3264 from krissetto/rename-env-var-in-release-pipeline
Change env var name in release pipeline to match hatch expectations
2024-05-23 13:09:32 +02:00
Christopher Petito 1ab40c8e92 Fix env var name in release pipeline to match hatch expectations
Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-05-23 10:49:23 +00:00
Laura Brehm b33088e0ca
Merge pull request #3263 from krissetto/fix-release-pipeline
Fix env var name in release pipeline
2024-05-23 11:32:12 +01:00
Christopher Petito 45488acfc1 Fix env var name in release pipeline
Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-05-23 10:14:18 +00:00
Laura Brehm 20879eca6a
Merge pull request #3262 from krissetto/changelog-7.1.0 2024-05-23 10:37:29 +01:00
Christopher Petito 4f2a26d21e Added 7.1.0 changelog
Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-05-23 09:30:30 +00:00
Sebastiaan van Stijn 7785ad913d
Merge pull request #3257 from felixfontein/requests-hotfix
Fix for requests 2.32
2024-05-22 15:02:59 +02:00
Felix Fontein d8e9bcb278 requests 2.32.0 and 2.32.1 have been yanked.
Signed-off-by: Felix Fontein <felix@fontein.de>
2024-05-22 14:50:14 +02:00
Felix Fontein 2a059a9f19 Extend fix to requests 2.32.2+.
Signed-off-by: Felix Fontein <felix@fontein.de>
2024-05-22 14:50:14 +02:00
Felix Fontein e33e0a437e Hotfix for requests 2.32.0.
Signed-off-by: Felix Fontein <felix@fontein.de>
2024-05-22 14:50:14 +02:00
Sebastiaan van Stijn b86573a3e3
Merge pull request #3260 from krissetto/fix-ci
CI fix
2024-05-22 14:45:11 +02:00
Christopher Petito e34bcf20d9 Update setup-python gh action
Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-05-22 11:10:22 +00:00
Christopher Petito 205d2f2bd0 Fix to get our CI working again since we rely on parsing tags.
See https://github.com/docker/docker-py/pull/3259 attempts for more details

Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-05-22 10:58:13 +00:00
Bob Du b6464dbed9
chore: fix return type docs for `container.logs()` (#2240) 2024-04-09 16:13:21 -04:00
Milas Bowman 9ad4bddc9e
chore(ci): fix-ups across Make / Docker / GitHub Actions (#3241) 2024-04-03 08:44:29 -04:00
Milas Bowman 336e65fc3c
Merge pull request #3212 from valohai/ruff-i 2024-03-29 13:31:57 -04:00
Milas Bowman 4c6437d292 Merge pull request #3212 from valohai/ruff-i 2024-03-29 13:28:43 -04:00
Milas Bowman 0fd79c8c0d
Merge pull request #3207 from valohai/modernize-build 2024-03-29 13:14:36 -04:00
Paweł Gronowski 3d79ce8c60
Merge pull request #3231 from vvoland/engine-25
Bump default API version to 1.44 (Moby 25.0)
2024-03-25 17:36:55 +01:00
Paweł Gronowski dd82f9ae8e
Bump minimum API version to 1.24
25.0 raised the minimum supported API verison: https://github.com/moby/moby/pull/46887

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-03-14 11:04:39 +01:00
Paweł Gronowski e91b280074
Bump default API version to 1.44 (Moby 25.0)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-03-14 11:04:37 +01:00
Rob Murray cb21af7f69 Fix tests that look at 'Aliases'
Inspect output for 'NetworkSettings.Networks.<network>.Aliases'
includes the container's short-id (although it will be removed
in API v1.45, in moby 26.0).

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-03-13 14:54:25 +00:00
Aarni Koskela 1818712b8c Untangle circular imports
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:40:11 +02:00
Aarni Koskela d50cc429c2 Enable Ruff I (import sort), autofix
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:28:56 +02:00
Aarni Koskela 047df6b0d3 Build wheel in CI, upload artifact for perusal
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:20:13 +02:00
Aarni Koskela ae45d477c4 Use `hatch` for packaging
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:20:11 +02:00
Aarni Koskela f128956034 Use `build` instead of calling setup.py
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:20:02 +02:00
Milas Bowman bd164f928a
Merge pull request #3205 from valohai/drop-packaging-dep
Drop `packaging` dependency
2024-01-03 14:19:02 -05:00
Aarni Koskela 249654d4d9 Drop `packaging` dependency
Compare versions like Moby (api/types/versions/compare.go)

Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 21:12:37 +02:00
Milas Bowman 694d9792e6
lint: fix string formatting (#3211)
Merged a linter upgrade along with an older PR, so this was immediately in violation

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2024-01-03 14:01:42 -05:00
Khushiyant eeb9ea1937
docs: change image.history() return type to list (#3202)
Fixes #3076.

Signed-off-by: Khushiyant <khushiyant2002@gmail.com>
2024-01-03 18:56:10 +00:00
Aarni Koskela 08956b5fbc
ci: update Ruff & fix some minor issues (#3206)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2024-01-03 13:49:07 -05:00
Khushiyant b8a6987cd5
fix: keyerror when creating new config (#3200)
Closes #3110.

---------

Signed-off-by: Khushiyant <khushiyant2002@gmail.com>
2024-01-03 18:44:53 +00:00
Sebastiaan van Stijn f467fd9df9
Merge pull request #3208 from vvoland/fix-datetime_to_timestamp
utils: Fix datetime_to_timestamp
2024-01-03 19:13:33 +01:00
Sven 3ec5a6849a
fix(build): tag regex should allow ports (#3196)
Update the regex and add test cases.

(There are some xfails here for cases that the regex is not currently
handling. It's too strict for IPv6 domains at the moment.)

Closes: https://github.com/docker/docker-py/issues/3195
Related: https://github.com/opencontainers/distribution-spec/pull/498

Signed-off-by: Sven Kieske <kieske@osism.tech>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2024-01-03 10:48:45 -05:00
Paweł Gronowski 1784cc2962
utils: Fix datetime_to_timestamp
Replace usage of deprecated function `datetime.utcfromtimestamp` and
make sure the input date is UTC before subtracting.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2023-12-22 10:57:52 +01:00
Guillaume Lours 6ceb08273c
Merge pull request #3203 from vvoland/deprecated-containerconfig
integration/commit: Don't check for deprecated fields
2023-12-20 17:43:12 +01:00
Guillaume Lours 097382b973
Merge pull request #3199 from vvoland/engine-24
Bump default API version to 1.43 (Moby 24.0)
2023-12-20 08:24:37 +01:00
Paweł Gronowski 0fad869cc6
integration/commit: Don't check for deprecated fields
Container related Image fields (`Container` and `ContainerConfig`) will
be deprecated in API v1.44 and will be removed in v1.45.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2023-12-19 10:28:28 +01:00
Paweł Gronowski 2a5f354b50
Bump default API version to 1.43 (Moby 24.0)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2023-12-15 10:40:27 +01:00
Guillaume Lours 7d8a161b12
Merge pull request #3193 from milas/prep-7.0
chore: update changelog and maintainer
2023-12-08 09:52:27 +01:00
Milas Bowman 5388413dde chore: update changelog and maintainer
Preparing for the 7.0.0 final release 🎉

Added a couple more changelog items that came in as part of
`7.0.0b2` and updated the maintainer to be generically Docker,
Inc. instead of an individual.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-12-07 15:41:29 -05:00
Emran Batmanghelich 3d0a3f1d77
feat: accept all / filters / keep_storage in prune_builds (#3192)
Added in API v1.39.

---------

Signed-off-by: Emran Batmanghelich <emran.bm@gmail.com>
2023-12-05 10:05:44 -05:00
Daniel Lombardi a9b5494fd0
fix: validate tag before build using OCI regex (#3191)
Sources:
 * https://github.com/opencontainers/distribution-spec
 * https://docs.docker.com/engine/reference/commandline/tag/

Closes #3153.
---------

Signed-off-by: Daniel Lombardi <lombardi.daniel.o@gmail.com>
2023-12-05 00:03:13 -05:00
Milas Bowman cb8f2c6630
chore: fix missing setuptools in CI (#3189)
Install `setuptools` in addition to `wheel` before trying to run
`python setup.py` manually.

Note that `setuptools` is already correctly listed in the
`pyproject.toml` file for consumers installing via `pip` etc, but
in CI the file is run directly to generate `sdist` and `bdist_wheel`
artifacts for PyPI.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-27 09:17:47 -05:00
Milas Bowman 7140969239
chore: update MAINTAINERS and remove CODEOWNERS (#3188)
Update `MAINTAINERS` with the current folks, adn remove the
`CODEOWNERS` file entirely -- it's not really helpful here,
as this project isn't big enough to have multiple subsections
with different maintainers/owners.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-21 12:17:12 -05:00
Milas Bowman 586988ce2d
chore: remove support for Python 3.7 (#3187)
Python 3.7 reached EOL in June 2023: https://endoflife.date/python

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-21 12:14:23 -05:00
Milas Bowman fd2f5029f0
chore: add changelog for 7.0.0 (#3186)
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-21 12:08:25 -05:00
Milas Bowman db4878118b
breaking: Python 3.12 compatibility & remove custom SSL adapter (#3185)
Add support for Python 3.12.

`match_hostname` is gone in Python 3.12 and has been unused by
Python since 3.7.

The custom SSL adapter allows passing a specific SSL version; this
was first introduced a looong time ago to handle some SSL issues
at the time.

Closes #3176.

---------

Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2023-11-21 10:42:53 -05:00
dependabot[bot] 976c84c481
build(deps): Bump urllib3 from 1.26.11 to 1.26.18 (#3183)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.11 to 1.26.18.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.11...1.26.18)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 16:56:50 -05:00
Milas Bowman b3349c88ef Merge branch 'pr-3147' 2023-11-20 16:18:23 -05:00
Milas Bowman b2378db7f1 chore: fix lint issue
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-20 16:18:08 -05:00
Milas Bowman 911f866f72
Merge branch 'main' into patch-1 2023-11-20 16:15:52 -05:00
Milas Bowman 26e07251d4 chore: fix lint issues
ruff ruff ruff!

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-11-20 16:11:06 -05:00
Aarni Koskela c9e3efddb8
feat: move websocket-client to extra dependency (#3123)
Also bump minimum version to that prescribed by #3022

Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-11-20 15:55:28 -05:00
Milas Bowman 4a88112345 Merge branch 'pr-3121'
https://github.com/docker/docker-py/pull/3121
2023-11-20 15:44:37 -05:00
Milas Bowman b70cbd0129 Merge remote-tracking branch 'upstream/main' into Skazza94/main 2023-11-20 15:38:27 -05:00
Mariano Scazzariello 7752996f78
Replace `network_config` with a dict of EndpointConfig
- Renamed parameter from `network_config` to `networking_config` to be more semantically correct with the rest of the API.
2023-09-30 00:20:44 +02:00
Jay Turner 5abae2dc8e
Merge branch 'main' into patch-1 2023-09-12 13:35:03 +01:00
Albin Kerouanton c38656dc78
integration: Remove test_create_check_duplicate (#3170)
integration: check_duplicate is now the default behavior

moby/moby#46251 marks CheckDuplicate as deprecated. Any NetworkCreate
request with a conflicting network name will now return an error.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2023-08-21 09:31:57 -04:00
Albin Kerouanton 378325363e
integration: Fix bad subnet declaration (#3169)
Some network integration tests are creating networks with subnet
`2001:389::1/64`. This is an invalid subnet as the host fragment is
non-zero (ie. it should be `2001:389::/64`).

PR moby/moby#45759 is adding strict validation of network configuration.
Docker API will now return an error whenever a bad subnet is passed.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2023-08-21 09:30:21 -04:00
Milas Bowman 0f0b20a6a7 Merge branch 'jannefleischer/main'
Manually merging #3164.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-15 14:04:29 -04:00
Janne Jakob Fleischer bea63224e0 volume: added support for bind propagation
https://docs.docker.com/storage/bind-mounts/#configure-bind-propagation

Signed-off-by: Janne Jakob Fleischer <janne.fleischer@ils-forschung.de>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-15 10:35:07 -04:00
Milas Bowman 8b9ad7807f Merge branch 'ruffify'
Manually merging #3126.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-15 09:58:21 -04:00
Aarni Koskela c68d532f54 Fix duplicate dict key literal (ruff F601)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela a9a3775b15 Noqa pytest.raises(Exception)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 3948540c89 Fix or noqa B003 (assigning to os.environ doesn't do what you expect)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 0566f1260c Fix missing asserts or assignments
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela cc76c9c20d Fix B082 (no explicit stacklevel for warnings)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 09f12f2046 Fix B005 (probably an actual bug too)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 6aec90a41b Fix Ruff B904s (be explicit about exception causes)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 8447f7b0f0 Enable Ruff B rules and autofix
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 601476733c Enable Ruff C rules and autofix
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela ec58856ee3 Clean up unnecessary noqa comments
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela fad792bfc7 Get rid of star import
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 9313536601 Switch linting from flake8 to ruff
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Aarni Koskela 8a3402c049 Replace string formatting with f-strings
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-08-15 13:36:53 +03:00
Sebastiaan van Stijn ee2310595d
test: remove APT_MIRROR from Dockerfile (#3145)
The official Python images on Docker Hub switched to debian bookworm,
which is now the current stable version of Debian.

However, the location of the apt repository config file changed, which
causes the Dockerfile build to fail;

    Loaded image: emptyfs:latest
    Loaded image ID: sha256:0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43
    INFO: Building docker-sdk-python3:5.0.3...
    tests/Dockerfile:6
    --------------------
       5 |     ARG APT_MIRROR
       6 | >>> RUN sed -ri "s/(httpredir|deb).debian.org/${APT_MIRROR:-deb.debian.org}/g" /etc/apt/sources.list \
       7 | >>>     && sed -ri "s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g" /etc/apt/sources.list
       8 |
    --------------------
    ERROR: failed to solve: process "/bin/sh -c sed -ri \"s/(httpredir|deb).debian.org/${APT_MIRROR:-deb.debian.org}/g\" /etc/apt/sources.list     && sed -ri \"s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g\" /etc/apt/sources.list" did not complete successfully: exit code: 2

The APT_MIRROR build-arg was originally added when the Debian package
repositories were known to be unreliable, but that hasn't been the
case for quite a while, so let's remove this altogether.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2023-08-14 15:12:44 -04:00
dependabot[bot] dbc061f4fa
build(deps): Bump requests from 2.28.1 to 2.31.0 (#3136)
Bumps [requests](https://github.com/psf/requests) from 2.28.1 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.28.1...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 15:08:38 -04:00
VincentLeeMax 4571f7f9b4
feat: add pause option to commit api (#3159)
add commit pause option

Signed-off-by: VincentLeeMax <lichlee@yeah.net>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2023-08-14 14:52:38 -04:00
Mehmet Nuri Deveci 0618951093
fix: use response.text to get string rather than bytes (#3156)
Signed-off-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2023-08-14 14:43:31 -04:00
Milas Bowman 806d36a8cd
Merge pull request #3167 from thaJeztah/fix_ci_badge
README: fix link for CI status badge
2023-08-14 09:59:38 -04:00
Milas Bowman 79c4c38b42
Merge pull request #3165 from thaJeztah/tests_relax
tests/integration: update some tests for updated error-messages
2023-08-14 09:52:37 -04:00
Sebastiaan van Stijn 62b4bb8489
README: fix link for CI status badge
The default branch was renamed from master to main, but the badge was still
linking to the status for the master branch.

Remove the branch-name so that the badge always refers to the "default"
branch

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-08-14 14:58:34 +02:00
Sebastiaan van Stijn 5064995bc4
tests/integration: update some tests for updated error-messages
I was in the process of cleaning up some error-messages, and it looks like
the docker-py tests were depending on strings that will be removed;

    =================================== FAILURES ===================================
    _____________ CreateContainerTest.test_create_with_restart_policy ______________
    tests/integration/api_container_test.py:126: in test_create_with_restart_policy
        assert 'You cannot remove ' in err
    E   AssertionError: assert 'You cannot remove ' in 'cannot remove container d11580f6078108691096ec8a23404a6bda9ad1d1b2bafe88b17d127a67728833: container is restarting: stop the container before removing or force remove'
    ____________________ ErrorsTest.test_api_error_parses_json _____________________
    tests/integration/errors_test.py:13: in test_api_error_parses_json
        assert 'You cannot remove a running container' in explanation
    E   AssertionError: assert 'You cannot remove a running container' in 'cannot remove container 4b90ce2e907dd0f99d0f561619b803e7a2a31809ced366c537874dd13f8a47ec: container is running: stop the container before removing or force remove'

This updates the tests to match on a string that will be present in both the
old and new error-messages, but added a "lower()", so that matching will be
done case-insensitive (Go errors generally should be lowercase).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-08-14 14:52:58 +02:00
Milas Bowman 54ec0c6bf7
Merge pull request #3166 from thaJeztah/fix_flake8_failures
tests/integration: fix flake8 failures (E721 do not compare types), and fix Dockerfile for debian "bookworm"
2023-08-14 08:37:58 -04:00
Sebastiaan van Stijn 83e93228ea
tests/Dockerfile: fix Dockerfile for debian bookworm
The Dockerfile failed to build due to the base-image having switched to "bookworm";

    Dockerfile:8
    --------------------
       7 |     ARG APT_MIRROR
       8 | >>> RUN sed -ri "s/(httpredir|deb).debian.org/${APT_MIRROR:-deb.debian.org}/g" /etc/apt/sources.list \
       9 | >>>     && sed -ri "s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g" /etc/apt/sources.list
      10 |
    --------------------
    ERROR: failed to solve: process "/bin/sh -c sed -ri \"s/(httpredir|deb).debian.org/${APT_MIRROR:-deb.debian.org}/g\" /etc/apt/sources.list     && sed -ri \"s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g\" /etc/apt/sources.list" did not complete successfully: exit code: 2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-08-14 10:32:04 +02:00
Sebastiaan van Stijn fb974de27a
tests/integration: fix flake8 failures (E721 do not compare types)
Run flake8 docker/ tests/
      flake8 docker/ tests/
      shell: /usr/bin/bash -e {0}
      env:
        DOCKER_BUILDKIT: 1
        pythonLocation: /opt/hostedtoolcache/Python/3.11.4/x64
        PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.11.4/x64/lib/pkgconfig
        Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.4/x64
        Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.4/x64
        Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.4/x64
        LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.11.4/x64/lib
    tests/integration/api_container_test.py:1395:16: E721 do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
    tests/integration/api_container_test.py:1408:24: E721 do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
    tests/integration/api_image_test.py:35:16: E721 do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
    tests/integration/api_image_test.py:46:16: E721 do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
    Error: Process completed with exit code 1.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-08-14 10:26:36 +02:00
Jay Turner f0d38fb7f4
Add health property to Containers model
Signed-off-by: Jay Turner <jay.turner@kayenta.io>
2023-06-27 12:51:40 +01:00
Hao Yu 84414e343e
fix user_guides/multiplex.rst (#3130)
Signed-off-by: Longin-Yu <longinyh@gmail.com>
2023-06-06 14:28:15 -04:00
Mathieu Virbel 78439ebbe1
fix: eventlet compatibility (#3132)
Check if poll attribute exists on select module instead of win32 platform check

The implementation done in #2865 is breaking usage of docker-py library within eventlet.
As per the Python `select.poll` documentation (https://docs.python.org/3/library/select.html#select.poll) and eventlet select removal advice (https://github.com/eventlet/eventlet/issues/608#issuecomment-612359458), it is preferable to use an implementation based on the availability of the `poll()` method that trying to check if the platform is `win32`.

Fixes #3131

Signed-off-by: Mathieu Virbel <mat@meltingrocks.com>
2023-06-01 10:19:01 -04:00
Mariano Scazzariello 0318ad8e7e
Fix blank line
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-15 14:51:44 +02:00
Mariano Scazzariello 8ca9c6394f
Merge branch 'docker:main' into main 2023-05-15 12:57:02 +02:00
Milas Bowman bc4c0d7cf4 ci: empty commit to trigger readthedocs
Fixing integration

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-05-11 16:05:16 -04:00
Milas Bowman 14e8d07d45
docs: update changelog (#3127)
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-05-11 15:35:42 -04:00
loicleyendecker c5e582c413
api: avoid socket timeouts when executing commands (#3125)
Only listen to read events when polling a socket in order
to avoid incorrectly trying to read from a socket that is
not actually ready.

Signed-off-by: Loïc Leyendecker <loic.leyendecker@gmail.com>
2023-05-11 13:36:37 -04:00
Imogen 9cadad009e
api: respect timeouts on Windows named pipes (#3112)
Signed-off-by: Imogen <59090860+ImogenBits@users.noreply.github.com>
2023-05-08 13:01:19 -04:00
RazCrimson 443a35360f
Fix container.stats infinite blocking on stream mode (#3120)
fix: api - container.stats infinite blocking on stream mode

Includes additional test for no streaming

Signed-off-by: Bharath Vignesh J K <52282402+RazCrimson@users.noreply.github.com>
2023-05-07 19:51:24 -04:00
Mariano Scazzariello e011ff5be8
More sanity checking of EndpointConfig params
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-07 12:40:08 +02:00
Mariano Scazzariello 7870503c52
Fix case when "network_config" is not passed
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-07 12:15:32 +02:00
Mariano Scazzariello a18f91bf08
Fix long line
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-07 11:49:59 +02:00
Mariano Scazzariello a662d5a305
Fix pytests
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-07 11:47:07 +02:00
Mariano Scazzariello 1d697680d2
Full support to networking config during container creation
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-05-07 11:46:58 +02:00
John Yang 576e47aaac
api: update return type of `diff` method (#3115)
Signed-off-by: John Yang <john.yang20@berkeley.edu>
2023-05-05 16:21:46 -04:00
Felix Fontein 3178c8d48b
deps: compatiblity with requests ≥ 2.29.0 and urllib3 2.x (#3116)
Requirements are the same, so it's still possible to use `urllib3 < 2`
or `requests == 2.28.2` for example.

Signed-off-by: Felix Fontein <felix@fontein.de>
2023-05-05 11:39:31 -04:00
I-question-this a02ba74333
socket: use poll() instead of select() except on Windows (#2865)
Fixes #2278, which was originally addressed in #2279, but was not
properly merged. Additionally it did not address the problem
of poll not existing on Windows. This patch falls back on the
more limited select method if host system is Windows.

Signed-off-by: Tyler Westland <tylerofthewest@gmail.com>
2023-04-21 17:53:58 -04:00
Aarni Koskela aaf68b7f98
api: note the data arg may also be a stream in `put_archive` (#2478)
Signed-off-by: Aarni Koskela <akx@iki.fi>
2023-02-22 14:05:19 -05:00
Milas Bowman f84623225e
socket: fix for errors on pipe close in Windows (#3099)
Need to return data, not size. By returning an empty
string, EOF will be detected properly since `len()`
will be `0`.

Fixes #3098.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-02-22 12:00:47 -05:00
Lorin Bucher 7cd7458f2f
api: add `status` parameter to services list (#3093)
Signed-off-by: Lorin Bucher <lorin@lbtec.dev>
2023-02-16 10:38:52 -05:00
Andy Roxby e9d4ddfaec
api: add `one-shot` option to container `stats` (#3089)
Signed-off-by: Andy Roxby <107427605+aroxby-wayscript@users.noreply.github.com>
2023-02-16 10:27:45 -05:00
Milas Bowman aca129dd69 Merge branch 'master'
(Old PR unintentionally went to the `master` branch.)
2023-01-27 09:27:42 -05:00
Mariano Scazzariello ee9151f336
client: add `network_driver_opt` to container run and create (#3083)
Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2023-01-27 09:26:21 -05:00
Peter Wu 34e6829dd4
exec: fix file handle leak with container.exec_* APIs (#2320)
Requests with stream=True MUST be closed or else the connection will
never be returned to the connection pool. Both ContainerApiMixin.attach
and ExecApiMixin.exec_start were leaking in the stream=False case.
exec_start was modified to follow attach for the stream=True case as
that allows the caller to close the stream when done (untested).

Tested with:

    # Test exec_run (stream=False) - observe one less leak
    make integration-test-py3 file=models_containers_test.py' -k test_exec_run_success -vs -W error::ResourceWarning'
    # Test exec_start (stream=True, fully reads from CancellableStream)
    make integration-test-py3 file=api_exec_test.py' -k test_execute_command -vs -W error::ResourceWarning'

After this change, one resource leak is removed, the remaining resource
leaks occur because none of the tests call client.close().

Fixes https://github.com/docker/docker-py/issues/1293
(Regression from https://github.com/docker/docker-py/pull/1130)

Signed-off-by: Peter Wu <pwu@cloudflare.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2023-01-13 15:41:01 -05:00
Yanlong Wang 22718ba59a
fix(store): warn on init instead of throw (#3080)
Signed-off-by: yanlong.wang <yanlong.wang@naiver.org>
2023-01-10 17:45:25 -05:00
dependabot[bot] d38b41a13c
build(deps): Bump setuptools from 63.2.0 to 65.5.1 (#3082)
Bumps [setuptools](https://github.com/pypa/setuptools) from 63.2.0 to 65.5.1.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/CHANGES.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v63.2.0...v65.5.1)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 17:25:12 -05:00
Maxim Mironyuk 3afb4b61c3
docs: fix wrong command syntax in code annotation (#3081)
Signed-off-by: Maxim Mironyuk <serieznyi@gmail.com>
2023-01-10 16:58:51 -05:00
loicleyendecker 82cf559b5a
volume: do not strip trailing characters from names (#3073)
Only remove `:ro` or `:rw` suffixes in their entirety; do not
strip arbitrary `r` / `o` / `w` / `:` characters individually.

Signed-off-by: Loïc Leyendecker <loic.leyendecker@gmail.com>
2022-12-02 14:48:04 -05:00
Hugo van Kemenade 8590eaad3c
ci: add support for Python 3.11 (#3064)
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2022-11-15 08:10:56 -05:00
Nick Santos 30022984f6
socket: handle npipe close on Windows (#3056)
Fixes https://github.com/docker/docker-py/issues/3045

Signed-off-by: Nick Santos <nick.santos@docker.com>
2022-11-02 15:31:00 -04:00
Brian Goff bc0a5fbacd
test: use anonymous volume for prune (#3051)
This is related to https://github.com/moby/moby/pull/44216

Prunes will, by default, no longer prune named volumes, only anonymous ones.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2022-10-05 13:54:45 -04:00
Rhiza 923e067ddd
api: add support for floats to docker logs params since / until (#3031)
Add support for floats to docker logs params `since` / `until` since the
Docker Engine APIs support it.

This allows using fractional seconds for greater precision.

Signed-off-by: Archi Moebius <poerhiz@gmail.com>
2022-08-19 15:10:53 -04:00
Sebastiaan van Stijn 1c27ec1f0c
ci: use latest stable syntax for Dockerfiles (#3035)
I noticed one Dockerfile was pinned to 1.4; given that there's a
backward compatibility guarantee on the stable syntax, the general
recommendation is to use `dockerfile:1`, which makes sure that the
latest stable release of the Dockerfile syntax is pulled before
building.

While changing, I also made some minor changes to some Dockerfiles
to reduce some unneeded layers.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-19 15:09:12 -04:00
Milas Bowman 2494d63f36
docs: install package in ReadTheDocs build (#3032)
Need to install ourselves so that we can introspect on version
using `setuptools_scm` in `docs/conf.py`.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-08-18 17:03:32 -04:00
Milas Bowman e901eac7a8
test: add additional tests for cgroupns option (#3024)
See #2930.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-08-12 14:27:53 -04:00
Chris Hand fc86ab0d85
swarm: add support for DataPathPort on init (#2987)
Adds support for setting the UDP port used for VXLAN traffic between
swarm nodes

Signed-off-by: Chris Hand <dexteradeus@users.noreply.github.com>
2022-08-12 09:58:57 -04:00
Milas Bowman 45bf9f9115 Merge remote-tracking branch 'upstream/main' into patch-1 2022-08-12 09:43:20 -04:00
Milas Bowman c03aeb659e Merge remote-tracking branch 'upstream/main' into connect-with-mac 2022-08-12 09:21:51 -04:00
Quentin Mathorel 58aa62bb15
swarm: add sysctl support for services (#3029)
Signed-off-by: Quentin Mathorel <quentin.mathorel@outlook.fr>
2022-08-12 08:55:19 -04:00
Milas Bowman ff0b4ac60b
docs: add changelog for 6.0.0 (#3019)
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-08-11 17:20:13 -04:00
Leonard Kinday 66402435d1
Support `global-job` and `replicated-job` modes in Docker Swarm (#3016)
Add `global-job` and `replicated-job` modes

Fixes #2829.

Signed-off-by: Leonard Kinday <leonard@kinday.ru>
2022-08-11 16:20:31 -04:00
Ningú 42789818be
credentials: eliminate distutils deprecation warnings (#3028)
While removing any usage of the deprecated `distutils` package,
("The distutils package is deprecated and slated for removal in Python 3.12.")
this internal utility can be removed straightaway because the
`shutil.which` replacement for `distutils.spawn.find_executable`
already honors the `PATHEXT` environment variable in windows systems.

See https://docs.python.org/3/library/shutil.html#shutil.which

Signed-off-by: Daniel Möller <n1ngu@riseup.net>
2022-08-02 10:19:50 -04:00
q0w ab5e927300
lint: remove extraneous logic for `preexec_func` (#2920)
`preexec_func` is still None if it is win32

Signed-off-by: q0w <43147888+q0w@users.noreply.github.com>
2022-08-02 10:11:07 -04:00
Saurav Maheshkar b7daa52feb
docs: add `gzip` arg to `BuildApiMixin` (#2929)
Signed-off-by: Saurav Maheshkar <sauravvmaheshkar@gmail.com>
2022-08-02 10:08:24 -04:00
Thomas Gassmann 3f0095a7c1
docs: remove duplicate 'on' in comment (#2370)
Remove duplicate 'on' in comment

Signed-off-by: Thomas Gassmann <thomas.gassmann@hotmail.com>
2022-07-30 12:43:29 -04:00
Milas Bowman 631b332cd9
ci: add missing wheel package
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-30 12:23:53 -04:00
Milas Bowman 7f1bde162f
ci: fix quoting in YAML
Because apparently `!` is special

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-30 12:20:50 -04:00
Milas Bowman cd2c35a9b6
ci: add workflow for releases (#3018)
GitHub Actions workflow to create a release: will upload to PyPI
and create a GitHub release with the `sdist` and `bdist_wheel`
as well.

The version code is switched to `setuptools_scm` to work well
with this flow (e.g. avoid needing to write a script that does
a `sed` on the version file and commits as part of release).

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-30 12:14:27 -04:00
Milas Bowman 828d06f5f5
docs: fix RollbackConfig/Order values (#3027)
Closes #2626.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-30 12:09:36 -04:00
Max Fan dff849f6bb
docs: image build clarifications/grammar (#2489)
I changed was build > was built and reorganized a few sentences to be more clear.

Signed-off-by: InnovativeInventor <theinnovativeinventor@gmail.com>
2022-07-29 16:15:58 -04:00
Hristo Georgiev 52fb27690c
docs: fix image save example (#2570)
Signed-off-by: Hristo Georgiev <hristo.a.georgiev@gmail.com>
2022-07-29 16:04:23 -04:00
Milas Bowman 547cc5794d Merge branch 'docs-healthcheck'
Manually merging #2595 to include a tiny fix.
2022-07-29 16:02:47 -04:00
Milas Bowman 003a16503a docs: fix list formatting
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-29 16:01:29 -04:00
Milas Bowman c6c2bbdcda Merge remote-tracking branch 'upstream/main' into HEAD 2022-07-29 15:56:01 -04:00
Ville Skyttä 73421027be
docs: clarify TLSConfig verify parameter (#2573)
Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2022-07-29 15:55:14 -04:00
Ville Skyttä 55f47299c4
docs: fix TLS server verify example (#2574)
Leaving out the verify parameter means verification will not be done.

Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2022-07-29 15:54:27 -04:00
Clément Loiselet 3ee3a2486f
build: trim trailing whitespace from dockerignore entries (#2733)
fix(dockerignore): trim trailing whitespace

Signed-off-by: Clément Loiselet <clement.loiselet@capgemini.com>
2022-07-29 15:33:23 -04:00
Milas Bowman 868e996269
model: add remove() to Image (#3026)
Allow an Image to be deleted by calling the remove() method on it,
just like a Volume.

Signed-off-by: Ahmon Dancy <dancy@dancysoft.com>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Ahmon Dancy <dancy@dancysoft.com>
2022-07-29 15:28:16 -04:00
ercildoune 26753c81de
api: add rollback_config to service create (#2917)
`rollback_config` was not in the list of `CREATE_SERVICE_KWARGS`
which prevented it from being an argument when creating services.
It has now been added and the problem fixed, allowing services to
have a rollback_config during creation and updating.

Fixes #2832.

Signed-off-by: Fraser Patten <pattenf00@gmail.com>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2022-07-29 14:54:55 -04:00
Till! 0031ac2186
api: add force to plugin disable (#2843)
Signed-off-by: till <till@php.net>
2022-07-29 14:51:43 -04:00
Peter Dave Hello b2a18d7209
build: disable pip cache in Dockerfile (#2828)
Signed-off-by: Peter Dave Hello <hsu@peterdavehello.org>
2022-07-29 14:09:06 -04:00
David d69de54d7c
api: add cgroupns option to container create (#2930)
Signed-off-by: David Otto <ottodavid@gmx.net>
2022-07-29 14:04:47 -04:00
Felix Fontein 1a4cacdfb6
api: add platform to container create (#2927)
Add platform parameter for container creation/run

Signed-off-by: Felix Fontein <felix@fontein.de>
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2022-07-29 13:57:30 -04:00
Milas Bowman 26064dd6b5
deps: upgrade websocket-client to latest (#3022)
* Upgrade websocket-client to latest
* Add basic integration test for streaming logs via websocket

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-29 11:09:47 -04:00
Milas Bowman 05e143429e
api: preserve cause when re-raising error (#3023)
Use `from e` to ensure that the error context is propagated
correctly.

Fixes #2702.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-29 11:08:00 -04:00
Ben Fasoli 23cf16f03a
client: use 12 character short IDs (#2862)
Use 12 characters for Docker resource IDs for
consistency with the Docker CLI.

Signed-off-by: Ben Fasoli <benfasoli@gmail.com>
2022-07-29 09:06:22 -04:00
Milas Bowman ab43018b02
docs: fix markdown rendering (#3020)
Follow instructions at https://www.sphinx-doc.org/en/master/usage/markdown.html.

This switches from `recommonmark` (deprecated) to `myst-parser`
(recommended).

Only impacts the changelog page, which was broken after recent upgrades
to Sphinx for Python 3.10 compatibility.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-28 16:38:57 -04:00
Milas Bowman 9bdb5ba2ba
lint: fix line length violation (#3017)
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-28 11:25:17 -04:00
Milas Bowman be942f8390
deps: upgrade & remove unnecessary dependencies (#3014)
The `requirements.txt` and `setup.py` had a lot of extra transitive
dependencies to try and address various SSL shortcomings from the
Python ecosystem.

Thankfully, between modern Python versions (3.6+) and corresponding
`requests` versions (2.26+), this is all unnecessary now!

As a result, a bunch of transitive dependencies have been removed
from `requirements.txt`, the minimum version of `requests` increased,
and the `tls` extra made into a no-op.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-28 08:32:00 -04:00
Milas Bowman bf026265e0
ci: bump version to 6.0.0-dev (#3013)
It's been a long time without a release, and we've included a
number of fixes as well as raised the minimum Python version,
so a major release seems in order.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-28 08:31:45 -04:00
Tim Gates d2d097efbb
docs: fix simple typo, containe -> container (#3015)
There is a small typo in docker/types/services.py.

Should read `container` rather than `containe`.

Signed-off-by: Tim Gates <tim.gates@iress.com>
2022-07-28 08:30:40 -04:00
Milas Bowman acdafbc116
ci: run SSH integration tests (#3012)
Fix & enable SSH integration test suite.

This also adds a new test for connecting to unknown hosts when
using the Python SSH implementation (Paramiko). See #2932 for
more info.

Because of the above, some of the config/static key files have been
moved around and adjusted.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-27 16:25:27 -04:00
Vilhelm Prytz ea4cefe4fd
lint: remove unnecessary pass statements (#2541)
Signed-off-by: Vilhelm Prytz <vilhelm@prytznet.se>
2022-07-27 15:31:04 -04:00
Karthikeyan Singaravelan adf5a97b12
lint: fix deprecation warnings from threading package (#2823)
Set `daemon` attribute instead of using `setDaemon` method that
was deprecated in Python 3.10.

Signed-off-by: Karthikeyan Singaravelan <tir.karthi@gmail.com>
2022-07-27 15:25:11 -04:00
Audun V. Nes d9298647d9
ssh: reject unknown host keys when using Python SSH impl (#2932)
In the Secure Shell (SSH) protocol, host keys are used to verify the identity of remote hosts. Accepting unknown host keys may leave the connection open to man-in-the-middle attacks.

Do not accept unknown host keys. In particular, do not set the default missing host key policy for the Paramiko library to either AutoAddPolicy or WarningPolicy. Both of these policies continue even when the host key is unknown. The default setting of RejectPolicy is secure because it throws an exception when it encounters an unknown host key.

Reference: https://cwe.mitre.org/data/definitions/295.html

NOTE: This only affects SSH connections using the native Python SSH implementation (Paramiko), when `use_ssh_client=False` (default). If using the system SSH client (`use_ssh_client=True`), the host configuration
(e.g. `~/.ssh/config`) will apply.

Signed-off-by: Audun Nes <audun.nes@gmail.com>
2022-07-27 15:01:41 -04:00
errorcode bb40ba051f
ssh: do not create unnecessary subshell on exec (#2910)
Signed-off-by: liubo <liubo@uniontech.com>
2022-07-27 14:57:26 -04:00
Milas Bowman 52e29bd446
deps: remove backports.ssl_match_hostname (#3011)
This is no longer needed as it exists in every supported (non-EOL)
version of Python that we target.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-27 14:44:50 -04:00
Milas Bowman da62a28837
deps: test on Python 3.10 by default (#3010)
* Upgrade to latest Sphinx / recommonmark
* Small CSS fix for issue in new version of Alabaster theme
* Fix `Makefile` target for macOS

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-27 14:44:36 -04:00
Leonard Kinday 0ee9f260e4
ci: run integration tests & fix race condition (#2947)
* Fix integration tests race condition
* Run integration tests on CI
* Use existing DIND version

Signed-off-by: Leonard Kinday <leonard@kinday.ru>

Co-authored-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 16:33:21 -04:00
Milas Bowman b9ca58a56d
Merge pull request #3009 from milas/lint-flake8
ci: add flake8 job
2022-07-26 15:48:55 -04:00
Milas Bowman cf6210316f
Merge pull request #3008 from milas/flaky-tests
test: fix a couple flaky/broken tests
2022-07-26 15:48:35 -04:00
Francesco Casalegno 2e6dad7983
deps: use `packaging` instead of deprecated `distutils` (#2931)
Replace `distutils.Version` (deprecated) with `packaging.Version`

Signed-off-by: Francesco Casalegno <francesco.casalegno@gmail.com>
2022-07-26 15:45:51 -04:00
Guy Lichtman 4e19cc48df
transport: fix ProxyCommand for SSH conn (#2993)
Signed-off-by: Guy Lichtman <glicht@users.noreply.github.com>
2022-07-26 15:16:12 -04:00
Milas Bowman 56dd6de7df
tls: use auto-negotiated highest version (#3007)
Specific TLS versions are deprecated in latest Python, which
causes test failures due to treating deprecation errors as
warnings.

Luckily, the fix here is straightforward: we can eliminate some
custom version selection logic by using `PROTOCOL_TLS_CLIENT`,
which is the recommended method and will select the highest TLS
version supported by both client and server.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 15:12:03 -04:00
Maor Kleinberger bb11197ee3
client: fix exception semantics in _raise_for_status (#2954)
We want "The above exception was the direct cause of the following exception:" instead of "During handling of the above exception, another exception occurred:"

Signed-off-by: Maor Kleinberger <kmaork@gmail.com>
2022-07-26 15:07:23 -04:00
Milas Bowman 3ffdd8a1c5 lint: fix outstanding flake8 violations
Since flake8 wasn't actually being run in CI, we'd accumulated some
violations.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 13:48:47 -04:00
Milas Bowman ce40d4bb34 ci: add flake8 job
Project is already configured for flake8 but it never gets run in
CI.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 13:48:10 -04:00
Milas Bowman 4765f62441 test: mark invalid test as xfail
This test looks for some behavior on non-chunked HTTP requests.

It now fails because it looks like recent versions of Docker Engine
ALWAYS return chunked responses (or perhaps this specific response
changed somehow to now trigger chunking whereas it did not previously).

The actual logic it's trying to test is also unusual because it's
trying to hackily propagate errors under the assumption that it'd get
a non-chunked response on failure, which is...not reliable. Arguably,
the chunked reader should be refactored somehow but that's a refactor
we can't really commit to (and it's evidently been ok enough as is
up until now).

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 13:00:49 -04:00
Milas Bowman 74e0c5eb8c test: fix flaky container log test
Ensure the container has exited before attempting to grab the logs.

Since we are not streaming them, it's possible to attach + grab logs
before the output is processed, resulting in a test failure. If the
container has exited, it's guaranteed to have logged :)

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 13:00:49 -04:00
Sebastiaan van Stijn 7168e09b16
test: fix for cgroupv2 (#2940)
This test was verifying that the container has the right options set (through
`docker inspect`), but also checks if the cgroup-rules are set within the
container by reading `/sys/fs/cgroup/devices/devices.list`

Unlike cgroups v1, on cgroups v2, there is no file interface, and rules are
handled through ebpf, which means that the test will fail because this file
is not present.

From the Linux documentation for cgroups v2:
https://github.com/torvalds/linux/blob/v5.16/Documentation/admin-guide/cgroup-v2.rst#device-controller

> (...)
> Device controller manages access to device files. It includes both creation of
> new device files (using mknod), and access to the existing device files.
>
> Cgroup v2 device controller has no interface files and is implemented on top
> of cgroup BPF. To control access to device files, a user may create bpf programs
> of type BPF_PROG_TYPE_CGROUP_DEVICE and attach them to cgroups with
> BPF_CGROUP_DEVICE flag. (...)

Given that setting the right cgroups is not really a responsibility of this SDK,
it should be sufficient to verify that the right options were set in the container
configuration, so this patch is removing the part that checks the cgroup, to
allow this test to be run on a host with cgroups v2 enabled.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-07-26 12:06:51 -04:00
Milas Bowman f16c4e1147
utils: fix IPv6 address w/ port parsing (#3006)
This was using a deprecated function (`urllib.splitnport`),
ostensibly to work around issues with brackets on IPv6 addresses.

Ironically, its usage was broken, and would result in mangled IPv6
addresses if they had a port specified in some instances.

Usage of the deprecated function has been eliminated and extra test
cases added where missing. All existing cases pass as-is. (The only
other change to the test was to improve assertion messages.)

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 11:35:44 -04:00
Milas Bowman 2933af2ca7
ci: remove Python 3.6 and add 3.11 pre-releases (#3005)
* Python 3.6 went EOL Dec 2021
* Python 3.11 is in beta and due for GA release in October 2022

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 08:51:52 -04:00
Milas Bowman a6db044bd4
deps: upgrade pywin32 & relax version constraint (#3004)
Upgrade to latest pywin32, which has support for Python 3.10 and
resolves a CVE (related to ACL APIs, outside the scope of what
`docker-py` relies on, which is npipe support, but still gets
flagged by scanners).

The version constraint has also been relaxed in `setup.py` to allow
newer versions of pywin32. This is similar to how we handle the
other packages there, and should be safe from a compatibility
perspective.

Fixes #2902.
Closes #2972 and closes #2980.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-07-26 08:43:45 -04:00
Laura Brehm e131955685
Merge pull request #2974 from docker/dependabot/pip/paramiko-2.10.1
Bump paramiko from 2.8.0 to 2.10.1
2022-07-13 12:50:12 +02:00
dependabot[bot] e0a3abfc37
Bump paramiko from 2.8.0 to 2.10.1
Bumps [paramiko](https://github.com/paramiko/paramiko) from 2.8.0 to 2.10.1.
- [Release notes](https://github.com/paramiko/paramiko/releases)
- [Changelog](https://github.com/paramiko/paramiko/blob/main/NEWS)
- [Commits](https://github.com/paramiko/paramiko/compare/2.8.0...2.10.1)

---
updated-dependencies:
- dependency-name: paramiko
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 21:55:39 +00:00
Stefan Scherer a48a5a9647
Merge pull request #2898 from hugovk/add-3.10
Add support for Python 3.10
2021-10-14 19:25:22 +02:00
Stefan Scherer ac5f6ef93a
Merge pull request #2897 from aiordache/changelog_5.0.3
Update changelog for 5.0.3
2021-10-14 10:46:17 +02:00
Hugo van Kemenade 4bb99311e2 Don't install mock backport
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2021-10-11 23:06:12 +03:00
Hugo van Kemenade bbbc29191a Bump minimum paramiko to support Python 3.10
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2021-10-11 23:06:12 +03:00
Hugo van Kemenade 72bcd1616d Bump pytest (and other dependencies) for Python 3.10
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2021-10-11 23:06:12 +03:00
Hugo van Kemenade 4150fc4d9d Universal wheels are for code expected to work on both Python 2 and 3
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2021-10-11 23:06:12 +03:00
Hugo van Kemenade a9de343210 Add support for Python 3.10
Signed-off-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2021-10-11 23:06:12 +03:00
Anca Iordache ecace769f5 Post-release changelog update
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2021-10-08 00:58:26 +02:00
Anca Iordache 7172269b06
Merge pull request #2806 from feliperuhland/fix-low-level-volumes-example
Fix volumes low-level documentation examples
2021-10-08 00:05:55 +02:00
Anca Iordache fcb35f4197
Merge pull request #2810 from feliperuhland/fix-low-level-swarm-example
Fix swarm low-level documentation examples
2021-10-08 00:04:02 +02:00
Anca Iordache 3c5f0d0ee1
Merge pull request #2805 from feliperuhland/fix-low-level-network-example
Fix network low-level documentation examples
2021-10-08 00:03:11 +02:00
Anca Iordache 7779b84e87
Merge pull request #2809 from feliperuhland/add-service-capability
Add service capability
2021-10-08 00:01:28 +02:00
Anca Iordache df59f538c2
Merge pull request #2852 from Phat3/feat/add_templating_parameter_docker_config
Add the possibility to set a templating driver when creating a new Docker config
2021-10-07 23:59:08 +02:00
Anca Iordache aae6be0c58
Merge branch 'master' into feat/add_templating_parameter_docker_config 2021-10-07 23:29:03 +02:00
Anca Iordache b8258679b3
Merge pull request #2888 from aiordache/changelog_5.0.2
Post-release changelog update
2021-10-07 23:17:38 +02:00
Anca Iordache b27faa62e7
Merge pull request #1959 from segevfiner/logs-read-timeout
Fix getting a read timeout for logs/attach with a tty and slow output
2021-09-17 12:58:19 +02:00
Segev Finer 63618b5e11 Fix getting a read timeout for logs/attach with a tty and slow output
Fixes #931

Signed-off-by: Segev Finer <segev208@gmail.com>
2021-09-17 13:01:14 +03:00
Anca Iordache a9265197d2 Post-release changelog update
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2021-09-01 19:23:59 +02:00
Anca Iordache 264688e37c
Merge pull request #2878 from sina-rostami/master
Improve containers documentation
2021-09-01 18:55:38 +02:00
Anca Iordache d06db4d9e1
Merge pull request #2884 from aiordache/changelog_update_5.0.1
Update changelog post-release 5.0.1
2021-09-01 18:46:28 +02:00
Ulysses Souza dbb28a5af1
Merge pull request #2886 from Aposhian/fix-disable-buffering
fix(transport): remove disable_buffering option
2021-09-01 06:44:48 -07:00
Adam Aposhian f9b85586ca fix(transport): remove disable_buffering option
Signed-off-by: Adam Aposhian <aposhian.dev@gmail.com>
2021-08-31 15:26:09 -06:00
Anca Iordache c5fc193857 Update changelog for 5.0.1 release
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2021-08-31 16:39:50 +02:00
Ulysses Souza 3c3aa69997
Merge pull request #2883 from aiordache/changelog
Update changelog for v5.0.0
2021-08-31 07:11:12 -07:00
Anca Iordache 4a3cddf4bf Update changelog for v5.0.0
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2021-08-31 15:57:32 +02:00
Anca Iordache 62af2bbb13
Merge pull request #2846 from shehzaman/feature/missing-identity-file
Put back identityfile parameter from .ssh/config
2021-08-31 14:42:26 +02:00
Shehzaman 8da03e0126 Put back identityfile parameter
Signed-off-by: Shehzaman <shehzi.1@gmail.com>
2021-08-31 09:23:44 -03:00
Ulysses Souza 5705d12813
Merge pull request #2874 from docker/dependabot/pip/pywin32-301
Bump pywin32 from 227 to 301
2021-08-31 04:53:47 -07:00
sinarostami 2fa56879a2 Improve containers documentation
Signed-off-by: sinarostami <Sinarostami4188.1@gmail.com>
2021-08-16 00:24:12 +04:30
dependabot[bot] e0d186d754
Bump pywin32 from 227 to 301
Bumps [pywin32](https://github.com/mhammond/pywin32) from 227 to 301.
- [Release notes](https://github.com/mhammond/pywin32/releases)
- [Changelog](https://github.com/mhammond/pywin32/blob/master/CHANGES.txt)
- [Commits](https://github.com/mhammond/pywin32/commits)

---
updated-dependencies:
- dependency-name: pywin32
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 20:57:04 +00:00
Ulysses Souza 1abeb46dfa
Merge pull request #2850 from docker/dependabot/pip/urllib3-1.26.5
Bump urllib3 from 1.24.3 to 1.26.5
2021-08-06 05:58:50 -07:00
dependabot[bot] 582f6277ce
Bump urllib3 from 1.24.3 to 1.26.5
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.24.3 to 1.26.5.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.24.3...1.26.5)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-06 12:46:56 +00:00
Ulysses Souza 2cf3900030
Merge pull request #2873 from ulyssessouza/bump-requests
Bump requests => 2.26.0
2021-08-06 05:46:04 -07:00
Ulysses Souza 19d6cd8a01 Bump requests => 2.26.0
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-08-06 09:32:42 -03:00
Ulysses Souza a9748a8b70
Merge pull request #2863 from asottile/py36_plus
use python3.6+ constructs
2021-07-08 11:29:25 -03:00
Anthony Sottile 5fcc293ba2 use python3.6+ constructs
Signed-off-by: Anthony Sottile <asottile@umich.edu>
2021-07-05 18:30:07 -04:00
Mathieu Champlon 650aad3a5f
Merge pull request #2851 from huogerac/issue836_better_docs
Update the Image.save documentation with a working example. Issue #836
2021-06-29 08:53:34 +02:00
Sebastiano Mariani f42a81dca2 Add the possibility to set a templating driver when creating a new Docker config
Signed-off-by: Sebastiano Mariani <smariani@vmware.com>
2021-06-03 15:51:52 -07:00
Roger Camargo d58ca97207 [DOCS] Update the Image.save documentation with a working example. Issue #836
Signed-off-by: Roger Camargo <huogerac@gmail.com>
2021-06-03 09:40:50 -03:00
Felipe Ruhland 13c316de69 Fix swarm low-level documentation examples
I realize that low-level documentation has outdated examples, so I
created issue #2800 to fix that

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-07 22:55:23 +02:00
Felipe Ruhland 7ac8b56730 Add `CapabilityAdd` and `CapabilityDrop` to
ContainerSpec

Docker Engine v1.41 added `CapAdd` and `CapDrop` as part of the
ContainerSpec, and `docker-py` should do the same.

```
GET /services now returns CapAdd and CapDrop as part of the ContainerSpec.
GET /services/{id} now returns CapAdd and CapDrop as part of the ContainerSpec.
POST /services/create now accepts CapAdd and CapDrop as part of the ContainerSpec.
POST /services/{id}/update now accepts CapAdd and CapDrop as part of the ContainerSpec.
GET /tasks now returns CapAdd and CapDrop as part of the ContainerSpec.
GET /tasks/{id} now returns CapAdd and CapDrop as part of the ContainerSpec.
```

I added capabilities on docstrings, `service.create` init method and
create tests for that.

That change was mention in issue #2802.

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-07 22:11:52 +02:00
Felipe Ruhland f53e615e0f Update API and Engine versions
The Makefile and `docker/constants.py` were with old versions, so I
updated them to the current one

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-07 21:44:24 +02:00
Felipe Ruhland 50a0ff596f Fix network low-level documentation examples
I realize that low-level documentation has outdated examples, so I
created issue #2800 to fix that

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-06 20:52:49 +02:00
Felipe Ruhland 4b44fa7e5d Fix volumes low-level documentation examples
I realize that low-level documentation has outdated examples, so I
created issue #2800 to fix that

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-06 20:32:05 +02:00
Anca Iordache 96c12726fd
Merge pull request #2804 from aiordache/update_maintainer
Update maintainers file and setup.py
2021-04-06 17:08:27 +01:00
Anca Iordache 8945fda6be Update maintainers
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2021-04-06 16:01:16 +02:00
Anca Iordache 18fdc23b7c
Merge pull request #2476 from feliperuhland/add-search-images-limit
Add limit parameter to image search endpoint
2021-04-06 14:43:07 +01:00
Anca Iordache 8813c3d2e0
Merge pull request #2799 from feliperuhland/fix-low-level-pull-example
Fix images low-level documentation examples
2021-04-06 14:41:44 +01:00
Anca Iordache 7a2ec95951
Merge pull request #2801 from feliperuhland/fix-low-level-containers-example
Fix containers low-level documentation examples
2021-04-06 14:40:39 +01:00
Felipe Ruhland ac9ae1f249 Fix containers low-level documentation examples
I realize that low-level documentation has outdated examples, so I
created issue #2800 to fix that

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-05 15:31:43 +02:00
Felipe Ruhland a34dd8b1a9 Fix images low-level documentation examples
I realize that the documentation of low-level `images` was outdated when
answering issue #2798

The issue can reproduce it with a simple test:

```py
In [1]: import docker
In [2]: client = docker.from_env()
In [3]: client.pull
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-d0931943ca5d> in <module>
----> 1 client.pull

~/docker-py/docker/client.py in __getattr__(self, name)
    219                      "object APIClient. See the low-level API section of the "
    220                      "documentation for more details.")
--> 221         raise AttributeError(' '.join(s))
    222
    223

AttributeError: 'DockerClient' object has no attribute 'pull' In Docker SDK for Python 2.0, this method is now on the object APIClient. See the low-level API section of the documentation for more details.

In [4]: client.push
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-f7d5b860a184> in <module>
----> 1 client.push

~/docker-py/docker/client.py in __getattr__(self, name)
    219                      "object APIClient. See the low-level API section of the "
    220                      "documentation for more details.")
--> 221         raise AttributeError(' '.join(s))
    222
    223

AttributeError: 'DockerClient' object has no attribute 'push' In Docker SDK for Python 2.0, this method is now on the object APIClient. See the low-level API section of the documentation for more details.

In [5]: client.tag
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-043bdfd088ca> in <module>
----> 1 client.tag

~/docker-py/docker/client.py in __getattr__(self, name)
    219                      "object APIClient. See the low-level API section of the "
    220                      "documentation for more details.")
--> 221         raise AttributeError(' '.join(s))
    222
    223

AttributeError: 'DockerClient' object has no attribute 'tag' In Docker SDK for Python 2.0, this method is now on the object APIClient. See the low-level API section of the documentation for more details.

In [6]: client.get_image
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-477c12713276> in <module>
----> 1 client.get_image

~/docker-py/docker/client.py in __getattr__(self, name)
    219                      "object APIClient. See the low-level API section of the "
    220                      "documentation for more details.")
--> 221         raise AttributeError(' '.join(s))
    222
    223

AttributeError: 'DockerClient' object has no attribute 'get_image' In Docker SDK for Python 2.0, this method is now on the object APIClient. See the low-level API section of the documentation for more details.

In [7]: client.api.get_image
Out[7]: <bound method ImageApiMixin.get_image of <docker.api.client.APIClient object at 0x7fad6a2037c0>>

In [8]: client.api.tag
Out[8]: <bound method ImageApiMixin.tag of <docker.api.client.APIClient object at 0x7fad6a2037c0>>

In [9]: client.api.pull
Out[9]: <bound method ImageApiMixin.pull of <docker.api.client.APIClient object at 0x7fad6a2037c0>>

In [10]: client.api.push
Out[10]: <bound method ImageApiMixin.push of <docker.api.client.APIClient object at 0x7fad6a2037c0>>
```

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-05 15:01:09 +02:00
Ulysses Souza 0892fcfc12
Merge pull request #2795 from feliperuhland/upgrade-cryptography-to-3.4.7
Upgrade cryptography library to version 3.4.7
2021-04-03 14:48:58 -03:00
Felipe Ruhland 2403774e76 Upgrade cryptography library to version 3.4.7
Dependabot opened a pull request
93bcc0497d to upgrade cryptography from
2.3 to 3.2.

However, only `requirements.txt` was updated.
The extra requirements were kept outdated.

This commit was made to update the library to the last version.

Fix #2791

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-04-02 02:51:20 +02:00
Ulysses Souza a60bd9a454
Merge pull request #2781 from feliperuhland/fix-readme-badge
Fix continuous integration status badged
2021-03-29 22:06:31 +02:00
Anca Iordache 30d482d359
Merge pull request #2788 from docker/set-minimal-python-to-3_6
Make python 3.6 the minimum version
2021-03-25 09:59:41 +01:00
Anca Iordache d2aa221638
Merge pull request #2793 from feliperuhland/create-secret-missing-name
Fix `KeyError` when creating a new secret
2021-03-25 09:58:02 +01:00
Felipe Ruhland d4310b2db0 Fix `KeyError` when creating a new secret
How to reproduce the issue:

```py
>>> import docker
>>> cli = docker.from_env()
>>> cli.secrets.create(name="any_name", data="1")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/docker-py/docker/models/secrets.py", line 10, in __repr__
    return "<%s: '%s'>" % (self.__class__.__name__, self.name)
  File "/home/docker-py/docker/models/secrets.py", line 14, in name
    return self.attrs['Spec']['Name']
KeyError: 'Spec'
```

The exception raises because create secrets API `/secrets/create` only
return the `id` attribute:
https://docs.docker.com/engine/api/v1.41/#operation/SecretCreate
The secret model is created using just the `id` attribute and fails
when looking for Spec.Name attribute.

```py
def __repr__(self):
    return "<%s: '%s'>" % (self.__class__.__name__, self.name)
```

```py
@property
def name(self):
    return self.attrs['Spec']['Name']
```

I came up with a ugly solution but will prevent the problem to happen
again:

```py
def create(self, **kwargs):
    obj = self.client.api.create_secret(**kwargs)
+   obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
    return self.prepare_model(obj)
```

After the API call, I added the name attribute to the right place to be
used on the property name.

```py
>>> import docker
>>> cli = docker.from_env()
>>> cli.secrets.create(name="any_name", data="1")
<Secret: 'any_name'>
```

It isn't the most elegant solution, but it will do the trick.
I had a previous PR #2517 when I propose using the `id` attribute
instead of `name` on the `__repr__` method, but I think this one will be better.

That fixes #2025

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-03-24 18:03:54 +01:00
Ulysses Souza c8fba210a2 Remove support to pre python 3.6
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-03-22 10:18:23 -03:00
Anca Iordache 31775a1532
Merge pull request #2782 from hakanardo/verify_tls
Verify TLS keys loaded from docker contexts
2021-03-10 16:17:34 +01:00
Nicolas De Loof 563124163a relax PORT_SPEC regex so it accept and ignore square brackets
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-03-10 11:25:18 +01:00
Hakan Ardo c239d66d5d Verify TLS keys loaded from docker contexts
This maches the behaviour of the docker cli when using contexts.

Signed-off-by: Hakan Ardo <hakan@debian.org>
2021-03-03 09:30:19 +01:00
Felipe Ruhland d836bb8703 Fix continuous integration status badged
https://docs.github.com/en/actions/managing-workflow-runs/
adding-a-workflow-status-badge

Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-02-26 22:02:55 +01:00
Ulysses Souza 55f405e04a
Merge pull request #2780 from aiordache/changelog_4.4.4
Update changelog for 4.4.4
2021-02-25 10:30:18 -03:00
Felipe Ruhland 7d316641a3 Add limit parameter to image search endpoint
Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
2021-02-24 23:42:20 +01:00
aiordache 148f9161e1 Update changelog for 4.4.4
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-24 18:20:24 +01:00
Anca Iordache d09fe8d225
Merge pull request #2778 from aiordache/openssl_mismatch_bug
Drop LD_LIBRARY_PATH env var for SSH shellout
2021-02-24 16:39:55 +01:00
Anca Iordache 69087ab977
Merge pull request #2772 from aiordache/update_changelog_4.4.3
Update changelog post-release 4.4.3
2021-02-24 14:32:02 +01:00
aiordache 43ca2f8ff9 Drop LD_LIBRARY_PATH env var for SSH shellout
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-23 19:04:03 +01:00
aiordache fe995ae79f Update changelog post-release 4.4.3
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-19 17:33:16 +01:00
aiordache e6689e0bb9 Post-release 4.4.2 changelog updates
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-18 11:19:11 -03:00
aiordache 2807fde6c9 Fix SSH port parsing and add regression tests
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-18 10:56:29 -03:00
Anca Iordache d065daf522
Merge pull request #2711 from vlad-ro/fix-doc-formatting
Fix doc formatting
2021-02-12 16:38:20 +01:00
Anca Iordache c15ee3925d
Merge pull request #2764 from StefanScherer/clean-home-docker
Use DOCKER_CONFIG to have creds in dind environment
2021-02-11 20:31:13 +01:00
aiordache 00da4dc0ea Run unit tests in a container with no .docker/config mount
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-11 19:58:35 +01:00
Stefan Scherer 6de6936f5d
Use DOCKER_CONFIG to have creds in dind environment
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2021-02-11 18:40:37 +01:00
Anca Iordache 407dcfd65b
Merge pull request #2763 from StefanScherer/revert-to-wrappedNode
Revert back to wrappedNode
2021-02-11 17:16:49 +01:00
Stefan Scherer 94d7983ef0
Revert back to wrappedNode
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2021-02-11 10:24:57 +01:00
Vlad Romanenko 56d4b09700 Fix doc formatting
Signed-off-by: Vlad Romanenko <vlad.romanenko@hotmail.com>
2021-02-10 21:27:15 +02:00
Anca Iordache b3aa239432
Merge pull request #2761 from StefanScherer/no-wrappedNode
Remove wrappedNode
2021-02-10 17:18:12 +01:00
Stefan Scherer 9556b890f9
Remove wrappedNode
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2021-02-10 16:57:30 +01:00
Anca Iordache caef663729
Merge pull request #2741 from WojciechowskiPiotr/maxreplicas
Support for docker.types.Placement.MaxReplicas
2021-02-10 14:54:55 +01:00
aiordache ccab78840e Bump cffi to 1.14.4
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-10 10:35:55 -03:00
aiordache f520b4c4eb Update GH action step
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-10 10:35:55 -03:00
WojciechowskiPiotr 6d1dffe3e5 Unit and integration tests added
Signed-off-by: WojciechowskiPiotr <devel@it-playground.pl>
2021-02-09 21:37:26 +01:00
Anca Iordache a653052276
Merge pull request #2759 from StefanScherer/ubuntu-2004
Update CI to ubuntu-2004
2021-02-09 20:54:21 +01:00
Stefan Scherer 9e007469ef
Update CI to ubuntu-2004
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2021-02-09 20:39:54 +01:00
Christian Clauss da32a2f1a2 GitHub Actions: Upgrade actions/checkout
https://github.com/actions/checkout/releases
Signed-off-by: Christian Clauss <cclauss@me.com>
2021-02-09 16:10:42 -03:00
WojciechowskiPiotr 514f98a0d6 Support for docker.types.Placement.MaxReplicas (new in API 1.40) in Docker Swarm Service
Signed-off-by: WojciechowskiPiotr <devel@it-playground.pl>
2021-02-09 19:45:52 +01:00
Anca Iordache d7b16ef0fb
Merge pull request #2755 from aiordache/fix_ssh_bug
Fix host trimming and remove quiet flag for the ssh connection
2021-02-09 18:26:44 +01:00
Anca Iordache 78f5249ed0
Merge pull request #2739 from cclauss/setup.py-py38-py39
setup.py: Add support for Python 3.8 and 3.9
2021-02-09 18:21:55 +01:00
Anca Iordache 8615a61bd1
Merge pull request #2743 from cclauss/patch-2
print() is a function in Python 3
2021-02-09 15:18:12 +01:00
aiordache caab390696 Fix host trimming and remove quiet flag for the ssh connection
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-09 10:17:00 +01:00
Anca Iordache f0517f842b
Merge pull request #2754 from aiordache/update_Jenkins_setup
Set the base image to `dockerpinata/docker-py` in Jenkinsfile
2021-02-08 20:35:58 +01:00
aiordache 0edea80c41 Update base image to `dockerpinata/docker-py` in Jenkinsfile
Signed-off-by: aiordache <anca.iordache@docker.com>
2021-02-08 20:04:14 +01:00
Christian Clauss 10ff403079 print() is a function in Python 3
Like #2740 but for the docs

Signed-off-by: Christian Clauss <cclauss@me.com>
2020-12-28 19:14:55 +01:00
Christian Clauss ce2669e3ed print() is a function in Python 3
Signed-off-by: Christian Clauss <cclauss@me.com>
2020-12-28 17:26:25 +00:00
Piotr Wojciechowski f0ab0ed25d Support for docker.types.Placement.MaxReplicas (new in API 1.40) in Docker Swarm Service
Signed-off-by: WojciechowskiPiotr <devel@it-playground.pl>
2020-12-25 16:39:44 +01:00
Christian Clauss 2426a5ffd5 setup.py: Add support for Python 3.8 and 3.9
Signed-off-by: Christian Clauss <cclauss@me.com>
2020-12-24 15:14:05 +01:00
Ulysses Souza b72926b382 Post 4.4.1 release
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-12-22 20:53:42 +00:00
Ulysses Souza 2f3e0f9fc4 Prepare release 4.4.1
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-12-22 20:37:46 +00:00
aiordache 3ec7fee736 Avoid setting unsuported parameter for subprocess.Popen on Windows
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-12-18 18:00:07 +00:00
Ulysses Souza 4757eea80c Trigger GHA on pull_request
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-12-18 14:53:55 +00:00
Anca Iordache f1af005eca
Merge pull request #2707 from aiordache/after_release
Post-release 4.4.0
2020-12-18 15:42:48 +01:00
Ulysses Souza ab0d65e2e0 Remove travis
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-12-18 14:39:40 +00:00
Ulysses Souza d8bbbf2351 Add Github Actions
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-12-18 14:39:40 +00:00
Sebastiaan van Stijn 1757c974fa docker/api/image: replace use of deprecated "filter" argument
The "filter" argument was deprecated in docker 1.13 (API version 1.25),
and removed from API v1.41 and up. See https://github.com/docker/cli/blob/v20.10.0-rc1/docs/deprecated.md#filter-param-for-imagesjson-endpoint

This patch applies the name as "reference" filter, instead of "filter" for API
1.25 and up.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-11-26 17:07:27 +00:00
aiordache 990ef4904c Post-release v4.4.0
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-23 13:16:23 +01:00
Anca Iordache 4328197660
Merge pull request #2705 from aiordache/jenkins_dockerhub_creds
Update Jenkinsfile with docker registry credentials
2020-11-20 09:54:51 +01:00
aiordache c854aba15e Mount docker config to DIND containers for authentication
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-19 19:33:24 +01:00
aiordache 260114229a Update Jenkinsfile with docker registry credentials
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-19 18:51:59 +01:00
Anca Iordache 5b471d4482
Merge pull request #2704 from aiordache/cleanup
Fix condition to avoid syntax warning
2020-11-19 18:18:09 +01:00
aiordache a0c51be228 Syntax warning fix
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-19 15:38:26 +01:00
Anca Iordache 27e3fa3e2e
Merge pull request #2703 from aiordache/cleanup_before_release
Fix docs typo and `port_bindings` check
2020-11-19 09:53:39 +01:00
aiordache db9af44e5b Fix docs typo
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-18 19:00:27 +01:00
Anca Iordache d83a2ad51b
Merge pull request #2691 from aiordache/fix_ssh_conn
Fix the connection via ssh client shellout
2020-11-18 15:55:25 +01:00
aiordache f5531a94e1 Fix ssh connection - don't override the host and port of the http pool
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-11-18 14:44:24 +01:00
Guillaume Tardif 6da140e26c
Merge pull request #2511 from daeseokyoun/handle-network-host
raise an error for binding specific ports in 'host' mode of network
2020-11-17 16:01:16 +01:00
Mariano Scazzariello bb1c528ab3
Add max_pool_size parameter (#2699)
* Add max_pool_size parameter

Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>

* Add client version to tests

Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>

* Fix parameter position

Signed-off-by: Mariano Scazzariello <marianoscazzariello@gmail.com>
2020-11-17 15:42:36 +01:00
Daeseok Youn 433264d04b Correct comments on ports_binding and host mode as network_mode
Signed-off-by: Daeseok Youn <daeseok.youn@navercorp.com>
2020-11-17 21:27:15 +09:00
Daeseok Youn 9c53024ead raise an error for binding specific ports in 'host' mode of network
The binding ports are ignored where the network mode is 'host'.
It could be a problem in case of using these options together on
Mac or Windows OS. Because the limitation that could not use
the 'host' in network_mode on Mac and Windows. When 'host' mode
is set on network_mode, the specific ports in 'ports' are ignored
 so the network is not able to be accessed through defined ports
by developer.

Signed-off-by: Daeseok Youn <daeseok.youn@navercorp.com>
2020-11-17 21:26:55 +09:00
Anca Iordache 800222268a
Merge pull request #2553 from fengbaolong/fix-unicode-in-dockerfile
fix docker build error when dockerfile contains unicode character.
2020-11-16 17:44:09 +01:00
Anca Iordache 8c893dd9c4
Merge pull request #2694 from docker/dependabot/pip/cryptography-3.2
Bump cryptography from 2.3 to 3.2
2020-11-16 16:48:19 +01:00
Anca Iordache 5b60bba104
Merge pull request #2642 from YuviGold/feature/api-server-error-message-url
Add response url to Server Error and Client Error messages
2020-11-16 15:50:04 +01:00
dependabot[bot] 93bcc0497d
Bump cryptography from 2.3 to 3.2
Bumps [cryptography](https://github.com/pyca/cryptography) from 2.3 to 3.2.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/2.3...3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-27 21:18:15 +00:00
Yuval Goldberg 3766f77c20 Add response url to Server Error and Client Error messages
Signed-off-by: Yuval Goldberg <yuvigoldi@hotmail.com>
2020-10-17 03:43:11 +03:00
Anca Iordache 2c6ef9b11d
Merge pull request #2687 from IamTheFij/fix-model-plugin-upgrade-reload
Fix plugin model upgrade
2020-10-16 11:37:36 +02:00
Anca Iordache daa9f179c3
Merge pull request #2671 from aiordache/default_tag
Set image default tag on pull
2020-10-16 11:35:16 +02:00
Anca Iordache e09b070575
Merge pull request #2680 from aiordache/replace_paramiko
Shell out to local SSH client as alternative to a paramiko connection
2020-10-16 11:34:00 +02:00
aiordache 180414dcbb Shell out to SSH client for an ssh connection
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-10-13 10:42:38 +02:00
Ian Fijolek 910cc12423 Fix plugin model upgrade
When upgrading a plugin via the model interface, it would yield the
following error:

    AttributeError: 'Plugin' object has no attribute '_reload'

It appears that the proper method is `self.reload()`. This is what is
used by all other methods in the class and base. I'm not finding any
references to `_reload` apart from this instance in the project either.

I've verified that this patch fixes the issue on my machine and all
tests pass.

Signed-off-by: Ian Fijolek <ian@iamthefij.com>
2020-10-09 18:29:26 -07:00
Anca Iordache 9d8cd023e8
Merge pull request #2673 from ulyssessouza/remove-tinyurl-link
Fix url of examples in ulimits
2020-09-28 11:14:27 +02:00
aiordache aed5700985 update `pull` method docs
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-09-21 10:23:29 +02:00
Ulysses Souza ea093a75dd Fix url of examples in ulimits
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-09-17 17:09:14 +02:00
Anca Iordache ff44e7e375
Merge pull request #2670 from ulyssessouza/add-github-maintainers
Add github supported CODEOWNERS file
2020-09-17 16:54:30 +02:00
aiordache cec152db5f Set image default tag on pull
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-09-16 17:36:50 +02:00
Ulysses Souza 84857a896c Add github supported CODEOWNERS file
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-09-15 15:33:04 +02:00
aiordache ed46fb0143 Add release 4.3.1 information to changelog
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-09-07 17:55:36 +02:00
Anca Iordache 2b1c7eb724
Merge pull request #2650 from aiordache/default_version_auto
Set default version to 'auto'
2020-08-20 20:58:25 +02:00
Anca Iordache f158888003
Merge pull request #2534 from HaaLeo/feature/openssh-identity-file
Support OpenSSH Identityfile option
2020-08-20 18:33:45 +02:00
aiordache c7c5b551fc set engine version for unit tests to avoid querying the engine
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-20 15:29:24 +02:00
aiordache 727080b3cc set version to 'auto' to avoid breaking on old engine versions
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-19 14:19:29 +02:00
Yuval Goldberg 0dfae33ce8 Add file environment variable to integration-dind
Signed-off-by: Yuval Goldberg <yuvigoldi@hotmail.com>
2020-08-18 15:18:52 +02:00
Anca Iordache 2c522fb362
Fix memory conversion to bytes (#2645)
* Fix memory conversion to bytes

Co-authored-by: Ulysses Souza <ulysses.souza@gmail.com>

Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-17 18:32:48 +02:00
Anca Iordache 30ff9f339c
Merge pull request #2638 from aiordache/release
Release 4.3.0
2020-08-10 19:35:01 +02:00
aiordache 5cdbbab3ee Update version to the next dev version
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 19:21:44 +02:00
aiordache 9579b7ac0e Fix changelog merge
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 19:16:52 +02:00
aiordache 8080fbb4ed Fix merge
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 19:15:50 +02:00
aiordache 746a2509ab Prepare release 4.3.0
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 19:15:50 +02:00
aiordache 3999707fb3 Make orchestrator field optional
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 19:15:50 +02:00
Ulysses Souza 2e274d00b3 Specify when to use `tls` on Context constructor
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-08-10 19:15:50 +02:00
Ulysses Souza 67b77f2fa3 Post release 4.2.0 update:
- Changelog
- Next Version

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-08-10 19:15:46 +02:00
Anca Iordache 087b3f0a49 Implement context management, lifecycle and unittests.
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2020-08-10 19:14:00 +02:00
Till Riedel fcd0093050 obey Hostname Username Port and ProxyCommand settings from .ssh/config
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-08-10 19:14:00 +02:00
Till Riedel 70cdb08f9a set logging level of paramiko to warn
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-08-10 19:14:00 +02:00
Sebastiaan van Stijn 0be75d54ca Update credentials-helpers to v0.6.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-10 19:14:00 +02:00
Sebastiaan van Stijn b4beaaac8c
Update default API version to v1.39 (#2512)
* Update default API version to v1.39

When running the docker-py integration tests in the Moby repository, some
tests were skipped because the API version used was too low:

    SKIPPED [1] tests/integration/api_service_test.py:882: API version is too low (< 1.38)
    SKIPPED [1] tests/integration/api_swarm_test.py:59: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:38: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:45: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:52: API version is too low (< 1.39)

While it's possible to override the API version to use for testing
using the `DOCKER_TEST_API_VERSION` environment variable, we may want
to set the default to a version that supports all features that were
added.

This patch updates the default API version to v1.39, which is the minimum
version required for those features, and corresponds with Docker 18.09.

Note that the API version of the current (19.03) Docker release is v1.40,
but using that version as default would exclude users that did not update
their Docker version yet (and would not be needed yet for the features provided).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

* Makefile: set DOCKER_TEST_API_VERSION to v1.39

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-07 16:45:20 +02:00
Anca Iordache cceae0c384
Merge pull request #2572 from scop/tlsconfig-error-messages
Fix parameter names in TLSConfig error messages
2020-08-07 15:14:39 +02:00
Anca Iordache 6367bbee2e
Merge pull request #2520 from Nicceboy/master
Disable compression by default when using container:get_archive method
2020-08-07 14:42:58 +02:00
Anca Iordache 62afadc13b
Merge pull request #2584 from thaJeztah/bump_engine
Update test engine version to 19.03.12
2020-08-07 14:34:06 +02:00
Ville Skyttä 631abd156a
Spelling fixes (#2571)
Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2020-08-07 14:33:19 +02:00
Lucidiot dd0450a14c
Add device requests (#2471)
* Add DeviceRequest type

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Add device_requests kwarg in host config

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Add unit test for device requests

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Fix unit test

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Use parentheses for multiline import

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Create 1.40 client for device-requests test

Signed-off-by: Laurie O <laurie_opperman@hotmail.com>

Co-authored-by: Laurie O <laurie_opperman@hotmail.com>
Co-authored-by: Bastien Abadie <abadie@teklia.com>
2020-08-07 13:58:35 +02:00
Sebastiaan van Stijn 26d8045ffa Fix CreateContainerTest.test_invalid_log_driver_raises_exception
This test was updated in 7d92fbdee1, but
omitted the "error" prefix in the message, causing the test to fail;

    _________ CreateContainerTest.test_invalid_log_driver_raises_exception _________
    tests/integration/api_container_test.py:293: in test_invalid_log_driver_raises_exception
        assert excinfo.value.explanation in expected_msgs
    E   AssertionError: assert 'error looking up logging plugin asdf: plugin "asdf" not found' in ["logger: no log driver named 'asdf' is registered", 'looking up logging plugin asdf: plugin "asdf" not found']
    E    +  where 'error looking up logging plugin asdf: plugin "asdf" not found' = APIError(HTTPError('400 Client Error: Bad Request for url: http+docker://localhost/v1.39/containers/create')).explanation
    E    +    where APIError(HTTPError('400 Client Error: Bad Request for url: http+docker://localhost/v1.39/containers/create')) = <ExceptionInfo APIError tblen=6>.value

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-07-17 15:59:12 +02:00
Ofek Lev c65d437843 Upgrade Windows dependency
Signed-off-by: Ofek Lev <ofekmeister@gmail.com>
2020-07-07 08:43:02 +02:00
Ulysses Souza 74a0734d37
Merge pull request #2551 from haboustak/2550-add-driveropts-to-endpointconfig
Add support for DriverOpts in EndpointConfig
2020-07-02 10:51:35 +02:00
Ulysses Souza d3adac9027
Merge pull request #2609 from ulyssessouza/post-release
Post release 4.2.2
2020-07-01 15:09:42 +02:00
Ulysses Souza 6d9847838a Update version to 4.3.0-dev
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-30 18:30:49 +02:00
Ulysses Souza e18a64b630 Bump 4.2.2
Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2020-06-30 18:25:58 +02:00
Sebastiaan van Stijn 2c68b382a8
Update test engine version to 19.03.12
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-30 17:57:36 +02:00
Ulysses Souza 26a7f85af7 Merge remote-tracking branch 'upstream/master' 2020-06-30 17:17:05 +02:00
Ulysses Souza 0a248c0906
Merge pull request #2602 from aiordache/fix_context_meta_load
Fix context load for non-docker endpoints
2020-06-30 17:16:10 +02:00
aiordache 309ce44052 Skip parsing non-docker endpoints
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-06-26 18:21:35 +02:00
Janosch Deurer bf1a3518f9 Add healthcheck doc for container.run
Signed-off-by: Janosch Deurer <deurer@mps-med.de>
2020-06-15 16:49:28 +02:00
Ulysses Souza 5efe28149a
Merge pull request #2585 from thaJeztah/better_worker_selection
Jenkinsfile: update node selection labels
2020-06-03 11:14:49 +02:00
Sebastiaan van Stijn fefa96cd0e
Jenkinsfile: update node selection labels
Make sure we use the LTS nodes, to prevent using machines that
we prepared with cgroups v2 (which is not yet supported by docker v19.03)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-03 10:43:42 +02:00
Ulysses Souza b49d22f053
Merge pull request #2583 from ulyssessouza/post-release-4.2.1
Post release 4.2.1
2020-06-02 17:25:24 +02:00
Ulysses Souza 57a8a0c561 Update version after 4.2.1 release
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 16:59:17 +02:00
Ulysses Souza 525ff592ee Bump 4.2.1
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 16:43:27 +02:00
Ulysses Souza 88163c00f2 Bump 4.2.0
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 16:43:24 +02:00
Ulysses Souza df08c14c87 Bump 3.7.2
Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2020-06-02 16:41:51 +02:00
Ulysses Souza 1b2029e08e Post release 4.2.1 update:
- Changelog

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 16:25:52 +02:00
Ulysses Souza b22095f742
Merge pull request #2580 from ulyssessouza/4.2.1-release
Bump 4.2.1
2020-06-02 16:15:59 +02:00
Ulysses Souza 9923746095 Bump 4.2.1
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 15:53:06 +02:00
aiordache 67cad6842c add test for context load without orchestrator
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-06-02 15:52:25 +02:00
aiordache 31276df6a3 Make orchestrator field optional
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-06-02 15:52:25 +02:00
Ulysses Souza 105efa02a9 Specify when to use `tls` on Context constructor
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-02 15:52:25 +02:00
Wilson Júnior 9b59e49113 Fix tests to support both log plugin feedbacks
Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com>
Docker-DCO-1.1-Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com> (github: wpjunior)
2020-06-02 15:52:25 +02:00
Sebastiaan van Stijn 913d129dc9 Update test engine version to 19.03.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-02 15:52:25 +02:00
Sebastiaan van Stijn 9713227d7b Jenkinsfile: remove obsolete engine versions
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-02 15:52:25 +02:00
Sebastiaan van Stijn db6a2471f5 Use official docker:dind image instead of custom image
This replaces the custom dockerswarm/dind image with the official
dind images, which should provide the same functionality.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-02 15:52:25 +02:00
Sebastiaan van Stijn fd4526a7d3 xfail "docker top" tests, and adjust for alpine image
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-02 15:52:25 +02:00
Ulysses Souza a5270865e6
Merge pull request #2578 from aiordache/fix_context_load_without_orch
Make context orchestrator field optional
2020-06-02 15:10:33 +02:00
aiordache 7133916798 add test for context load without orchestrator
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-06-02 10:50:03 +02:00
aiordache 1e11ecec34 Make orchestrator field optional
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-05-30 11:01:22 +02:00
Ulysses Souza 71adba9a1d
Merge pull request #2576 from ulyssessouza/fix-tls-host-protocol
Specify when to use `tls` on Context constructor
2020-05-28 21:37:24 +02:00
Ulysses Souza 3ce2d8959d Specify when to use `tls` on Context constructor
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-05-28 20:53:45 +02:00
Ville Skyttä 81eb5d42c9 Fix parameter names in TLSConfig error messages and comments
Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2020-05-25 08:52:23 +03:00
Mike Haboustak df7bf5f5e0
Add support for DriverOpts in EndpointConfig
Docker API 1.32 added support for providing options to a network driver
via EndpointConfig when connecting a container to a network.

Signed-off-by: Mike Haboustak <haboustak@gmail.com>
2020-04-30 05:27:45 -04:00
fengbaolong a07b5ee16c fix docker build error when dockerfile contains unicode character.
if dockerfile contains unicode character,len(contents) will return character length,this length will less than len(contents_encoded) length,so contants data will be truncated.

Signed-off-by: fengbaolong <fengbaolong@hotmail.com>
2020-04-28 20:01:59 +08:00
Ulysses Souza 9a24df5cdd
Merge pull request #2549 from wpjunior/fix-plugin-feedback
Fix tests to support both log plugin feedbacks
2020-04-22 10:30:02 +02:00
Wilson Júnior 7d92fbdee1
Fix tests to support both log plugin feedbacks
Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com>
Docker-DCO-1.1-Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com> (github: wpjunior)
2020-04-21 17:00:48 -03:00
Leo Hanisch dac038aca2 Fixes docker/docker-py#2533
Signed-off-by: Leo Hanisch <23164374+HaaLeo@users.noreply.github.com>
2020-03-20 12:40:58 +01:00
Niklas Saari 51fd6dd1ce
Disable compression by default when using get_archive method
Signed-off-by: Niklas Saari <niklas.saari@tutanota.com>
2020-02-26 23:09:38 +02:00
Ulysses Souza 030af62dca
Merge pull request #2514 from thaJeztah/remove_obsolete_versions
Jenkinsfile: remove obsolete engine versions
2020-02-21 16:17:07 +01:00
Ulysses Souza 12fe645aba
Merge pull request #2515 from thaJeztah/use_official_dind_images
Use official docker:dind image instead of custom image
2020-02-21 16:16:36 +01:00
Sebastiaan van Stijn 8ced47dca9
Use official docker:dind image instead of custom image
This replaces the custom dockerswarm/dind image with the official
dind images, which should provide the same functionality.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-17 10:25:56 +01:00
Sebastiaan van Stijn da90bb3259
xfail "docker top" tests, and adjust for alpine image
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-17 10:25:53 +01:00
Sebastiaan van Stijn 7bef5e8676
Update test engine version to 19.03.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-15 00:00:48 +01:00
Sebastiaan van Stijn 789b6715ca
Jenkinsfile: remove obsolete engine versions
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-15 00:00:41 +01:00
Ulysses Souza a9867c9912
Merge pull request #2508 from ulyssessouza/update-release-4.2.0
Post release 4.2.0 update
2020-02-07 11:24:25 +01:00
Ulysses Souza 7c4194ce5d Post release 4.2.0 update:
- Changelog
- Next Version

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-02-07 01:00:18 +01:00
Ulysses Souza 1d1532f0be
Merge pull request #2506 from ulyssessouza/4.2.0-release
4.2.0 release
2020-02-06 11:43:03 +01:00
Ulysses Souza ab5678469c Bump 4.2.0
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-02-06 10:41:28 +01:00
Anca Iordache 6e44d8422c Implement context management, lifecycle and unittests.
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2020-02-06 10:33:31 +01:00
Sebastiaan van Stijn 6c29375fd1 Fix ImageCollectionTest.test_pull_multiple flakiness
The ImageCollectionTest.test_pull_multiple test performs a `docker pull` without
a `:tag` specified) to pull all tags of the given repository (image).

After pulling the image, the image(s) pulled are checked to verify if the list
of images contains the `:latest` tag.

However, the test assumes that all tags of the image are tags for the same
version of the image (same digest), and thus a *single* image is returned, which
is not always the case.

Currently, the `hello-world:latest` and `hello-world:linux` tags point to a
different digest, therefore the `client.images.pull()` returns multiple images:
one image for digest, making the test fail:

    =================================== FAILURES ===================================
    ____________________ ImageCollectionTest.test_pull_multiple ____________________
    tests/integration/models_images_test.py:90: in test_pull_multiple
        assert len(images) == 1
    E   AssertionError: assert 2 == 1
    E    +  where 2 = len([<Image: 'hello-world:linux'>, <Image: 'hello-world:latest'>])

This patch updates the test to not assume a single image is returned, and instead
loop through the list of images and check if any of the images contains the
`:latest` tag.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-06 10:33:31 +01:00
Christopher Crone 9b0d07f9a8 Version bump
Signed-off-by: Christopher Crone <christopher.crone@docker.com>
2020-02-06 10:33:31 +01:00
rentu 61e2d5f69b Fix win32pipe.WaitNamedPipe throw exception in windows container.
Signed-off-by: Renlong Tu <rentu@microsoft.com>
2020-02-06 10:33:31 +01:00
Nicolas De Loof a67d180e2c Fix CI labels so we run on amd64 nodes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2020-02-06 10:33:31 +01:00
Till Riedel ed9b208e15 obey Hostname Username Port and ProxyCommand settings from .ssh/config
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-02-06 10:33:31 +01:00
Till Riedel bc6777eb01 set host key policy for ssh transport to WarningPolicy()
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-02-06 10:33:31 +01:00
Till Riedel 1e567223ef set logging level of paramiko to warn
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-02-06 10:33:31 +01:00
Ulysses Souza c6a33f25dc
Merge pull request #2495 from aiordache/issue#28_compose_context_target
Implement docker contexts
2020-02-05 15:30:48 +01:00
Anca Iordache 64fdb32ae8 Implement context management, lifecycle and unittests.
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2020-02-05 14:49:42 +01:00
Ulysses Souza f2e09ae632
Merge pull request #2485 from thaJeztah/fix_ImageCollectionTest_test_pull_multiple
Fix ImageCollectionTest.test_pull_multiple flakiness
2020-01-07 17:13:28 +01:00
Ulysses Souza 6010527ac6
Merge pull request #2444 from chris-crone/sync-release-4.1.0
Sync release 4.1.0
2020-01-07 17:12:50 +01:00
Sebastiaan van Stijn 940805dde6
Fix ImageCollectionTest.test_pull_multiple flakiness
The ImageCollectionTest.test_pull_multiple test performs a `docker pull` without
a `:tag` specified) to pull all tags of the given repository (image).

After pulling the image, the image(s) pulled are checked to verify if the list
of images contains the `:latest` tag.

However, the test assumes that all tags of the image are tags for the same
version of the image (same digest), and thus a *single* image is returned, which
is not always the case.

Currently, the `hello-world:latest` and `hello-world:linux` tags point to a
different digest, therefore the `client.images.pull()` returns multiple images:
one image for digest, making the test fail:

    =================================== FAILURES ===================================
    ____________________ ImageCollectionTest.test_pull_multiple ____________________
    tests/integration/models_images_test.py:90: in test_pull_multiple
        assert len(images) == 1
    E   AssertionError: assert 2 == 1
    E    +  where 2 = len([<Image: 'hello-world:linux'>, <Image: 'hello-world:latest'>])

This patch updates the test to not assume a single image is returned, and instead
loop through the list of images and check if any of the images contains the
`:latest` tag.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-01-06 13:46:23 +01:00
Yuval Goldberg 656db96b4a Fix mac_address connect usage in network functions && addind appropriate test
Signed-off-by: Yuval Goldberg <yuvigoldi@hotmail.com>
2019-12-19 15:43:03 +02:00
Hongbin Lu 755fd73566 Add mac_address to connect_container_to_network
Signed-off-by: Hongbin Lu <hongbin.lu@huawei.com>
2019-12-19 15:40:29 +02:00
Djordje Lukic a0b9c3d0b3
Merge pull request #2421 from SLdragon/master
Fix win32pipe.WaitNamedPipe throw exception in windows container
2019-11-06 09:29:39 +01:00
Jack Laxson 19171d0e1e remove hyphens in literals
Signed-off-by: Jack Laxson <jackjrabbit@gmail.com>
2019-10-28 05:45:28 -04:00
Nicolas De Loof 1d8aa3019e Fix CI labels so we run on amd64 nodes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2019-10-16 14:31:50 +02:00
Till Riedel c285bee1bc obey Hostname Username Port and ProxyCommand settings from .ssh/config
Signed-off-by: Till Riedel <riedel@teco.edu>
2019-10-16 14:31:50 +02:00
Till Riedel eb8c78c3b3 set host key policy for ssh transport to WarningPolicy()
Signed-off-by: Till Riedel <riedel@teco.edu>
2019-10-16 14:31:50 +02:00
Till Riedel 2dc569a232 set logging level of paramiko to warn
Signed-off-by: Till Riedel <riedel@teco.edu>
2019-10-16 14:31:50 +02:00
Christopher Crone efc7e3c4b0 Version bump
Signed-off-by: Christopher Crone <christopher.crone@docker.com>
2019-10-03 16:40:23 +02:00
Christopher Crone c81200a483 Bump 4.1.0
Signed-off-by: Christopher Crone <christopher.crone@docker.com>
2019-10-03 16:39:40 +02:00
Chris Crone 66495870de
Merge pull request #2443 from docker/4.1.0-release
4.1.0 release
2019-10-03 16:16:42 +02:00
Christopher Crone 2bb08b3985 Bump 4.1.0
Signed-off-by: Christopher Crone <christopher.crone@docker.com>
2019-10-03 15:49:27 +02:00
Kir Kolyshkin 88219c682c Bump pytest to 4.3.1
Pytest 4.3.1 includes the fix from

	https://github.com/pytest-dev/pytest/pull/4795

which should fix the following failure:

> INFO: Building docker-sdk-python3:4.0.2...
> sha256:c7a40413c985b6e75df324fae39b1c30cb78a25df71b7892f1a4a15449537fb3
> INFO: Starting docker-py tests...
> Traceback (most recent call last):
>   File "/usr/local/bin/pytest", line 10, in <module>
>     sys.exit(main())
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 61, in main
>     config = _prepareconfig(args, plugins)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 182, in _prepareconfig
>     config = get_config()
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 156, in get_config
>     pluginmanager.import_plugin(spec)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 530, in import_plugin
>     __import__(importspec)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/tmpdir.py", line 25, in <module>
>     class TempPathFactory(object):
>   File "/usr/local/lib/python3.6/site-packages/_pytest/tmpdir.py", line 35, in TempPathFactory
>     lambda p: Path(os.path.abspath(six.text_type(p)))
> TypeError: attrib() got an unexpected keyword argument 'convert'
> Sending interrupt signal to process
> Terminated
> script returned exit code 143

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn bc89de6047 Fix broken test due to BUSYBOX -> TEST_IMG rename
The BUSYBOX variable was renamed to TEST_IMG in
54b48a9b7a, however
0ddf428b6c got merged
after that change, but was out of date, and therefore
caused the tests to fail:

```
=================================== FAILURES ===================================
________ ServiceTest.test_create_service_with_network_attachment_config ________
tests/integration/api_service_test.py:379: in test_create_service_with_network_attachment_config
    container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
E   NameError: global name 'BUSYBOX' is not defined
```

Fix the test by using the correct variable name.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:27 +02:00
Hannes Ljungberg 7c8264ce96 Correctly reference SecretReference
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-10-03 15:49:27 +02:00
Hannes Ljungberg ec63237da0 Correctly reference ConfigReference
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-10-03 15:49:27 +02:00
Hannes Ljungberg 934072a5e7 Add NetworkAttachmentConfig type
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn 0be550dcf0 Jenkinsfile: update python 3.6 -> 3.7
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn 38fe3983ba Jenkinsfile: update API version matrix; set default to v1.40
- Added new entry for Docker 19.03
- Removed obsolete engine versions that reached EOL (both
  as Community Edition and Enterprise Edition)
- Set the fallback/default API version to v1.40, which
  corresponds with Docker 19.03 (current release)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:27 +02:00
Frank Sachsenheim c88205c5ce Amends the docs concerning multiple label filters
Closes #2338

Signed-off-by: Frank Sachsenheim <funkyfuture@riseup.net>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn 63760b1922 test/Dockerfile: allow using a mirror for the apt repository
With this change applied, the default debian package repository can be
replaced with a mirror;

```
make APT_MIRROR=cdn-fastly.deb.debian.org build-py3

...

Step 5/19 : RUN apt-get update && apt-get -y install     gnupg2     pass     curl
 ---> Running in 01c1101a0bd0
Get:1 http://cdn-fastly.deb.debian.org/debian buster InRelease [118 kB]
Get:2 http://cdn-fastly.deb.debian.org/debian-security buster/updates InRelease [39.1 kB]
Get:3 http://cdn-fastly.deb.debian.org/debian buster-updates InRelease [46.8 kB]
Get:4 http://cdn-fastly.deb.debian.org/debian buster/main amd64 Packages [7897 kB]
Get:5 http://cdn-fastly.deb.debian.org/debian-security buster/updates/main amd64 Packages [22.8 kB]
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:27 +02:00
Matt Fluet 06c606300c Correct INDEX_URL logic in build.py _set_auth_headers
Signed-off-by: Matt Fluet <matt.fluet@appian.com>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn c238315c64 pytest: update to v4.2.1 - use xunit2 for compatibility with Jenkins
- pytest-dev/pytest#3547: `--junitxml` can emit XML compatible with Jenkins
  xUnit. `junit_family` INI option accepts `legacy|xunit1`, which produces
  old style output, and `xunit2` that conforms more strictly to
  https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:27 +02:00
Sebastiaan van Stijn 57c2193f6d pytest: set junitxml suite name to "docker-py"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Ryan McCullagh 2d327bf743 Fix typo in comment. networks => network
Signed-off-by: Ryan McCullagh <ryan@amezmo.com>
2019-10-03 15:49:26 +02:00
Matt Fluet cce0954089 Fix for empty auth keys in config.json
Signed-off-by: Matt Fluet <matt.fluet@appian.com>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn 73ad8b8f19 Update alpine version to 3.10, and rename BUSYBOX variable
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn 23635d43ab Adjust `--platform` tests for changes in docker engine
These tests started failing on recent versions of the engine because the error string changed,
and due to a regression, the status code for one endpoint changed from a 400 to a 500.

On Docker 18.03:

The `docker build` case properly returns a 400, and "invalid platform" as error string;

```bash
docker build --platform=foobar -<<EOF
FROM busybox
EOF

Sending build context to Docker daemon  2.048kB
Error response from daemon: invalid platform: invalid platform os "foobar"
```

```
DEBU[2019-07-15T12:17:22.745511870Z] Calling GET /_ping
DEBU[2019-07-15T12:17:22.748224796Z] Calling POST /session
DEBU[2019-07-15T12:17:22.748692282Z] Calling POST /v1.37/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&platform=foobar&rm=1&session=d7b6ceba9d8d0aed67a805528554feb5285781fe888a4bf4e0c15cb09bffd614&shmsize=0&target=&ulimits=null
```

The `docker pull --platform=foobar hello-world:latest` case incorrectly returns a 500

```
DEBU[2019-07-15T12:16:08.744827612Z] Calling POST /v1.37/images/create?fromImage=hello-world&platform=foobar&tag=latest
DEBU[2019-07-15T12:16:08.745594874Z] FIXME: Got an API for which error does not match any expected type!!!: invalid platform: invalid platform os "foobar"  error_type="*errors.errorString" module=api
ERRO[2019-07-15T12:16:08.745916686Z] Handler for POST /v1.37/images/create returned error: invalid platform: invalid platform os "foobar"
DEBU[2019-07-15T12:16:08.746191172Z] FIXME: Got an API for which error does not match any expected type!!!: invalid platform: invalid platform os "foobar"  error_type="*errors.errorString" module=api
```

On Docker 18.09;

```bash
docker build --platform=foobar -<<EOF
FROM busybox
EOF

Error response from daemon: "foobar": unknown operating system or architecture: invalid argument
```

Which incorrectly returns a 500 status;

```
DEBU[2019-07-15T11:59:20.687268380Z] Calling POST /v1.39/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&platform=foobar&rm=1&session=jko7kejjvs93judyfnq7shoda&shmsize=0&target=&ulimits=null&version=1
DEBU[2019-07-15T11:59:20.687282279Z] Calling POST /session
INFO[2019-07-15T11:59:20.687761392Z] parsed scheme: ""                             module=grpc
INFO[2019-07-15T11:59:20.687833668Z] scheme "" not registered, fallback to default scheme  module=grpc
INFO[2019-07-15T11:59:20.688017578Z] ccResolverWrapper: sending new addresses to cc: [{ 0  <nil>}]  module=grpc
INFO[2019-07-15T11:59:20.688270160Z] ClientConn switching balancer to "pick_first"  module=grpc
INFO[2019-07-15T11:59:20.688353083Z] pickfirstBalancer: HandleSubConnStateChange: 0xc4209b0630, CONNECTING  module=grpc
INFO[2019-07-15T11:59:20.688985698Z] pickfirstBalancer: HandleSubConnStateChange: 0xc4209b0630, READY  module=grpc
DEBU[2019-07-15T11:59:20.812700550Z] client is session enabled
DEBU[2019-07-15T11:59:20.813139288Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
ERRO[2019-07-15T11:59:20.813210677Z] Handler for POST /v1.39/build returned error: "foobar": unknown operating system or architecture: invalid argument
DEBU[2019-07-15T11:59:20.813276737Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
```

Same for the `docker pull --platform=foobar hello-world:latest` case:

```bash
docker pull --platform=foobar hello-world:latest
Error response from daemon: "foobar": unknown operating system or architecture: invalid argument
```

```
DEBU[2019-07-15T12:00:18.812995330Z] Calling POST /v1.39/images/create?fromImage=hello-world&platform=foobar&tag=latest
DEBU[2019-07-15T12:00:18.813229172Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
ERRO[2019-07-15T12:00:18.813365546Z] Handler for POST /v1.39/images/create returned error: "foobar": unknown operating system or architecture: invalid argument
DEBU[2019-07-15T12:00:18.813461428Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn cd3a696603 xfail test_init_swarm_data_path_addr
This test can fail if `eth0` has multiple IP addresses;

   E   docker.errors.APIError: 400 Client Error: Bad Request ("interface eth0 has more than one IPv6 address (2001:db8:1::242:ac11:2 and fe80::42:acff:fe11:2)")

Which is not a failiure, but depends on the environment that
the test is run in.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn 38d18a2d1f Update credentials-helpers to v0.6.3
full diff:
https://github.com/docker/docker-credential-helpers/compare/v0.6.2...v0.6.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Francis Laniel a316e6a927 Add documentation to argument 'mem_reservation'.
The documentation was added for function ContainerCollection::run and
ContainerApiMixin::create_host_config.

Signed-off-by: Francis Laniel <francis.laniel@lip6.fr>

Add documentation to argument 'mem_reservation'.

The documentation was added for function ContainerCollection::run and
ContainerApiMixin::create_host_config.

Signed-off-by: Francis Laniel <francis.laniel@lip6.fr>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn ea4fbd7ddf Update to python 3.7 (buster) and use build-args
The build arg can be used to either test different versions, but
also makes it easier to "grep" when upgrading versions.

The output format of `gpg2 --list-secret-keys` changed in the version
installed on Buster, so `grep` was replaced with `awk` to address
the new output format;

Debian Jessie:

    gpg2 --no-auto-check-trustdb --list-secret-keys
    /root/.gnupg/secring.gpg
    ------------------------
    sec   1024D/A7B21401 2018-04-25
    uid                  Sakuya Izayoi <sakuya@gensokyo.jp>
    ssb   1024g/C235E4CE 2018-04-25

Debian Buster:

    gpg2 --no-auto-check-trustdb --list-secret-keys
    /root/.gnupg/pubring.kbx
    ------------------------
    sec   dsa1024 2018-04-25 [SCA]
          9781B87DAB042E6FD51388A5464ED987A7B21401
    uid           [ultimate] Sakuya Izayoi <sakuya@gensokyo.jp>
    ssb   elg1024 2018-04-25 [E]

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Djordje Lukic 546bc63244 Bump dev
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-10-03 15:49:26 +02:00
Sebastiaan van Stijn f3961244a0 Update credentials-helpers to v0.6.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-03 15:49:26 +02:00
Michael Crosby c2ed66552b Remove exec detach test
Forking off an exec process and detaching isn't a supported method

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2019-10-03 15:49:26 +02:00
Ulysses Souza 8077d80118
Merge pull request #2441 from kolyshkin/pytest-4.3
Bump pytest to 4.3.1
2019-10-02 13:32:06 +02:00
Kir Kolyshkin 44904f696f Bump pytest to 4.3.1
Pytest 4.3.1 includes the fix from

	https://github.com/pytest-dev/pytest/pull/4795

which should fix the following failure:

> INFO: Building docker-sdk-python3:4.0.2...
> sha256:c7a40413c985b6e75df324fae39b1c30cb78a25df71b7892f1a4a15449537fb3
> INFO: Starting docker-py tests...
> Traceback (most recent call last):
>   File "/usr/local/bin/pytest", line 10, in <module>
>     sys.exit(main())
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 61, in main
>     config = _prepareconfig(args, plugins)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 182, in _prepareconfig
>     config = get_config()
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 156, in get_config
>     pluginmanager.import_plugin(spec)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/config/__init__.py", line 530, in import_plugin
>     __import__(importspec)
>   File "/usr/local/lib/python3.6/site-packages/_pytest/tmpdir.py", line 25, in <module>
>     class TempPathFactory(object):
>   File "/usr/local/lib/python3.6/site-packages/_pytest/tmpdir.py", line 35, in TempPathFactory
>     lambda p: Path(os.path.abspath(six.text_type(p)))
> TypeError: attrib() got an unexpected keyword argument 'convert'
> Sending interrupt signal to process
> Terminated
> script returned exit code 143

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2019-10-01 17:21:38 -07:00
rentu 015f44d8f8 Fix win32pipe.WaitNamedPipe throw exception in windows container.
Signed-off-by: Renlong Tu <rentu@microsoft.com>
2019-08-30 10:19:33 +01:00
Joffrey F 8acd2c3d08
Merge pull request #2420 from thaJeztah/fix_master_builds
Fix broken test due to BUSYBOX -> TEST_IMG rename
2019-08-29 15:21:57 -07:00
Sebastiaan van Stijn 53469e0dd3
Fix broken test due to BUSYBOX -> TEST_IMG rename
The BUSYBOX variable was renamed to TEST_IMG in
54b48a9b7a, however
0ddf428b6c got merged
after that change, but was out of date, and therefore
caused the tests to fail:

```
=================================== FAILURES ===================================
________ ServiceTest.test_create_service_with_network_attachment_config ________
tests/integration/api_service_test.py:379: in test_create_service_with_network_attachment_config
    container_spec = docker.types.ContainerSpec(BUSYBOX, ['true'])
E   NameError: global name 'BUSYBOX' is not defined
```

Fix the test by using the correct variable name.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-30 00:14:20 +02:00
Joffrey F 8fea5738d3
Merge pull request #2333 from hannseman/network-attachment-config
Add NetworkAttachmentConfig for service create/update
2019-08-27 00:21:27 -07:00
Joffrey F 1fc62e6a32
Merge pull request #2408 from thaJeztah/jenkins_update_versions
Jenkinsfile: update API versions and update to Python 3.7
2019-08-27 00:17:59 -07:00
Joffrey F 912824e599
Merge pull request #2352 from funkyfuture/container_list_docs
Amends the docs concerning multiple label filters
2019-08-27 00:17:04 -07:00
Joffrey F 5660730d36
Merge pull request #2383 from thaJeztah/support_mirrors
test/Dockerfile: allow using a mirror for the apt repository
2019-08-27 00:16:00 -07:00
Joffrey F 33b430ab7f
Merge pull request #2399 from fluetm/build-index-url-fix
Correct INDEX_URL logic in build.py _set_auth_headers
2019-08-27 00:14:40 -07:00
Joffrey F 9443decdf6
Merge pull request #2409 from thaJeztah/update_pytest
Update pytest to v4.2.1, use xunit2 for Jenkins compatibility, and small config changes
2019-08-27 00:13:28 -07:00
Joffrey F 70907325a5
Merge pull request #2414 from rmccullagh/patch-1
Fix typo in comment. networks => network
2019-08-27 00:11:48 -07:00
Joffrey F 1c36417d8f
Merge pull request #2401 from fluetm/fix-osxkeychain-issue
Fix for empty auth keys in config.json
2019-08-26 11:11:32 -07:00
Ryan McCullagh 93dc5082de Fix typo in comment. networks => network
Signed-off-by: Ryan McCullagh <ryan@amezmo.com>
2019-08-23 10:38:59 -05:00
Matt Fluet aa13df40b1 Fix for empty auth keys in config.json
Signed-off-by: Matt Fluet <matt.fluet@appian.com>
2019-08-15 18:15:57 -04:00
Joffrey F 845d31dbd2
Merge pull request #2407 from thaJeztah/update_alpine
Update alpine version to 3.10, and rename BUSYBOX variable
2019-08-12 11:14:32 -07:00
Sebastiaan van Stijn 9ea3da37a7
pytest: update to v4.2.1 - use xunit2 for compatibility with Jenkins
- pytest-dev/pytest#3547: `--junitxml` can emit XML compatible with Jenkins
  xUnit. `junit_family` INI option accepts `legacy|xunit1`, which produces
  old style output, and `xunit2` that conforms more strictly to
  https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-11 02:13:30 +02:00
Sebastiaan van Stijn f0fc266eb5
Jenkinsfile: update python 3.6 -> 3.7
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-10 19:22:52 +02:00
Sebastiaan van Stijn 16794d1d23
Jenkinsfile: update API version matrix; set default to v1.40
- Added new entry for Docker 19.03
- Removed obsolete engine versions that reached EOL (both
  as Community Edition and Enterprise Edition)
- Set the fallback/default API version to v1.40, which
  corresponds with Docker 19.03 (current release)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-10 19:19:32 +02:00
Sebastiaan van Stijn 54b48a9b7a
Update alpine version to 3.10, and rename BUSYBOX variable
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-10 19:11:58 +02:00
Sebastiaan van Stijn 7b22b14715
pytest: set junitxml suite name to "docker-py"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-08-10 18:50:36 +02:00
Matt Fluet d7caa6039d Correct INDEX_URL logic in build.py _set_auth_headers
Signed-off-by: Matt Fluet <matt.fluet@appian.com>
2019-08-06 12:40:17 -04:00
Joffrey F 17c86429e4
Merge pull request #2382 from thaJeztah/fix_platform_tests
Adjust `--platform` tests for changes in docker engine
2019-07-18 22:49:54 -07:00
Joffrey F b20b10cac4
Merge pull request #2380 from thaJeztah/xfail_test_init_swarm_data_path_addr
xfail test_init_swarm_data_path_addr
2019-07-18 22:47:21 -07:00
Joffrey F fd32dc4f15
Merge pull request #2384 from thaJeztah/bump_credential_helpers_0.6.3
Update credentials-helpers to v0.6.3
2019-07-18 22:45:56 -07:00
Sebastiaan van Stijn b2a1b03163
Update credentials-helpers to v0.6.3
full diff:
https://github.com/docker/docker-credential-helpers/compare/v0.6.2...v0.6.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-17 10:54:08 +02:00
Joffrey F 01dcde2f0c
Merge pull request #2217 from eiffel-fl/master
Add documentation to argument 'mem_reservation' in ContainerCollection::run
2019-07-16 10:55:03 -07:00
Francis Laniel a1bc6c289b Add documentation to argument 'mem_reservation'.
The documentation was added for function ContainerCollection::run and
ContainerApiMixin::create_host_config.

Signed-off-by: Francis Laniel <francis.laniel@lip6.fr>

Add documentation to argument 'mem_reservation'.

The documentation was added for function ContainerCollection::run and
ContainerApiMixin::create_host_config.

Signed-off-by: Francis Laniel <francis.laniel@lip6.fr>
2019-07-16 18:25:43 +02:00
Sebastiaan van Stijn 5a91c2e83e
test/Dockerfile: allow using a mirror for the apt repository
With this change applied, the default debian package repository can be
replaced with a mirror;

```
make APT_MIRROR=cdn-fastly.deb.debian.org build-py3

...

Step 5/19 : RUN apt-get update && apt-get -y install     gnupg2     pass     curl
 ---> Running in 01c1101a0bd0
Get:1 http://cdn-fastly.deb.debian.org/debian buster InRelease [118 kB]
Get:2 http://cdn-fastly.deb.debian.org/debian-security buster/updates InRelease [39.1 kB]
Get:3 http://cdn-fastly.deb.debian.org/debian buster-updates InRelease [46.8 kB]
Get:4 http://cdn-fastly.deb.debian.org/debian buster/main amd64 Packages [7897 kB]
Get:5 http://cdn-fastly.deb.debian.org/debian-security buster/updates/main amd64 Packages [22.8 kB]
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-16 12:51:01 +02:00
Joffrey F 068ddf835d
Merge pull request #2379 from thaJeztah/bump_python
Update to python 3.7 (buster) and use build-args
2019-07-15 10:56:36 -07:00
Joffrey F 4c71641544
Merge pull request #2366 from docker/sync-release
Sync release
2019-07-15 10:53:07 -07:00
Sebastiaan van Stijn bc46490a68
Adjust `--platform` tests for changes in docker engine
These tests started failing on recent versions of the engine because the error string changed,
and due to a regression, the status code for one endpoint changed from a 400 to a 500.

On Docker 18.03:

The `docker build` case properly returns a 400, and "invalid platform" as error string;

```bash
docker build --platform=foobar -<<EOF
FROM busybox
EOF

Sending build context to Docker daemon  2.048kB
Error response from daemon: invalid platform: invalid platform os "foobar"
```

```
DEBU[2019-07-15T12:17:22.745511870Z] Calling GET /_ping
DEBU[2019-07-15T12:17:22.748224796Z] Calling POST /session
DEBU[2019-07-15T12:17:22.748692282Z] Calling POST /v1.37/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&platform=foobar&rm=1&session=d7b6ceba9d8d0aed67a805528554feb5285781fe888a4bf4e0c15cb09bffd614&shmsize=0&target=&ulimits=null
```

The `docker pull --platform=foobar hello-world:latest` case incorrectly returns a 500

```
DEBU[2019-07-15T12:16:08.744827612Z] Calling POST /v1.37/images/create?fromImage=hello-world&platform=foobar&tag=latest
DEBU[2019-07-15T12:16:08.745594874Z] FIXME: Got an API for which error does not match any expected type!!!: invalid platform: invalid platform os "foobar"  error_type="*errors.errorString" module=api
ERRO[2019-07-15T12:16:08.745916686Z] Handler for POST /v1.37/images/create returned error: invalid platform: invalid platform os "foobar"
DEBU[2019-07-15T12:16:08.746191172Z] FIXME: Got an API for which error does not match any expected type!!!: invalid platform: invalid platform os "foobar"  error_type="*errors.errorString" module=api
```

On Docker 18.09;

```bash
docker build --platform=foobar -<<EOF
FROM busybox
EOF

Error response from daemon: "foobar": unknown operating system or architecture: invalid argument
```

Which incorrectly returns a 500 status;

```
DEBU[2019-07-15T11:59:20.687268380Z] Calling POST /v1.39/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&platform=foobar&rm=1&session=jko7kejjvs93judyfnq7shoda&shmsize=0&target=&ulimits=null&version=1
DEBU[2019-07-15T11:59:20.687282279Z] Calling POST /session
INFO[2019-07-15T11:59:20.687761392Z] parsed scheme: ""                             module=grpc
INFO[2019-07-15T11:59:20.687833668Z] scheme "" not registered, fallback to default scheme  module=grpc
INFO[2019-07-15T11:59:20.688017578Z] ccResolverWrapper: sending new addresses to cc: [{ 0  <nil>}]  module=grpc
INFO[2019-07-15T11:59:20.688270160Z] ClientConn switching balancer to "pick_first"  module=grpc
INFO[2019-07-15T11:59:20.688353083Z] pickfirstBalancer: HandleSubConnStateChange: 0xc4209b0630, CONNECTING  module=grpc
INFO[2019-07-15T11:59:20.688985698Z] pickfirstBalancer: HandleSubConnStateChange: 0xc4209b0630, READY  module=grpc
DEBU[2019-07-15T11:59:20.812700550Z] client is session enabled
DEBU[2019-07-15T11:59:20.813139288Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
ERRO[2019-07-15T11:59:20.813210677Z] Handler for POST /v1.39/build returned error: "foobar": unknown operating system or architecture: invalid argument
DEBU[2019-07-15T11:59:20.813276737Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
```

Same for the `docker pull --platform=foobar hello-world:latest` case:

```bash
docker pull --platform=foobar hello-world:latest
Error response from daemon: "foobar": unknown operating system or architecture: invalid argument
```

```
DEBU[2019-07-15T12:00:18.812995330Z] Calling POST /v1.39/images/create?fromImage=hello-world&platform=foobar&tag=latest
DEBU[2019-07-15T12:00:18.813229172Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
ERRO[2019-07-15T12:00:18.813365546Z] Handler for POST /v1.39/images/create returned error: "foobar": unknown operating system or architecture: invalid argument
DEBU[2019-07-15T12:00:18.813461428Z] FIXME: Got an API for which error does not match any expected type!!!: invalid argument
github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs.init
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/errdefs/errors.go:40
github.com/docker/docker/vendor/github.com/containerd/containerd/content.init
	<autogenerated>:1
github.com/docker/docker/builder/builder-next.init
	<autogenerated>:1
github.com/docker/docker/api/server/backend/build.init
	<autogenerated>:1
main.init
	<autogenerated>:1
runtime.main
	/usr/local/go/src/runtime/proc.go:186
runtime.goexit
	/usr/local/go/src/runtime/asm_amd64.s:2361  error_type="*errors.fundamental" module=api
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-15 15:48:06 +02:00
Sebastiaan van Stijn 6f6572bb8a
Update to python 3.7 (buster) and use build-args
The build arg can be used to either test different versions, but
also makes it easier to "grep" when upgrading versions.

The output format of `gpg2 --list-secret-keys` changed in the version
installed on Buster, so `grep` was replaced with `awk` to address
the new output format;

Debian Jessie:

    gpg2 --no-auto-check-trustdb --list-secret-keys
    /root/.gnupg/secring.gpg
    ------------------------
    sec   1024D/A7B21401 2018-04-25
    uid                  Sakuya Izayoi <sakuya@gensokyo.jp>
    ssb   1024g/C235E4CE 2018-04-25

Debian Buster:

    gpg2 --no-auto-check-trustdb --list-secret-keys
    /root/.gnupg/pubring.kbx
    ------------------------
    sec   dsa1024 2018-04-25 [SCA]
          9781B87DAB042E6FD51388A5464ED987A7B21401
    uid           [ultimate] Sakuya Izayoi <sakuya@gensokyo.jp>
    ssb   elg1024 2018-04-25 [E]

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-15 13:27:07 +02:00
Sebastiaan van Stijn 1126ea9d6f
xfail test_init_swarm_data_path_addr
This test can fail if `eth0` has multiple IP addresses;

   E   docker.errors.APIError: 400 Client Error: Bad Request ("interface eth0 has more than one IPv6 address (2001:db8:1::242:ac11:2 and fe80::42:acff:fe11:2)")

Which is not a failiure, but depends on the environment that
the test is run in.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-12 22:50:35 +02:00
Joffrey F 53a182d6e2
Merge pull request #2377 from thaJeztah/bump_credential_helpers
Update credentials-helpers to v0.6.2
2019-07-12 10:32:50 -07:00
Sebastiaan van Stijn df340bea60
Update credentials-helpers to v0.6.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-07-12 01:28:41 +02:00
Joffrey F 4d8e5eb2ad
Merge pull request #2369 from crosbymichael/detach-exec
Remove exec detach test
2019-06-25 11:26:57 -07:00
Michael Crosby 8303884612 Remove exec detach test
Forking off an exec process and detaching isn't a supported method

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2019-06-25 13:08:39 -04:00
Djordje Lukic 4db37a1267 Bump dev
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-06-20 13:34:03 +02:00
Djordje Lukic 46fdeffb10 Bump 4.0.2
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-06-20 13:33:44 +02:00
Djordje Lukic 1308cfb78d
Merge pull request #2365 from docker/4.0.2-release
4.0.2 release
2019-06-20 13:23:27 +02:00
Djordje Lukic 805f5f4b38 Bump 4.0.2
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-06-20 12:58:37 +02:00
Djordje Lukic a821502b9e Bump websocket-client -> 0.56.0
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-06-20 12:58:37 +02:00
Kajetan Champlewski 1f38d270e0 Clean up healtcheck.py docs
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-06-20 12:58:37 +02:00
Kajetan Champlewski 241aaaab23 Handle str in setter for test.
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-06-20 12:58:37 +02:00
Kajetan Champlewski c5ca2ef85e Fix documentation for inspect_secret referring to removal.
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-06-20 12:58:37 +02:00
Joffrey F 1ef822afee dev version
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-06-20 12:58:37 +02:00
Chris Crone 20b5491363
Merge pull request #2362 from rumpl/bump-websockets
Bump websocket-client -> 0.56.0
2019-06-19 08:27:04 -07:00
Djordje Lukic 7dd3d563f6 Bump websocket-client -> 0.56.0
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2019-06-19 14:09:47 +02:00
Joffrey F 0f368939de
Merge pull request #2353 from mildlyincompetent/improve-healthcheck
Handle `str` in setter for test on HealthCheck
2019-05-31 10:42:29 -07:00
Kajetan Champlewski dcff8876b1
Clean up healtcheck.py docs
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-05-31 09:13:30 +00:00
Kajetan Champlewski 7302d1af04
Handle str in setter for test.
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-05-31 09:11:20 +00:00
Frank Sachsenheim 4b924dbaf4 Amends the docs concerning multiple label filters
Closes #2338

Signed-off-by: Frank Sachsenheim <funkyfuture@riseup.net>
2019-05-27 22:07:24 +02:00
Joffrey F a208de9e5b
Merge pull request #2351 from mildlyincompetent/master
Fix documentation for inspect_secret referring to removal.
2019-05-24 10:16:27 -07:00
Kajetan Champlewski 0624ccc9cc
Fix documentation for inspect_secret referring to removal.
Signed-off-by: Kajetan Champlewski <contact@kajetan.ch>
2019-05-24 15:37:58 +00:00
Joffrey F 542251af3a
Merge pull request #2349 from docker/resync-release
Resync release notes (4.0.1)
2019-05-18 21:00:20 -07:00
Joffrey F 0200e051c0 dev version
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 20:28:19 -07:00
Joffrey F 4828138f50 Changelog 4.0.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 20:27:35 -07:00
Joffrey F bc827a2ea9 Bump 4.0.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 20:27:35 -07:00
Joffrey F ead0bb9e08
Merge pull request #2348 from docker/4.0.1-release
4.0.1 release
2019-05-18 20:18:03 -07:00
Joffrey F 307e2b3eda Changelog 4.0.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 20:05:20 -07:00
Joffrey F 4d08f2c33d Bump 4.0.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:54:35 -07:00
Joffrey F fc0285c09b Version bump
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:54:35 -07:00
Simon Gurcke df182fd42d Change os.errno to errno for py3.7 compatibility
Signed-off-by: Simon Gurcke <simon@gurcke.de>
2019-05-18 19:54:35 -07:00
Joffrey F 4a8a86eed4 Add readthedocs config
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:54:35 -07:00
Batuhan Taşkaya 80f68c81cd reference swarm page correctly
Signed-off-by: Batuhan Taşkaya <btaskaya33@gmail.com>
2019-05-18 19:54:35 -07:00
Joffrey F c657ca316e
Merge pull request #2347 from docker/resync-release
Resync release branch with master
2019-05-18 19:45:41 -07:00
Joffrey F a33dbd8682
Merge pull request #2335 from isidentical/master
reference swarm page correctly
2019-05-18 19:38:48 -07:00
Joffrey F c2ceb89329
Merge pull request #2346 from itssimon/master
Change os.errno to errno for py3.7 compatibility
2019-05-18 19:34:36 -07:00
Joffrey F eee115c2b8 Version bump
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:33:48 -07:00
Joffrey F b406bfb463 Update changelog for 4.0.0
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:33:48 -07:00
Joffrey F 3267d1f0cc Bump version 4.0.0
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:33:48 -07:00
Ulysses Souza efdac34ef4 Bump 3.7.2
Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2019-05-18 19:33:48 -07:00
Ulysses Souza ccd9ca4947 Xfail test_attach_stream_and_cancel on TLS
This test is quite flaky on ssl integration test

Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2019-05-18 19:33:48 -07:00
Simon Gurcke 31fec93872 Change os.errno to errno for py3.7 compatibility
Signed-off-by: Simon Gurcke <simon@gurcke.de>
2019-05-19 12:20:12 +10:00
Joffrey F 181fade33a
Merge pull request #2343 from docker/rtd-conf
Add readthedocs config
2019-05-18 19:17:54 -07:00
Joffrey F 31236ba5a7 Add readthedocs config
Signed-off-by: Joffrey F <joffrey@docker.com>
2019-05-18 19:13:45 -07:00
Joffrey F 37e442aaed
Merge pull request #2339 from ulyssessouza/bump-urllib3
Bump urllib3 -> 1.24.3
2019-05-15 13:06:14 -07:00
Ulysses Souza 690b0ce9c4 Bump urllib3 -> 1.24.3
Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2019-05-15 10:21:26 +02:00
Batuhan Taşkaya 384043229b reference swarm page correctly
Signed-off-by: Batuhan Taşkaya <btaskaya33@gmail.com>
2019-05-09 20:34:42 +03:00
Hannes Ljungberg bcd61e40dd Correctly reference SecretReference
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-05-03 22:27:56 +02:00
Hannes Ljungberg ff0cbe4c79 Correctly reference ConfigReference
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-05-03 22:27:32 +02:00
Hannes Ljungberg 0ddf428b6c Add NetworkAttachmentConfig type
Signed-off-by: Hannes Ljungberg <hannes@5monkeys.se>
2019-05-03 22:27:32 +02:00
169 changed files with 6674 additions and 2882 deletions

View File

@ -9,3 +9,6 @@ max_line_length = 80
[*.md]
trim_trailing_whitespace = false
[*.{yaml,yml}]
indent_size = 2

72
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,72 @@
name: Python package
on: [push, pull_request]
env:
DOCKER_BUILDKIT: '1'
FORCE_COLOR: 1
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: pip install -U ruff==0.1.8
- name: Run ruff
run: ruff docker tests
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: pip3 install build && python -m build .
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
pip3 install '.[ssh,dev]'
- name: Run unit tests
run: |
docker logout
rm -rf ~/.docker
py.test -v --cov=docker tests/unit
integration-tests:
runs-on: ubuntu-latest
strategy:
matrix:
variant: [ "integration-dind", "integration-dind-ssl", "integration-dind-ssh" ]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: make ${{ matrix.variant }}
run: |
docker logout
rm -rf ~/.docker
make ${{ matrix.variant }}

53
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Release
on:
workflow_dispatch:
inputs:
tag:
description: "Release Tag WITHOUT `v` Prefix (e.g. 6.0.0)"
required: true
dry-run:
description: 'Dry run'
required: false
type: boolean
default: true
env:
DOCKER_BUILDKIT: '1'
FORCE_COLOR: 1
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Generate Package
run: |
pip3 install build
python -m build .
env:
# This is also supported by Hatch; see
# https://github.com/ofek/hatch-vcs#version-source-environment-variables
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ inputs.tag }}
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: '! inputs.dry-run'
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Create GitHub release
uses: ncipollo/release-action@v1
if: '! inputs.dry-run'
with:
artifacts: "dist/*"
generateReleaseNotes: true
draft: true
commit: ${{ github.sha }}
token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ inputs.tag }}

4
.gitignore vendored
View File

@ -13,6 +13,10 @@ html/*
_build/
README.rst
# setuptools_scm
_version.py
env/
venv/
.idea/
*.iml

17
.readthedocs.yml Normal file
View File

@ -0,0 +1,17 @@
version: 2
sphinx:
configuration: docs/conf.py
build:
os: ubuntu-22.04
tools:
python: '3.12'
python:
install:
- method: pip
path: .
extra_requirements:
- ssh
- docs

View File

@ -1,20 +0,0 @@
sudo: false
language: python
matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
dist: xenial
sudo: true
- env: TOXENV=flake8
install:
- pip install tox
script:
- tox

View File

@ -44,7 +44,7 @@ paragraph in the Docker contribution guidelines.
Before we can review your pull request, please ensure that nothing has been
broken by your changes by running the test suite. You can do so simply by
running `make test` in the project root. This also includes coding style using
`flake8`
`ruff`
### 3. Write clear, self-contained commits

View File

@ -1,13 +1,13 @@
FROM python:2.7
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}
RUN mkdir /src
WORKDIR /src
COPY . .
COPY requirements.txt /src/requirements.txt
RUN pip install -r requirements.txt
COPY test-requirements.txt /src/test-requirements.txt
RUN pip install -r test-requirements.txt
COPY . /src
RUN pip install .
ARG VERSION=0.0.0.dev0
RUN --mount=type=cache,target=/cache/pip \
PIP_CACHE_DIR=/cache/pip \
SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \
pip install .[ssh]

View File

@ -1,4 +1,8 @@
FROM python:3.5
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}
ARG uid=1000
ARG gid=1000
@ -7,7 +11,12 @@ RUN addgroup --gid $gid sphinx \
&& useradd --uid $uid --gid $gid -M sphinx
WORKDIR /src
COPY requirements.txt docs-requirements.txt ./
RUN pip install -r requirements.txt -r docs-requirements.txt
COPY . .
ARG VERSION=0.0.0.dev0
RUN --mount=type=cache,target=/cache/pip \
PIP_CACHE_DIR=/cache/pip \
SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \
pip install .[ssh,docs]
USER sphinx

View File

@ -1,13 +0,0 @@
FROM python:3.6
RUN mkdir /src
WORKDIR /src
COPY requirements.txt /src/requirements.txt
RUN pip install -r requirements.txt
COPY test-requirements.txt /src/test-requirements.txt
RUN pip install -r test-requirements.txt
COPY . /src
RUN pip install .

121
Jenkinsfile vendored
View File

@ -1,121 +0,0 @@
#!groovy
def imageNameBase = "dockerbuildbot/docker-py"
def imageNamePy2
def imageNamePy3
def images = [:]
def buildImage = { name, buildargs, pyTag ->
img = docker.image(name)
try {
img.pull()
} catch (Exception exc) {
img = docker.build(name, buildargs)
img.push()
}
images[pyTag] = img.id
}
def buildImages = { ->
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
stage("build image") {
checkout(scm)
imageNamePy2 = "${imageNameBase}:py2-${gitCommit()}"
imageNamePy3 = "${imageNameBase}:py3-${gitCommit()}"
buildImage(imageNamePy2, "-f tests/Dockerfile --build-arg PYTHON_VERSION=2.7 .", "py2.7")
buildImage(imageNamePy3, "-f tests/Dockerfile --build-arg PYTHON_VERSION=3.6 .", "py3.6")
}
}
}
def getDockerVersions = { ->
def dockerVersions = ["17.06.2-ce"]
wrappedNode(label: "ubuntu && !zfs") {
def result = sh(script: """docker run --rm \\
--entrypoint=python \\
${imageNamePy3} \\
/src/scripts/versions.py
""", returnStdout: true
)
dockerVersions = dockerVersions + result.trim().tokenize(' ')
}
return dockerVersions
}
def getAPIVersion = { engineVersion ->
def versionMap = [
'17.06': '1.30', '17.12': '1.35', '18.02': '1.36', '18.03': '1.37',
'18.06': '1.38', '18.09': '1.39'
]
def result = versionMap[engineVersion.substring(0, 5)]
if (!result) {
return '1.39'
}
return result
}
def runTests = { Map settings ->
def dockerVersion = settings.get("dockerVersion", null)
def pythonVersion = settings.get("pythonVersion", null)
def testImage = settings.get("testImage", null)
def apiVersion = getAPIVersion(dockerVersion)
if (!testImage) {
throw new Exception("Need test image object, e.g.: `runTests(testImage: img)`")
}
if (!dockerVersion) {
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '1.12.3')`")
}
if (!pythonVersion) {
throw new Exception("Need Python version being tested, e.g.: `runTests(pythonVersion: 'py2.7')`")
}
{ ->
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
stage("test python=${pythonVersion} / docker=${dockerVersion}") {
checkout(scm)
def dindContainerName = "dpy-dind-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
def testContainerName = "dpy-tests-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
def testNetwork = "dpy-testnet-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
try {
sh """docker network create ${testNetwork}"""
sh """docker run -d --name ${dindContainerName} -v /tmp --privileged --network ${testNetwork} \\
dockerswarm/dind:${dockerVersion} dockerd -H tcp://0.0.0.0:2375
"""
sh """docker run \\
--name ${testContainerName} \\
-e "DOCKER_HOST=tcp://${dindContainerName}:2375" \\
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
--network ${testNetwork} \\
--volumes-from ${dindContainerName} \\
${testImage} \\
py.test -v -rxs --cov=docker tests/
"""
} finally {
sh """
docker stop ${dindContainerName} ${testContainerName}
docker rm -vf ${dindContainerName} ${testContainerName}
docker network rm ${testNetwork}
"""
}
}
}
}
}
buildImages()
def dockerVersions = getDockerVersions()
def testMatrix = [failFast: false]
for (imgKey in new ArrayList(images.keySet())) {
for (version in dockerVersions) {
testMatrix["${imgKey}_${version}"] = runTests([testImage: images[imgKey], dockerVersion: version, pythonVersion: imgKey])
}
}
parallel(testMatrix)

View File

@ -11,15 +11,19 @@
[Org]
[Org."Core maintainers"]
people = [
"shin-",
"glours",
"milas",
]
[Org.Alumni]
people = [
"aiordache",
"aanand",
"bfirsh",
"dnephin",
"mnowster",
"mpetazzoni",
"shin-",
"ulyssessouza",
]
[people]
@ -35,6 +39,11 @@
Email = "aanand@docker.com"
GitHub = "aanand"
[people.aiordache]
Name = "Anca Iordache"
Email = "anca.iordache@docker.com"
GitHub = "aiordache"
[people.bfirsh]
Name = "Ben Firshman"
Email = "b@fir.sh"
@ -45,6 +54,16 @@
Email = "dnephin@gmail.com"
GitHub = "dnephin"
[people.glours]
Name = "Guillaume Lours"
Email = "705411+glours@users.noreply.github.com"
GitHub = "glours"
[people.milas]
Name = "Milas Bowman"
Email = "devnull@milas.dev"
GitHub = "milas"
[people.mnowster]
Name = "Mazz Mosley"
Email = "mazz@houseofmnowster.com"
@ -59,3 +78,8 @@
Name = "Joffrey F"
Email = "joffrey@docker.com"
GitHub = "shin-"
[people.ulyssessouza]
Name = "Ulysses Domiciano Souza"
Email = "ulysses.souza@docker.com"
GitHub = "ulyssessouza"

View File

@ -1,9 +0,0 @@
include test-requirements.txt
include requirements.txt
include README.md
include README.rst
include LICENSE
recursive-include tests *.py
recursive-include tests/unit/testdata *
recursive-include tests/integration/testdata *
recursive-include tests/gpg-keys *

197
Makefile
View File

@ -1,95 +1,184 @@
TEST_API_VERSION ?= 1.45
TEST_ENGINE_VERSION ?= 26.1
ifeq ($(OS),Windows_NT)
PLATFORM := Windows
else
PLATFORM := $(shell sh -c 'uname -s 2>/dev/null || echo Unknown')
endif
ifeq ($(PLATFORM),Linux)
uid_args := "--build-arg uid=$(shell id -u) --build-arg gid=$(shell id -g)"
endif
SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER ?= $(shell git describe --match '[0-9]*' --dirty='.m' --always --tags 2>/dev/null | sed -r 's/-([0-9]+)/.dev\1/' | sed 's/-/+/')
ifeq ($(SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER),)
SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER = "0.0.0.dev0"
endif
.PHONY: all
all: test
.PHONY: clean
clean:
-docker rm -f dpy-dind-py2 dpy-dind-py3 dpy-dind-certs dpy-dind-ssl
-docker rm -f dpy-dind dpy-dind-certs dpy-dind-ssl
find -name "__pycache__" | xargs rm -rf
.PHONY: build-dind-ssh
build-dind-ssh:
docker build \
--pull \
-t docker-dind-ssh \
-f tests/Dockerfile-ssh-dind \
--build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \
--build-arg ENGINE_VERSION=${TEST_ENGINE_VERSION} \
--build-arg API_VERSION=${TEST_API_VERSION} \
.
.PHONY: build
build:
docker build -t docker-sdk-python -f tests/Dockerfile --build-arg PYTHON_VERSION=2.7 .
.PHONY: build-py3
build-py3:
docker build -t docker-sdk-python3 -f tests/Dockerfile .
docker build \
--pull \
-t docker-sdk-python3 \
-f tests/Dockerfile \
--build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \
.
.PHONY: build-docs
build-docs:
docker build -t docker-sdk-python-docs -f Dockerfile-docs --build-arg uid=$(shell id -u) --build-arg gid=$(shell id -g) .
docker build \
-t docker-sdk-python-docs \
-f Dockerfile-docs \
--build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \
$(uid_args) \
.
.PHONY: build-dind-certs
build-dind-certs:
docker build -t dpy-dind-certs -f tests/Dockerfile-dind-certs .
docker build \
-t dpy-dind-certs \
-f tests/Dockerfile-dind-certs \
--build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \
.
.PHONY: test
test: flake8 unit-test unit-test-py3 integration-dind integration-dind-ssl
test: ruff unit-test integration-dind integration-dind-ssl
.PHONY: unit-test
unit-test: build
docker run -t --rm docker-sdk-python py.test tests/unit
.PHONY: unit-test-py3
unit-test-py3: build-py3
docker run -t --rm docker-sdk-python3 py.test tests/unit
.PHONY: integration-test
integration-test: build
docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python py.test -v tests/integration/${file}
.PHONY: integration-test-py3
integration-test-py3: build-py3
docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 py.test -v tests/integration/${file}
TEST_API_VERSION ?= 1.35
TEST_ENGINE_VERSION ?= 18.09.5
.PHONY: setup-network
setup-network:
docker network inspect dpy-tests || docker network create dpy-tests
.PHONY: integration-dind
integration-dind: integration-dind-py2 integration-dind-py3
integration-dind: build setup-network
docker rm -vf dpy-dind || :
.PHONY: integration-dind-py2
integration-dind-py2: build setup-network
docker rm -vf dpy-dind-py2 || :
docker run -d --network dpy-tests --name dpy-dind-py2 --privileged\
dockerswarm/dind:${TEST_ENGINE_VERSION} dockerd -H tcp://0.0.0.0:2375 --experimental
docker run -t --rm --env="DOCKER_HOST=tcp://dpy-dind-py2:2375" --env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}"\
--network dpy-tests docker-sdk-python py.test tests/integration
docker rm -vf dpy-dind-py2
docker run \
--detach \
--name dpy-dind \
--network dpy-tests \
--pull=always \
--privileged \
docker:${TEST_ENGINE_VERSION}-dind \
dockerd -H tcp://0.0.0.0:2375 --experimental
# Wait for Docker-in-Docker to come to life
docker run \
--network dpy-tests \
--rm \
--tty \
busybox \
sh -c 'while ! nc -z dpy-dind 2375; do sleep 1; done'
docker run \
--env="DOCKER_HOST=tcp://dpy-dind:2375" \
--env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}" \
--network dpy-tests \
--rm \
--tty \
docker-sdk-python3 \
py.test tests/integration/${file}
docker rm -vf dpy-dind
.PHONY: integration-dind-ssh
integration-dind-ssh: build-dind-ssh build setup-network
docker rm -vf dpy-dind-ssh || :
docker run -d --network dpy-tests --name dpy-dind-ssh --privileged \
docker-dind-ssh dockerd --experimental
# start SSH daemon for known key
docker exec dpy-dind-ssh sh -c "/usr/sbin/sshd -h /etc/ssh/known_ed25519 -p 22"
docker exec dpy-dind-ssh sh -c "/usr/sbin/sshd -h /etc/ssh/unknown_ed25519 -p 2222"
docker run \
--tty \
--rm \
--env="DOCKER_HOST=ssh://dpy-dind-ssh" \
--env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}" \
--env="UNKNOWN_DOCKER_SSH_HOST=ssh://dpy-dind-ssh:2222" \
--network dpy-tests \
docker-sdk-python3 py.test tests/ssh/${file}
docker rm -vf dpy-dind-ssh
.PHONY: integration-dind-py3
integration-dind-py3: build-py3 setup-network
docker rm -vf dpy-dind-py3 || :
docker run -d --network dpy-tests --name dpy-dind-py3 --privileged\
dockerswarm/dind:${TEST_ENGINE_VERSION} dockerd -H tcp://0.0.0.0:2375 --experimental
docker run -t --rm --env="DOCKER_HOST=tcp://dpy-dind-py3:2375" --env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}"\
--network dpy-tests docker-sdk-python3 py.test tests/integration
docker rm -vf dpy-dind-py3
.PHONY: integration-dind-ssl
integration-dind-ssl: build-dind-certs build build-py3
integration-dind-ssl: build-dind-certs build setup-network
docker rm -vf dpy-dind-certs dpy-dind-ssl || :
docker run -d --name dpy-dind-certs dpy-dind-certs
docker run -d --env="DOCKER_HOST=tcp://localhost:2375" --env="DOCKER_TLS_VERIFY=1"\
--env="DOCKER_CERT_PATH=/certs" --volumes-from dpy-dind-certs --name dpy-dind-ssl\
--network dpy-tests --network-alias docker -v /tmp --privileged\
dockerswarm/dind:${TEST_ENGINE_VERSION}\
dockerd --tlsverify --tlscacert=/certs/ca.pem --tlscert=/certs/server-cert.pem\
--tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375 --experimental
docker run -t --rm --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375"\
--env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}"\
--network dpy-tests docker-sdk-python py.test tests/integration
docker run -t --rm --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375"\
--env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}"\
--network dpy-tests docker-sdk-python3 py.test tests/integration
docker run \
--detach \
--env="DOCKER_CERT_PATH=/certs" \
--env="DOCKER_HOST=tcp://localhost:2375" \
--env="DOCKER_TLS_VERIFY=1" \
--name dpy-dind-ssl \
--network dpy-tests \
--network-alias docker \
--pull=always \
--privileged \
--volume /tmp \
--volumes-from dpy-dind-certs \
docker:${TEST_ENGINE_VERSION}-dind \
dockerd \
--tlsverify \
--tlscacert=/certs/ca.pem \
--tlscert=/certs/server-cert.pem \
--tlskey=/certs/server-key.pem \
-H tcp://0.0.0.0:2375 \
--experimental
# Wait for Docker-in-Docker to come to life
docker run \
--network dpy-tests \
--rm \
--tty \
busybox \
sh -c 'while ! nc -z dpy-dind-ssl 2375; do sleep 1; done'
docker run \
--env="DOCKER_CERT_PATH=/certs" \
--env="DOCKER_HOST=tcp://docker:2375" \
--env="DOCKER_TEST_API_VERSION=${TEST_API_VERSION}" \
--env="DOCKER_TLS_VERIFY=1" \
--network dpy-tests \
--rm \
--volumes-from dpy-dind-ssl \
--tty \
docker-sdk-python3 \
py.test tests/integration/${file}
docker rm -vf dpy-dind-ssl dpy-dind-certs
.PHONY: flake8
flake8: build
docker run -t --rm docker-sdk-python flake8 docker tests
.PHONY: ruff
ruff: build
docker run -t --rm docker-sdk-python3 ruff docker tests
.PHONY: docs
docs: build-docs
@ -97,4 +186,4 @@ docs: build-docs
.PHONY: shell
shell: build
docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python python
docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 python

View File

@ -1,18 +1,17 @@
# Docker SDK for Python
[![Build Status](https://travis-ci.org/docker/docker-py.svg?branch=master)](https://travis-ci.org/docker/docker-py)
[![Build Status](https://github.com/docker/docker-py/actions/workflows/ci.yml/badge.svg)](https://github.com/docker/docker-py/actions/workflows/ci.yml)
A Python library for the Docker Engine API. It lets you do anything the `docker` command does, but from within Python apps run containers, manage containers, manage Swarms, etc.
## Installation
The latest stable version [is available on PyPI](https://pypi.python.org/pypi/docker/). Either add `docker` to your `requirements.txt` file or install with pip:
The latest stable version [is available on PyPI](https://pypi.python.org/pypi/docker/). Install with pip:
pip install docker
If you are intending to connect to a docker host via TLS, add `docker[tls]` to your requirements instead, or install with pip:
pip install docker[tls]
> Older versions (< 6.0) required installing `docker[tls]` for SSL/TLS support.
> This is no longer necessary and is a no-op, but is supported for backwards compatibility.
## Usage
@ -58,7 +57,7 @@ You can stream logs:
```python
>>> for line in container.logs(stream=True):
... print line.strip()
... print(line.strip())
Reticulating spline 2...
Reticulating spline 3...
...

View File

@ -1,12 +0,0 @@
version: '{branch}-{build}'
install:
- "SET PATH=C:\\Python27-x64;C:\\Python27-x64\\Scripts;%PATH%"
- "python --version"
- "pip install tox==2.9.1"
# Build the binary after tests
build: false
test_script:
- "tox"

View File

@ -1,7 +1,7 @@
# flake8: noqa
from .api import APIClient
from .client import DockerClient, from_env
from .version import version, version_info
from .context import Context, ContextAPI
from .tls import TLSConfig
from .version import __version__
__version__ = version
__title__ = 'docker'

View File

@ -1,2 +1 @@
# flake8: noqa
from .client import APIClient

View File

@ -3,16 +3,12 @@ import logging
import os
import random
from .. import auth
from .. import constants
from .. import errors
from .. import utils
from .. import auth, constants, errors, utils
log = logging.getLogger(__name__)
class BuildApiMixin(object):
class BuildApiMixin:
def build(self, path=None, tag=None, quiet=False, fileobj=None,
nocache=False, rm=False, timeout=None,
custom_context=False, encoding=None, pull=False,
@ -76,6 +72,7 @@ class BuildApiMixin(object):
forcerm (bool): Always remove intermediate containers, even after
unsuccessful builds
dockerfile (str): path within the build context to the Dockerfile
gzip (bool): If set to ``True``, gzip compression/encoding is used
buildargs (dict): A dictionary of build arguments
container_limits (dict): A dictionary of limits applied to each
container created by the build process. Valid keys:
@ -128,13 +125,16 @@ class BuildApiMixin(object):
raise errors.DockerException(
'Can not use custom encoding if gzip is enabled'
)
if tag is not None:
if not utils.match_tag(tag):
raise errors.DockerException(
f"invalid tag '{tag}': invalid reference format"
)
for key in container_limits.keys():
if key not in constants.CONTAINER_LIMITS_KEYS:
raise errors.DockerException(
'Invalid container_limits key {0}'.format(key)
f"invalid tag '{tag}': invalid reference format"
)
if custom_context:
if not fileobj:
raise TypeError("You must specify fileobj with custom_context")
@ -150,10 +150,10 @@ class BuildApiMixin(object):
dockerignore = os.path.join(path, '.dockerignore')
exclude = None
if os.path.exists(dockerignore):
with open(dockerignore, 'r') as f:
with open(dockerignore) as f:
exclude = list(filter(
lambda x: x != '' and x[0] != '#',
[l.strip() for l in f.read().splitlines()]
[line.strip() for line in f.read().splitlines()]
))
dockerfile = process_dockerfile(dockerfile, path)
context = utils.tar(
@ -275,10 +275,24 @@ class BuildApiMixin(object):
return self._stream_helper(response, decode=decode)
@utils.minimum_version('1.31')
def prune_builds(self):
def prune_builds(self, filters=None, keep_storage=None, all=None):
"""
Delete the builder cache
Args:
filters (dict): Filters to process on the prune list.
Needs Docker API v1.39+
Available filters:
- dangling (bool): When set to true (or 1), prune only
unused and untagged images.
- until (str): Can be Unix timestamps, date formatted
timestamps, or Go duration strings (e.g. 10m, 1h30m) computed
relative to the daemon's local time.
keep_storage (int): Amount of disk space in bytes to keep for cache.
Needs Docker API v1.39+
all (bool): Remove all types of build cache.
Needs Docker API v1.39+
Returns:
(dict): A dictionary containing information about the operation's
result. The ``SpaceReclaimed`` key indicates the amount of
@ -289,7 +303,20 @@ class BuildApiMixin(object):
If the server returns an error.
"""
url = self._url("/build/prune")
return self._result(self._post(url), True)
if (filters, keep_storage, all) != (None, None, None) \
and utils.version_lt(self._version, '1.39'):
raise errors.InvalidVersion(
'`filters`, `keep_storage`, and `all` args are only available '
'for API version > 1.38'
)
params = {}
if filters is not None:
params['filters'] = utils.convert_filters(filters)
if keep_storage is not None:
params['keep-storage'] = keep_storage
if all is not None:
params['all'] = all
return self._result(self._post(url, params=params), True)
def _set_auth_headers(self, headers):
log.debug('Looking for auth config')
@ -308,13 +335,13 @@ class BuildApiMixin(object):
auth_data = self._auth_configs.get_all_credentials()
# See https://github.com/docker/docker-py/issues/1683
if auth.INDEX_URL not in auth_data and auth.INDEX_URL in auth_data:
if (auth.INDEX_URL not in auth_data and
auth.INDEX_NAME in auth_data):
auth_data[auth.INDEX_URL] = auth_data.get(auth.INDEX_NAME, {})
log.debug(
'Sending auth config ({0})'.format(
', '.join(repr(k) for k in auth_data.keys())
)
"Sending auth config (%s)",
', '.join(repr(k) for k in auth_data),
)
if auth_data:
@ -334,18 +361,15 @@ def process_dockerfile(dockerfile, path):
abs_dockerfile = os.path.join(path, dockerfile)
if constants.IS_WINDOWS_PLATFORM and path.startswith(
constants.WINDOWS_LONGPATH_PREFIX):
abs_dockerfile = '{}{}'.format(
constants.WINDOWS_LONGPATH_PREFIX,
os.path.normpath(
abs_dockerfile[len(constants.WINDOWS_LONGPATH_PREFIX):]
)
)
normpath = os.path.normpath(
abs_dockerfile[len(constants.WINDOWS_LONGPATH_PREFIX):])
abs_dockerfile = f'{constants.WINDOWS_LONGPATH_PREFIX}{normpath}'
if (os.path.splitdrive(path)[0] != os.path.splitdrive(abs_dockerfile)[0] or
os.path.relpath(abs_dockerfile, path).startswith('..')):
# Dockerfile not in context - read data to insert into tar later
with open(abs_dockerfile, 'r') as df:
with open(abs_dockerfile) as df:
return (
'.dockerfile.{0:x}'.format(random.getrandbits(160)),
f'.dockerfile.{random.getrandbits(160):x}',
df.read()
)

View File

@ -1,12 +1,35 @@
import json
import struct
import urllib
from functools import partial
import requests
import requests.adapters
import requests.exceptions
import six
import websocket
from .. import auth
from ..constants import (
DEFAULT_MAX_POOL_SIZE,
DEFAULT_NUM_POOLS,
DEFAULT_NUM_POOLS_SSH,
DEFAULT_TIMEOUT_SECONDS,
DEFAULT_USER_AGENT,
IS_WINDOWS_PLATFORM,
MINIMUM_DOCKER_API_VERSION,
STREAM_HEADER_SIZE_BYTES,
)
from ..errors import (
DockerException,
InvalidVersion,
TLSParameterError,
create_api_error_from_http_exception,
)
from ..tls import TLSConfig
from ..transport import UnixHTTPAdapter
from ..utils import check_resource, config, update_headers, utils
from ..utils.json_stream import json_stream
from ..utils.proxy import ProxyConfig
from ..utils.socket import consume_socket_output, demux_adaptor, frames_iter
from .build import BuildApiMixin
from .config import ConfigApiMixin
from .container import ContainerApiMixin
@ -19,22 +42,7 @@ from .secret import SecretApiMixin
from .service import ServiceApiMixin
from .swarm import SwarmApiMixin
from .volume import VolumeApiMixin
from .. import auth
from ..constants import (
DEFAULT_TIMEOUT_SECONDS, DEFAULT_USER_AGENT, IS_WINDOWS_PLATFORM,
DEFAULT_DOCKER_API_VERSION, MINIMUM_DOCKER_API_VERSION,
STREAM_HEADER_SIZE_BYTES, DEFAULT_NUM_POOLS_SSH, DEFAULT_NUM_POOLS
)
from ..errors import (
DockerException, InvalidVersion, TLSParameterError,
create_api_error_from_http_exception
)
from ..tls import TLSConfig
from ..transport import SSLHTTPAdapter, UnixHTTPAdapter
from ..utils import utils, check_resource, update_headers, config
from ..utils.socket import frames_iter, consume_socket_output, demux_adaptor
from ..utils.json_stream import json_stream
from ..utils.proxy import ProxyConfig
try:
from ..transport import NpipeHTTPAdapter
except ImportError:
@ -91,6 +99,11 @@ class APIClient(
user_agent (str): Set a custom user agent for requests to the server.
credstore_env (dict): Override environment variables when calling the
credential store process.
use_ssh_client (bool): If set to `True`, an ssh connection is made
via shelling out to the ssh client. Ensure the ssh client is
installed and configured on the host.
max_pool_size (int): The maximum number of connections
to save in the pool.
"""
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
@ -102,8 +115,9 @@ class APIClient(
def __init__(self, base_url=None, version=None,
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
user_agent=DEFAULT_USER_AGENT, num_pools=None,
credstore_env=None):
super(APIClient, self).__init__()
credstore_env=None, use_ssh_client=False,
max_pool_size=DEFAULT_MAX_POOL_SIZE):
super().__init__()
if tls and not base_url:
raise TLSParameterError(
@ -138,7 +152,8 @@ class APIClient(
if base_url.startswith('http+unix://'):
self._custom_adapter = UnixHTTPAdapter(
base_url, timeout, pool_connections=num_pools
base_url, timeout, pool_connections=num_pools,
max_pool_size=max_pool_size
)
self.mount('http+docker://', self._custom_adapter)
self._unmount('http://', 'https://')
@ -152,23 +167,25 @@ class APIClient(
)
try:
self._custom_adapter = NpipeHTTPAdapter(
base_url, timeout, pool_connections=num_pools
base_url, timeout, pool_connections=num_pools,
max_pool_size=max_pool_size
)
except NameError:
except NameError as err:
raise DockerException(
'Install pypiwin32 package to enable npipe:// support'
)
) from err
self.mount('http+docker://', self._custom_adapter)
self.base_url = 'http+docker://localnpipe'
elif base_url.startswith('ssh://'):
try:
self._custom_adapter = SSHHTTPAdapter(
base_url, timeout, pool_connections=num_pools
base_url, timeout, pool_connections=num_pools,
max_pool_size=max_pool_size, shell_out=use_ssh_client
)
except NameError:
except NameError as err:
raise DockerException(
'Install paramiko package to enable ssh:// support'
)
) from err
self.mount('http+docker://ssh', self._custom_adapter)
self._unmount('http://', 'https://')
self.base_url = 'http+docker://ssh'
@ -177,43 +194,42 @@ class APIClient(
if isinstance(tls, TLSConfig):
tls.configure_client(self)
elif tls:
self._custom_adapter = SSLHTTPAdapter(
self._custom_adapter = requests.adapters.HTTPAdapter(
pool_connections=num_pools)
self.mount('https://', self._custom_adapter)
self.base_url = base_url
# version detection needs to be after unix adapter mounting
if version is None:
self._version = DEFAULT_DOCKER_API_VERSION
elif isinstance(version, six.string_types):
if version.lower() == 'auto':
if version is None or (isinstance(
version,
str
) and version.lower() == 'auto'):
self._version = self._retrieve_server_version()
else:
self._version = version
else:
if not isinstance(self._version, str):
raise DockerException(
'Version parameter must be a string or None. Found {0}'.format(
type(version).__name__
)
'Version parameter must be a string or None. '
f'Found {type(version).__name__}'
)
if utils.version_lt(self._version, MINIMUM_DOCKER_API_VERSION):
raise InvalidVersion(
'API versions below {} are no longer supported by this '
'library.'.format(MINIMUM_DOCKER_API_VERSION)
f'API versions below {MINIMUM_DOCKER_API_VERSION} are '
f'no longer supported by this library.'
)
def _retrieve_server_version(self):
try:
return self.version(api_version=False)["ApiVersion"]
except KeyError:
except KeyError as ke:
raise DockerException(
'Invalid response from docker daemon: key "ApiVersion"'
' is missing.'
)
) from ke
except Exception as e:
raise DockerException(
'Error while fetching server API version: {0}'.format(e)
)
f'Error while fetching server API version: {e}'
) from e
def _set_request_timeout(self, kwargs):
"""Prepare the kwargs for an HTTP request by inserting the timeout
@ -239,28 +255,26 @@ class APIClient(
def _url(self, pathfmt, *args, **kwargs):
for arg in args:
if not isinstance(arg, six.string_types):
if not isinstance(arg, str):
raise ValueError(
'Expected a string but found {0} ({1}) '
'instead'.format(arg, type(arg))
f'Expected a string but found {arg} ({type(arg)}) instead'
)
quote_f = partial(six.moves.urllib.parse.quote, safe="/:")
quote_f = partial(urllib.parse.quote, safe="/:")
args = map(quote_f, args)
formatted_path = pathfmt.format(*args)
if kwargs.get('versioned_api', True):
return '{0}/v{1}{2}'.format(
self.base_url, self._version, pathfmt.format(*args)
)
return f'{self.base_url}/v{self._version}{formatted_path}'
else:
return '{0}{1}'.format(self.base_url, pathfmt.format(*args))
return f'{self.base_url}{formatted_path}'
def _raise_for_status(self, response):
"""Raises stored :class:`APIError`, if one occurred."""
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise create_api_error_from_http_exception(e)
raise create_api_error_from_http_exception(e) from e
def _result(self, response, json=False, binary=False):
assert not (json and binary)
@ -277,7 +291,7 @@ class APIClient(
# so we do this disgusting thing here.
data2 = {}
if data is not None and isinstance(data, dict):
for k, v in six.iteritems(data):
for k, v in iter(data.items()):
if v is not None:
data2[k] = v
elif data is not None:
@ -305,7 +319,16 @@ class APIClient(
return self._create_websocket_connection(full_url)
def _create_websocket_connection(self, url):
try:
import websocket
return websocket.create_connection(url)
except ImportError as ie:
raise DockerException(
'The `websocket-client` library is required '
'for using websocket connections. '
'You can install the `docker` library '
'with the [websocket] extra to install it.'
) from ie
def _get_raw_response_socket(self, response):
self._raise_for_status(response)
@ -313,12 +336,10 @@ class APIClient(
sock = response.raw._fp.fp.raw.sock
elif self.base_url.startswith('http+docker://ssh'):
sock = response.raw._fp.fp.channel
elif six.PY3:
else:
sock = response.raw._fp.fp.raw
if self.base_url.startswith("https://"):
sock = sock._sock
else:
sock = response.raw._fp.fp._sock
try:
# Keep a reference to the response to stop it being garbage
# collected. If the response is garbage collected, it will
@ -336,8 +357,7 @@ class APIClient(
if response.raw._fp.chunked:
if decode:
for chunk in json_stream(self._stream_helper(response, False)):
yield chunk
yield from json_stream(self._stream_helper(response, False))
else:
reader = response.raw
while not reader.closed:
@ -393,10 +413,19 @@ class APIClient(
def _stream_raw_result(self, response, chunk_size=1, decode=True):
''' Stream result for TTY-enabled container and raw binary data'''
self._raise_for_status(response)
for out in response.iter_content(chunk_size, decode):
yield out
# Disable timeout on the underlying socket to prevent
# Read timed out(s) for long running processes
socket = self._get_raw_response_socket(response)
self._disable_socket_timeout(socket)
yield from response.iter_content(chunk_size, decode)
def _read_from_socket(self, response, stream, tty=True, demux=False):
"""Consume all data from the socket, close the response and return the
data. If stream=True, then a generator is returned instead and the
caller is responsible for closing the response.
"""
socket = self._get_raw_response_socket(response)
gen = frames_iter(socket, tty)
@ -411,8 +440,11 @@ class APIClient(
if stream:
return gen
else:
# Wait for all the frames, concatenate them, and return the result
try:
# Wait for all frames, concatenate them, and return the result
return consume_socket_output(gen, demux=demux)
finally:
response.close()
def _disable_socket_timeout(self, socket):
""" Depending on the combination of python version and whether we're
@ -458,12 +490,12 @@ class APIClient(
self._result(res, binary=True)
self._raise_for_status(res)
sep = six.binary_type()
sep = b''
if stream:
return self._multiplexed_response_stream_helper(res)
else:
return sep.join(
[x for x in self._multiplexed_buffer_helper(res)]
list(self._multiplexed_buffer_helper(res))
)
def _unmount(self, *args):
@ -472,7 +504,7 @@ class APIClient(
def get_adapter(self, url):
try:
return super(APIClient, self).get_adapter(url)
return super().get_adapter(url)
except requests.exceptions.InvalidSchema as e:
if self._custom_adapter:
return self._custom_adapter

View File

@ -1,13 +1,11 @@
import base64
import six
from .. import utils
class ConfigApiMixin(object):
class ConfigApiMixin:
@utils.minimum_version('1.30')
def create_config(self, name, data, labels=None):
def create_config(self, name, data, labels=None, templating=None):
"""
Create a config
@ -15,6 +13,9 @@ class ConfigApiMixin(object):
name (string): Name of the config
data (bytes): Config data to be stored
labels (dict): A mapping of labels to assign to the config
templating (dict): dictionary containing the name of the
templating driver to be used expressed as
{ name: <templating_driver_name>}
Returns (dict): ID of the newly created config
"""
@ -22,12 +23,12 @@ class ConfigApiMixin(object):
data = data.encode('utf-8')
data = base64.b64encode(data)
if six.PY3:
data = data.decode('ascii')
body = {
'Data': data,
'Name': name,
'Labels': labels
'Labels': labels,
'Templating': templating
}
url = self._url('/configs/create')

View File

@ -1,18 +1,17 @@
from datetime import datetime
import six
from .. import errors
from .. import utils
from .. import errors, utils
from ..constants import DEFAULT_DATA_CHUNK_SIZE
from ..types import CancellableStream
from ..types import ContainerConfig
from ..types import EndpointConfig
from ..types import HostConfig
from ..types import NetworkingConfig
from ..types import (
CancellableStream,
ContainerConfig,
EndpointConfig,
HostConfig,
NetworkingConfig,
)
class ContainerApiMixin(object):
class ContainerApiMixin:
@utils.check_resource('container')
def attach(self, container, stdout=True, stderr=True,
stream=False, logs=False, demux=False):
@ -114,7 +113,7 @@ class ContainerApiMixin(object):
@utils.check_resource('container')
def commit(self, container, repository=None, tag=None, message=None,
author=None, changes=None, conf=None):
author=None, pause=True, changes=None, conf=None):
"""
Commit a container to an image. Similar to the ``docker commit``
command.
@ -125,6 +124,7 @@ class ContainerApiMixin(object):
tag (str): The tag to push
message (str): A commit message
author (str): The name of the author
pause (bool): Whether to pause the container before committing
changes (str): Dockerfile instructions to apply while committing
conf (dict): The configuration for the container. See the
`Engine API documentation
@ -141,6 +141,7 @@ class ContainerApiMixin(object):
'tag': tag,
'comment': message,
'author': author,
'pause': pause,
'changes': changes
}
u = self._url("/commit")
@ -174,7 +175,8 @@ class ContainerApiMixin(object):
- `exited` (int): Only containers with specified exit code
- `status` (str): One of ``restarting``, ``running``,
``paused``, ``exited``
- `label` (str): format either ``"key"`` or ``"key=value"``
- `label` (str|list): format either ``"key"``, ``"key=value"``
or a list of such.
- `id` (str): The id of the container.
- `name` (str): The name of the container.
- `ancestor` (str): Filter by container ancestor. Format of
@ -224,7 +226,7 @@ class ContainerApiMixin(object):
mac_address=None, labels=None, stop_signal=None,
networking_config=None, healthcheck=None,
stop_timeout=None, runtime=None,
use_config_proxy=True):
use_config_proxy=True, platform=None):
"""
Creates a container. Parameters are similar to those for the ``docker
run`` command except it doesn't support the attach options (``-a``).
@ -243,9 +245,9 @@ class ContainerApiMixin(object):
.. code-block:: python
container_id = cli.create_container(
container_id = client.api.create_container(
'busybox', 'ls', ports=[1111, 2222],
host_config=cli.create_host_config(port_bindings={
host_config=client.api.create_host_config(port_bindings={
1111: 4567,
2222: None
})
@ -257,22 +259,24 @@ class ContainerApiMixin(object):
.. code-block:: python
cli.create_host_config(port_bindings={1111: ('127.0.0.1', 4567)})
client.api.create_host_config(
port_bindings={1111: ('127.0.0.1', 4567)}
)
Or without host port assignment:
.. code-block:: python
cli.create_host_config(port_bindings={1111: ('127.0.0.1',)})
client.api.create_host_config(port_bindings={1111: ('127.0.0.1',)})
If you wish to use UDP instead of TCP (default), you need to declare
ports as such in both the config and host config:
.. code-block:: python
container_id = cli.create_container(
container_id = client.api.create_container(
'busybox', 'ls', ports=[(1111, 'udp'), 2222],
host_config=cli.create_host_config(port_bindings={
host_config=client.api.create_host_config(port_bindings={
'1111/udp': 4567, 2222: None
})
)
@ -282,7 +286,7 @@ class ContainerApiMixin(object):
.. code-block:: python
cli.create_host_config(port_bindings={
client.api.create_host_config(port_bindings={
1111: [1234, 4567]
})
@ -290,7 +294,7 @@ class ContainerApiMixin(object):
.. code-block:: python
cli.create_host_config(port_bindings={
client.api.create_host_config(port_bindings={
1111: [
('192.168.0.100', 1234),
('192.168.0.101', 1234)
@ -306,9 +310,9 @@ class ContainerApiMixin(object):
.. code-block:: python
container_id = cli.create_container(
container_id = client.api.create_container(
'busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'],
host_config=cli.create_host_config(binds={
host_config=client.api.create_host_config(binds={
'/home/user1/': {
'bind': '/mnt/vol2',
'mode': 'rw',
@ -316,6 +320,11 @@ class ContainerApiMixin(object):
'/var/www': {
'bind': '/mnt/vol1',
'mode': 'ro',
},
'/autofs/user1': {
'bind': '/mnt/vol3',
'mode': 'rw',
'propagation': 'shared'
}
})
)
@ -325,11 +334,12 @@ class ContainerApiMixin(object):
.. code-block:: python
container_id = cli.create_container(
'busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'],
host_config=cli.create_host_config(binds=[
container_id = client.api.create_container(
'busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2', '/mnt/vol3'],
host_config=client.api.create_host_config(binds=[
'/home/user1/:/mnt/vol2',
'/var/www:/mnt/vol1:ro',
'/autofs/user1:/mnt/vol3:rw,shared',
])
)
@ -345,15 +355,15 @@ class ContainerApiMixin(object):
.. code-block:: python
networking_config = docker_client.create_networking_config({
'network1': docker_client.create_endpoint_config(
networking_config = client.api.create_networking_config({
'network1': client.api.create_endpoint_config(
ipv4_address='172.28.0.124',
aliases=['foo', 'bar'],
links=['container2']
)
})
ctnr = docker_client.create_container(
ctnr = client.api.create_container(
img, command, networking_config=networking_config
)
@ -397,6 +407,7 @@ class ContainerApiMixin(object):
configuration file (``~/.docker/config.json`` by default)
contains a proxy configuration, the corresponding environment
variables will be set in the container being created.
platform (str): Platform in the format ``os[/arch[/variant]]``.
Returns:
A dictionary with an image 'Id' key and a 'Warnings' key.
@ -407,7 +418,7 @@ class ContainerApiMixin(object):
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
if isinstance(volumes, six.string_types):
if isinstance(volumes, str):
volumes = [volumes, ]
if isinstance(environment, dict):
@ -426,16 +437,22 @@ class ContainerApiMixin(object):
stop_signal, networking_config, healthcheck,
stop_timeout, runtime
)
return self.create_container_from_config(config, name)
return self.create_container_from_config(config, name, platform)
def create_container_config(self, *args, **kwargs):
return ContainerConfig(self._version, *args, **kwargs)
def create_container_from_config(self, config, name=None):
def create_container_from_config(self, config, name=None, platform=None):
u = self._url("/containers/create")
params = {
'name': name
}
if platform:
if utils.version_lt(self._version, '1.41'):
raise errors.InvalidVersion(
'platform is not supported for API version < 1.41'
)
params['platform'] = platform
res = self._post_json(u, data=config, params=params)
return self._result(res, True)
@ -479,6 +496,9 @@ class ContainerApiMixin(object):
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
dns (:py:class:`list`): Set custom DNS servers.
dns_opt (:py:class:`list`): Additional options to be added to the
container's ``resolv.conf`` file
@ -502,6 +522,7 @@ class ContainerApiMixin(object):
bytes) or a string with a units identification char
(``100000b``, ``1000k``, ``128m``, ``1g``). If a string is
specified without a units character, bytes are assumed as an
mem_reservation (float or str): Memory soft limit.
mem_swappiness (int): Tune a container's memory swappiness
behavior. Accepts number between 0 and 100.
memswap_limit (str or int): Maximum amount of memory + swap a
@ -518,6 +539,8 @@ class ContainerApiMixin(object):
- ``container:<name|id>`` Reuse another container's network
stack.
- ``host`` Use the host network stack.
This mode is incompatible with ``port_bindings``.
oom_kill_disable (bool): Whether to disable OOM killer.
oom_score_adj (int): An integer value containing the score given
to the container in order to tune OOM killer preferences.
@ -527,6 +550,7 @@ class ContainerApiMixin(object):
unlimited.
port_bindings (dict): See :py:meth:`create_container`
for more information.
Imcompatible with ``host`` in ``network_mode``.
privileged (bool): Give extended privileges to this container.
publish_all_ports (bool): Publish all ports to the host.
read_only (bool): Mount the container's root filesystem as read
@ -573,8 +597,11 @@ class ContainerApiMixin(object):
Example:
>>> cli.create_host_config(privileged=True, cap_drop=['MKNOD'],
volumes_from=['nostalgic_newton'])
>>> client.api.create_host_config(
... privileged=True,
... cap_drop=['MKNOD'],
... volumes_from=['nostalgic_newton'],
... )
{'CapDrop': ['MKNOD'], 'LxcConf': None, 'Privileged': True,
'VolumesFrom': ['nostalgic_newton'], 'PublishAllPorts': False}
@ -604,11 +631,11 @@ class ContainerApiMixin(object):
Example:
>>> docker_client.create_network('network1')
>>> networking_config = docker_client.create_networking_config({
'network1': docker_client.create_endpoint_config()
>>> client.api.create_network('network1')
>>> networking_config = client.api.create_networking_config({
'network1': client.api.create_endpoint_config()
})
>>> container = docker_client.create_container(
>>> container = client.api.create_container(
img, command, networking_config=networking_config
)
@ -634,13 +661,15 @@ class ContainerApiMixin(object):
network, using the IPv6 protocol. Defaults to ``None``.
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
addresses.
driver_opt (dict): A dictionary of options to provide to the
network driver. Defaults to ``None``.
Returns:
(dict) An endpoint config.
Example:
>>> endpoint_config = client.create_endpoint_config(
>>> endpoint_config = client.api.create_endpoint_config(
aliases=['web', 'app'],
links={'app_db': 'db', 'another': None},
ipv4_address='132.65.0.123'
@ -658,7 +687,8 @@ class ContainerApiMixin(object):
container (str): The container to diff
Returns:
(str)
(list) A list of dictionaries containing the attributes `Path`
and `Kind`.
Raises:
:py:class:`docker.errors.APIError`
@ -692,7 +722,8 @@ class ContainerApiMixin(object):
return self._stream_raw_result(res, chunk_size, False)
@utils.check_resource('container')
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
encode_stream=False):
"""
Retrieve a file or folder from a container in the form of a tar
archive.
@ -703,6 +734,8 @@ class ContainerApiMixin(object):
chunk_size (int): The number of bytes returned by each iteration
of the generator. If ``None``, data will be streamed as it is
received. Default: 2 MB
encode_stream (bool): Determines if data should be encoded
(gzip-compressed) during transmission. Default: False
Returns:
(tuple): First element is a raw tar data stream. Second element is
@ -716,7 +749,7 @@ class ContainerApiMixin(object):
>>> c = docker.APIClient()
>>> f = open('./sh_bin.tar', 'wb')
>>> bits, stat = c.get_archive(container, '/bin/sh')
>>> bits, stat = c.api.get_archive(container, '/bin/sh')
>>> print(stat)
{'name': 'sh', 'size': 1075464, 'mode': 493,
'mtime': '2018-10-01T15:37:48-07:00', 'linkTarget': ''}
@ -727,8 +760,13 @@ class ContainerApiMixin(object):
params = {
'path': path
}
headers = {
"Accept-Encoding": "gzip, deflate"
} if encode_stream else {
"Accept-Encoding": "identity"
}
url = self._url('/containers/{0}/archive', container)
res = self._get(url, params=params, stream=True)
res = self._get(url, params=params, stream=True, headers=headers)
self._raise_for_status(res)
encoded_stat = res.headers.get('x-docker-container-path-stat')
return (
@ -772,7 +810,7 @@ class ContainerApiMixin(object):
url = self._url("/containers/{0}/kill", container)
params = {}
if signal is not None:
if not isinstance(signal, six.string_types):
if not isinstance(signal, str):
signal = int(signal)
params['signal'] = signal
res = self._post(url, params=params)
@ -798,14 +836,15 @@ class ContainerApiMixin(object):
tail (str or int): Output specified number of lines at the end of
logs. Either an integer of number of lines or the string
``all``. Default ``all``
since (datetime or int): Show logs since a given datetime or
integer epoch (in seconds)
since (datetime, int, or float): Show logs since a given datetime,
integer epoch (in seconds) or float (in fractional seconds)
follow (bool): Follow log output. Default ``False``
until (datetime or int): Show logs that occurred before the given
datetime or integer epoch (in seconds)
until (datetime, int, or float): Show logs that occurred before
the given datetime, integer epoch (in seconds), or
float (in fractional seconds)
Returns:
(generator or str)
(generator of bytes or bytes)
Raises:
:py:class:`docker.errors.APIError`
@ -827,10 +866,12 @@ class ContainerApiMixin(object):
params['since'] = utils.datetime_to_timestamp(since)
elif (isinstance(since, int) and since > 0):
params['since'] = since
elif (isinstance(since, float) and since > 0.0):
params['since'] = since
else:
raise errors.InvalidArgument(
'since value should be datetime or positive int, '
'not {}'.format(type(since))
'since value should be datetime or positive int/float,'
f' not {type(since)}'
)
if until is not None:
@ -842,10 +883,12 @@ class ContainerApiMixin(object):
params['until'] = utils.datetime_to_timestamp(until)
elif (isinstance(until, int) and until > 0):
params['until'] = until
elif (isinstance(until, float) and until > 0.0):
params['until'] = until
else:
raise errors.InvalidArgument(
'until value should be datetime or positive int, '
'not {}'.format(type(until))
f'until value should be datetime or positive int/float, '
f'not {type(until)}'
)
url = self._url("/containers/{0}/logs", container)
@ -898,7 +941,7 @@ class ContainerApiMixin(object):
.. code-block:: python
>>> cli.port('7174d6347063', 80)
>>> client.api.port('7174d6347063', 80)
[{'HostIp': '0.0.0.0', 'HostPort': '80'}]
"""
res = self._get(self._url("/containers/{0}/json", container))
@ -917,7 +960,7 @@ class ContainerApiMixin(object):
return port_settings.get(private_port)
for protocol in ['tcp', 'udp', 'sctp']:
h_ports = port_settings.get(private_port + '/' + protocol)
h_ports = port_settings.get(f"{private_port}/{protocol}")
if h_ports:
break
@ -933,7 +976,7 @@ class ContainerApiMixin(object):
container (str): The container where the file(s) will be extracted
path (str): Path inside the container where the file(s) will be
extracted. Must exist.
data (bytes): tar data to be extracted
data (bytes or stream): tar data to be extracted
Returns:
(bool): True if the call succeeds.
@ -1077,10 +1120,10 @@ class ContainerApiMixin(object):
Example:
>>> container = cli.create_container(
>>> container = client.api.create_container(
... image='busybox:latest',
... command='/bin/sleep 30')
>>> cli.start(container=container.get('Id'))
>>> client.api.start(container=container.get('Id'))
"""
if args or kwargs:
raise errors.DeprecatedMethod(
@ -1093,7 +1136,7 @@ class ContainerApiMixin(object):
self._raise_for_status(res)
@utils.check_resource('container')
def stats(self, container, decode=None, stream=True):
def stats(self, container, decode=None, stream=True, one_shot=None):
"""
Stream statistics for a specific container. Similar to the
``docker stats`` command.
@ -1105,6 +1148,9 @@ class ContainerApiMixin(object):
False by default.
stream (bool): If set to false, only the current stats will be
returned instead of a stream. True by default.
one_shot (bool): If set to true, Only get a single stat instead of
waiting for 2 cycles. Must be used with stream=false. False by
default.
Raises:
:py:class:`docker.errors.APIError`
@ -1112,16 +1158,30 @@ class ContainerApiMixin(object):
"""
url = self._url("/containers/{0}/stats", container)
params = {
'stream': stream
}
if one_shot is not None:
if utils.version_lt(self._version, '1.41'):
raise errors.InvalidVersion(
'one_shot is not supported for API version < 1.41'
)
params['one-shot'] = one_shot
if stream:
return self._stream_helper(self._get(url, stream=True),
decode=decode)
if one_shot:
raise errors.InvalidArgument(
'one_shot is only available in conjunction with '
'stream=False'
)
return self._stream_helper(
self._get(url, stream=True, params=params), decode=decode
)
else:
if decode:
raise errors.InvalidArgument(
"decode is only available in conjuction with stream=True"
"decode is only available in conjunction with stream=True"
)
return self._result(self._get(url, params={'stream': False}),
json=True)
return self._result(self._get(url, params=params), json=True)
@utils.check_resource('container')
def stop(self, container, timeout=None):
@ -1204,8 +1264,8 @@ class ContainerApiMixin(object):
cpu_shares (int): CPU shares (relative weight)
cpuset_cpus (str): CPUs in which to allow execution
cpuset_mems (str): MEMs in which to allow execution
mem_limit (int or str): Memory limit
mem_reservation (int or str): Memory soft limit
mem_limit (float or str): Memory limit
mem_reservation (float or str): Memory soft limit
memswap_limit (int or str): Total memory (memory + swap), -1 to
disable swap
kernel_memory (int or str): Kernel memory limit

View File

@ -4,7 +4,7 @@ from datetime import datetime
from .. import auth, types, utils
class DaemonApiMixin(object):
class DaemonApiMixin:
@utils.minimum_version('1.25')
def df(self):
"""

View File

@ -1,10 +1,8 @@
import six
from .. import errors
from .. import utils
from .. import errors, utils
from ..types import CancellableStream
class ExecApiMixin(object):
class ExecApiMixin:
@utils.check_resource('container')
def exec_create(self, container, cmd, stdout=True, stderr=True,
stdin=False, tty=False, privileged=False, user='',
@ -45,7 +43,7 @@ class ExecApiMixin(object):
'Setting environment for exec is not supported in API < 1.25'
)
if isinstance(cmd, six.string_types):
if isinstance(cmd, str):
cmd = utils.split_command(cmd)
if isinstance(environment, dict):
@ -127,9 +125,10 @@ class ExecApiMixin(object):
detach (bool): If true, detach from the exec command.
Default: False
tty (bool): Allocate a pseudo-TTY. Default: False
stream (bool): Stream response data. Default: False
stream (bool): Return response data progressively as an iterator
of strings, rather than a single string.
socket (bool): Return the connection socket to allow custom
read/write operations.
read/write operations. Must be closed by the caller when done.
demux (bool): Return stdout and stderr separately
Returns:
@ -163,7 +162,15 @@ class ExecApiMixin(object):
stream=True
)
if detach:
try:
return self._result(res)
finally:
res.close()
if socket:
return self._get_raw_response_socket(res)
return self._read_from_socket(res, stream, tty=tty, demux=demux)
output = self._read_from_socket(res, stream, tty=tty, demux=demux)
if stream:
return CancellableStream(output, res)
else:
return output

View File

@ -1,15 +1,13 @@
import logging
import os
import six
from .. import auth, errors, utils
from ..constants import DEFAULT_DATA_CHUNK_SIZE
log = logging.getLogger(__name__)
class ImageApiMixin(object):
class ImageApiMixin:
@utils.check_resource('image')
def get_image(self, image, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
@ -31,7 +29,7 @@ class ImageApiMixin(object):
Example:
>>> image = cli.get_image("busybox:latest")
>>> image = client.api.get_image("busybox:latest")
>>> f = open('/tmp/busybox-latest.tar', 'wb')
>>> for chunk in image:
>>> f.write(chunk)
@ -49,7 +47,7 @@ class ImageApiMixin(object):
image (str): The image to show history for
Returns:
(str): The history of the image
(list): The history of the image
Raises:
:py:class:`docker.errors.APIError`
@ -70,7 +68,8 @@ class ImageApiMixin(object):
filters (dict): Filters to be processed on the image list.
Available filters:
- ``dangling`` (bool)
- ``label`` (str): format either ``key`` or ``key=value``
- `label` (str|list): format either ``"key"``, ``"key=value"``
or a list of such.
Returns:
(dict or list): A list if ``quiet=True``, otherwise a dict.
@ -80,10 +79,18 @@ class ImageApiMixin(object):
If the server returns an error.
"""
params = {
'filter': name,
'only_ids': 1 if quiet else 0,
'all': 1 if all else 0,
}
if name:
if utils.version_lt(self._version, '1.25'):
# only use "filter" on API 1.24 and under, as it is deprecated
params['filter'] = name
else:
if filters:
filters['reference'] = name
else:
filters = {'reference': name}
if filters:
params['filters'] = utils.convert_filters(filters)
res = self._result(self._get(self._url("/images/json"), params=params),
@ -121,7 +128,7 @@ class ImageApiMixin(object):
params = _import_image_params(
repository, tag, image,
src=(src if isinstance(src, six.string_types) else None),
src=(src if isinstance(src, str) else None),
changes=changes
)
headers = {'Content-Type': 'application/tar'}
@ -130,7 +137,7 @@ class ImageApiMixin(object):
return self._result(
self._post(u, data=None, params=params)
)
elif isinstance(src, six.string_types): # from file path
elif isinstance(src, str): # from file path
with open(src, 'rb') as f:
return self._result(
self._post(
@ -342,13 +349,14 @@ class ImageApiMixin(object):
return self._result(self._post(url, params=params), True)
def pull(self, repository, tag=None, stream=False, auth_config=None,
decode=False, platform=None):
decode=False, platform=None, all_tags=False):
"""
Pulls an image. Similar to the ``docker pull`` command.
Args:
repository (str): The repository to pull
tag (str): The tag to pull
tag (str): The tag to pull. If ``tag`` is ``None`` or empty, it
is set to ``latest``.
stream (bool): Stream the output as a generator. Make sure to
consume the generator, otherwise pull might get cancelled.
auth_config (dict): Override the credentials that are found in the
@ -357,6 +365,8 @@ class ImageApiMixin(object):
decode (bool): Decode the JSON data from the server into dicts.
Only applies with ``stream=True``
platform (str): Platform in the format ``os[/arch[/variant]]``
all_tags (bool): Pull all image tags, the ``tag`` parameter is
ignored.
Returns:
(generator or str): The output
@ -367,7 +377,8 @@ class ImageApiMixin(object):
Example:
>>> for line in cli.pull('busybox', stream=True, decode=True):
>>> resp = client.api.pull('busybox', stream=True, decode=True)
... for line in resp:
... print(json.dumps(line, indent=4))
{
"status": "Pulling image (latest) from busybox",
@ -381,8 +392,12 @@ class ImageApiMixin(object):
}
"""
if not tag:
repository, tag = utils.parse_repository_tag(repository)
repository, image_tag = utils.parse_repository_tag(repository)
tag = tag or image_tag or 'latest'
if all_tags:
tag = None
registry, repo_name = auth.resolve_repository_name(repository)
params = {
@ -442,7 +457,12 @@ class ImageApiMixin(object):
If the server returns an error.
Example:
>>> for line in cli.push('yourname/app', stream=True, decode=True):
>>> resp = client.api.push(
... 'yourname/app',
... stream=True,
... decode=True,
... )
... for line in resp:
... print(line)
{'status': 'Pushing repository yourname/app (1 tags)'}
{'status': 'Pushing','progressDetail': {}, 'id': '511136ea3c5a'}
@ -493,13 +513,14 @@ class ImageApiMixin(object):
res = self._delete(self._url("/images/{0}", image), params=params)
return self._result(res, True)
def search(self, term):
def search(self, term, limit=None):
"""
Search for images on Docker Hub. Similar to the ``docker search``
command.
Args:
term (str): A term to search for.
limit (int): The maximum number of results to return.
Returns:
(list of dicts): The response of the search.
@ -508,8 +529,12 @@ class ImageApiMixin(object):
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
params = {'term': term}
if limit is not None:
params['limit'] = limit
return self._result(
self._get(self._url("/images/search"), params={'term': term}),
self._get(self._url("/images/search"), params=params),
True
)
@ -533,7 +558,7 @@ class ImageApiMixin(object):
Example:
>>> client.tag('ubuntu', 'localhost:5000/ubuntu', 'latest',
>>> client.api.tag('ubuntu', 'localhost:5000/ubuntu', 'latest',
force=True)
"""
params = {
@ -550,7 +575,7 @@ class ImageApiMixin(object):
def is_file(src):
try:
return (
isinstance(src, six.string_types) and
isinstance(src, str) and
os.path.isfile(src)
)
except TypeError: # a data string will make isfile() raise a TypeError

View File

@ -1,13 +1,12 @@
from ..errors import InvalidVersion
from ..utils import check_resource, minimum_version
from ..utils import version_lt
from .. import utils
from ..errors import InvalidVersion
from ..utils import check_resource, minimum_version, version_lt
class NetworkApiMixin(object):
class NetworkApiMixin:
def networks(self, names=None, ids=None, filters=None):
"""
List networks. Similar to the ``docker networks ls`` command.
List networks. Similar to the ``docker network ls`` command.
Args:
names (:py:class:`list`): List of names to filter by
@ -15,7 +14,8 @@ class NetworkApiMixin(object):
filters (dict): Filters to be processed on the network list.
Available filters:
- ``driver=[<driver-name>]`` Matches a network's driver.
- ``label=[<key>]`` or ``label=[<key>=<value>]``.
- ``label=[<key>]``, ``label=[<key>=<value>]`` or a list of
such.
- ``type=["custom"|"builtin"]`` Filters networks by type.
Returns:
@ -74,7 +74,7 @@ class NetworkApiMixin(object):
Example:
A network using the bridge driver:
>>> client.create_network("network1", driver="bridge")
>>> client.api.create_network("network1", driver="bridge")
You can also create more advanced networks with custom IPAM
configurations. For example, setting the subnet to
@ -89,7 +89,7 @@ class NetworkApiMixin(object):
>>> ipam_config = docker.types.IPAMConfig(
pool_configs=[ipam_pool]
)
>>> docker_client.create_network("network1", driver="bridge",
>>> client.api.create_network("network1", driver="bridge",
ipam=ipam_config)
"""
if options is not None and not isinstance(options, dict):
@ -215,7 +215,8 @@ class NetworkApiMixin(object):
def connect_container_to_network(self, container, net_id,
ipv4_address=None, ipv6_address=None,
aliases=None, links=None,
link_local_ips=None):
link_local_ips=None, driver_opt=None,
mac_address=None):
"""
Connect a container to a network.
@ -234,12 +235,16 @@ class NetworkApiMixin(object):
network, using the IPv6 protocol. Defaults to ``None``.
link_local_ips (:py:class:`list`): A list of link-local
(IPv4/IPv6) addresses.
mac_address (str): The MAC address of this container on the
network. Defaults to ``None``.
"""
data = {
"Container": container,
"EndpointConfig": self.create_endpoint_config(
aliases=aliases, links=links, ipv4_address=ipv4_address,
ipv6_address=ipv6_address, link_local_ips=link_local_ips
ipv6_address=ipv6_address, link_local_ips=link_local_ips,
driver_opt=driver_opt,
mac_address=mac_address
),
}

View File

@ -1,9 +1,7 @@
import six
from .. import auth, utils
class PluginApiMixin(object):
class PluginApiMixin:
@utils.minimum_version('1.25')
@utils.check_resource('name')
def configure_plugin(self, name, options):
@ -21,7 +19,7 @@ class PluginApiMixin(object):
url = self._url('/plugins/{0}/set', name)
data = options
if isinstance(data, dict):
data = ['{0}={1}'.format(k, v) for k, v in six.iteritems(data)]
data = [f'{k}={v}' for k, v in data.items()]
res = self._post_json(url, data=data)
self._raise_for_status(res)
return True
@ -53,19 +51,20 @@ class PluginApiMixin(object):
return True
@utils.minimum_version('1.25')
def disable_plugin(self, name):
def disable_plugin(self, name, force=False):
"""
Disable an installed plugin.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
force (bool): To enable the force query parameter.
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}/disable', name)
res = self._post(url)
res = self._post(url, params={'force': force})
self._raise_for_status(res)
return True

View File

@ -1,12 +1,9 @@
import base64
import six
from .. import errors
from .. import utils
from .. import errors, utils
class SecretApiMixin(object):
class SecretApiMixin:
@utils.minimum_version('1.25')
def create_secret(self, name, data, labels=None, driver=None):
"""
@ -25,7 +22,6 @@ class SecretApiMixin(object):
data = data.encode('utf-8')
data = base64.b64encode(data)
if six.PY3:
data = data.decode('ascii')
body = {
'Data': data,
@ -53,7 +49,7 @@ class SecretApiMixin(object):
Retrieve secret metadata
Args:
id (string): Full ID of the secret to remove
id (string): Full ID of the secret to inspect
Returns (dict): A dictionary of metadata

View File

@ -7,9 +7,7 @@ def _check_api_features(version, task_template, update_config, endpoint_spec,
def raise_version_error(param, min_version):
raise errors.InvalidVersion(
'{} is not supported in API version < {}'.format(
param, min_version
)
f'{param} is not supported in API version < {min_version}'
)
if update_config is not None:
@ -113,7 +111,7 @@ def _merge_task_template(current, override):
return merged
class ServiceApiMixin(object):
class ServiceApiMixin:
@utils.minimum_version('1.24')
def create_service(
self, task_template, name=None, labels=None, mode=None,
@ -135,8 +133,9 @@ class ServiceApiMixin(object):
of the service. Default: ``None``
rollback_config (RollbackConfig): Specification for the rollback
strategy of the service. Default: ``None``
networks (:py:class:`list`): List of network names or IDs to attach
the service to. Default: ``None``.
networks (:py:class:`list`): List of network names or IDs or
:py:class:`~docker.types.NetworkAttachmentConfig` to attach the
service to. Default: ``None``.
endpoint_spec (EndpointSpec): Properties that can be configured to
access and load balance a service. Default: ``None``.
@ -261,7 +260,7 @@ class ServiceApiMixin(object):
return True
@utils.minimum_version('1.24')
def services(self, filters=None):
def services(self, filters=None, status=None):
"""
List services.
@ -269,6 +268,8 @@ class ServiceApiMixin(object):
filters (dict): Filters to process on the nodes list. Valid
filters: ``id``, ``name`` , ``label`` and ``mode``.
Default: ``None``.
status (bool): Include the service task count of running and
desired tasks. Default: ``None``.
Returns:
A list of dictionaries containing data about each service.
@ -280,6 +281,12 @@ class ServiceApiMixin(object):
params = {
'filters': utils.convert_filters(filters) if filters else None
}
if status is not None:
if utils.version_lt(self._version, '1.41'):
raise errors.InvalidVersion(
'status is not supported in API version < 1.41'
)
params['status'] = status
url = self._url('/services')
return self._result(self._get(url, params=params), True)
@ -383,8 +390,9 @@ class ServiceApiMixin(object):
of the service. Default: ``None``.
rollback_config (RollbackConfig): Specification for the rollback
strategy of the service. Default: ``None``
networks (:py:class:`list`): List of network names or IDs to attach
the service to. Default: ``None``.
networks (:py:class:`list`): List of network names or IDs or
:py:class:`~docker.types.NetworkAttachmentConfig` to attach the
service to. Default: ``None``.
endpoint_spec (EndpointSpec): Properties that can be configured to
access and load balance a service. Default: ``None``.
fetch_current_spec (boolean): Use the undefined settings from the

View File

@ -1,14 +1,13 @@
import http.client as http_client
import logging
from six.moves import http_client
from .. import errors, types, utils
from ..constants import DEFAULT_SWARM_ADDR_POOL, DEFAULT_SWARM_SUBNET_SIZE
from .. import errors
from .. import types
from .. import utils
log = logging.getLogger(__name__)
class SwarmApiMixin(object):
class SwarmApiMixin:
def create_swarm_spec(self, *args, **kwargs):
"""
@ -58,10 +57,10 @@ class SwarmApiMixin(object):
Example:
>>> spec = client.create_swarm_spec(
>>> spec = client.api.create_swarm_spec(
snapshot_interval=5000, log_entries_for_slow_followers=1200
)
>>> client.init_swarm(
>>> client.api.init_swarm(
advertise_addr='eth0', listen_addr='0.0.0.0:5000',
force_new_cluster=False, swarm_spec=spec
)
@ -85,7 +84,7 @@ class SwarmApiMixin(object):
def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
force_new_cluster=False, swarm_spec=None,
default_addr_pool=None, subnet_size=None,
data_path_addr=None):
data_path_addr=None, data_path_port=None):
"""
Initialize a new Swarm using the current connected engine as the first
node.
@ -118,6 +117,9 @@ class SwarmApiMixin(object):
networks created from the default subnet pool. Default: None
data_path_addr (string): Address or interface to use for data path
traffic. For example, 192.168.1.1, or an interface, like eth0.
data_path_port (int): Port number to use for data path traffic.
Acceptable port range is 1024 to 49151. If set to ``None`` or
0, the default port 4789 will be used. Default: None
Returns:
(str): The ID of the created node.
@ -166,6 +168,14 @@ class SwarmApiMixin(object):
)
data['DataPathAddr'] = data_path_addr
if data_path_port is not None:
if utils.version_lt(self._version, '1.40'):
raise errors.InvalidVersion(
'Data path port is only available for '
'API version >= 1.40'
)
data['DataPathPort'] = data_path_port
response = self._post_json(url, data=data)
return self._result(response, json=True)
@ -354,8 +364,8 @@ class SwarmApiMixin(object):
Example:
>>> key = client.get_unlock_key()
>>> client.unlock_node(key)
>>> key = client.api.get_unlock_key()
>>> client.unlock_swarm(key)
"""
if isinstance(key, dict):
@ -396,7 +406,7 @@ class SwarmApiMixin(object):
'Role': 'manager',
'Labels': {'foo': 'bar'}
}
>>> client.update_node(node_id='24ifsmvkjbyhk', version=8,
>>> client.api.update_node(node_id='24ifsmvkjbyhk', version=8,
node_spec=node_spec)
"""

View File

@ -1,8 +1,7 @@
from .. import errors
from .. import utils
from .. import errors, utils
class VolumeApiMixin(object):
class VolumeApiMixin:
def volumes(self, filters=None):
"""
List volumes currently registered by the docker daemon. Similar to the
@ -21,7 +20,7 @@ class VolumeApiMixin(object):
Example:
>>> cli.volumes()
>>> client.api.volumes()
{u'Volumes': [{u'Driver': u'local',
u'Mountpoint': u'/var/lib/docker/volumes/foobar/_data',
u'Name': u'foobar'},
@ -56,10 +55,13 @@ class VolumeApiMixin(object):
Example:
>>> volume = cli.create_volume(name='foobar', driver='local',
driver_opts={'foo': 'bar', 'baz': 'false'},
labels={"key": "value"})
>>> print(volume)
>>> volume = client.api.create_volume(
... name='foobar',
... driver='local',
... driver_opts={'foo': 'bar', 'baz': 'false'},
... labels={"key": "value"},
... )
... print(volume)
{u'Driver': u'local',
u'Labels': {u'key': u'value'},
u'Mountpoint': u'/var/lib/docker/volumes/foobar/_data',
@ -104,7 +106,7 @@ class VolumeApiMixin(object):
Example:
>>> cli.inspect_volume('foobar')
>>> client.api.inspect_volume('foobar')
{u'Driver': u'local',
u'Mountpoint': u'/var/lib/docker/volumes/foobar/_data',
u'Name': u'foobar'}

View File

@ -2,14 +2,11 @@ import base64
import json
import logging
import six
from . import credentials
from . import errors
from . import credentials, errors
from .utils import config
INDEX_NAME = 'docker.io'
INDEX_URL = 'https://index.{0}/v1/'.format(INDEX_NAME)
INDEX_URL = f'https://index.{INDEX_NAME}/v1/'
TOKEN_USERNAME = '<token>'
log = logging.getLogger(__name__)
@ -18,21 +15,21 @@ log = logging.getLogger(__name__)
def resolve_repository_name(repo_name):
if '://' in repo_name:
raise errors.InvalidRepository(
'Repository name cannot contain a scheme ({0})'.format(repo_name)
f'Repository name cannot contain a scheme ({repo_name})'
)
index_name, remote_name = split_repo_name(repo_name)
if index_name[0] == '-' or index_name[-1] == '-':
raise errors.InvalidRepository(
'Invalid index name ({0}). Cannot begin or end with a'
' hyphen.'.format(index_name)
f'Invalid index name ({index_name}). '
'Cannot begin or end with a hyphen.'
)
return resolve_index_name(index_name), remote_name
def resolve_index_name(index_name):
index_name = convert_to_hostname(index_name)
if index_name == 'index.' + INDEX_NAME:
if index_name == f"index.{INDEX_NAME}":
index_name = INDEX_NAME
return index_name
@ -98,12 +95,10 @@ class AuthConfig(dict):
"""
conf = {}
for registry, entry in six.iteritems(entries):
for registry, entry in entries.items():
if not isinstance(entry, dict):
log.debug(
'Config entry for key {0} is not auth config'.format(
registry
)
f'Config entry for key {registry} is not auth config'
)
# We sometimes fall back to parsing the whole config as if it
# was the auth config by itself, for legacy purposes. In that
@ -111,17 +106,11 @@ class AuthConfig(dict):
# keys is not formatted properly.
if raise_on_error:
raise errors.InvalidConfigFile(
'Invalid configuration for registry {0}'.format(
registry
)
f'Invalid configuration for registry {registry}'
)
return {}
if 'identitytoken' in entry:
log.debug(
'Found an IdentityToken entry for registry {0}'.format(
registry
)
)
log.debug(f'Found an IdentityToken entry for registry {registry}')
conf[registry] = {
'IdentityToken': entry['identitytoken']
}
@ -132,16 +121,15 @@ class AuthConfig(dict):
# a valid value in the auths config.
# https://github.com/docker/compose/issues/3265
log.debug(
'Auth data for {0} is absent. Client might be using a '
'credentials store instead.'.format(registry)
f'Auth data for {registry} is absent. '
f'Client might be using a credentials store instead.'
)
conf[registry] = {}
continue
username, password = decode_auth(entry['auth'])
log.debug(
'Found entry (registry={0}, username={1})'
.format(repr(registry), repr(username))
f'Found entry (registry={registry!r}, username={username!r})'
)
conf[registry] = {
@ -170,7 +158,7 @@ class AuthConfig(dict):
try:
with open(config_file) as f:
config_dict = json.load(f)
except (IOError, KeyError, ValueError) as e:
except (OSError, KeyError, ValueError) as e:
# Likely missing new Docker config file or it's in an
# unknown format, continue to attempt to read old location
# and format.
@ -230,7 +218,7 @@ class AuthConfig(dict):
store_name = self.get_credential_store(registry)
if store_name is not None:
log.debug(
'Using credentials store "{0}"'.format(store_name)
f'Using credentials store "{store_name}"'
)
cfg = self._resolve_authconfig_credstore(registry, store_name)
if cfg is not None:
@ -239,15 +227,15 @@ class AuthConfig(dict):
# Default to the public index server
registry = resolve_index_name(registry) if registry else INDEX_NAME
log.debug("Looking for auth entry for {0}".format(repr(registry)))
log.debug(f"Looking for auth entry for {repr(registry)}")
if registry in self.auths:
log.debug("Found {0}".format(repr(registry)))
log.debug(f"Found {repr(registry)}")
return self.auths[registry]
for key, conf in six.iteritems(self.auths):
for key, conf in self.auths.items():
if resolve_index_name(key) == registry:
log.debug("Found {0}".format(repr(key)))
log.debug(f"Found {repr(key)}")
return conf
log.debug("No entry found")
@ -258,7 +246,7 @@ class AuthConfig(dict):
# The ecosystem is a little schizophrenic with index.docker.io VS
# docker.io - in that case, it seems the full URL is necessary.
registry = INDEX_URL
log.debug("Looking for auth entry for {0}".format(repr(registry)))
log.debug(f"Looking for auth entry for {repr(registry)}")
store = self._get_store_instance(credstore_name)
try:
data = store.get(registry)
@ -278,8 +266,8 @@ class AuthConfig(dict):
return None
except credentials.StoreError as e:
raise errors.DockerException(
'Credentials store error: {0}'.format(repr(e))
)
f'Credentials store error: {repr(e)}'
) from e
def _get_store_instance(self, name):
if name not in self._stores:
@ -303,12 +291,14 @@ class AuthConfig(dict):
auth_data[k] = self._resolve_authconfig_credstore(
k, self.creds_store
)
auth_data[convert_to_hostname(k)] = auth_data[k]
# credHelpers entries take priority over all others
for reg, store_name in self.cred_helpers.items():
auth_data[reg] = self._resolve_authconfig_credstore(
reg, store_name
)
auth_data[convert_to_hostname(reg)] = auth_data[reg]
return auth_data
@ -327,7 +317,7 @@ def convert_to_hostname(url):
def decode_auth(auth):
if isinstance(auth, six.string_types):
if isinstance(auth, str):
auth = auth.encode('ascii')
s = base64.b64decode(auth)
login, pwd = s.split(b':', 1)
@ -383,7 +373,6 @@ def _load_legacy_config(config_file):
}}
except Exception as e:
log.debug(e)
pass
log.debug("All parsing attempts failed - returning empty config")
return {}

View File

@ -1,5 +1,5 @@
from .api.client import APIClient
from .constants import DEFAULT_TIMEOUT_SECONDS
from .constants import DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS
from .models.configs import ConfigCollection
from .models.containers import ContainerCollection
from .models.images import ImageCollection
@ -13,7 +13,7 @@ from .models.volumes import VolumeCollection
from .utils import kwargs_from_env
class DockerClient(object):
class DockerClient:
"""
A client for communicating with a Docker server.
@ -35,6 +35,11 @@ class DockerClient(object):
user_agent (str): Set a custom user agent for requests to the server.
credstore_env (dict): Override environment variables when calling the
credential store process.
use_ssh_client (bool): If set to `True`, an ssh connection is made
via shelling out to the ssh client. Ensure the ssh client is
installed and configured on the host.
max_pool_size (int): The maximum number of connections
to save in the pool.
"""
def __init__(self, *args, **kwargs):
self.api = APIClient(*args, **kwargs)
@ -62,14 +67,17 @@ class DockerClient(object):
Args:
version (str): The version of the API to use. Set to ``auto`` to
automatically detect the server's version. Default: ``1.35``
automatically detect the server's version. Default: ``auto``
timeout (int): Default timeout for API calls, in seconds.
ssl_version (int): A valid `SSL version`_.
assert_hostname (bool): Verify the hostname of the server.
max_pool_size (int): The maximum number of connections
to save in the pool.
environment (dict): The environment to read environment variables
from. Default: the value of ``os.environ``
credstore_env (dict): Override environment variables when calling
the credential store process.
use_ssh_client (bool): If set to `True`, an ssh connection is
made via shelling out to the ssh client. Ensure the ssh
client is installed and configured on the host.
Example:
@ -80,9 +88,15 @@ class DockerClient(object):
https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_TLSv1
"""
timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE)
version = kwargs.pop('version', None)
use_ssh_client = kwargs.pop('use_ssh_client', False)
return cls(
timeout=timeout, version=version, **kwargs_from_env(**kwargs)
timeout=timeout,
max_pool_size=max_pool_size,
version=version,
use_ssh_client=use_ssh_client,
**kwargs_from_env(**kwargs)
)
# Resources
@ -196,7 +210,7 @@ class DockerClient(object):
close.__doc__ = APIClient.close.__doc__
def __getattr__(self, name):
s = ["'DockerClient' object has no attribute '{}'".format(name)]
s = [f"'DockerClient' object has no attribute '{name}'"]
# If a user calls a method on APIClient, they
if hasattr(APIClient, name):
s.append("In Docker SDK for Python 2.0, this method is now on the "

View File

@ -1,14 +1,27 @@
import sys
from .version import version
DEFAULT_DOCKER_API_VERSION = '1.35'
MINIMUM_DOCKER_API_VERSION = '1.21'
from .version import __version__
DEFAULT_DOCKER_API_VERSION = '1.45'
MINIMUM_DOCKER_API_VERSION = '1.24'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8
CONTAINER_LIMITS_KEYS = [
'memory', 'memswap', 'cpushares', 'cpusetcpus'
]
DEFAULT_HTTP_HOST = "127.0.0.1"
DEFAULT_UNIX_SOCKET = "http+unix:///var/run/docker.sock"
DEFAULT_NPIPE = 'npipe:////./pipe/docker_engine'
BYTE_UNITS = {
'b': 1,
'k': 1024,
'm': 1024 * 1024,
'g': 1024 * 1024 * 1024
}
INSECURE_REGISTRY_DEPRECATION_WARNING = \
'The `insecure_registry` argument to {} ' \
'is deprecated and non-functional. Please remove it.'
@ -16,7 +29,7 @@ INSECURE_REGISTRY_DEPRECATION_WARNING = \
IS_WINDOWS_PLATFORM = (sys.platform == 'win32')
WINDOWS_LONGPATH_PREFIX = '\\\\?\\'
DEFAULT_USER_AGENT = "docker-sdk-python/{0}".format(version)
DEFAULT_USER_AGENT = f"docker-sdk-python/{__version__}"
DEFAULT_NUM_POOLS = 25
# The OpenSSH server default value for MaxSessions is 10 which means we can
@ -24,6 +37,8 @@ DEFAULT_NUM_POOLS = 25
# For more details see: https://github.com/docker/docker-py/issues/2246
DEFAULT_NUM_POOLS_SSH = 9
DEFAULT_MAX_POOL_SIZE = 10
DEFAULT_DATA_CHUNK_SIZE = 1024 * 2048
DEFAULT_SWARM_ADDR_POOL = ['10.0.0.0/8']

View File

@ -0,0 +1,2 @@
from .api import ContextAPI
from .context import Context

206
docker/context/api.py Normal file
View File

@ -0,0 +1,206 @@
import json
import os
from docker import errors
from .config import (
METAFILE,
get_current_context_name,
get_meta_dir,
write_context_name_to_docker_config,
)
from .context import Context
class ContextAPI:
"""Context API.
Contains methods for context management:
create, list, remove, get, inspect.
"""
DEFAULT_CONTEXT = Context("default", "swarm")
@classmethod
def create_context(
cls, name, orchestrator=None, host=None, tls_cfg=None,
default_namespace=None, skip_tls_verify=False):
"""Creates a new context.
Returns:
(Context): a Context object.
Raises:
:py:class:`docker.errors.MissingContextParameter`
If a context name is not provided.
:py:class:`docker.errors.ContextAlreadyExists`
If a context with the name already exists.
:py:class:`docker.errors.ContextException`
If name is default.
Example:
>>> from docker.context import ContextAPI
>>> ctx = ContextAPI.create_context(name='test')
>>> print(ctx.Metadata)
{
"Name": "test",
"Metadata": {},
"Endpoints": {
"docker": {
"Host": "unix:///var/run/docker.sock",
"SkipTLSVerify": false
}
}
}
"""
if not name:
raise errors.MissingContextParameter("name")
if name == "default":
raise errors.ContextException(
'"default" is a reserved context name')
ctx = Context.load_context(name)
if ctx:
raise errors.ContextAlreadyExists(name)
endpoint = "docker"
if orchestrator and orchestrator != "swarm":
endpoint = orchestrator
ctx = Context(name, orchestrator)
ctx.set_endpoint(
endpoint, host, tls_cfg,
skip_tls_verify=skip_tls_verify,
def_namespace=default_namespace)
ctx.save()
return ctx
@classmethod
def get_context(cls, name=None):
"""Retrieves a context object.
Args:
name (str): The name of the context
Example:
>>> from docker.context import ContextAPI
>>> ctx = ContextAPI.get_context(name='test')
>>> print(ctx.Metadata)
{
"Name": "test",
"Metadata": {},
"Endpoints": {
"docker": {
"Host": "unix:///var/run/docker.sock",
"SkipTLSVerify": false
}
}
}
"""
if not name:
name = get_current_context_name()
if name == "default":
return cls.DEFAULT_CONTEXT
return Context.load_context(name)
@classmethod
def contexts(cls):
"""Context list.
Returns:
(Context): List of context objects.
Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
names = []
for dirname, dirnames, fnames in os.walk(get_meta_dir()):
for filename in fnames + dirnames:
if filename == METAFILE:
try:
data = json.load(
open(os.path.join(dirname, filename)))
names.append(data["Name"])
except Exception as e:
raise errors.ContextException(
f"Failed to load metafile {filename}: {e}",
) from e
contexts = [cls.DEFAULT_CONTEXT]
for name in names:
contexts.append(Context.load_context(name))
return contexts
@classmethod
def get_current_context(cls):
"""Get current context.
Returns:
(Context): current context object.
"""
return cls.get_context()
@classmethod
def set_current_context(cls, name="default"):
ctx = cls.get_context(name)
if not ctx:
raise errors.ContextNotFound(name)
err = write_context_name_to_docker_config(name)
if err:
raise errors.ContextException(
f'Failed to set current context: {err}')
@classmethod
def remove_context(cls, name):
"""Remove a context. Similar to the ``docker context rm`` command.
Args:
name (str): The name of the context
Raises:
:py:class:`docker.errors.MissingContextParameter`
If a context name is not provided.
:py:class:`docker.errors.ContextNotFound`
If a context with the name does not exist.
:py:class:`docker.errors.ContextException`
If name is default.
Example:
>>> from docker.context import ContextAPI
>>> ContextAPI.remove_context(name='test')
>>>
"""
if not name:
raise errors.MissingContextParameter("name")
if name == "default":
raise errors.ContextException(
'context "default" cannot be removed')
ctx = Context.load_context(name)
if not ctx:
raise errors.ContextNotFound(name)
if name == get_current_context_name():
write_context_name_to_docker_config(None)
ctx.remove()
@classmethod
def inspect_context(cls, name="default"):
"""Remove a context. Similar to the ``docker context inspect`` command.
Args:
name (str): The name of the context
Raises:
:py:class:`docker.errors.MissingContextParameter`
If a context name is not provided.
:py:class:`docker.errors.ContextNotFound`
If a context with the name does not exist.
Example:
>>> from docker.context import ContextAPI
>>> ContextAPI.remove_context(name='test')
>>>
"""
if not name:
raise errors.MissingContextParameter("name")
if name == "default":
return cls.DEFAULT_CONTEXT()
ctx = Context.load_context(name)
if not ctx:
raise errors.ContextNotFound(name)
return ctx()

81
docker/context/config.py Normal file
View File

@ -0,0 +1,81 @@
import hashlib
import json
import os
from docker import utils
from docker.constants import DEFAULT_UNIX_SOCKET, IS_WINDOWS_PLATFORM
from docker.utils.config import find_config_file
METAFILE = "meta.json"
def get_current_context_name():
name = "default"
docker_cfg_path = find_config_file()
if docker_cfg_path:
try:
with open(docker_cfg_path) as f:
name = json.load(f).get("currentContext", "default")
except Exception:
return "default"
return name
def write_context_name_to_docker_config(name=None):
if name == 'default':
name = None
docker_cfg_path = find_config_file()
config = {}
if docker_cfg_path:
try:
with open(docker_cfg_path) as f:
config = json.load(f)
except Exception as e:
return e
current_context = config.get("currentContext", None)
if current_context and not name:
del config["currentContext"]
elif name:
config["currentContext"] = name
else:
return
try:
with open(docker_cfg_path, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
return e
def get_context_id(name):
return hashlib.sha256(name.encode('utf-8')).hexdigest()
def get_context_dir():
return os.path.join(os.path.dirname(find_config_file() or ""), "contexts")
def get_meta_dir(name=None):
meta_dir = os.path.join(get_context_dir(), "meta")
if name:
return os.path.join(meta_dir, get_context_id(name))
return meta_dir
def get_meta_file(name):
return os.path.join(get_meta_dir(name), METAFILE)
def get_tls_dir(name=None, endpoint=""):
context_dir = get_context_dir()
if name:
return os.path.join(context_dir, "tls", get_context_id(name), endpoint)
return os.path.join(context_dir, "tls")
def get_context_host(path=None, tls=False):
host = utils.parse_host(path, IS_WINDOWS_PLATFORM, tls)
if host == DEFAULT_UNIX_SOCKET:
# remove http+ from default docker socket url
if host.startswith("http+"):
host = host[5:]
return host

249
docker/context/context.py Normal file
View File

@ -0,0 +1,249 @@
import json
import os
from shutil import copyfile, rmtree
from docker.errors import ContextException
from docker.tls import TLSConfig
from .config import (
get_context_host,
get_meta_dir,
get_meta_file,
get_tls_dir,
)
class Context:
"""A context."""
def __init__(self, name, orchestrator=None, host=None, endpoints=None,
tls=False):
if not name:
raise Exception("Name not provided")
self.name = name
self.context_type = None
self.orchestrator = orchestrator
self.endpoints = {}
self.tls_cfg = {}
self.meta_path = "IN MEMORY"
self.tls_path = "IN MEMORY"
if not endpoints:
# set default docker endpoint if no endpoint is set
default_endpoint = "docker" if (
not orchestrator or orchestrator == "swarm"
) else orchestrator
self.endpoints = {
default_endpoint: {
"Host": get_context_host(host, tls),
"SkipTLSVerify": not tls
}
}
return
# check docker endpoints
for k, v in endpoints.items():
if not isinstance(v, dict):
# unknown format
raise ContextException(
f"Unknown endpoint format for context {name}: {v}",
)
self.endpoints[k] = v
if k != "docker":
continue
self.endpoints[k]["Host"] = v.get("Host", get_context_host(
host, tls))
self.endpoints[k]["SkipTLSVerify"] = bool(v.get(
"SkipTLSVerify", not tls))
def set_endpoint(
self, name="docker", host=None, tls_cfg=None,
skip_tls_verify=False, def_namespace=None):
self.endpoints[name] = {
"Host": get_context_host(host, not skip_tls_verify),
"SkipTLSVerify": skip_tls_verify
}
if def_namespace:
self.endpoints[name]["DefaultNamespace"] = def_namespace
if tls_cfg:
self.tls_cfg[name] = tls_cfg
def inspect(self):
return self.__call__()
@classmethod
def load_context(cls, name):
meta = Context._load_meta(name)
if meta:
instance = cls(
meta["Name"],
orchestrator=meta["Metadata"].get("StackOrchestrator", None),
endpoints=meta.get("Endpoints", None))
instance.context_type = meta["Metadata"].get("Type", None)
instance._load_certs()
instance.meta_path = get_meta_dir(name)
return instance
return None
@classmethod
def _load_meta(cls, name):
meta_file = get_meta_file(name)
if not os.path.isfile(meta_file):
return None
metadata = {}
try:
with open(meta_file) as f:
metadata = json.load(f)
except (OSError, KeyError, ValueError) as e:
# unknown format
raise Exception(
f"Detected corrupted meta file for context {name} : {e}"
) from e
# for docker endpoints, set defaults for
# Host and SkipTLSVerify fields
for k, v in metadata["Endpoints"].items():
if k != "docker":
continue
metadata["Endpoints"][k]["Host"] = v.get(
"Host", get_context_host(None, False))
metadata["Endpoints"][k]["SkipTLSVerify"] = bool(
v.get("SkipTLSVerify", True))
return metadata
def _load_certs(self):
certs = {}
tls_dir = get_tls_dir(self.name)
for endpoint in self.endpoints.keys():
if not os.path.isdir(os.path.join(tls_dir, endpoint)):
continue
ca_cert = None
cert = None
key = None
for filename in os.listdir(os.path.join(tls_dir, endpoint)):
if filename.startswith("ca"):
ca_cert = os.path.join(tls_dir, endpoint, filename)
elif filename.startswith("cert"):
cert = os.path.join(tls_dir, endpoint, filename)
elif filename.startswith("key"):
key = os.path.join(tls_dir, endpoint, filename)
if all([ca_cert, cert, key]):
verify = None
if endpoint == "docker" and not self.endpoints["docker"].get(
"SkipTLSVerify", False):
verify = True
certs[endpoint] = TLSConfig(
client_cert=(cert, key), ca_cert=ca_cert, verify=verify)
self.tls_cfg = certs
self.tls_path = tls_dir
def save(self):
meta_dir = get_meta_dir(self.name)
if not os.path.isdir(meta_dir):
os.makedirs(meta_dir)
with open(get_meta_file(self.name), "w") as f:
f.write(json.dumps(self.Metadata))
tls_dir = get_tls_dir(self.name)
for endpoint, tls in self.tls_cfg.items():
if not os.path.isdir(os.path.join(tls_dir, endpoint)):
os.makedirs(os.path.join(tls_dir, endpoint))
ca_file = tls.ca_cert
if ca_file:
copyfile(ca_file, os.path.join(
tls_dir, endpoint, os.path.basename(ca_file)))
if tls.cert:
cert_file, key_file = tls.cert
copyfile(cert_file, os.path.join(
tls_dir, endpoint, os.path.basename(cert_file)))
copyfile(key_file, os.path.join(
tls_dir, endpoint, os.path.basename(key_file)))
self.meta_path = get_meta_dir(self.name)
self.tls_path = get_tls_dir(self.name)
def remove(self):
if os.path.isdir(self.meta_path):
rmtree(self.meta_path)
if os.path.isdir(self.tls_path):
rmtree(self.tls_path)
def __repr__(self):
return f"<{self.__class__.__name__}: '{self.name}'>"
def __str__(self):
return json.dumps(self.__call__(), indent=2)
def __call__(self):
result = self.Metadata
result.update(self.TLSMaterial)
result.update(self.Storage)
return result
def is_docker_host(self):
return self.context_type is None
@property
def Name(self):
return self.name
@property
def Host(self):
if not self.orchestrator or self.orchestrator == "swarm":
endpoint = self.endpoints.get("docker", None)
if endpoint:
return endpoint.get("Host", None)
return None
return self.endpoints[self.orchestrator].get("Host", None)
@property
def Orchestrator(self):
return self.orchestrator
@property
def Metadata(self):
meta = {}
if self.orchestrator:
meta = {"StackOrchestrator": self.orchestrator}
return {
"Name": self.name,
"Metadata": meta,
"Endpoints": self.endpoints
}
@property
def TLSConfig(self):
key = self.orchestrator
if not key or key == "swarm":
key = "docker"
if key in self.tls_cfg.keys():
return self.tls_cfg[key]
return None
@property
def TLSMaterial(self):
certs = {}
for endpoint, tls in self.tls_cfg.items():
cert, key = tls.cert
certs[endpoint] = list(
map(os.path.basename, [tls.ca_cert, cert, key]))
return {
"TLSMaterial": certs
}
@property
def Storage(self):
return {
"Storage": {
"MetadataPath": self.meta_path,
"TLSPath": self.tls_path
}}

View File

@ -1,4 +1,8 @@
# flake8: noqa
from .constants import (
DEFAULT_LINUX_STORE,
DEFAULT_OSX_STORE,
DEFAULT_WIN32_STORE,
PROGRAM_PREFIX,
)
from .errors import CredentialsNotFound, StoreError
from .store import Store
from .errors import StoreError, CredentialsNotFound
from .constants import *

View File

@ -13,13 +13,5 @@ class InitializationError(StoreError):
def process_store_error(cpe, program):
message = cpe.output.decode('utf-8')
if 'credentials not found in native keychain' in message:
return CredentialsNotFound(
'No matching credentials in {}'.format(
program
)
)
return StoreError(
'Credentials store {} exited with "{}".'.format(
program, cpe.output.decode('utf-8').strip()
)
)
return CredentialsNotFound(f'No matching credentials in {program}')
return StoreError(f'Credentials store {program} exited with "{message}".')

View File

@ -1,36 +1,33 @@
import errno
import json
import os
import shutil
import subprocess
import warnings
import six
from . import constants
from . import errors
from . import constants, errors
from .utils import create_environment_dict
from .utils import find_executable
class Store(object):
class Store:
def __init__(self, program, environment=None):
""" Create a store object that acts as an interface to
perform the basic operations for storing, retrieving
and erasing credentials using `program`.
"""
self.program = constants.PROGRAM_PREFIX + program
self.exe = find_executable(self.program)
self.exe = shutil.which(self.program)
self.environment = environment
if self.exe is None:
raise errors.InitializationError(
'{} not installed or not available in PATH'.format(
self.program
)
warnings.warn(
f'{self.program} not installed or not available in PATH',
stacklevel=1,
)
def get(self, server):
""" Retrieve credentials for `server`. If no credentials are found,
a `StoreError` will be raised.
"""
if not isinstance(server, six.binary_type):
if not isinstance(server, bytes):
server = server.encode('utf-8')
data = self._execute('get', server)
result = json.loads(data.decode('utf-8'))
@ -41,7 +38,7 @@ class Store(object):
# raise CredentialsNotFound
if result['Username'] == '' and result['Secret'] == '':
raise errors.CredentialsNotFound(
'No matching credentials in {}'.format(self.program)
f'No matching credentials in {self.program}'
)
return result
@ -61,7 +58,7 @@ class Store(object):
""" Erase credentials for `server`. Raises a `StoreError` if an error
occurs.
"""
if not isinstance(server, six.binary_type):
if not isinstance(server, bytes):
server = server.encode('utf-8')
self._execute('erase', server)
@ -72,36 +69,25 @@ class Store(object):
return json.loads(data.decode('utf-8'))
def _execute(self, subcmd, data_input):
if self.exe is None:
raise errors.StoreError(
f'{self.program} not installed or not available in PATH'
)
output = None
env = create_environment_dict(self.environment)
try:
if six.PY3:
output = subprocess.check_output(
[self.exe, subcmd], input=data_input, env=env,
)
else:
process = subprocess.Popen(
[self.exe, subcmd], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, env=env,
)
output, err = process.communicate(data_input)
if process.returncode != 0:
raise subprocess.CalledProcessError(
returncode=process.returncode, cmd='', output=output
)
except subprocess.CalledProcessError as e:
raise errors.process_store_error(e, self.program)
raise errors.process_store_error(e, self.program) from e
except OSError as e:
if e.errno == os.errno.ENOENT:
if e.errno == errno.ENOENT:
raise errors.StoreError(
'{} not installed or not available in PATH'.format(
self.program
)
)
f'{self.program} not installed or not available in PATH'
) from e
else:
raise errors.StoreError(
'Unexpected OS error "{}", errno={}'.format(
e.strerror, e.errno
)
)
f'Unexpected OS error "{e.strerror}", errno={e.errno}'
) from e
return output

View File

@ -1,32 +1,4 @@
import distutils.spawn
import os
import sys
def find_executable(executable, path=None):
"""
As distutils.spawn.find_executable, but on Windows, look up
every extension declared in PATHEXT instead of just `.exe`
"""
if sys.platform != 'win32':
return distutils.spawn.find_executable(executable, path)
if path is None:
path = os.environ['PATH']
paths = path.split(os.pathsep)
extensions = os.environ.get('PATHEXT', '.exe').split(os.pathsep)
base, ext = os.path.splitext(executable)
if not os.path.isfile(executable):
for p in paths:
for ext in extensions:
f = os.path.join(p, base + ext)
if os.path.isfile(f):
return f
return None
else:
return executable
def create_environment_dict(overrides):

View File

@ -1,5 +1,14 @@
import requests
_image_not_found_explanation_fragments = frozenset(
fragment.lower() for fragment in [
'no such image',
'not found: does not exist or no pull access',
'repository does not exist',
'was found but does not match the specified platform',
]
)
class DockerException(Exception):
"""
@ -18,17 +27,16 @@ def create_api_error_from_http_exception(e):
try:
explanation = response.json()['message']
except ValueError:
explanation = (response.content or '').strip()
explanation = (response.text or '').strip()
cls = APIError
if response.status_code == 404:
if explanation and ('No such image' in str(explanation) or
'not found: does not exist or no pull access'
in str(explanation) or
'repository does not exist' in str(explanation)):
explanation_msg = (explanation or '').lower()
if any(fragment in explanation_msg
for fragment in _image_not_found_explanation_fragments):
cls = ImageNotFound
else:
cls = NotFound
raise cls(e, response=response, explanation=explanation)
raise cls(e, response=response, explanation=explanation) from e
class APIError(requests.exceptions.HTTPError, DockerException):
@ -38,23 +46,27 @@ class APIError(requests.exceptions.HTTPError, DockerException):
def __init__(self, message, response=None, explanation=None):
# requests 1.2 supports response as a keyword argument, but
# requests 1.1 doesn't
super(APIError, self).__init__(message)
super().__init__(message)
self.response = response
self.explanation = explanation
def __str__(self):
message = super(APIError, self).__str__()
message = super().__str__()
if self.is_client_error():
message = '{0} Client Error: {1}'.format(
self.response.status_code, self.response.reason)
message = (
f'{self.response.status_code} Client Error for '
f'{self.response.url}: {self.response.reason}'
)
elif self.is_server_error():
message = '{0} Server Error: {1}'.format(
self.response.status_code, self.response.reason)
message = (
f'{self.response.status_code} Server Error for '
f'{self.response.url}: {self.response.reason}'
)
if self.explanation:
message = '{0} ("{1}")'.format(message, self.explanation)
message = f'{message} ("{self.explanation}")'
return message
@ -131,11 +143,11 @@ class ContainerError(DockerException):
self.image = image
self.stderr = stderr
err = ": {}".format(stderr) if stderr is not None else ""
msg = ("Command '{}' in image '{}' returned non-zero exit "
"status {}{}").format(command, image, exit_status, err)
super(ContainerError, self).__init__(msg)
err = f": {stderr}" if stderr is not None else ""
super().__init__(
f"Command '{command}' in image '{image}' "
f"returned non-zero exit status {exit_status}{err}"
)
class StreamParseError(RuntimeError):
@ -145,7 +157,7 @@ class StreamParseError(RuntimeError):
class BuildError(DockerException):
def __init__(self, reason, build_log):
super(BuildError, self).__init__(reason)
super().__init__(reason)
self.msg = reason
self.build_log = build_log
@ -155,11 +167,43 @@ class ImageLoadError(DockerException):
def create_unexpected_kwargs_error(name, kwargs):
quoted_kwargs = ["'{}'".format(k) for k in sorted(kwargs)]
text = ["{}() ".format(name)]
quoted_kwargs = [f"'{k}'" for k in sorted(kwargs)]
text = [f"{name}() "]
if len(quoted_kwargs) == 1:
text.append("got an unexpected keyword argument ")
else:
text.append("got unexpected keyword arguments ")
text.append(', '.join(quoted_kwargs))
return TypeError(''.join(text))
class MissingContextParameter(DockerException):
def __init__(self, param):
self.param = param
def __str__(self):
return (f"missing parameter: {self.param}")
class ContextAlreadyExists(DockerException):
def __init__(self, name):
self.name = name
def __str__(self):
return (f"context {self.name} already exists")
class ContextException(DockerException):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return (self.msg)
class ContextNotFound(DockerException):
def __init__(self, name):
self.name = name
def __str__(self):
return (f"context '{self.name}' not found")

View File

@ -1,5 +1,5 @@
from ..api import APIClient
from .resource import Model, Collection
from .resource import Collection, Model
class Config(Model):
@ -7,7 +7,7 @@ class Config(Model):
id_attribute = 'ID'
def __repr__(self):
return "<%s: '%s'>" % (self.__class__.__name__, self.name)
return f"<{self.__class__.__name__}: '{self.name}'>"
@property
def name(self):
@ -30,6 +30,7 @@ class ConfigCollection(Collection):
def create(self, **kwargs):
obj = self.client.api.create_config(**kwargs)
obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
return self.prepare_model(obj)
create.__doc__ = APIClient.create_config.__doc__

View File

@ -5,10 +5,13 @@ from collections import namedtuple
from ..api import APIClient
from ..constants import DEFAULT_DATA_CHUNK_SIZE
from ..errors import (
ContainerError, DockerException, ImageNotFound,
NotFound, create_unexpected_kwargs_error
ContainerError,
DockerException,
ImageNotFound,
NotFound,
create_unexpected_kwargs_error,
)
from ..types import HostConfig
from ..types import HostConfig, NetworkingConfig
from ..utils import version_gte
from .images import Image
from .resource import Collection, Model
@ -21,6 +24,7 @@ class Container(Model):
query the Docker daemon for the current properties, causing
:py:attr:`attrs` to be refreshed.
"""
@property
def name(self):
"""
@ -47,11 +51,11 @@ class Container(Model):
try:
result = self.attrs['Config'].get('Labels')
return result or {}
except KeyError:
except KeyError as ke:
raise DockerException(
'Label data is not available for sparse objects. Call reload()'
' to retrieve all information'
)
) from ke
@property
def status(self):
@ -62,6 +66,15 @@ class Container(Model):
return self.attrs['State']['Status']
return self.attrs['State']
@property
def health(self):
"""
The healthcheck status of the container.
For example, ``healthy`, or ``unhealthy`.
"""
return self.attrs.get('State', {}).get('Health', {}).get('Status', 'unknown')
@property
def ports(self):
"""
@ -121,6 +134,7 @@ class Container(Model):
tag (str): The tag to push
message (str): A commit message
author (str): The name of the author
pause (bool): Whether to pause the container before committing
changes (str): Dockerfile instructions to apply while committing
conf (dict): The configuration for the container. See the
`Engine API documentation
@ -141,7 +155,8 @@ class Container(Model):
Inspect changes on a container's filesystem.
Returns:
(str)
(list) A list of dictionaries containing the attributes `Path`
and `Kind`.
Raises:
:py:class:`docker.errors.APIError`
@ -166,7 +181,8 @@ class Container(Model):
user (str): User to execute command as. Default: root
detach (bool): If true, detach from the exec command.
Default: False
stream (bool): Stream response data. Default: False
stream (bool): Stream response data. Ignored if ``detach`` is true.
Default: False
socket (bool): Return the connection socket to allow custom
read/write operations. Default: False
environment (dict or list): A dictionary or a list of strings in
@ -225,7 +241,8 @@ class Container(Model):
"""
return self.client.api.export(self.id, chunk_size)
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
encode_stream=False):
"""
Retrieve a file or folder from the container in the form of a tar
archive.
@ -235,6 +252,8 @@ class Container(Model):
chunk_size (int): The number of bytes returned by each iteration
of the generator. If ``None``, data will be streamed as it is
received. Default: 2 MB
encode_stream (bool): Determines if data should be encoded
(gzip-compressed) during transmission. Default: False
Returns:
(tuple): First element is a raw tar data stream. Second element is
@ -255,7 +274,8 @@ class Container(Model):
... f.write(chunk)
>>> f.close()
"""
return self.client.api.get_archive(self.id, path, chunk_size)
return self.client.api.get_archive(self.id, path,
chunk_size, encode_stream)
def kill(self, signal=None):
"""
@ -286,14 +306,15 @@ class Container(Model):
tail (str or int): Output specified number of lines at the end of
logs. Either an integer of number of lines or the string
``all``. Default ``all``
since (datetime or int): Show logs since a given datetime or
integer epoch (in seconds)
since (datetime, int, or float): Show logs since a given datetime,
integer epoch (in seconds) or float (in nanoseconds)
follow (bool): Follow log output. Default ``False``
until (datetime or int): Show logs that occurred before the given
datetime or integer epoch (in seconds)
until (datetime, int, or float): Show logs that occurred before
the given datetime, integer epoch (in seconds), or
float (in nanoseconds)
Returns:
(generator or str): Logs from the container.
(generator of bytes or bytes): Logs from the container.
Raises:
:py:class:`docker.errors.APIError`
@ -319,7 +340,7 @@ class Container(Model):
Args:
path (str): Path inside the container where the file(s) will be
extracted. Must exist.
data (bytes): tar data to be extracted
data (bytes or stream): tar data to be extracted
Returns:
(bool): True if the call succeeds.
@ -549,6 +570,11 @@ class ContainerCollection(Collection):
``["SYS_ADMIN", "MKNOD"]``.
cap_drop (list of str): Drop kernel capabilities.
cgroup_parent (str): Override the default parent cgroup.
cgroupns (str): Override the default cgroup namespace mode for the
container. One of:
- ``private`` the container runs in its own private cgroup
namespace.
- ``host`` use the host system's cgroup namespace.
cpu_count (int): Number of usable CPUs (Windows only).
cpu_percent (int): Usable percentage of the available CPUs
(Windows only).
@ -579,6 +605,9 @@ class ContainerCollection(Collection):
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
dns (:py:class:`list`): Set custom DNS servers.
dns_opt (:py:class:`list`): Additional options to be added to the
container's ``resolv.conf`` file.
@ -593,7 +622,28 @@ class ContainerCollection(Collection):
group_add (:py:class:`list`): List of additional group names and/or
IDs that the container process will run as.
healthcheck (dict): Specify a test to perform to check that the
container is healthy.
container is healthy. The dict takes the following keys:
- test (:py:class:`list` or str): Test to perform to determine
container health. Possible values:
- Empty list: Inherit healthcheck from parent image
- ``["NONE"]``: Disable healthcheck
- ``["CMD", args...]``: exec arguments directly.
- ``["CMD-SHELL", command]``: Run command in the system's
default shell.
If a string is provided, it will be used as a ``CMD-SHELL``
command.
- interval (int): The time to wait between checks in
nanoseconds. It should be 0 or at least 1000000 (1 ms).
- timeout (int): The time to wait before considering the check
to have hung. It should be 0 or at least 1000000 (1 ms).
- retries (int): The number of consecutive failures needed to
consider a container as unhealthy.
- start_period (int): Start period for the container to
initialize before starting health-retries countdown in
nanoseconds. It should be 0 or at least 1000000 (1 ms).
hostname (str): Optional hostname for the container.
init (bool): Run an init inside the container that forwards
signals and reaps processes
@ -618,7 +668,7 @@ class ContainerCollection(Collection):
(``100000b``, ``1000k``, ``128m``, ``1g``). If a string is
specified without a units character, bytes are assumed as an
intended unit.
mem_reservation (int or str): Memory soft limit
mem_reservation (int or str): Memory soft limit.
mem_swappiness (int): Tune a container's memory swappiness
behavior. Accepts number between 0 and 100.
memswap_limit (str or int): Maximum amount of memory + swap a
@ -637,13 +687,22 @@ class ContainerCollection(Collection):
network_mode (str): One of:
- ``bridge`` Create a new network stack for the container on
on the bridge network.
the bridge network.
- ``none`` No networking for this container.
- ``container:<name|id>`` Reuse another container's network
stack.
- ``host`` Use the host network stack.
This mode is incompatible with ``ports``.
Incompatible with ``network``.
networking_config (Dict[str, EndpointConfig]):
Dictionary of EndpointConfig objects for each container network.
The key is the name of the network.
Defaults to ``None``.
Used in conjuction with ``network``.
Incompatible with ``network_mode``.
oom_kill_disable (bool): Whether to disable OOM killer.
oom_score_adj (int): An integer value containing the score given
to the container in order to tune OOM killer preferences.
@ -675,6 +734,7 @@ class ContainerCollection(Collection):
to a single container port. For example,
``{'1111/tcp': [1234, 4567]}``.
Incompatible with ``host`` network mode.
privileged (bool): Give extended privileges to this container.
publish_all_ports (bool): Publish all ports to the host.
read_only (bool): Mount the container's root filesystem as read
@ -752,6 +812,15 @@ class ContainerCollection(Collection):
{'/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'},
'/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'}}
Or a list of strings which each one of its elements specifies a
mount volume.
For example:
.. code-block:: python
['/home/user1/:/mnt/vol2','/var/www:/mnt/vol1']
volumes_from (:py:class:`list`): List of container names or IDs to
get volumes from.
working_dir (str): Path to the working directory.
@ -783,7 +852,7 @@ class ContainerCollection(Collection):
image = image.id
stream = kwargs.pop('stream', False)
detach = kwargs.pop('detach', False)
platform = kwargs.pop('platform', None)
platform = kwargs.get('platform', None)
if detach and remove:
if version_gte(self.client.api._version, '1.25'):
@ -798,6 +867,12 @@ class ContainerCollection(Collection):
'together.'
)
if kwargs.get('networking_config') and not kwargs.get('network'):
raise RuntimeError(
'The option "networking_config" can not be used '
'without "network".'
)
try:
container = self.create(image=image, command=command,
detach=detach, **kwargs)
@ -832,9 +907,9 @@ class ContainerCollection(Collection):
container, exit_status, command, image, out
)
return out if stream or out is None else b''.join(
[line for line in out]
)
if stream or out is None:
return out
return b''.join(out)
def create(self, image, command=None, **kwargs):
"""
@ -900,7 +975,8 @@ class ContainerCollection(Collection):
- `exited` (int): Only containers with specified exit code
- `status` (str): One of ``restarting``, ``running``,
``paused``, ``exited``
- `label` (str): format either ``"key"`` or ``"key=value"``
- `label` (str|list): format either ``"key"``, ``"key=value"``
or a list of such.
- `id` (str): The id of the container.
- `name` (str): The name of the container.
- `ancestor` (str): Filter by container ancestor. Format of
@ -949,6 +1025,7 @@ class ContainerCollection(Collection):
def prune(self, filters=None):
return self.client.api.prune_containers(filters=filters)
prune.__doc__ = APIClient.prune_containers.__doc__
@ -966,6 +1043,7 @@ RUN_CREATE_KWARGS = [
'mac_address',
'name',
'network_disabled',
'platform',
'stdin_open',
'stop_signal',
'tty',
@ -982,6 +1060,7 @@ RUN_HOST_CONFIG_KWARGS = [
'cap_add',
'cap_drop',
'cgroup_parent',
'cgroupns',
'cpu_count',
'cpu_percent',
'cpu_period',
@ -997,6 +1076,7 @@ RUN_HOST_CONFIG_KWARGS = [
'device_write_bps',
'device_write_iops',
'devices',
'device_requests',
'dns_opt',
'dns_search',
'dns',
@ -1064,8 +1144,17 @@ def _create_container_args(kwargs):
host_config_kwargs['binds'] = volumes
network = kwargs.pop('network', None)
networking_config = kwargs.pop('networking_config', None)
if network:
create_kwargs['networking_config'] = {network: None}
if networking_config:
# Sanity check: check if the network is defined in the
# networking config dict, otherwise switch to None
if network not in networking_config:
networking_config = None
create_kwargs['networking_config'] = NetworkingConfig(
networking_config
) if networking_config else {network: None}
host_config_kwargs['network_mode'] = network
# All kwargs should have been consumed by this point, so raise
@ -1098,8 +1187,10 @@ def _host_volume_from_bind(bind):
bits = rest.split(':', 1)
if len(bits) == 1 or bits[1] in ('ro', 'rw'):
return drive + bits[0]
elif bits[1].endswith(':ro') or bits[1].endswith(':rw'):
return bits[1][:-3]
else:
return bits[1].rstrip(':ro').rstrip(':rw')
return bits[1]
ExecResult = namedtuple('ExecResult', 'exit_code,output')

View File

@ -2,8 +2,6 @@ import itertools
import re
import warnings
import six
from ..api import APIClient
from ..constants import DEFAULT_DATA_CHUNK_SIZE
from ..errors import BuildError, ImageLoadError, InvalidArgument
@ -17,7 +15,8 @@ class Image(Model):
An image on the server.
"""
def __repr__(self):
return "<%s: '%s'>" % (self.__class__.__name__, "', '".join(self.tags))
tag_str = "', '".join(self.tags)
return f"<{self.__class__.__name__}: '{tag_str}'>"
@property
def labels(self):
@ -30,12 +29,12 @@ class Image(Model):
@property
def short_id(self):
"""
The ID of the image truncated to 10 characters, plus the ``sha256:``
The ID of the image truncated to 12 characters, plus the ``sha256:``
prefix.
"""
if self.id.startswith('sha256:'):
return self.id[:17]
return self.id[:10]
return self.id[:19]
return self.id[:12]
@property
def tags(self):
@ -52,7 +51,7 @@ class Image(Model):
Show the history of an image.
Returns:
(str): The history of the image.
(list): The history of the image.
Raises:
:py:class:`docker.errors.APIError`
@ -60,6 +59,24 @@ class Image(Model):
"""
return self.client.api.history(self.id)
def remove(self, force=False, noprune=False):
"""
Remove this image.
Args:
force (bool): Force removal of the image
noprune (bool): Do not delete untagged parents
Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
return self.client.api.remove_image(
self.id,
force=force,
noprune=noprune,
)
def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE, named=False):
"""
Get a tarball of an image. Similar to the ``docker save`` command.
@ -84,19 +101,19 @@ class Image(Model):
Example:
>>> image = cli.get_image("busybox:latest")
>>> image = cli.images.get("busybox:latest")
>>> f = open('/tmp/busybox-latest.tar', 'wb')
>>> for chunk in image:
>>> for chunk in image.save():
>>> f.write(chunk)
>>> f.close()
"""
img = self.id
if named:
img = self.tags[0] if self.tags else img
if isinstance(named, six.string_types):
if isinstance(named, str):
if named not in self.tags:
raise InvalidArgument(
"{} is not a valid tag for this image".format(named)
f"{named} is not a valid tag for this image"
)
img = named
@ -127,7 +144,7 @@ class RegistryData(Model):
Image metadata stored on the registry, including available platforms.
"""
def __init__(self, image_name, *args, **kwargs):
super(RegistryData, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.image_name = image_name
@property
@ -140,10 +157,10 @@ class RegistryData(Model):
@property
def short_id(self):
"""
The ID of the image truncated to 10 characters, plus the ``sha256:``
The ID of the image truncated to 12 characters, plus the ``sha256:``
prefix.
"""
return self.id[:17]
return self.id[:19]
def pull(self, platform=None):
"""
@ -180,7 +197,7 @@ class RegistryData(Model):
parts = platform.split('/')
if len(parts) > 3 or len(parts) < 1:
raise InvalidArgument(
'"{0}" is not a valid platform descriptor'.format(platform)
f'"{platform}" is not a valid platform descriptor'
)
platform = {'os': parts[0]}
if len(parts) > 2:
@ -205,10 +222,10 @@ class ImageCollection(Collection):
Build an image and return it. Similar to the ``docker build``
command. Either ``path`` or ``fileobj`` must be set.
If you have a tar file for the Docker build context (including a
Dockerfile) already, pass a readable file-like object to ``fileobj``
and also pass ``custom_context=True``. If the stream is compressed
also, set ``encoding`` to the correct value (e.g ``gzip``).
If you already have a tar file for the Docker build context (including
a Dockerfile), pass a readable file-like object to ``fileobj``
and also pass ``custom_context=True``. If the stream is also
compressed, set ``encoding`` to the correct value (e.g ``gzip``).
If you want to get the raw output of the build, use the
:py:meth:`~docker.api.build.BuildApiMixin.build` method in the
@ -265,7 +282,7 @@ class ImageCollection(Collection):
Returns:
(tuple): The first item is the :py:class:`Image` object for the
image that was build. The second item is a generator of the
image that was built. The second item is a generator of the
build logs as JSON-decoded objects.
Raises:
@ -277,7 +294,7 @@ class ImageCollection(Collection):
If neither ``path`` nor ``fileobj`` is specified.
"""
resp = self.client.api.build(**kwargs)
if isinstance(resp, six.string_types):
if isinstance(resp, str):
return self.get(resp)
last_event = None
image_id = None
@ -350,7 +367,8 @@ class ImageCollection(Collection):
filters (dict): Filters to be processed on the image list.
Available filters:
- ``dangling`` (bool)
- ``label`` (str): format either ``key`` or ``key=value``
- `label` (str|list): format either ``"key"``, ``"key=value"``
or a list of such.
Returns:
(list of :py:class:`Image`): The images.
@ -389,17 +407,18 @@ class ImageCollection(Collection):
if match:
image_id = match.group(2)
images.append(image_id)
if 'error' in chunk:
raise ImageLoadError(chunk['error'])
if 'errorDetail' in chunk:
raise ImageLoadError(chunk['errorDetail']['message'])
return [self.get(i) for i in images]
def pull(self, repository, tag=None, **kwargs):
def pull(self, repository, tag=None, all_tags=False, **kwargs):
"""
Pull an image of the given name and return it. Similar to the
``docker pull`` command.
If no tag is specified, all tags from that repository will be
pulled.
If ``tag`` is ``None`` or empty, it is set to ``latest``.
If ``all_tags`` is set, the ``tag`` parameter is ignored and all image
tags will be pulled.
If you want to get the raw pull output, use the
:py:meth:`~docker.api.image.ImageApiMixin.pull` method in the
@ -412,10 +431,11 @@ class ImageCollection(Collection):
config for this request. ``auth_config`` should contain the
``username`` and ``password`` keys to be valid.
platform (str): Platform in the format ``os[/arch[/variant]]``
all_tags (bool): Pull all image tags
Returns:
(:py:class:`Image` or list): The image that has been pulled.
If no ``tag`` was specified, the method will return a list
If ``all_tags`` is True, the method will return a list
of :py:class:`Image` objects belonging to this repository.
Raises:
@ -425,33 +445,33 @@ class ImageCollection(Collection):
Example:
>>> # Pull the image tagged `latest` in the busybox repo
>>> image = client.images.pull('busybox:latest')
>>> image = client.images.pull('busybox')
>>> # Pull all tags in the busybox repo
>>> images = client.images.pull('busybox')
>>> images = client.images.pull('busybox', all_tags=True)
"""
if not tag:
repository, tag = parse_repository_tag(repository)
repository, image_tag = parse_repository_tag(repository)
tag = tag or image_tag or 'latest'
if 'stream' in kwargs:
warnings.warn(
'`stream` is not a valid parameter for this method'
' and will be overridden'
' and will be overridden',
stacklevel=1,
)
del kwargs['stream']
pull_log = self.client.api.pull(
repository, tag=tag, stream=True, **kwargs
repository, tag=tag, stream=True, all_tags=all_tags, **kwargs
)
for _ in pull_log:
# We don't do anything with the logs, but we need
# to keep the connection alive and wait for the image
# to be pulled.
pass
if tag:
return self.get('{0}{2}{1}'.format(
repository, tag, '@' if tag.startswith('sha256:') else ':'
))
if not all_tags:
sep = '@' if tag.startswith('sha256:') else ':'
return self.get(f'{repository}{sep}{tag}')
return self.list(repository)
def push(self, repository, tag=None, **kwargs):

View File

@ -1,7 +1,7 @@
from ..api import APIClient
from ..utils import version_gte
from .containers import Container
from .resource import Model, Collection
from .resource import Collection, Model
class Network(Model):
@ -46,6 +46,8 @@ class Network(Model):
network, using the IPv6 protocol. Defaults to ``None``.
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
addresses.
driver_opt (dict): A dictionary of options to provide to the
network driver. Defaults to ``None``.
Raises:
:py:class:`docker.errors.APIError`
@ -182,7 +184,7 @@ class NetworkCollection(Collection):
def list(self, *args, **kwargs):
"""
List networks. Similar to the ``docker networks ls`` command.
List networks. Similar to the ``docker network ls`` command.
Args:
names (:py:class:`list`): List of names to filter by.
@ -190,7 +192,8 @@ class NetworkCollection(Collection):
filters (dict): Filters to be processed on the network list.
Available filters:
- ``driver=[<driver-name>]`` Matches a network's driver.
- ``label=[<key>]`` or ``label=[<key>=<value>]``.
- `label` (str|list): format either ``"key"``, ``"key=value"``
or a list of such.
- ``type=["custom"|"builtin"]`` Filters networks by type.
greedy (bool): Fetch more details for each network individually.
You might want this to get the containers attached to them.

View File

@ -1,4 +1,4 @@
from .resource import Model, Collection
from .resource import Collection, Model
class Node(Model):

View File

@ -7,7 +7,7 @@ class Plugin(Model):
A plugin on the server.
"""
def __repr__(self):
return "<%s: '%s'>" % (self.__class__.__name__, self.name)
return f"<{self.__class__.__name__}: '{self.name}'>"
@property
def name(self):
@ -44,16 +44,19 @@ class Plugin(Model):
self.client.api.configure_plugin(self.name, options)
self.reload()
def disable(self):
def disable(self, force=False):
"""
Disable the plugin.
Args:
force (bool): Force disable. Default: False
Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
self.client.api.disable_plugin(self.name)
self.client.api.disable_plugin(self.name, force)
self.reload()
def enable(self, timeout=0):
@ -117,9 +120,12 @@ class Plugin(Model):
if remote is None:
remote = self.name
privileges = self.client.api.plugin_privileges(remote)
for d in self.client.api.upgrade_plugin(self.name, remote, privileges):
yield d
self._reload()
yield from self.client.api.upgrade_plugin(
self.name,
remote,
privileges,
)
self.reload()
class PluginCollection(Collection):
@ -181,7 +187,7 @@ class PluginCollection(Collection):
"""
privileges = self.client.api.plugin_privileges(remote_name)
it = self.client.api.pull_plugin(remote_name, privileges, local_name)
for data in it:
for _data in it:
pass
return self.get(local_name or remote_name)

View File

@ -1,5 +1,4 @@
class Model(object):
class Model:
"""
A base class for representing a single object on the server.
"""
@ -18,13 +17,13 @@ class Model(object):
self.attrs = {}
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.short_id)
return f"<{self.__class__.__name__}: {self.short_id}>"
def __eq__(self, other):
return isinstance(other, self.__class__) and self.id == other.id
def __hash__(self):
return hash("%s:%s" % (self.__class__.__name__, self.id))
return hash(f"{self.__class__.__name__}:{self.id}")
@property
def id(self):
@ -36,9 +35,9 @@ class Model(object):
@property
def short_id(self):
"""
The ID of the object, truncated to 10 characters.
The ID of the object, truncated to 12 characters.
"""
return self.id[:10]
return self.id[:12]
def reload(self):
"""
@ -49,7 +48,7 @@ class Model(object):
self.attrs = new_model.attrs
class Collection(object):
class Collection:
"""
A base class for representing all objects of a particular type on the
server.
@ -65,9 +64,10 @@ class Collection(object):
def __call__(self, *args, **kwargs):
raise TypeError(
"'{}' object is not callable. You might be trying to use the old "
"(pre-2.0) API - use docker.APIClient if so."
.format(self.__class__.__name__))
f"'{self.__class__.__name__}' object is not callable. "
"You might be trying to use the old (pre-2.0) API - "
"use docker.APIClient if so."
)
def list(self):
raise NotImplementedError
@ -89,5 +89,4 @@ class Collection(object):
elif isinstance(attrs, dict):
return self.model(attrs=attrs, client=self.client, collection=self)
else:
raise Exception("Can't create %s from %s" %
(self.model.__name__, attrs))
raise Exception(f"Can't create {self.model.__name__} from {attrs}")

View File

@ -1,5 +1,5 @@
from ..api import APIClient
from .resource import Model, Collection
from .resource import Collection, Model
class Secret(Model):
@ -7,7 +7,7 @@ class Secret(Model):
id_attribute = 'ID'
def __repr__(self):
return "<%s: '%s'>" % (self.__class__.__name__, self.name)
return f"<{self.__class__.__name__}: '{self.name}'>"
@property
def name(self):
@ -30,6 +30,7 @@ class SecretCollection(Collection):
def create(self, **kwargs):
obj = self.client.api.create_secret(**kwargs)
obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
return self.prepare_model(obj)
create.__doc__ = APIClient.create_secret.__doc__

View File

@ -1,7 +1,9 @@
import copy
from docker.errors import create_unexpected_kwargs_error, InvalidArgument
from docker.types import TaskTemplate, ContainerSpec, Placement, ServiceMode
from .resource import Model, Collection
from docker.errors import InvalidArgument, create_unexpected_kwargs_error
from docker.types import ContainerSpec, Placement, ServiceMode, TaskTemplate
from .resource import Collection, Model
class Service(Model):
@ -157,6 +159,8 @@ class ServiceCollection(Collection):
constraints.
preferences (list of tuple): :py:class:`~docker.types.Placement`
preferences.
maxreplicas (int): :py:class:`~docker.types.Placement` maxreplicas
or (int) representing maximum number of replicas per node.
platforms (list of tuple): A list of platform constraints
expressed as ``(arch, os)`` tuples.
container_labels (dict): Labels to apply to the container.
@ -178,11 +182,12 @@ class ServiceCollection(Collection):
``source:target:options``, where options is either
``ro`` or ``rw``.
name (str): Name to give to the service.
networks (list of str): List of network names or IDs to attach
the service to. Default: ``None``.
networks (:py:class:`list`): List of network names or IDs or
:py:class:`~docker.types.NetworkAttachmentConfig` to attach the
service to. Default: ``None``.
resources (Resources): Resource limits and reservations.
restart_policy (RestartPolicy): Restart policy for containers.
secrets (list of :py:class:`docker.types.SecretReference`): List
secrets (list of :py:class:`~docker.types.SecretReference`): List
of secrets accessible to containers for this service.
stop_grace_period (int): Amount of time to wait for
containers to terminate before forcefully killing them.
@ -205,10 +210,17 @@ class ServiceCollection(Collection):
the container's `hosts` file.
dns_config (DNSConfig): Specification for DNS
related configurations in resolver configuration file.
configs (:py:class:`list`): List of :py:class:`ConfigReference`
that will be exposed to the service.
configs (:py:class:`list`): List of
:py:class:`~docker.types.ConfigReference` that will be exposed
to the service.
privileges (Privileges): Security options for the service's
containers.
cap_add (:py:class:`list`): A list of kernel capabilities to add to
the default set for the container.
cap_drop (:py:class:`list`): A list of kernel capabilities to drop
from the default set for the container.
sysctls (:py:class:`dict`): A dict of sysctl values to add to the
container
Returns:
:py:class:`Service`: The created service.
@ -256,6 +268,8 @@ class ServiceCollection(Collection):
filters (dict): Filters to process on the nodes list. Valid
filters: ``id``, ``name`` , ``label`` and ``mode``.
Default: ``None``.
status (bool): Include the service task count of running and
desired tasks. Default: ``None``.
Returns:
list of :py:class:`Service`: The services.
@ -273,6 +287,8 @@ class ServiceCollection(Collection):
# kwargs to copy straight over to ContainerSpec
CONTAINER_SPEC_KWARGS = [
'args',
'cap_add',
'cap_drop',
'command',
'configs',
'dns_config',
@ -295,6 +311,7 @@ CONTAINER_SPEC_KWARGS = [
'tty',
'user',
'workdir',
'sysctls',
]
# kwargs to copy straight over to TaskTemplate
@ -310,6 +327,7 @@ CREATE_SERVICE_KWARGS = [
'labels',
'mode',
'update_config',
'rollback_config',
'endpoint_spec',
]
@ -317,6 +335,7 @@ PLACEMENT_KWARGS = [
'constraints',
'preferences',
'platforms',
'maxreplicas',
]

View File

@ -1,5 +1,6 @@
from docker.api import APIClient
from docker.errors import APIError
from .resource import Model
@ -11,7 +12,7 @@ class Swarm(Model):
id_attribute = 'ID'
def __init__(self, *args, **kwargs):
super(Swarm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if self.client:
try:
self.reload()
@ -35,7 +36,8 @@ class Swarm(Model):
def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
force_new_cluster=False, default_addr_pool=None,
subnet_size=None, data_path_addr=None, **kwargs):
subnet_size=None, data_path_addr=None, data_path_port=None,
**kwargs):
"""
Initialize a new swarm on this Engine.
@ -65,6 +67,9 @@ class Swarm(Model):
networks created from the default subnet pool. Default: None
data_path_addr (string): Address or interface to use for data path
traffic. For example, 192.168.1.1, or an interface, like eth0.
data_path_port (int): Port number to use for data path traffic.
Acceptable port range is 1024 to 49151. If set to ``None`` or
0, the default port 4789 will be used. Default: None
task_history_retention_limit (int): Maximum number of tasks
history stored.
snapshot_interval (int): Number of logs entries between snapshot.
@ -121,6 +126,7 @@ class Swarm(Model):
'default_addr_pool': default_addr_pool,
'subnet_size': subnet_size,
'data_path_addr': data_path_addr,
'data_path_port': data_path_port,
}
init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs)
node_id = self.client.api.init_swarm(**init_kwargs)

View File

@ -1,5 +1,5 @@
from ..api import APIClient
from .resource import Model, Collection
from .resource import Collection, Model
class Volume(Model):

View File

@ -1,68 +1,31 @@
import os
import ssl
from . import errors
from .transport import SSLHTTPAdapter
class TLSConfig(object):
class TLSConfig:
"""
TLS configuration.
Args:
client_cert (tuple of str): Path to client cert, path to client key.
ca_cert (str): Path to CA cert file.
verify (bool or str): This can be ``False`` or a path to a CA cert
file.
ssl_version (int): A valid `SSL version`_.
assert_hostname (bool): Verify the hostname of the server.
.. _`SSL version`:
https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_TLSv1
verify (bool or str): This can be a bool or a path to a CA cert
file to verify against. If ``True``, verify using ca_cert;
if ``False`` or not specified, do not verify.
"""
cert = None
ca_cert = None
verify = None
ssl_version = None
def __init__(self, client_cert=None, ca_cert=None, verify=None,
ssl_version=None, assert_hostname=None,
assert_fingerprint=None):
def __init__(self, client_cert=None, ca_cert=None, verify=None):
# Argument compatibility/mapping with
# https://docs.docker.com/engine/articles/https/
# This diverges from the Docker CLI in that users can specify 'tls'
# here, but also disable any public/default CA pool verification by
# leaving tls_verify=False
# leaving verify=False
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
# TODO(dperny): according to the python docs, PROTOCOL_TLSvWhatever is
# depcreated, and it's recommended to use OPT_NO_TLSvWhatever instead
# to exclude versions. But I think that might require a bigger
# architectural change, so I've opted not to pursue it at this time
# If the user provides an SSL version, we should use their preference
if ssl_version:
self.ssl_version = ssl_version
else:
# If the user provides no ssl version, we should default to
# TLSv1_2. This option is the most secure, and will work for the
# majority of users with reasonably up-to-date software. However,
# before doing so, detect openssl version to ensure we can support
# it.
if ssl.OPENSSL_VERSION_INFO[:3] >= (1, 0, 1) and hasattr(
ssl, 'PROTOCOL_TLSv1_2'):
# If the OpenSSL version is high enough to support TLSv1_2,
# then we should use it.
self.ssl_version = getattr(ssl, 'PROTOCOL_TLSv1_2')
else:
# Otherwise, TLS v1.0 seems to be the safest default;
# SSLv23 fails in mysterious ways:
# https://github.com/docker/docker-py/issues/963
self.ssl_version = ssl.PROTOCOL_TLSv1
# "tls" and "tls_verify" must have both or neither cert/key files In
# "client_cert" must have both or neither cert/key files. In
# either case, Alert the user when both are expected, but any are
# missing.
@ -71,15 +34,15 @@ class TLSConfig(object):
tls_cert, tls_key = client_cert
except ValueError:
raise errors.TLSParameterError(
'client_config must be a tuple of'
'client_cert must be a tuple of'
' (client certificate, key file)'
)
) from None
if not (tls_cert and tls_key) or (not os.path.isfile(tls_cert) or
not os.path.isfile(tls_key)):
raise errors.TLSParameterError(
'Path to a certificate and key files must be provided'
' through the client_config param'
' through the client_cert param'
)
self.cert = (tls_cert, tls_key)
@ -88,15 +51,13 @@ class TLSConfig(object):
self.ca_cert = ca_cert
if self.verify and self.ca_cert and not os.path.isfile(self.ca_cert):
raise errors.TLSParameterError(
'Invalid CA certificate provided for `tls_ca_cert`.'
'Invalid CA certificate provided for `ca_cert`.'
)
def configure_client(self, client):
"""
Configure a client with these TLS options.
"""
client.ssl_version = self.ssl_version
if self.verify and self.ca_cert:
client.verify = self.ca_cert
else:
@ -104,9 +65,3 @@ class TLSConfig(object):
if self.cert:
client.cert = self.cert
client.mount('https://', SSLHTTPAdapter(
ssl_version=self.ssl_version,
assert_hostname=self.assert_hostname,
assert_fingerprint=self.assert_fingerprint,
))

View File

@ -1,6 +1,5 @@
# flake8: noqa
from .unixconn import UnixHTTPAdapter
from .ssladapter import SSLHTTPAdapter
try:
from .npipeconn import NpipeHTTPAdapter
from .npipesocket import NpipeSocket

View File

@ -3,6 +3,11 @@ import requests.adapters
class BaseHTTPAdapter(requests.adapters.HTTPAdapter):
def close(self):
super(BaseHTTPAdapter, self).close()
super().close()
if hasattr(self, 'pools'):
self.pools.clear()
# Fix for requests 2.32.2+:
# https://github.com/psf/requests/commit/c98e4d133ef29c46a9b68cd783087218a8075e05
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
return self.get_connection(request.url, proxies)

View File

@ -1,26 +1,19 @@
import six
import queue
import requests.adapters
from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants
from .npipesocket import NpipeSocket
if six.PY3:
import http.client as httplib
else:
import httplib
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3
import urllib3.connection
from .. import constants
from .basehttpadapter import BaseHTTPAdapter
from .npipesocket import NpipeSocket
RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
class NpipeHTTPConnection(httplib.HTTPConnection, object):
class NpipeHTTPConnection(urllib3.connection.HTTPConnection):
def __init__(self, npipe_path, timeout=60):
super(NpipeHTTPConnection, self).__init__(
super().__init__(
'localhost', timeout=timeout
)
self.npipe_path = npipe_path
@ -35,7 +28,7 @@ class NpipeHTTPConnection(httplib.HTTPConnection, object):
class NpipeHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
def __init__(self, npipe_path, timeout=60, maxsize=10):
super(NpipeHTTPConnectionPool, self).__init__(
super().__init__(
'localhost', timeout=timeout, maxsize=maxsize
)
self.npipe_path = npipe_path
@ -53,18 +46,17 @@ class NpipeHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
conn = None
try:
conn = self.pool.get(block=self.block, timeout=timeout)
except AttributeError as ae: # self.pool is None
raise urllib3.exceptions.ClosedPoolError(self, "Pool is closed.") from ae
except AttributeError: # self.pool is None
raise urllib3.exceptions.ClosedPoolError(self, "Pool is closed.")
except six.moves.queue.Empty:
except queue.Empty:
if self.block:
raise urllib3.exceptions.EmptyPoolError(
self,
"Pool reached maximum size and no more "
"connections are allowed."
)
pass # Oh well, we'll create a new connection then
) from None
# Oh well, we'll create a new connection then
return conn or self._new_conn()
@ -73,16 +65,19 @@ class NpipeHTTPAdapter(BaseHTTPAdapter):
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['npipe_path',
'pools',
'timeout']
'timeout',
'max_pool_size']
def __init__(self, base_url, timeout=60,
pool_connections=constants.DEFAULT_NUM_POOLS):
pool_connections=constants.DEFAULT_NUM_POOLS,
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE):
self.npipe_path = base_url.replace('npipe://', '')
self.timeout = timeout
self.max_pool_size = max_pool_size
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(NpipeHTTPAdapter, self).__init__()
super().__init__()
def get_connection(self, url, proxies=None):
with self.pools.lock:
@ -91,7 +86,8 @@ class NpipeHTTPAdapter(BaseHTTPAdapter):
return pool
pool = NpipeHTTPConnectionPool(
self.npipe_path, self.timeout
self.npipe_path, self.timeout,
maxsize=self.max_pool_size
)
self.pools[url] = pool

View File

@ -1,7 +1,10 @@
import functools
import io
import time
import six
import pywintypes
import win32api
import win32event
import win32file
import win32pipe
@ -9,7 +12,7 @@ cERROR_PIPE_BUSY = 0xe7
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
RETRY_WAIT_TIMEOUT = 10000
MAXIMUM_RETRY_COUNT = 10
def check_closed(f):
@ -23,7 +26,7 @@ def check_closed(f):
return wrapped
class NpipeSocket(object):
class NpipeSocket:
""" Partial implementation of the socket API over windows named pipes.
This implementation is only designed to be used as a client socket,
and server-specific methods (bind, listen, accept...) are not
@ -46,8 +49,7 @@ class NpipeSocket(object):
self._closed = True
@check_closed
def connect(self, address):
win32pipe.WaitNamedPipe(address, self._timeout)
def connect(self, address, retry_count=0):
try:
handle = win32file.CreateFile(
address,
@ -55,7 +57,9 @@ class NpipeSocket(object):
0,
None,
win32file.OPEN_EXISTING,
cSECURITY_ANONYMOUS | cSECURITY_SQOS_PRESENT,
(cSECURITY_ANONYMOUS
| cSECURITY_SQOS_PRESENT
| win32file.FILE_FLAG_OVERLAPPED),
0
)
except win32pipe.error as e:
@ -65,8 +69,10 @@ class NpipeSocket(object):
# Another program or thread has grabbed our pipe instance
# before we got to it. Wait for availability and attempt to
# connect again.
win32pipe.WaitNamedPipe(address, RETRY_WAIT_TIMEOUT)
return self.connect(address)
retry_count = retry_count + 1
if (retry_count < MAXIMUM_RETRY_COUNT):
time.sleep(1)
return self.connect(address, retry_count)
raise e
self.flags = win32pipe.GetNamedPipeInfo(handle)[0]
@ -126,29 +132,41 @@ class NpipeSocket(object):
@check_closed
def recv_into(self, buf, nbytes=0):
if six.PY2:
return self._recv_into_py2(buf, nbytes)
readbuf = buf
if not isinstance(buf, memoryview):
readbuf = memoryview(buf)
event = win32event.CreateEvent(None, True, True, None)
try:
overlapped = pywintypes.OVERLAPPED()
overlapped.hEvent = event
err, data = win32file.ReadFile(
self._handle,
readbuf[:nbytes] if nbytes else readbuf
readbuf[:nbytes] if nbytes else readbuf,
overlapped
)
return len(data)
def _recv_into_py2(self, buf, nbytes):
err, data = win32file.ReadFile(self._handle, nbytes or len(buf))
n = len(data)
buf[:n] = data
return n
wait_result = win32event.WaitForSingleObject(event, self._timeout)
if wait_result == win32event.WAIT_TIMEOUT:
win32file.CancelIo(self._handle)
raise TimeoutError
return win32file.GetOverlappedResult(self._handle, overlapped, 0)
finally:
win32api.CloseHandle(event)
@check_closed
def send(self, string, flags=0):
err, nbytes = win32file.WriteFile(self._handle, string)
return nbytes
event = win32event.CreateEvent(None, True, True, None)
try:
overlapped = pywintypes.OVERLAPPED()
overlapped.hEvent = event
win32file.WriteFile(self._handle, string, overlapped)
wait_result = win32event.WaitForSingleObject(event, self._timeout)
if wait_result == win32event.WAIT_TIMEOUT:
win32file.CancelIo(self._handle)
raise TimeoutError
return win32file.GetOverlappedResult(self._handle, overlapped, 0)
finally:
win32api.CloseHandle(event)
@check_closed
def sendall(self, string, flags=0):
@ -167,15 +185,12 @@ class NpipeSocket(object):
def settimeout(self, value):
if value is None:
# Blocking mode
self._timeout = win32pipe.NMPWAIT_WAIT_FOREVER
self._timeout = win32event.INFINITE
elif not isinstance(value, (float, int)) or value < 0:
raise ValueError('Timeout value out of range')
elif value == 0:
# Non-blocking mode
self._timeout = win32pipe.NMPWAIT_NO_WAIT
else:
# Timeout mode - Value converted to milliseconds
self._timeout = value * 1000
self._timeout = int(value * 1000)
def gettimeout(self):
return self._timeout
@ -193,7 +208,7 @@ class NpipeFileIOBase(io.RawIOBase):
self.sock = npipe_socket
def close(self):
super(NpipeFileIOBase, self).close()
super().close()
self.sock = None
def fileno(self):

View File

@ -1,50 +1,137 @@
import logging
import os
import queue
import signal
import socket
import subprocess
import urllib.parse
import paramiko
import requests.adapters
import six
from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants
if six.PY3:
import http.client as httplib
else:
import httplib
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3
import urllib3.connection
from .. import constants
from .basehttpadapter import BaseHTTPAdapter
RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
class SSHConnection(httplib.HTTPConnection, object):
def __init__(self, ssh_transport, timeout=60):
super(SSHConnection, self).__init__(
class SSHSocket(socket.socket):
def __init__(self, host):
super().__init__(
socket.AF_INET, socket.SOCK_STREAM)
self.host = host
self.port = None
self.user = None
if ':' in self.host:
self.host, self.port = self.host.split(':')
if '@' in self.host:
self.user, self.host = self.host.split('@')
self.proc = None
def connect(self, **kwargs):
args = ['ssh']
if self.user:
args = args + ['-l', self.user]
if self.port:
args = args + ['-p', self.port]
args = args + ['--', self.host, 'docker system dial-stdio']
preexec_func = None
if not constants.IS_WINDOWS_PLATFORM:
def f():
signal.signal(signal.SIGINT, signal.SIG_IGN)
preexec_func = f
env = dict(os.environ)
# drop LD_LIBRARY_PATH and SSL_CERT_FILE
env.pop('LD_LIBRARY_PATH', None)
env.pop('SSL_CERT_FILE', None)
self.proc = subprocess.Popen(
args,
env=env,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
preexec_fn=preexec_func)
def _write(self, data):
if not self.proc or self.proc.stdin.closed:
raise Exception('SSH subprocess not initiated.'
'connect() must be called first.')
written = self.proc.stdin.write(data)
self.proc.stdin.flush()
return written
def sendall(self, data):
self._write(data)
def send(self, data):
return self._write(data)
def recv(self, n):
if not self.proc:
raise Exception('SSH subprocess not initiated.'
'connect() must be called first.')
return self.proc.stdout.read(n)
def makefile(self, mode):
if not self.proc:
self.connect()
self.proc.stdout.channel = self
return self.proc.stdout
def close(self):
if not self.proc or self.proc.stdin.closed:
return
self.proc.stdin.write(b'\n\n')
self.proc.stdin.flush()
self.proc.terminate()
class SSHConnection(urllib3.connection.HTTPConnection):
def __init__(self, ssh_transport=None, timeout=60, host=None):
super().__init__(
'localhost', timeout=timeout
)
self.ssh_transport = ssh_transport
self.timeout = timeout
self.ssh_host = host
def connect(self):
if self.ssh_transport:
sock = self.ssh_transport.open_session()
sock.settimeout(self.timeout)
sock.exec_command('docker system dial-stdio')
else:
sock = SSHSocket(self.ssh_host)
sock.settimeout(self.timeout)
sock.connect()
self.sock = sock
class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
scheme = 'ssh'
def __init__(self, ssh_client, timeout=60, maxsize=10):
super(SSHConnectionPool, self).__init__(
def __init__(self, ssh_client=None, timeout=60, maxsize=10, host=None):
super().__init__(
'localhost', timeout=timeout, maxsize=maxsize
)
self.ssh_transport = ssh_client.get_transport()
self.ssh_transport = None
self.timeout = timeout
if ssh_client:
self.ssh_transport = ssh_client.get_transport()
self.ssh_host = host
def _new_conn(self):
return SSHConnection(self.ssh_transport, self.timeout)
return SSHConnection(self.ssh_transport, self.timeout, self.ssh_host)
# When re-using connections, urllib3 calls fileno() on our
# SSH channel instance, quickly overloading our fd limit. To avoid this,
@ -54,17 +141,17 @@ class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
try:
conn = self.pool.get(block=self.block, timeout=timeout)
except AttributeError: # self.pool is None
raise urllib3.exceptions.ClosedPoolError(self, "Pool is closed.")
except AttributeError as ae: # self.pool is None
raise urllib3.exceptions.ClosedPoolError(self, "Pool is closed.") from ae
except six.moves.queue.Empty:
except queue.Empty:
if self.block:
raise urllib3.exceptions.EmptyPoolError(
self,
"Pool reached maximum size and no more "
"connections are allowed."
)
pass # Oh well, we'll create a new connection then
) from None
# Oh well, we'll create a new connection then
return conn or self._new_conn()
@ -72,45 +159,92 @@ class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
class SSHHTTPAdapter(BaseHTTPAdapter):
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [
'pools', 'timeout', 'ssh_client',
'pools', 'timeout', 'ssh_client', 'ssh_params', 'max_pool_size'
]
def __init__(self, base_url, timeout=60,
pool_connections=constants.DEFAULT_NUM_POOLS):
self.ssh_client = paramiko.SSHClient()
self.ssh_client.load_system_host_keys()
self.base_url = base_url
pool_connections=constants.DEFAULT_NUM_POOLS,
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE,
shell_out=False):
self.ssh_client = None
if not shell_out:
self._create_paramiko_client(base_url)
self._connect()
self.ssh_host = base_url
if base_url.startswith('ssh://'):
self.ssh_host = base_url[len('ssh://'):]
self.timeout = timeout
self.max_pool_size = max_pool_size
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(SSHHTTPAdapter, self).__init__()
super().__init__()
def _create_paramiko_client(self, base_url):
logging.getLogger("paramiko").setLevel(logging.WARNING)
self.ssh_client = paramiko.SSHClient()
base_url = urllib.parse.urlparse(base_url)
self.ssh_params = {
"hostname": base_url.hostname,
"port": base_url.port,
"username": base_url.username
}
ssh_config_file = os.path.expanduser("~/.ssh/config")
if os.path.exists(ssh_config_file):
conf = paramiko.SSHConfig()
with open(ssh_config_file) as f:
conf.parse(f)
host_config = conf.lookup(base_url.hostname)
if 'proxycommand' in host_config:
self.ssh_params["sock"] = paramiko.ProxyCommand(
host_config['proxycommand']
)
if 'hostname' in host_config:
self.ssh_params['hostname'] = host_config['hostname']
if base_url.port is None and 'port' in host_config:
self.ssh_params['port'] = host_config['port']
if base_url.username is None and 'user' in host_config:
self.ssh_params['username'] = host_config['user']
if 'identityfile' in host_config:
self.ssh_params['key_filename'] = host_config['identityfile']
self.ssh_client.load_system_host_keys()
self.ssh_client.set_missing_host_key_policy(paramiko.RejectPolicy())
def _connect(self):
parsed = six.moves.urllib_parse.urlparse(self.base_url)
self.ssh_client.connect(
parsed.hostname, parsed.port, parsed.username,
)
if self.ssh_client:
self.ssh_client.connect(**self.ssh_params)
def get_connection(self, url, proxies=None):
if not self.ssh_client:
return SSHConnectionPool(
ssh_client=self.ssh_client,
timeout=self.timeout,
maxsize=self.max_pool_size,
host=self.ssh_host
)
with self.pools.lock:
pool = self.pools.get(url)
if pool:
return pool
# Connection is closed try a reconnect
if not self.ssh_client.get_transport():
if self.ssh_client and not self.ssh_client.get_transport():
self._connect()
pool = SSHConnectionPool(
self.ssh_client, self.timeout
ssh_client=self.ssh_client,
timeout=self.timeout,
maxsize=self.max_pool_size,
host=self.ssh_host
)
self.pools[url] = pool
return pool
def close(self):
super(SSHHTTPAdapter, self).close()
super().close()
if self.ssh_client:
self.ssh_client.close()

View File

@ -1,73 +0,0 @@
""" Resolves OpenSSL issues in some servers:
https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/
https://github.com/kennethreitz/requests/pull/799
"""
import sys
from distutils.version import StrictVersion
from requests.adapters import HTTPAdapter
from docker.transport.basehttpadapter import BaseHTTPAdapter
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3
PoolManager = urllib3.poolmanager.PoolManager
# Monkey-patching match_hostname with a version that supports
# IP-address checking. Not necessary for Python 3.5 and above
if sys.version_info[0] < 3 or sys.version_info[1] < 5:
from backports.ssl_match_hostname import match_hostname
urllib3.connection.match_hostname = match_hostname
class SSLHTTPAdapter(BaseHTTPAdapter):
'''An HTTPS Transport Adapter that uses an arbitrary SSL version.'''
__attrs__ = HTTPAdapter.__attrs__ + ['assert_fingerprint',
'assert_hostname',
'ssl_version']
def __init__(self, ssl_version=None, assert_hostname=None,
assert_fingerprint=None, **kwargs):
self.ssl_version = ssl_version
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
super(SSLHTTPAdapter, self).__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
kwargs = {
'num_pools': connections,
'maxsize': maxsize,
'block': block,
'assert_hostname': self.assert_hostname,
'assert_fingerprint': self.assert_fingerprint,
}
if self.ssl_version and self.can_override_ssl_version():
kwargs['ssl_version'] = self.ssl_version
self.poolmanager = PoolManager(**kwargs)
def get_connection(self, *args, **kwargs):
"""
Ensure assert_hostname is set correctly on our pool
We already take care of a normal poolmanager via init_poolmanager
But we still need to take care of when there is a proxy poolmanager
"""
conn = super(SSLHTTPAdapter, self).get_connection(*args, **kwargs)
if conn.assert_hostname != self.assert_hostname:
conn.assert_hostname = self.assert_hostname
return conn
def can_override_ssl_version(self):
urllib_ver = urllib3.__version__.split('-')[0]
if urllib_ver is None:
return False
if urllib_ver == 'dev':
return True
return StrictVersion(urllib_ver) > StrictVersion('1.5')

View File

@ -1,41 +1,24 @@
import six
import requests.adapters
import socket
from six.moves import http_client as httplib
from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import requests.adapters
import urllib3
import urllib3.connection
from .. import constants
from .basehttpadapter import BaseHTTPAdapter
RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
class UnixHTTPResponse(httplib.HTTPResponse, object):
def __init__(self, sock, *args, **kwargs):
disable_buffering = kwargs.pop('disable_buffering', False)
if six.PY2:
# FIXME: We may need to disable buffering on Py3 as well,
# but there's no clear way to do it at the moment. See:
# https://github.com/docker/docker-py/issues/1799
kwargs['buffering'] = not disable_buffering
super(UnixHTTPResponse, self).__init__(sock, *args, **kwargs)
class UnixHTTPConnection(httplib.HTTPConnection, object):
class UnixHTTPConnection(urllib3.connection.HTTPConnection):
def __init__(self, base_url, unix_socket, timeout=60):
super(UnixHTTPConnection, self).__init__(
super().__init__(
'localhost', timeout=timeout
)
self.base_url = base_url
self.unix_socket = unix_socket
self.timeout = timeout
self.disable_buffering = False
def connect(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@ -43,21 +26,10 @@ class UnixHTTPConnection(httplib.HTTPConnection, object):
sock.connect(self.unix_socket)
self.sock = sock
def putheader(self, header, *values):
super(UnixHTTPConnection, self).putheader(header, *values)
if header == 'Connection' and 'Upgrade' in values:
self.disable_buffering = True
def response_class(self, sock, *args, **kwargs):
if self.disable_buffering:
kwargs['disable_buffering'] = True
return UnixHTTPResponse(sock, *args, **kwargs)
class UnixHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
def __init__(self, base_url, socket_path, timeout=60, maxsize=10):
super(UnixHTTPConnectionPool, self).__init__(
super().__init__(
'localhost', timeout=timeout, maxsize=maxsize
)
self.base_url = base_url
@ -74,19 +46,22 @@ class UnixHTTPAdapter(BaseHTTPAdapter):
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['pools',
'socket_path',
'timeout']
'timeout',
'max_pool_size']
def __init__(self, socket_url, timeout=60,
pool_connections=constants.DEFAULT_NUM_POOLS):
pool_connections=constants.DEFAULT_NUM_POOLS,
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE):
socket_path = socket_url.replace('http+unix://', '')
if not socket_path.startswith('/'):
socket_path = '/' + socket_path
socket_path = f"/{socket_path}"
self.socket_path = socket_path
self.timeout = timeout
self.max_pool_size = max_pool_size
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(UnixHTTPAdapter, self).__init__()
super().__init__()
def get_connection(self, url, proxies=None):
with self.pools.lock:
@ -95,7 +70,8 @@ class UnixHTTPAdapter(BaseHTTPAdapter):
return pool
pool = UnixHTTPConnectionPool(
url, self.socket_path, self.timeout
url, self.socket_path, self.timeout,
maxsize=self.max_pool_size
)
self.pools[url] = pool

View File

@ -1,12 +1,24 @@
# flake8: noqa
from .containers import ContainerConfig, HostConfig, LogConfig, Ulimit
from .containers import ContainerConfig, DeviceRequest, HostConfig, LogConfig, Ulimit
from .daemon import CancellableStream
from .healthcheck import Healthcheck
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig
from .services import (
ConfigReference, ContainerSpec, DNSConfig, DriverConfig, EndpointSpec,
Mount, Placement, PlacementPreference, Privileges, Resources,
RestartPolicy, RollbackConfig, SecretReference, ServiceMode, TaskTemplate,
UpdateConfig
ConfigReference,
ContainerSpec,
DNSConfig,
DriverConfig,
EndpointSpec,
Mount,
NetworkAttachmentConfig,
Placement,
PlacementPreference,
Privileges,
Resources,
RestartPolicy,
RollbackConfig,
SecretReference,
ServiceMode,
TaskTemplate,
UpdateConfig,
)
from .swarm import SwarmSpec, SwarmExternalCA
from .swarm import SwarmExternalCA, SwarmSpec

View File

@ -1,7 +1,4 @@
import six
class DictType(dict):
def __init__(self, init):
for k, v in six.iteritems(init):
for k, v in init.items():
self[k] = v

View File

@ -1,16 +1,22 @@
import six
from .. import errors
from ..utils.utils import (
convert_port_bindings, convert_tmpfs_mounts, convert_volume_binds,
format_environment, format_extra_hosts, normalize_links, parse_bytes,
parse_devices, split_command, version_gte, version_lt,
convert_port_bindings,
convert_tmpfs_mounts,
convert_volume_binds,
format_environment,
format_extra_hosts,
normalize_links,
parse_bytes,
parse_devices,
split_command,
version_gte,
version_lt,
)
from .base import DictType
from .healthcheck import Healthcheck
class LogConfigTypesEnum(object):
class LogConfigTypesEnum:
_values = (
'json-file',
'syslog',
@ -50,8 +56,11 @@ class LogConfig(DictType):
>>> container = client.create_container('busybox', 'true',
... host_config=hc)
>>> client.inspect_container(container)['HostConfig']['LogConfig']
{'Type': 'json-file', 'Config': {'labels': 'production_status,geo', 'max-size': '1g'}}
""" # noqa: E501
{
'Type': 'json-file',
'Config': {'labels': 'production_status,geo', 'max-size': '1g'}
}
"""
types = LogConfigTypesEnum
def __init__(self, **kwargs):
@ -61,7 +70,7 @@ class LogConfig(DictType):
if config and not isinstance(config, dict):
raise ValueError("LogConfig.config must be a dictionary")
super(LogConfig, self).__init__({
super().__init__({
'Type': log_driver_type,
'Config': config
})
@ -97,8 +106,8 @@ class Ulimit(DictType):
Args:
name (str): Which ulimit will this apply to. A list of valid names can
be found `here <http://tinyurl.me/ZWRkM2Ztwlykf>`_.
name (str): Which ulimit will this apply to. The valid names can be
found in '/etc/security/limits.conf' on a gnu/linux system.
soft (int): The soft limit for this ulimit. Optional.
hard (int): The hard limit for this ulimit. Optional.
@ -117,13 +126,13 @@ class Ulimit(DictType):
name = kwargs.get('name', kwargs.get('Name'))
soft = kwargs.get('soft', kwargs.get('Soft'))
hard = kwargs.get('hard', kwargs.get('Hard'))
if not isinstance(name, six.string_types):
if not isinstance(name, str):
raise ValueError("Ulimit.name must be a string")
if soft and not isinstance(soft, int):
raise ValueError("Ulimit.soft must be an integer")
if hard and not isinstance(hard, int):
raise ValueError("Ulimit.hard must be an integer")
super(Ulimit, self).__init__({
super().__init__({
'Name': name,
'Soft': soft,
'Hard': hard
@ -154,6 +163,104 @@ class Ulimit(DictType):
self['Hard'] = value
class DeviceRequest(DictType):
"""
Create a device request to be used with
:py:meth:`~docker.api.container.ContainerApiMixin.create_host_config`.
Args:
driver (str): Which driver to use for this device. Optional.
count (int): Number or devices to request. Optional.
Set to -1 to request all available devices.
device_ids (list): List of strings for device IDs. Optional.
Set either ``count`` or ``device_ids``.
capabilities (list): List of lists of strings to request
capabilities. Optional. The global list acts like an OR,
and the sub-lists are AND. The driver will try to satisfy
one of the sub-lists.
Available capabilities for the ``nvidia`` driver can be found
`here <https://github.com/NVIDIA/nvidia-container-runtime>`_.
options (dict): Driver-specific options. Optional.
"""
def __init__(self, **kwargs):
driver = kwargs.get('driver', kwargs.get('Driver'))
count = kwargs.get('count', kwargs.get('Count'))
device_ids = kwargs.get('device_ids', kwargs.get('DeviceIDs'))
capabilities = kwargs.get('capabilities', kwargs.get('Capabilities'))
options = kwargs.get('options', kwargs.get('Options'))
if driver is None:
driver = ''
elif not isinstance(driver, str):
raise ValueError('DeviceRequest.driver must be a string')
if count is None:
count = 0
elif not isinstance(count, int):
raise ValueError('DeviceRequest.count must be an integer')
if device_ids is None:
device_ids = []
elif not isinstance(device_ids, list):
raise ValueError('DeviceRequest.device_ids must be a list')
if capabilities is None:
capabilities = []
elif not isinstance(capabilities, list):
raise ValueError('DeviceRequest.capabilities must be a list')
if options is None:
options = {}
elif not isinstance(options, dict):
raise ValueError('DeviceRequest.options must be a dict')
super().__init__({
'Driver': driver,
'Count': count,
'DeviceIDs': device_ids,
'Capabilities': capabilities,
'Options': options
})
@property
def driver(self):
return self['Driver']
@driver.setter
def driver(self, value):
self['Driver'] = value
@property
def count(self):
return self['Count']
@count.setter
def count(self, value):
self['Count'] = value
@property
def device_ids(self):
return self['DeviceIDs']
@device_ids.setter
def device_ids(self, value):
self['DeviceIDs'] = value
@property
def capabilities(self):
return self['Capabilities']
@capabilities.setter
def capabilities(self, value):
self['Capabilities'] = value
@property
def options(self):
return self['Options']
@options.setter
def options(self, value):
self['Options'] = value
class HostConfig(dict):
def __init__(self, version, binds=None, port_bindings=None,
lxc_conf=None, publish_all_ports=False, links=None,
@ -176,7 +283,8 @@ class HostConfig(dict):
volume_driver=None, cpu_count=None, cpu_percent=None,
nano_cpus=None, cpuset_mems=None, runtime=None, mounts=None,
cpu_rt_period=None, cpu_rt_runtime=None,
device_cgroup_rules=None):
device_cgroup_rules=None, device_requests=None,
cgroupns=None):
if mem_limit is not None:
self['Memory'] = parse_bytes(mem_limit)
@ -199,7 +307,7 @@ class HostConfig(dict):
self['MemorySwappiness'] = mem_swappiness
if shm_size is not None:
if isinstance(shm_size, six.string_types):
if isinstance(shm_size, str):
shm_size = parse_bytes(shm_size)
self['ShmSize'] = shm_size
@ -236,10 +344,11 @@ class HostConfig(dict):
if dns_search:
self['DnsSearch'] = dns_search
if network_mode:
self['NetworkMode'] = network_mode
elif network_mode is None:
self['NetworkMode'] = 'default'
if network_mode == 'host' and port_bindings:
raise host_config_incompatible_error(
'network_mode', 'host', 'port_bindings'
)
self['NetworkMode'] = network_mode or 'default'
if restart_policy:
if not isinstance(restart_policy, dict):
@ -259,7 +368,7 @@ class HostConfig(dict):
self['Devices'] = parse_devices(devices)
if group_add:
self['GroupAdd'] = [six.text_type(grp) for grp in group_add]
self['GroupAdd'] = [str(grp) for grp in group_add]
if dns is not None:
self['Dns'] = dns
@ -279,11 +388,11 @@ class HostConfig(dict):
if not isinstance(sysctls, dict):
raise host_config_type_error('sysctls', sysctls, 'dict')
self['Sysctls'] = {}
for k, v in six.iteritems(sysctls):
self['Sysctls'][k] = six.text_type(v)
for k, v in sysctls.items():
self['Sysctls'][k] = str(v)
if volumes_from is not None:
if isinstance(volumes_from, six.string_types):
if isinstance(volumes_from, str):
volumes_from = volumes_from.split(',')
self['VolumesFrom'] = volumes_from
@ -305,7 +414,7 @@ class HostConfig(dict):
if isinstance(lxc_conf, dict):
formatted = []
for k, v in six.iteritems(lxc_conf):
for k, v in lxc_conf.items():
formatted.append({'Key': k, 'Value': str(v)})
lxc_conf = formatted
@ -460,7 +569,7 @@ class HostConfig(dict):
self["PidsLimit"] = pids_limit
if isolation:
if not isinstance(isolation, six.string_types):
if not isinstance(isolation, str):
raise host_config_type_error('isolation', isolation, 'string')
if version_lt(version, '1.24'):
raise host_config_version_error('isolation', '1.24')
@ -510,7 +619,7 @@ class HostConfig(dict):
self['CpuPercent'] = cpu_percent
if nano_cpus:
if not isinstance(nano_cpus, six.integer_types):
if not isinstance(nano_cpus, int):
raise host_config_type_error('nano_cpus', nano_cpus, 'int')
if version_lt(version, '1.25'):
raise host_config_version_error('nano_cpus', '1.25')
@ -536,21 +645,44 @@ class HostConfig(dict):
)
self['DeviceCgroupRules'] = device_cgroup_rules
if device_requests is not None:
if version_lt(version, '1.40'):
raise host_config_version_error('device_requests', '1.40')
if not isinstance(device_requests, list):
raise host_config_type_error(
'device_requests', device_requests, 'list'
)
self['DeviceRequests'] = []
for req in device_requests:
if not isinstance(req, DeviceRequest):
req = DeviceRequest(**req)
self['DeviceRequests'].append(req)
if cgroupns:
self['CgroupnsMode'] = cgroupns
def host_config_type_error(param, param_value, expected):
error_msg = 'Invalid type for {0} param: expected {1} but found {2}'
return TypeError(error_msg.format(param, expected, type(param_value)))
return TypeError(
f'Invalid type for {param} param: expected {expected} '
f'but found {type(param_value)}'
)
def host_config_version_error(param, version, less_than=True):
operator = '<' if less_than else '>'
error_msg = '{0} param is not supported in API versions {1} {2}'
return errors.InvalidVersion(error_msg.format(param, operator, version))
return errors.InvalidVersion(
f'{param} param is not supported in API versions {operator} {version}',
)
def host_config_value_error(param, param_value):
error_msg = 'Invalid value for {0} param: {1}'
return ValueError(error_msg.format(param, param_value))
return ValueError(f'Invalid value for {param} param: {param_value}')
def host_config_incompatible_error(param, param_value, incompatible_param):
return errors.InvalidArgument(
f'\"{param_value}\" {param} is incompatible with {incompatible_param}'
)
class ContainerConfig(dict):
@ -580,17 +712,17 @@ class ContainerConfig(dict):
'version 1.29'
)
if isinstance(command, six.string_types):
if isinstance(command, str):
command = split_command(command)
if isinstance(entrypoint, six.string_types):
if isinstance(entrypoint, str):
entrypoint = split_command(entrypoint)
if isinstance(environment, dict):
environment = format_environment(environment)
if isinstance(labels, list):
labels = dict((lbl, six.text_type('')) for lbl in labels)
labels = {lbl: '' for lbl in labels}
if isinstance(ports, list):
exposed_ports = {}
@ -601,10 +733,10 @@ class ContainerConfig(dict):
if len(port_definition) == 2:
proto = port_definition[1]
port = port_definition[0]
exposed_ports['{0}/{1}'.format(port, proto)] = {}
exposed_ports[f'{port}/{proto}'] = {}
ports = exposed_ports
if isinstance(volumes, six.string_types):
if isinstance(volumes, str):
volumes = [volumes, ]
if isinstance(volumes, list):
@ -633,7 +765,7 @@ class ContainerConfig(dict):
'Hostname': hostname,
'Domainname': domainname,
'ExposedPorts': ports,
'User': six.text_type(user) if user is not None else None,
'User': str(user) if user is not None else None,
'Tty': tty,
'OpenStdin': stdin_open,
'StdinOnce': stdin_once,

View File

@ -1,14 +1,11 @@
import socket
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3
from ..errors import DockerException
class CancellableStream(object):
class CancellableStream:
"""
Stream wrapper for real-time events, logs, etc. from the server.
@ -31,9 +28,9 @@ class CancellableStream(object):
try:
return next(self._stream)
except urllib3.exceptions.ProtocolError:
raise StopIteration
except socket.error:
raise StopIteration
raise StopIteration from None
except OSError:
raise StopIteration from None
next = __next__

View File

@ -1,7 +1,5 @@
from .base import DictType
import six
class Healthcheck(DictType):
"""
@ -14,7 +12,7 @@ class Healthcheck(DictType):
- Empty list: Inherit healthcheck from parent image
- ``["NONE"]``: Disable healthcheck
- ``["CMD", args...]``: exec arguments directly.
- ``["CMD-SHELL", command]``: RUn command in the system's
- ``["CMD-SHELL", command]``: Run command in the system's
default shell.
If a string is provided, it will be used as a ``CMD-SHELL``
@ -23,15 +21,15 @@ class Healthcheck(DictType):
should be 0 or at least 1000000 (1 ms).
timeout (int): The time to wait before considering the check to
have hung. It should be 0 or at least 1000000 (1 ms).
retries (integer): The number of consecutive failures needed to
retries (int): The number of consecutive failures needed to
consider a container as unhealthy.
start_period (integer): Start period for the container to
start_period (int): Start period for the container to
initialize before starting health-retries countdown in
nanoseconds. It should be 0 or at least 1000000 (1 ms).
"""
def __init__(self, **kwargs):
test = kwargs.get('test', kwargs.get('Test'))
if isinstance(test, six.string_types):
if isinstance(test, str):
test = ["CMD-SHELL", test]
interval = kwargs.get('interval', kwargs.get('Interval'))
@ -39,7 +37,7 @@ class Healthcheck(DictType):
retries = kwargs.get('retries', kwargs.get('Retries'))
start_period = kwargs.get('start_period', kwargs.get('StartPeriod'))
super(Healthcheck, self).__init__({
super().__init__({
'Test': test,
'Interval': interval,
'Timeout': timeout,
@ -53,6 +51,8 @@ class Healthcheck(DictType):
@test.setter
def test(self, value):
if isinstance(value, str):
value = ["CMD-SHELL", value]
self['Test'] = value
@property

View File

@ -4,7 +4,8 @@ from ..utils import normalize_links, version_lt
class EndpointConfig(dict):
def __init__(self, version, aliases=None, links=None, ipv4_address=None,
ipv6_address=None, link_local_ips=None):
ipv6_address=None, link_local_ips=None, driver_opt=None,
mac_address=None):
if version_lt(version, '1.22'):
raise errors.InvalidVersion(
'Endpoint config is not supported for API version < 1.22'
@ -23,6 +24,13 @@ class EndpointConfig(dict):
if ipv6_address:
ipam_config['IPv6Address'] = ipv6_address
if mac_address:
if version_lt(version, '1.25'):
raise errors.InvalidVersion(
'mac_address is not supported for API version < 1.25'
)
self['MacAddress'] = mac_address
if link_local_ips is not None:
if version_lt(version, '1.24'):
raise errors.InvalidVersion(
@ -33,6 +41,15 @@ class EndpointConfig(dict):
if ipam_config:
self['IPAMConfig'] = ipam_config
if driver_opt:
if version_lt(version, '1.32'):
raise errors.InvalidVersion(
'DriverOpts is not supported for API version < 1.32'
)
if not isinstance(driver_opt, dict):
raise TypeError('driver_opt must be a dictionary')
self['DriverOpts'] = driver_opt
class NetworkingConfig(dict):
def __init__(self, endpoints_config=None):

View File

@ -1,10 +1,12 @@
import six
from .. import errors
from ..constants import IS_WINDOWS_PLATFORM
from ..utils import (
check_resource, format_environment, format_extra_hosts, parse_bytes,
split_command, convert_service_networks,
check_resource,
convert_service_networks,
format_environment,
format_extra_hosts,
parse_bytes,
split_command,
)
@ -26,11 +28,12 @@ class TaskTemplate(dict):
placement (Placement): Placement instructions for the scheduler.
If a list is passed instead, it is assumed to be a list of
constraints as part of a :py:class:`Placement` object.
networks (:py:class:`list`): List of network names or IDs to attach
the containers to.
networks (:py:class:`list`): List of network names or IDs or
:py:class:`NetworkAttachmentConfig` to attach the service to.
force_update (int): A counter that triggers an update even if no
relevant parameters have been changed.
"""
def __init__(self, container_spec, resources=None, restart_policy=None,
placement=None, log_driver=None, networks=None,
force_update=None):
@ -112,16 +115,24 @@ class ContainerSpec(dict):
containers. Only used for Windows containers.
init (boolean): Run an init inside the container that forwards signals
and reaps processes.
cap_add (:py:class:`list`): A list of kernel capabilities to add to the
default set for the container.
cap_drop (:py:class:`list`): A list of kernel capabilities to drop from
the default set for the container.
sysctls (:py:class:`dict`): A dict of sysctl values to add to
the container
"""
def __init__(self, image, command=None, args=None, hostname=None, env=None,
workdir=None, user=None, labels=None, mounts=None,
stop_grace_period=None, secrets=None, tty=None, groups=None,
open_stdin=None, read_only=None, stop_signal=None,
healthcheck=None, hosts=None, dns_config=None, configs=None,
privileges=None, isolation=None, init=None):
privileges=None, isolation=None, init=None, cap_add=None,
cap_drop=None, sysctls=None):
self['Image'] = image
if isinstance(command, six.string_types):
if isinstance(command, str):
command = split_command(command)
self['Command'] = command
self['Args'] = args
@ -151,7 +162,7 @@ class ContainerSpec(dict):
if mounts is not None:
parsed_mounts = []
for mount in mounts:
if isinstance(mount, six.string_types):
if isinstance(mount, str):
parsed_mounts.append(Mount.parse_mount_string(mount))
else:
# If mount already parsed
@ -188,6 +199,24 @@ class ContainerSpec(dict):
if init is not None:
self['Init'] = init
if cap_add is not None:
if not isinstance(cap_add, list):
raise TypeError('cap_add must be a list')
self['CapabilityAdd'] = cap_add
if cap_drop is not None:
if not isinstance(cap_drop, list):
raise TypeError('cap_drop must be a list')
self['CapabilityDrop'] = cap_drop
if sysctls is not None:
if not isinstance(sysctls, dict):
raise TypeError('sysctls must be a dict')
self['Sysctls'] = sysctls
class Mount(dict):
"""
@ -213,18 +242,20 @@ class Mount(dict):
for the ``volume`` type.
driver_config (DriverConfig): Volume driver configuration. Only valid
for the ``volume`` type.
subpath (str): Path inside a volume to mount instead of the volume root.
tmpfs_size (int or string): The size for the tmpfs mount in bytes.
tmpfs_mode (int): The permission mode for the tmpfs mount.
"""
def __init__(self, target, source, type='volume', read_only=False,
consistency=None, propagation=None, no_copy=False,
labels=None, driver_config=None, tmpfs_size=None,
tmpfs_mode=None):
tmpfs_mode=None, subpath=None):
self['Target'] = target
self['Source'] = source
if type not in ('bind', 'volume', 'tmpfs', 'npipe'):
raise errors.InvalidArgument(
'Unsupported mount type: "{}"'.format(type)
f'Unsupported mount type: "{type}"'
)
self['Type'] = type
self['ReadOnly'] = read_only
@ -237,7 +268,7 @@ class Mount(dict):
self['BindOptions'] = {
'Propagation': propagation
}
if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode]):
if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode, subpath]):
raise errors.InvalidArgument(
'Incompatible options have been provided for the bind '
'type mount.'
@ -250,6 +281,8 @@ class Mount(dict):
volume_opts['Labels'] = labels
if driver_config:
volume_opts['DriverConfig'] = driver_config
if subpath:
volume_opts['Subpath'] = subpath
if volume_opts:
self['VolumeOptions'] = volume_opts
if any([propagation, tmpfs_size, tmpfs_mode]):
@ -260,7 +293,7 @@ class Mount(dict):
elif type == 'tmpfs':
tmpfs_opts = {}
if tmpfs_mode:
if not isinstance(tmpfs_mode, six.integer_types):
if not isinstance(tmpfs_mode, int):
raise errors.InvalidArgument(
'tmpfs_mode must be an integer'
)
@ -280,7 +313,7 @@ class Mount(dict):
parts = string.split(':')
if len(parts) > 3:
raise errors.InvalidArgument(
'Invalid mount format "{0}"'.format(string)
f'Invalid mount format "{string}"'
)
if len(parts) == 1:
return cls(target=parts[0], source=None)
@ -316,6 +349,7 @@ class Resources(dict):
``{ resource_name: resource_value }``. Alternatively, a list of
of resource specifications as defined by the Engine API.
"""
def __init__(self, cpu_limit=None, mem_limit=None, cpu_reservation=None,
mem_reservation=None, generic_resources=None):
limits = {}
@ -344,19 +378,19 @@ def _convert_generic_resources_dict(generic_resources):
if not isinstance(generic_resources, dict):
raise errors.InvalidArgument(
'generic_resources must be a dict or a list '
' (found {})'.format(type(generic_resources))
f'(found {type(generic_resources)})'
)
resources = []
for kind, value in six.iteritems(generic_resources):
for kind, value in generic_resources.items():
resource_type = None
if isinstance(value, int):
resource_type = 'DiscreteResourceSpec'
elif isinstance(value, str):
resource_type = 'NamedResourceSpec'
else:
kv = {kind: value}
raise errors.InvalidArgument(
'Unsupported generic resource reservation '
'type: {}'.format({kind: value})
f'Unsupported generic resource reservation type: {kv}'
)
resources.append({
resource_type: {'Kind': kind, 'Value': value}
@ -384,8 +418,9 @@ class UpdateConfig(dict):
an update before the failure action is invoked, specified as a
floating point number between 0 and 1. Default: 0
order (string): Specifies the order of operations when rolling out an
updated task. Either ``start_first`` or ``stop_first`` are accepted.
updated task. Either ``start-first`` or ``stop-first`` are accepted.
"""
def __init__(self, parallelism=0, delay=None, failure_action='continue',
monitor=None, max_failure_ratio=None, order=None):
self['Parallelism'] = parallelism
@ -421,7 +456,8 @@ class UpdateConfig(dict):
class RollbackConfig(UpdateConfig):
"""
Used to specify the way containe rollbacks should be performed by a service
Used to specify the way container rollbacks should be performed by a
service
Args:
parallelism (int): Maximum number of tasks to be rolled back in one
@ -437,13 +473,13 @@ class RollbackConfig(UpdateConfig):
a rollback before the failure action is invoked, specified as a
floating point number between 0 and 1. Default: 0
order (string): Specifies the order of operations when rolling out a
rolled back task. Either ``start_first`` or ``stop_first`` are
rolled back task. Either ``start-first`` or ``stop-first`` are
accepted.
"""
pass
class RestartConditionTypesEnum(object):
class RestartConditionTypesEnum:
_values = (
'none',
'on-failure',
@ -474,7 +510,7 @@ class RestartPolicy(dict):
max_attempts=0, window=0):
if condition not in self.condition_types._values:
raise TypeError(
'Invalid RestartPolicy condition {0}'.format(condition)
f'Invalid RestartPolicy condition {condition}'
)
self['Condition'] = condition
@ -496,6 +532,7 @@ class DriverConfig(dict):
name (string): Name of the driver to use.
options (dict): Driver-specific options. Default: ``None``.
"""
def __init__(self, name, options=None):
self['Name'] = name
if options:
@ -517,6 +554,7 @@ class EndpointSpec(dict):
is ``(target_port [, protocol [, publish_mode]])``.
Ports can only be provided if the ``vip`` resolution mode is used.
"""
def __init__(self, mode=None, ports=None):
if ports:
self['Ports'] = convert_service_ports(ports)
@ -533,7 +571,7 @@ def convert_service_ports(ports):
)
result = []
for k, v in six.iteritems(ports):
for k, v in ports.items():
port_spec = {
'Protocol': 'tcp',
'PublishedPort': k
@ -559,38 +597,71 @@ def convert_service_ports(ports):
class ServiceMode(dict):
"""
Indicate whether a service should be deployed as a replicated or global
service, and associated parameters
Indicate whether a service or a job should be deployed as a replicated
or global service, and associated parameters
Args:
mode (string): Can be either ``replicated`` or ``global``
mode (string): Can be either ``replicated``, ``global``,
``replicated-job`` or ``global-job``
replicas (int): Number of replicas. For replicated services only.
concurrency (int): Number of concurrent jobs. For replicated job
services only.
"""
def __init__(self, mode, replicas=None):
if mode not in ('replicated', 'global'):
raise errors.InvalidArgument(
'mode must be either "replicated" or "global"'
)
if mode != 'replicated' and replicas is not None:
raise errors.InvalidArgument(
'replicas can only be used for replicated mode'
)
self[mode] = {}
if replicas is not None:
self[mode]['Replicas'] = replicas
@property
def mode(self):
if 'global' in self:
return 'global'
return 'replicated'
def __init__(self, mode, replicas=None, concurrency=None):
replicated_modes = ('replicated', 'replicated-job')
supported_modes = replicated_modes + ('global', 'global-job')
if mode not in supported_modes:
raise errors.InvalidArgument(
'mode must be either "replicated", "global", "replicated-job"'
' or "global-job"'
)
if mode not in replicated_modes:
if replicas is not None:
raise errors.InvalidArgument(
'replicas can only be used for "replicated" or'
' "replicated-job" mode'
)
if concurrency is not None:
raise errors.InvalidArgument(
'concurrency can only be used for "replicated-job" mode'
)
service_mode = self._convert_mode(mode)
self.mode = service_mode
self[service_mode] = {}
if replicas is not None:
if mode == 'replicated':
self[service_mode]['Replicas'] = replicas
if mode == 'replicated-job':
self[service_mode]['MaxConcurrent'] = concurrency or 1
self[service_mode]['TotalCompletions'] = replicas
@staticmethod
def _convert_mode(original_mode):
if original_mode == 'global-job':
return 'GlobalJob'
if original_mode == 'replicated-job':
return 'ReplicatedJob'
return original_mode
@property
def replicas(self):
if self.mode != 'replicated':
return None
if 'replicated' in self:
return self['replicated'].get('Replicas')
if 'ReplicatedJob' in self:
return self['ReplicatedJob'].get('TotalCompletions')
return None
class SecretReference(dict):
"""
@ -659,10 +730,13 @@ class Placement(dict):
are provided in order from highest to lowest precedence and
are expressed as ``(strategy, descriptor)`` tuples. See
:py:class:`PlacementPreference` for details.
maxreplicas (int): Maximum number of replicas per node
platforms (:py:class:`list` of tuple): A list of platforms
expressed as ``(arch, os)`` tuples
"""
def __init__(self, constraints=None, preferences=None, platforms=None):
def __init__(self, constraints=None, preferences=None, platforms=None,
maxreplicas=None):
if constraints is not None:
self['Constraints'] = constraints
if preferences is not None:
@ -671,6 +745,8 @@ class Placement(dict):
if isinstance(pref, tuple):
pref = PlacementPreference(*pref)
self['Preferences'].append(pref)
if maxreplicas is not None:
self['MaxReplicas'] = maxreplicas
if platforms:
self['Platforms'] = []
for plat in platforms:
@ -691,11 +767,12 @@ class PlacementPreference(dict):
the scheduler will try to spread tasks evenly over groups of
nodes identified by this label.
"""
def __init__(self, strategy, descriptor):
if strategy != 'spread':
raise errors.InvalidArgument(
'PlacementPreference strategy value is invalid ({}):'
' must be "spread".'.format(strategy)
f'PlacementPreference strategy value is invalid ({strategy}): '
'must be "spread".'
)
self['Spread'] = {'SpreadDescriptor': descriptor}
@ -712,6 +789,7 @@ class DNSConfig(dict):
options (:py:class:`list`): A list of internal resolver variables
to be modified (e.g., ``debug``, ``ndots:3``, etc.).
"""
def __init__(self, nameservers=None, search=None, options=None):
self['Nameservers'] = nameservers
self['Search'] = search
@ -742,6 +820,7 @@ class Privileges(dict):
selinux_type (string): SELinux type label
selinux_level (string): SELinux level label
"""
def __init__(self, credentialspec_file=None, credentialspec_registry=None,
selinux_disable=None, selinux_user=None, selinux_role=None,
selinux_type=None, selinux_level=None):
@ -770,3 +849,22 @@ class Privileges(dict):
if len(selinux_context) > 0:
self['SELinuxContext'] = selinux_context
class NetworkAttachmentConfig(dict):
"""
Network attachment options for a service.
Args:
target (str): The target network for attachment.
Can be a network name or ID.
aliases (:py:class:`list`): A list of discoverable alternate names
for the service.
options (:py:class:`dict`): Driver attachment options for the
network target.
"""
def __init__(self, target, aliases=None, options=None):
self['Target'] = target
self['Aliases'] = aliases
self['DriverOpts'] = options

View File

@ -1,13 +1,28 @@
# flake8: noqa
from .build import create_archive, exclude_paths, mkbuildcontext, tar
from .build import create_archive, exclude_paths, match_tag, mkbuildcontext, tar
from .decorators import check_resource, minimum_version, update_headers
from .utils import (
compare_version, convert_port_bindings, convert_volume_binds,
parse_repository_tag, parse_host,
kwargs_from_env, convert_filters, datetime_to_timestamp,
create_host_config, parse_bytes, parse_env_file, version_lt,
version_gte, decode_json_header, split_command, create_ipam_config,
create_ipam_pool, parse_devices, normalize_links, convert_service_networks,
format_environment, format_extra_hosts
compare_version,
convert_filters,
convert_port_bindings,
convert_service_networks,
convert_volume_binds,
create_host_config,
create_ipam_config,
create_ipam_pool,
datetime_to_timestamp,
decode_json_header,
format_environment,
format_extra_hosts,
kwargs_from_env,
normalize_links,
parse_bytes,
parse_devices,
parse_env_file,
parse_host,
parse_repository_tag,
split_command,
version_gte,
version_lt,
)

View File

@ -4,13 +4,19 @@ import re
import tarfile
import tempfile
import six
from .fnmatch import fnmatch
from ..constants import IS_WINDOWS_PLATFORM
from .fnmatch import fnmatch
_SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')
_TAG = re.compile(
r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*"
r"(?::[0-9]+)?(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*"
r"(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$"
)
def match_tag(tag: str) -> bool:
return bool(_TAG.match(tag))
def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
@ -44,7 +50,7 @@ def exclude_paths(root, patterns, dockerfile=None):
if dockerfile is None:
dockerfile = 'Dockerfile'
patterns.append('!' + dockerfile)
patterns.append(f"!{dockerfile}")
pm = PatternMatcher(patterns)
return set(pm.walk(root))
@ -69,7 +75,7 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
if files is None:
files = build_file_list(root)
extra_names = set(e[0] for e in extra_files)
extra_names = {e[0] for e in extra_files}
for path in files:
if path in extra_names:
# Extra files override context files with the same name
@ -95,18 +101,19 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
try:
with open(full_path, 'rb') as f:
t.addfile(i, f)
except IOError:
raise IOError(
'Can not read file in context: {}'.format(full_path)
)
except OSError as oe:
raise OSError(
f'Can not read file in context: {full_path}'
) from oe
else:
# Directories, FIFOs, symlinks... don't need to be read.
t.addfile(i, None)
for name, contents in extra_files:
info = tarfile.TarInfo(name)
info.size = len(contents)
t.addfile(info, io.BytesIO(contents.encode('utf-8')))
contents_encoded = contents.encode('utf-8')
info.size = len(contents_encoded)
t.addfile(info, io.BytesIO(contents_encoded))
t.close()
fileobj.seek(0)
@ -118,12 +125,8 @@ def mkbuildcontext(dockerfile):
t = tarfile.open(mode='w', fileobj=f)
if isinstance(dockerfile, io.StringIO):
dfinfo = tarfile.TarInfo('Dockerfile')
if six.PY3:
raise TypeError('Please use io.BytesIO to create in-memory '
'Dockerfiles with Python 3')
else:
dfinfo.size = len(dockerfile.getvalue())
dockerfile.seek(0)
elif isinstance(dockerfile, io.BytesIO):
dfinfo = tarfile.TarInfo('Dockerfile')
dfinfo.size = len(dockerfile.getvalue())
@ -153,7 +156,7 @@ def walk(root, patterns, default=True):
# Heavily based on
# https://github.com/moby/moby/blob/master/pkg/fileutils/fileutils.go
class PatternMatcher(object):
class PatternMatcher:
def __init__(self, patterns):
self.patterns = list(filter(
lambda p: p.dirs, [Pattern(p) for p in patterns]
@ -185,7 +188,7 @@ class PatternMatcher(object):
fpath = os.path.join(
os.path.relpath(current_dir, root), f
)
if fpath.startswith('.' + os.path.sep):
if fpath.startswith(f".{os.path.sep}"):
fpath = fpath[2:]
match = self.matches(fpath)
if not match:
@ -211,13 +214,12 @@ class PatternMatcher(object):
break
if skip:
continue
for sub in rec_walk(cur):
yield sub
yield from rec_walk(cur)
return rec_walk(root)
class Pattern(object):
class Pattern:
def __init__(self, pattern_str):
self.exclusion = False
if pattern_str.startswith('!'):
@ -230,6 +232,9 @@ class Pattern(object):
@classmethod
def normalize(cls, p):
# Remove trailing spaces
p = p.strip()
# Leading and trailing slashes are not relevant. Yes,
# "foo.py/" must exclude the "foo.py" regular file. "."
# components are not relevant either, even if the whole

View File

@ -18,11 +18,11 @@ def find_config_file(config_path=None):
os.path.join(home_dir(), LEGACY_DOCKER_CONFIG_FILENAME), # 4
]))
log.debug("Trying paths: {0}".format(repr(paths)))
log.debug(f"Trying paths: {repr(paths)}")
for path in paths:
if os.path.exists(path):
log.debug("Found file at path: {0}".format(path))
log.debug(f"Found file at path: {path}")
return path
log.debug("No config file found")
@ -57,7 +57,7 @@ def load_general_config(config_path=None):
try:
with open(config_file) as f:
return json.load(f)
except (IOError, ValueError) as e:
except (OSError, ValueError) as e:
# In the case of a legacy `.dockercfg` file, we won't
# be able to load any JSON data.
log.debug(e)

View File

@ -27,9 +27,7 @@ def minimum_version(version):
def wrapper(self, *args, **kwargs):
if utils.version_lt(self._version, version):
raise errors.InvalidVersion(
'{0} is not available for version < {1}'.format(
f.__name__, version
)
f'{f.__name__} is not available for version < {version}',
)
return f(self, *args, **kwargs)
return wrapper

View File

@ -79,18 +79,18 @@ def translate(pat):
i = i + 1
if i >= n:
# is "**EOF" - to align with .gitignore just accept all
res = res + '.*'
res = f"{res}.*"
else:
# is "**"
# Note that this allows for any # of /'s (even 0) because
# the .* will eat everything, even /'s
res = res + '(.*/)?'
res = f"{res}(.*/)?"
else:
# is "*" so map it to anything but "/"
res = res + '[^/]*'
res = f"{res}[^/]*"
elif c == '?':
# "?" is any char except "/"
res = res + '[^/]'
res = f"{res}[^/]"
elif c == '[':
j = i
if j < n and pat[j] == '!':
@ -100,16 +100,16 @@ def translate(pat):
while j < n and pat[j] != ']':
j = j + 1
if j >= n:
res = res + '\\['
res = f"{res}\\["
else:
stuff = pat[i:j].replace('\\', '\\\\')
i = j + 1
if stuff[0] == '!':
stuff = '^' + stuff[1:]
stuff = f"^{stuff[1:]}"
elif stuff[0] == '^':
stuff = '\\' + stuff
res = '%s[%s]' % (res, stuff)
stuff = f"\\{stuff}"
res = f'{res}[{stuff}]'
else:
res = res + re.escape(c)
return res + '$'
return f"{res}$"

View File

@ -1,14 +1,8 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import json
import json.decoder
import six
from ..errors import StreamParseError
json_decoder = json.JSONDecoder()
@ -20,7 +14,7 @@ def stream_as_text(stream):
instead of byte streams.
"""
for data in stream:
if not isinstance(data, six.text_type):
if not isinstance(data, str):
data = data.decode('utf-8', 'replace')
yield data
@ -46,8 +40,8 @@ def json_stream(stream):
return split_buffer(stream, json_splitter, json_decoder.decode)
def line_splitter(buffer, separator=u'\n'):
index = buffer.find(six.text_type(separator))
def line_splitter(buffer, separator='\n'):
index = buffer.find(str(separator))
if index == -1:
return None
return buffer[:index + 1], buffer[index + 1:]
@ -61,7 +55,7 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a):
of the input.
"""
splitter = splitter or line_splitter
buffered = six.text_type('')
buffered = ''
for data in stream_as_text(stream):
buffered += data
@ -77,4 +71,4 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a):
try:
yield decoder(buffered)
except Exception as e:
raise StreamParseError(e)
raise StreamParseError(e) from e

View File

@ -3,7 +3,7 @@ import re
PORT_SPEC = re.compile(
"^" # Match full string
"(" # External part
r"((?P<host>[a-fA-F\d.:]+):)?" # Address
r"(\[?(?P<host>[a-fA-F\d.:]+)\]?:)?" # Address
r"(?P<ext>[\d]*)(-(?P<ext_end>[\d]+))?:" # External range
")?"
r"(?P<int>[\d]+)(-(?P<int_end>[\d]+))?" # Internal range
@ -49,7 +49,7 @@ def port_range(start, end, proto, randomly_available_port=False):
if not end:
return [start + proto]
if randomly_available_port:
return ['{}-{}'.format(start, end) + proto]
return [f"{start}-{end}{proto}"]
return [str(port) + proto for port in range(int(start), int(end) + 1)]

View File

@ -69,5 +69,9 @@ class ProxyConfig(dict):
return proxy_env + environment
def __str__(self):
return 'ProxyConfig(http={}, https={}, ftp={}, no_proxy={})'.format(
self.http, self.https, self.ftp, self.no_proxy)
return (
'ProxyConfig('
f'http={self.http}, https={self.https}, '
f'ftp={self.ftp}, no_proxy={self.no_proxy}'
')'
)

View File

@ -4,8 +4,6 @@ import select
import socket as pysocket
import struct
import six
try:
from ..transport import NpipeSocket
except ImportError:
@ -20,6 +18,11 @@ class SocketError(Exception):
pass
# NpipeSockets have their own error types
# pywintypes.error: (109, 'ReadFile', 'The pipe has been ended.')
NPIPE_ENDED = 109
def read(socket, n=4096):
"""
Reads at most n bytes from socket
@ -27,18 +30,33 @@ def read(socket, n=4096):
recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)
if six.PY3 and not isinstance(socket, NpipeSocket):
if not isinstance(socket, NpipeSocket):
if not hasattr(select, "poll"):
# Limited to 1024
select.select([socket], [], [])
else:
poll = select.poll()
poll.register(socket, select.POLLIN | select.POLLPRI)
poll.poll()
try:
if hasattr(socket, 'recv'):
return socket.recv(n)
if six.PY3 and isinstance(socket, getattr(pysocket, 'SocketIO')):
if isinstance(socket, pysocket.SocketIO):
return socket.read(n)
return os.read(socket.fileno(), n)
except EnvironmentError as e:
except OSError as e:
if e.errno not in recoverable_errors:
raise
except Exception as e:
is_pipe_ended = (isinstance(socket, NpipeSocket) and
len(e.args) > 0 and
e.args[0] == NPIPE_ENDED)
if is_pipe_ended:
# npipes don't support duplex sockets, so we interpret
# a PIPE_ENDED error as a close operation (0-length read).
return ''
raise
def read_exactly(socket, n):
@ -46,7 +64,7 @@ def read_exactly(socket, n):
Reads exactly n bytes from socket
Raises SocketError if there isn't enough data
"""
data = six.binary_type()
data = b""
while len(data) < n:
next_data = read(socket, n - len(data))
if not next_data:
@ -134,7 +152,7 @@ def consume_socket_output(frames, demux=False):
if demux is False:
# If the streams are multiplexed, the generator returns strings, that
# we just need to concatenate.
return six.binary_type().join(frames)
return b"".join(frames)
# If the streams are demultiplexed, the generator yields tuples
# (stdout, stderr)
@ -166,4 +184,4 @@ def demux_adaptor(stream_id, data):
elif stream_id == STDERR:
return (None, data)
else:
raise ValueError('{0} is not a valid stream'.format(stream_id))
raise ValueError(f'{stream_id} is not a valid stream')

View File

@ -1,33 +1,28 @@
import base64
import collections
import json
import os
import os.path
import shlex
import string
from datetime import datetime
from distutils.version import StrictVersion
import six
from datetime import datetime, timezone
from functools import lru_cache
from itertools import zip_longest
from urllib.parse import urlparse, urlunparse
from .. import errors
from .. import tls
from ..constants import (
BYTE_UNITS,
DEFAULT_HTTP_HOST,
DEFAULT_NPIPE,
DEFAULT_UNIX_SOCKET,
)
from ..tls import TLSConfig
if six.PY2:
from urllib import splitnport
from urlparse import urlparse
else:
from urllib.parse import splitnport, urlparse
DEFAULT_HTTP_HOST = "127.0.0.1"
DEFAULT_UNIX_SOCKET = "http+unix:///var/run/docker.sock"
DEFAULT_NPIPE = 'npipe:////./pipe/docker_engine'
BYTE_UNITS = {
'b': 1,
'k': 1024,
'm': 1024 * 1024,
'g': 1024 * 1024 * 1024
}
URLComponents = collections.namedtuple(
'URLComponents',
'scheme netloc url params query fragment',
)
def create_ipam_pool(*args, **kwargs):
@ -46,11 +41,11 @@ def create_ipam_config(*args, **kwargs):
def decode_json_header(header):
data = base64.b64decode(header)
if six.PY3:
data = data.decode('utf-8')
return json.loads(data)
@lru_cache(maxsize=None)
def compare_version(v1, v2):
"""Compare docker versions
@ -63,14 +58,20 @@ def compare_version(v1, v2):
>>> compare_version(v2, v2)
0
"""
s1 = StrictVersion(v1)
s2 = StrictVersion(v2)
if s1 == s2:
if v1 == v2:
return 0
elif s1 > s2:
# Split into `sys.version_info` like tuples.
s1 = tuple(int(p) for p in v1.split('.'))
s2 = tuple(int(p) for p in v2.split('.'))
# Compare each component, padding with 0 if necessary.
for c1, c2 in zip_longest(s1, s2, fillvalue=0):
if c1 == c2:
continue
elif c1 > c2:
return -1
else:
return 1
return 0
def version_lt(v1, v2):
@ -87,7 +88,7 @@ def _convert_port_binding(binding):
if len(binding) == 2:
result['HostPort'] = binding[1]
result['HostIp'] = binding[0]
elif isinstance(binding[0], six.string_types):
elif isinstance(binding[0], str):
result['HostIp'] = binding[0]
else:
result['HostPort'] = binding[0]
@ -111,7 +112,7 @@ def _convert_port_binding(binding):
def convert_port_bindings(port_bindings):
result = {}
for k, v in six.iteritems(port_bindings):
for k, v in iter(port_bindings.items()):
key = str(k)
if '/' not in key:
key += '/tcp'
@ -128,18 +129,17 @@ def convert_volume_binds(binds):
result = []
for k, v in binds.items():
if isinstance(k, six.binary_type):
if isinstance(k, bytes):
k = k.decode('utf-8')
if isinstance(v, dict):
if 'ro' in v and 'mode' in v:
raise ValueError(
'Binding cannot contain both "ro" and "mode": {}'
.format(repr(v))
f'Binding cannot contain both "ro" and "mode": {v!r}'
)
bind = v['bind']
if isinstance(bind, six.binary_type):
if isinstance(bind, bytes):
bind = bind.decode('utf-8')
if 'ro' in v:
@ -149,14 +149,30 @@ def convert_volume_binds(binds):
else:
mode = 'rw'
# NOTE: this is only relevant for Linux hosts
# (doesn't apply in Docker Desktop)
propagation_modes = [
'rshared',
'shared',
'rslave',
'slave',
'rprivate',
'private',
]
if 'propagation' in v and v['propagation'] in propagation_modes:
if mode:
mode = f"{mode},{v['propagation']}"
else:
mode = v['propagation']
result.append(
six.text_type('{0}:{1}:{2}').format(k, bind, mode)
f'{k}:{bind}:{mode}'
)
else:
if isinstance(v, six.binary_type):
if isinstance(v, bytes):
v = v.decode('utf-8')
result.append(
six.text_type('{0}:{1}:rw').format(k, v)
f'{k}:{v}:rw'
)
return result
@ -167,13 +183,13 @@ def convert_tmpfs_mounts(tmpfs):
if not isinstance(tmpfs, list):
raise ValueError(
'Expected tmpfs value to be either a list or a dict, found: {}'
.format(type(tmpfs).__name__)
'Expected tmpfs value to be either a list or a dict, '
f'found: {type(tmpfs).__name__}'
)
result = {}
for mount in tmpfs:
if isinstance(mount, six.string_types):
if isinstance(mount, str):
if ":" in mount:
name, options = mount.split(":", 1)
else:
@ -182,8 +198,8 @@ def convert_tmpfs_mounts(tmpfs):
else:
raise ValueError(
"Expected item in tmpfs list to be a string, found: {}"
.format(type(mount).__name__)
"Expected item in tmpfs list to be a string, "
f"found: {type(mount).__name__}"
)
result[name] = options
@ -198,7 +214,7 @@ def convert_service_networks(networks):
result = []
for n in networks:
if isinstance(n, six.string_types):
if isinstance(n, str):
n = {'Target': n}
result.append(n)
return result
@ -215,10 +231,6 @@ def parse_repository_tag(repo_name):
def parse_host(addr, is_win32=False, tls=False):
path = ''
port = None
host = None
# Sensible defaults
if not addr and is_win32:
return DEFAULT_NPIPE
@ -229,9 +241,9 @@ def parse_host(addr, is_win32=False, tls=False):
parsed_url = urlparse(addr)
proto = parsed_url.scheme
if not proto or any([x not in string.ascii_letters + '+' for x in proto]):
if not proto or any(x not in f"{string.ascii_letters}+" for x in proto):
# https://bugs.python.org/issue754016
parsed_url = urlparse('//' + addr, 'tcp')
parsed_url = urlparse(f"//{addr}", 'tcp')
proto = 'tcp'
if proto == 'fd':
@ -247,14 +259,14 @@ def parse_host(addr, is_win32=False, tls=False):
if proto not in ('tcp', 'unix', 'npipe', 'ssh'):
raise errors.DockerException(
"Invalid bind address protocol: {}".format(addr)
f"Invalid bind address protocol: {addr}"
)
if proto == 'tcp' and not parsed_url.netloc:
# "tcp://" is exceptionally disallowed by convention;
# omitting a hostname for other protocols is fine
raise errors.DockerException(
'Invalid bind address format: {}'.format(addr)
f'Invalid bind address format: {addr}'
)
if any([
@ -262,45 +274,51 @@ def parse_host(addr, is_win32=False, tls=False):
parsed_url.password
]):
raise errors.DockerException(
'Invalid bind address format: {}'.format(addr)
f'Invalid bind address format: {addr}'
)
if parsed_url.path and proto == 'ssh':
raise errors.DockerException(
'Invalid bind address format: no path allowed for this protocol:'
' {}'.format(addr)
f'Invalid bind address format: no path allowed for this protocol: {addr}'
)
else:
path = parsed_url.path
if proto == 'unix' and parsed_url.hostname is not None:
# For legacy reasons, we consider unix://path
# to be valid and equivalent to unix:///path
path = '/'.join((parsed_url.hostname, path))
path = f"{parsed_url.hostname}/{path}"
netloc = parsed_url.netloc
if proto in ('tcp', 'ssh'):
# parsed_url.hostname strips brackets from IPv6 addresses,
# which can be problematic hence our use of splitnport() instead.
host, port = splitnport(parsed_url.netloc)
if port is None or port < 0:
port = parsed_url.port or 0
if port <= 0:
if proto != 'ssh':
raise errors.DockerException(
'Invalid bind address format: port is required:'
' {}'.format(addr)
f'Invalid bind address format: port is required: {addr}'
)
port = 22
netloc = f'{parsed_url.netloc}:{port}'
if not host:
host = DEFAULT_HTTP_HOST
if not parsed_url.hostname:
netloc = f'{DEFAULT_HTTP_HOST}:{port}'
# Rewrite schemes to fit library internals (requests adapters)
if proto == 'tcp':
proto = 'http{}'.format('s' if tls else '')
proto = f"http{'s' if tls else ''}"
elif proto == 'unix':
proto = 'http+unix'
if proto in ('http+unix', 'npipe'):
return "{}://{}".format(proto, path).rstrip('/')
return '{0}://{1}:{2}{3}'.format(proto, host, port, path).rstrip('/')
return f"{proto}://{path}".rstrip('/')
return urlunparse(URLComponents(
scheme=proto,
netloc=netloc,
url=path,
params='',
query='',
fragment='',
)).rstrip('/')
def parse_devices(devices):
@ -309,9 +327,9 @@ def parse_devices(devices):
if isinstance(device, dict):
device_list.append(device)
continue
if not isinstance(device, six.string_types):
if not isinstance(device, str):
raise errors.DockerException(
'Invalid device type {0}'.format(type(device))
f'Invalid device type {type(device)}'
)
device_mapping = device.split(':')
if device_mapping:
@ -332,7 +350,7 @@ def parse_devices(devices):
return device_list
def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
def kwargs_from_env(environment=None):
if not environment:
environment = os.environ
host = environment.get('DOCKER_HOST')
@ -360,18 +378,11 @@ def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
if not cert_path:
cert_path = os.path.join(os.path.expanduser('~'), '.docker')
if not tls_verify and assert_hostname is None:
# assert_hostname is a subset of TLS verification,
# so if it's not set already then set it to false.
assert_hostname = False
params['tls'] = tls.TLSConfig(
params['tls'] = TLSConfig(
client_cert=(os.path.join(cert_path, 'cert.pem'),
os.path.join(cert_path, 'key.pem')),
ca_cert=os.path.join(cert_path, 'ca.pem'),
verify=tls_verify,
ssl_version=ssl_version,
assert_hostname=assert_hostname,
)
return params
@ -379,26 +390,26 @@ def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
def convert_filters(filters):
result = {}
for k, v in six.iteritems(filters):
for k, v in iter(filters.items()):
if isinstance(v, bool):
v = 'true' if v else 'false'
if not isinstance(v, list):
v = [v, ]
result[k] = [
str(item) if not isinstance(item, six.string_types) else item
str(item) if not isinstance(item, str) else item
for item in v
]
return json.dumps(result)
def datetime_to_timestamp(dt):
"""Convert a UTC datetime to a Unix timestamp"""
delta = dt - datetime.utcfromtimestamp(0)
"""Convert a datetime to a Unix timestamp"""
delta = dt.astimezone(timezone.utc) - datetime(1970, 1, 1, tzinfo=timezone.utc)
return delta.seconds + delta.days * 24 * 3600
def parse_bytes(s):
if isinstance(s, six.integer_types + (float,)):
if isinstance(s, (int, float,)):
return s
if len(s) == 0:
return 0
@ -419,20 +430,19 @@ def parse_bytes(s):
if suffix in units.keys() or suffix.isdigit():
try:
digits = int(digits_part)
except ValueError:
digits = float(digits_part)
except ValueError as ve:
raise errors.DockerException(
'Failed converting the string value for memory ({0}) to'
' an integer.'.format(digits_part)
)
'Failed converting the string value for memory '
f'({digits_part}) to an integer.'
) from ve
# Reconvert to long for the final result
s = int(digits * units[suffix])
else:
raise errors.DockerException(
'The specified value for memory ({0}) should specify the'
' units. The postfix should be one of the `b` `k` `m` `g`'
' characters'.format(s)
f'The specified value for memory ({s}) should specify the units. '
'The postfix should be one of the `b` `k` `m` `g` characters'
)
return s
@ -440,9 +450,9 @@ def parse_bytes(s):
def normalize_links(links):
if isinstance(links, dict):
links = six.iteritems(links)
links = iter(links.items())
return ['{0}:{1}'.format(k, v) if v else k for k, v in sorted(links)]
return [f'{k}:{v}' if v else k for k, v in sorted(links)]
def parse_env_file(env_file):
@ -452,7 +462,7 @@ def parse_env_file(env_file):
"""
environment = {}
with open(env_file, 'r') as f:
with open(env_file) as f:
for line in f:
if line[0] == '#':
@ -468,15 +478,12 @@ def parse_env_file(env_file):
environment[k] = v
else:
raise errors.DockerException(
'Invalid line in environment file {0}:\n{1}'.format(
env_file, line))
f'Invalid line in environment file {env_file}:\n{line}')
return environment
def split_command(command):
if six.PY2 and not isinstance(command, six.binary_type):
command = command.encode('utf-8')
return shlex.split(command)
@ -484,22 +491,22 @@ def format_environment(environment):
def format_env(key, value):
if value is None:
return key
if isinstance(value, six.binary_type):
if isinstance(value, bytes):
value = value.decode('utf-8')
return u'{key}={value}'.format(key=key, value=value)
return [format_env(*var) for var in six.iteritems(environment)]
return f'{key}={value}'
return [format_env(*var) for var in iter(environment.items())]
def format_extra_hosts(extra_hosts, task=False):
# Use format dictated by Swarm API if container is part of a task
if task:
return [
'{} {}'.format(v, k) for k, v in sorted(six.iteritems(extra_hosts))
f'{v} {k}' for k, v in sorted(iter(extra_hosts.items()))
]
return [
'{}:{}'.format(k, v) for k, v in sorted(six.iteritems(extra_hosts))
f'{k}:{v}' for k, v in sorted(iter(extra_hosts.items()))
]

View File

@ -1,2 +1,8 @@
version = "4.0.0"
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])
try:
from ._version import __version__
except ImportError:
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version('docker')
except PackageNotFoundError:
__version__ = '0.0.0'

View File

@ -1,2 +0,0 @@
recommonmark==0.4.0
Sphinx==1.4.6

View File

@ -1,3 +1,12 @@
dl.hide-signature > dt {
display: none;
}
dl.field-list > dt {
/* prevent code blocks from forcing wrapping on the "Parameters" header */
word-break: initial;
}
code.literal{
hyphens: none;
}

View File

@ -142,6 +142,7 @@ Configuration types
.. autoclass:: IPAMPool
.. autoclass:: LogConfig
.. autoclass:: Mount
.. autoclass:: NetworkAttachmentConfig
.. autoclass:: Placement
.. autoclass:: PlacementPreference
.. autoclass:: Privileges

View File

@ -1,6 +1,360 @@
Changelog
==========
7.1.0
-----
### Upgrade Notes
- Bumped minimum engine API version to 1.24
- Bumped default engine API version to 1.44 (Moby 25.0)
### Bugfixes
- Fixed issue with tag parsing when the registry address includes ports that resulted in `invalid tag format` errors
- Fixed issue preventing creating new configs (`ConfigCollection`), which failed with a `KeyError` due to the `name` field
- Fixed an issue due to an update in the [requests](https://github.com/psf/requests) package breaking `docker-py` by applying the [suggested fix](https://github.com/psf/requests/pull/6710)
### Miscellaneous
- Documentation improvements
- Updated Ruff (linter) and fixed minor linting issues
- Packaging/CI updates
- Started using hatch for packaging (https://github.com/pypa/hatch)
- Updated `setup-python` github action
- Updated tests
- Stopped checking for deprecated container and image related fields (`Container` and `ContainerConfig`)
- Updated tests that check `NetworkSettings.Networks.<network>.Aliases` due to engine changes
7.0.0
-----
### Upgrade Notes
- Removed SSL version (`ssl_version`) and explicit hostname check (`assert_hostname`) options
- `assert_hostname` has not been used since Python 3.6 and was removed in 3.12
- Python 3.7+ supports TLSv1.3 by default
- Websocket support is no longer included by default
- Use `pip install docker[websockets]` to include `websocket-client` dependency
- By default, `docker-py` hijacks the TCP connection and does not use Websockets
- Websocket client is only required to use `attach_socket(container, ws=True)`
- Python 3.7 no longer officially supported (reached end-of-life June 2023)
### Features
- Python 3.12 support
- Full `networking_config` support for `containers.create()`
- Replaces `network_driver_opt` (added in 6.1.0)
- Add `health()` property to container that returns status (e.g. `unhealthy`)
- Add `pause` option to `container.commit()`
- Add support for bind mount propagation (e.g. `rshared`, `private`)
- Add `filters`, `keep_storage`, and `all` parameters to `prune_builds()` (requires API v1.39+)
### Bugfixes
- Consistently return `docker.errors.NotFound` on 404 responses
- Validate tag format before image push
### Miscellaneous
- Upgraded urllib3 version in `requirements.txt` (used for development/tests)
- Documentation typo fixes & formatting improvements
- Fixed integration test compatibility for newer Moby engine versions
- Switch to [ruff](https://github.com/astral-sh/ruff) for linting
6.1.3
-----
#### Bugfixes
- Fix compatibility with [`eventlet/eventlet`](https://github.com/eventlet/eventlet)
6.1.2
-----
#### Bugfixes
- Fix for socket timeouts on long `docker exec` calls
6.1.1
-----
#### Bugfixes
- Fix `containers.stats()` hanging with `stream=True`
- Correct return type in docs for `containers.diff()` method
6.1.0
-----
### Upgrade Notes
- Errors are no longer returned during client initialization if the credential helper cannot be found. A warning will be emitted instead, and an error is returned if the credential helper is used.
### Features
- Python 3.11 support
- Use `poll()` instead of `select()` on non-Windows platforms
- New API fields
- `network_driver_opt` on container run / create
- `one-shot` on container stats
- `status` on services list
### Bugfixes
- Support for requests 2.29.0+ and urllib3 2.x
- Do not strip characters from volume names
- Fix connection leak on container.exec_* operations
- Fix errors closing named pipes on Windows
6.0.1
-----
### Bugfixes
- Fix for `The pipe has been ended errors` on Windows
- Support floats for container log filtering by timestamp (`since` / `until`)
6.0.0
-----
### Upgrade Notes
- Minimum supported Python version is 3.7+
- When installing with pip, the `docker[tls]` extra is deprecated and a no-op,
use `docker` for same functionality (TLS support is always available now)
- Native Python SSH client (used by default / `use_ssh_client=False`) will now
reject unknown host keys with `paramiko.ssh_exception.SSHException`
- Short IDs are now 12 characters instead of 10 characters (same as Docker CLI)
### Features
- Python 3.10 support
- Automatically negotiate most secure TLS version
- Add `platform` (e.g. `linux/amd64`, `darwin/arm64`) to container create & run
- Add support for `GlobalJob` and `ReplicatedJobs` for Swarm
- Add `remove()` method on `Image`
- Add `force` param to `disable()` on `Plugin`
### Bugfixes
- Fix install issues on Windows related to `pywin32`
- Do not accept unknown SSH host keys in native Python SSH mode
- Use 12 character short IDs for consistency with Docker CLI
- Ignore trailing whitespace in `.dockerignore` files
- Fix IPv6 host parsing when explicit port specified
- Fix `ProxyCommand` option for SSH connections
- Do not spawn extra subshell when launching external SSH client
- Improve exception semantics to preserve context
- Documentation improvements (formatting, examples, typos, missing params)
### Miscellaneous
- Upgrade dependencies in `requirements.txt` to latest versions
- Remove extraneous transitive dependencies
- Eliminate usages of deprecated functions/methods
- Test suite reliability improvements
- GitHub Actions workflows for linting, unit tests, integration tests, and
publishing releases
5.0.3
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/76?closed=1)
### Features
- Add `cap_add` and `cap_drop` parameters to service create and ContainerSpec
- Add `templating` parameter to config create
### Bugfixes
- Fix getting a read timeout for logs/attach with a tty and slow output
### Miscellaneous
- Fix documentation examples
5.0.2
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/75?closed=1)
### Bugfixes
- Fix `disable_buffering` regression
5.0.1
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/74?closed=1)
### Bugfixes
- Bring back support for ssh identity file
- Cleanup remaining python-2 dependencies
- Fix image save example in docs
### Miscellaneous
- Bump urllib3 to 1.26.5
- Bump requests to 2.26.0
5.0.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/70?closed=1)
### Breaking changes
- Remove support for Python 2.7
- Make Python 3.6 the minimum version supported
### Features
- Add `limit` parameter to image search endpoint
### Bugfixes
- Fix `KeyError` exception on secret create
- Verify TLS keys loaded from docker contexts
- Update PORT_SPEC regex to allow square brackets for IPv6 addresses
- Fix containers and images documentation examples
4.4.4
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/73?closed=1)
### Bugfixes
- Remove `LD_LIBRARY_PATH` and `SSL_CERT_FILE` environment variables when shelling out to the ssh client
4.4.3
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/72?closed=1)
### Features
- Add support for docker.types.Placement.MaxReplicas
### Bugfixes
- Fix SSH port parsing when shelling out to the ssh client
4.4.2
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/71?closed=1)
### Bugfixes
- Fix SSH connection bug where the hostname was incorrectly trimmed and the error was hidden
- Fix docs example
### Miscellaneous
- Add Python3.8 and 3.9 in setup.py classifier list
4.4.1
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/69?closed=1)
### Bugfixes
- Avoid setting unsuported parameter for subprocess.Popen on Windows
- Replace use of deprecated "filter" argument on ""docker/api/image"
4.4.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/67?closed=1)
### Features
- Add an alternative SSH connection to the paramiko one, based on shelling out to the SSh client. Similar to the behaviour of Docker cli
- Default image tag to `latest` on `pull`
### Bugfixes
- Fix plugin model upgrade
- Fix examples URL in ulimits
### Miscellaneous
- Improve exception messages for server and client errors
- Bump cryptography from 2.3 to 3.2
4.3.1
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/68?closed=1)
### Miscellaneous
- Set default API version to `auto`
- Fix conversion to bytes for `float`
- Support OpenSSH `identityfile` option
4.3.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/64?closed=1)
### Features
- Add `DeviceRequest` type to expose host resources such as GPUs
- Add support for `DriverOpts` in EndpointConfig
- Disable compression by default when using container.get_archive method
### Miscellaneous
- Update default API version to v1.39
- Update test engine version to 19.03.12
4.2.2
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/66?closed=1)
### Bugfixes
- Fix context load for non-docker endpoints
4.2.1
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/65?closed=1)
### Features
- Add option on when to use `tls` on Context constructor
- Make context orchestrator field optional
4.2.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/63?closed=1)
### Bugfixes
- Fix `win32pipe.WaitNamedPipe` throw exception in Windows containers
- Use `Hostname`, `Username`, `Port` and `ProxyCommand` settings from `.ssh/config` when on SSH
- Set host key policy for ssh transport to `paramiko.WarningPolicy()`
- Set logging level of `paramiko` to warn
### Features
- Add support for docker contexts through `docker.ContextAPI`
4.1.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/61?closed=1)
### Bugfixes
- Correct `INDEX_URL` logic in build.py _set_auth_headers
- Fix for empty auth keys in config.json
### Features
- Add `NetworkAttachmentConfig` for service create/update
### Miscellaneous
- Bump pytest to 4.3.1
- Adjust `--platform` tests for changes in docker engine
- Update credentials-helpers to v0.6.3
4.0.2
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/62?closed=1)
### Bugfixes
- Unified the way `HealthCheck` is created/configured
### Miscellaneous
- Bumped version of websocket-client
4.0.1
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/60?closed=1)
### Bugfixes
- Fixed an obsolete import in the `credentials` subpackage that caused import errors in
Python 3.7
### Miscellaneous
- Docs building has been repaired
4.0.0
-----
@ -45,7 +399,7 @@ Change log
### Bugfixes
* Fix base_url to keep TCP protocol on utils.py by letting the responsability of changing the
* Fix base_url to keep TCP protocol on utils.py by letting the responsibility of changing the
protocol to `parse_host` afterwards, letting `base_url` with the original value.
* XFAIL test_attach_stream_and_cancel on TLS
@ -1149,7 +1503,7 @@ like the others
(`Client.volumes`, `Client.create_volume`, `Client.inspect_volume`,
`Client.remove_volume`).
* Added support for the `group_add` parameter in `create_host_config`.
* Added support for the CPU CFS (`cpu_quota` and `cpu_period`) parameteres
* Added support for the CPU CFS (`cpu_quota` and `cpu_period`) parameters
in `create_host_config`.
* Added support for the archive API endpoint (`Client.get_archive`,
`Client.put_archive`).

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# docker-sdk-python documentation build configuration file, created by
# sphinx-quickstart on Wed Sep 14 15:48:58 2016.
@ -19,6 +18,8 @@
import datetime
import os
import sys
from importlib.metadata import version
sys.path.insert(0, os.path.abspath('..'))
@ -34,24 +35,19 @@ sys.path.insert(0, os.path.abspath('..'))
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'myst_parser'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
from recommonmark.parser import CommonMarkParser
source_parsers = {
'.md': CommonMarkParser,
source_suffix = {
'.rst': 'restructuredtext',
'.txt': 'markdown',
'.md': 'markdown',
}
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
source_suffix = ['.rst', '.md']
# source_suffix = '.md'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
@ -60,28 +56,26 @@ source_suffix = ['.rst', '.md']
master_doc = 'index'
# General information about the project.
project = u'Docker SDK for Python'
project = 'Docker SDK for Python'
year = datetime.datetime.now().year
copyright = u'%d Docker Inc' % year
author = u'Docker Inc'
copyright = f'{year} Docker Inc'
author = 'Docker Inc'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
with open('../docker/version.py', 'r') as vfile:
exec(vfile.read())
# The full version, including alpha/beta/rc tags.
release = version
# The short X.Y version.
version = '{}.{}'.format(version_info[0], version_info[1])
# see https://github.com/pypa/setuptools_scm#usage-from-sphinx
release = version('docker')
# for example take major/minor
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
@ -283,8 +277,8 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'docker-sdk-python.tex', u'docker-sdk-python Documentation',
u'Docker Inc.', 'manual'),
(master_doc, 'docker-sdk-python.tex', 'docker-sdk-python Documentation',
'Docker Inc.', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -325,7 +319,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'docker-sdk-python', u'docker-sdk-python Documentation',
(master_doc, 'docker-sdk-python', 'docker-sdk-python Documentation',
[author], 1)
]
@ -340,7 +334,7 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'docker-sdk-python', u'docker-sdk-python Documentation',
(master_doc, 'docker-sdk-python', 'docker-sdk-python Documentation',
author, 'docker-sdk-python', 'One line description of project.',
'Miscellaneous'),
]

View File

@ -58,7 +58,7 @@ You can stream logs:
.. code-block:: python
>>> for line in container.logs(stream=True):
... print line.strip()
... print(line.strip())
Reticulating spline 2...
Reticulating spline 3...
...

View File

@ -15,7 +15,7 @@ For example, to check the server against a specific CA certificate:
.. code-block:: python
tls_config = docker.tls.TLSConfig(ca_cert='/path/to/ca.pem')
tls_config = docker.tls.TLSConfig(ca_cert='/path/to/ca.pem', verify=True)
client = docker.DockerClient(base_url='<https_url>', tls=tls_config)
This is the equivalent of ``docker --tlsverify --tlscacert /path/to/ca.pem ...``.

View File

@ -16,10 +16,13 @@ Prepare the command we are going to use. It prints "hello stdout"
in `stdout`, followed by "hello stderr" in `stderr`:
>>> cmd = '/bin/sh -c "echo hello stdout ; echo hello stderr >&2"'
We'll run this command with all four the combinations of ``stream``
and ``demux``.
With ``stream=False`` and ``demux=False``, the output is a string
that contains both the `stdout` and the `stderr` output:
>>> res = container.exec_run(cmd, stream=False, demux=False)
>>> res.output
b'hello stderr\nhello stdout\n'
@ -52,15 +55,8 @@ Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Finally, with ``stream=False`` and ``demux=True``, the whole output
is returned, but the streams are still separated:
Finally, with ``stream=False`` and ``demux=True``, the output is a tuple ``(stdout, stderr)``:
>>> res = container.exec_run(cmd, stream=True, demux=True)
>>> next(res.output)
(b'hello stdout\n', None)
>>> next(res.output)
(None, b'hello stderr\n')
>>> next(res.output)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> res = container.exec_run(cmd, stream=False, demux=True)
>>> res.output
(b'hello stdout\n', b'hello stderr\n')

View File

@ -6,7 +6,7 @@
Starting with Engine version 1.12 (API 1.24), it is possible to manage services
using the Docker Engine API. Note that the engine needs to be part of a
[Swarm cluster](../swarm.rst) before you can use the service-related methods.
[Swarm cluster](../swarm.html) before you can use the service-related methods.
## Creating a service

102
pyproject.toml Normal file
View File

@ -0,0 +1,102 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "docker"
dynamic = ["version"]
description = "A Python library for the Docker Engine API."
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.8"
maintainers = [
{ name = "Docker Inc.", email = "no-reply@docker.com" },
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development",
"Topic :: Utilities",
]
dependencies = [
"requests >= 2.26.0",
"urllib3 >= 1.26.0",
"pywin32>=304; sys_platform == \"win32\"",
]
[project.optional-dependencies]
# ssh feature allows DOCKER_HOST=ssh://... style connections
ssh = [
"paramiko>=2.4.3",
]
# tls is always supported, the feature is a no-op for backwards compatibility
tls = []
# websockets can be used as an alternate container attach mechanism but
# by default docker-py hijacks the TCP connection and does not use Websockets
# unless attach_socket(container, ws=True) is called
websockets = [
"websocket-client >= 1.3.0",
]
# docs are dependencies required to build the ReadTheDocs site
# this is only needed for CI / working on the docs!
docs = [
"myst-parser==0.18.0",
"Sphinx==5.1.1",
]
# dev are dependencies required to test & lint this project
# this is only needed if you are making code changes to docker-py!
dev = [
"coverage==7.2.7",
"pytest==7.4.2",
"pytest-cov==4.1.0",
"pytest-timeout==2.1.0",
"ruff==0.1.8",
]
[project.urls]
Changelog = "https://docker-py.readthedocs.io/en/stable/change-log.html"
Documentation = "https://docker-py.readthedocs.io"
Homepage = "https://github.com/docker/docker-py"
Source = "https://github.com/docker/docker-py"
Tracker = "https://github.com/docker/docker-py/issues"
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "docker/_version.py"
[tool.hatch.build.targets.sdist]
include = [
"/docker",
]
[tool.ruff]
target-version = "py38"
extend-select = [
"B",
"C",
"F",
"I",
"UP",
"W",
]
ignore = [
"UP012", # unnecessary `UTF-8` argument (we want to be explicit)
"C901", # too complex (there's a whole bunch of these)
]
[tool.ruff.per-file-ignores]
"**/__init__.py" = ["F401"]

View File

@ -1,2 +1,5 @@
[pytest]
addopts = --tb=short -rxs
junit_suite_name = docker-py
junit_family = xunit2

View File

@ -1,19 +0,0 @@
appdirs==1.4.3
asn1crypto==0.22.0
backports.ssl-match-hostname==3.5.0.1
cffi==1.10.0
cryptography==2.3
enum34==1.1.6
idna==2.5
ipaddress==1.0.18
packaging==16.8
paramiko==2.4.2
pycparser==2.17
pyOpenSSL==18.0.0
pyparsing==2.2.0
pypiwin32==219; sys_platform == 'win32' and python_version < '3.6'
pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6'
requests==2.20.0
six==1.10.0
urllib3==1.24.3
websocket-client==0.40.0

View File

@ -52,8 +52,8 @@ class Version(namedtuple('_Version', 'major minor patch stage edition')):
return (int(self.major), int(self.minor), int(self.patch)) + stage
def __str__(self):
stage = '-{}'.format(self.stage) if self.stage else ''
edition = '-{}'.format(self.edition) if self.edition else ''
stage = f'-{self.stage}' if self.stage else ''
edition = f'-{self.edition}' if self.edition else ''
return '.'.join(map(str, self[:3])) + edition + stage
@ -72,6 +72,5 @@ def main():
results.add(str(latest))
print(' '.join(results))
if __name__ == '__main__':
main()

View File

@ -1,6 +0,0 @@
[bdist_wheel]
universal = 1
[metadata]
description_file = README.rst
license = Apache License 2.0

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python
from __future__ import print_function
import codecs
import os
from setuptools import find_packages
from setuptools import setup
ROOT_DIR = os.path.dirname(__file__)
SOURCE_DIR = os.path.join(ROOT_DIR)
requirements = [
'six >= 1.4.0',
'websocket-client >= 0.32.0',
'requests >= 2.14.2, != 2.18.0',
]
extras_require = {
':python_version < "3.5"': 'backports.ssl_match_hostname >= 3.5',
# While not imported explicitly, the ipaddress module is required for
# ssl_match_hostname to verify hosts match with certificates via
# ServerAltname: https://pypi.python.org/pypi/backports.ssl_match_hostname
':python_version < "3.3"': 'ipaddress >= 1.0.16',
# win32 APIs if on Windows (required for npipe support)
# Python 3.6 is only compatible with v220 ; Python < 3.5 is not supported
# on v220 ; ALL versions are broken for v222 (as of 2018-01-26)
':sys_platform == "win32" and python_version < "3.6"': 'pypiwin32==219',
':sys_platform == "win32" and python_version >= "3.6"': 'pypiwin32==223',
# If using docker-py over TLS, highly recommend this option is
# pip-installed or pinned.
# TODO: if pip installing both "requests" and "requests[security]", the
# extra package from the "security" option are not installed (see
# https://github.com/pypa/pip/issues/4391). Once that's fixed, instead of
# installing the extra dependencies, install the following instead:
# 'requests[security] >= 2.5.2, != 2.11.0, != 2.12.2'
'tls': ['pyOpenSSL>=17.5.0', 'cryptography>=1.3.4', 'idna>=2.0.0'],
# Only required when connecting using the ssh:// protocol
'ssh': ['paramiko>=2.4.2'],
}
version = None
exec(open('docker/version.py').read())
with open('./test-requirements.txt') as test_reqs_txt:
test_requirements = [line for line in test_reqs_txt]
long_description = ''
with codecs.open('./README.md', encoding='utf-8') as readme_md:
long_description = readme_md.read()
setup(
name="docker",
version=version,
description="A Python library for the Docker Engine API.",
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/docker/docker-py',
project_urls={
'Documentation': 'https://docker-py.readthedocs.io',
'Changelog': 'https://docker-py.readthedocs.io/en/stable/change-log.html', # noqa: E501
'Source': 'https://github.com/docker/docker-py',
'Tracker': 'https://github.com/docker/docker-py/issues',
},
packages=find_packages(exclude=["tests.*", "tests"]),
install_requires=requirements,
tests_require=test_requirements,
extras_require=extras_require,
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
zip_safe=False,
test_suite='tests',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development',
'Topic :: Utilities',
'License :: OSI Approved :: Apache Software License',
],
maintainer='Joffrey F',
maintainer_email='joffrey@docker.com',
)

View File

@ -1,6 +0,0 @@
coverage==4.5.2
flake8==3.6.0
mock==1.0.1
pytest==4.1.0
pytest-cov==2.6.1
pytest-timeout==1.3.3

View File

@ -1,16 +1,24 @@
ARG PYTHON_VERSION=3.6
FROM python:$PYTHON_VERSION-jessie
RUN apt-get update && apt-get -y install \
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}
RUN apt-get update && apt-get -y install --no-install-recommends \
gnupg2 \
pass \
curl
pass
# Add SSH keys and set permissions
COPY tests/ssh/config/client /root/.ssh
COPY tests/ssh/config/server/known_ed25519.pub /root/.ssh/known_hosts
RUN sed -i '1s;^;dpy-dind-ssh ;' /root/.ssh/known_hosts
RUN chmod -R 600 /root/.ssh
COPY ./tests/gpg-keys /gpg-keys
RUN gpg2 --import gpg-keys/secret
RUN gpg2 --import-ownertrust gpg-keys/ownertrust
RUN yes | pass init $(gpg2 --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1)
RUN yes | pass init $(gpg2 --no-auto-check-trustdb --list-secret-key | awk '/^sec/{getline; $1=$1; print}')
RUN gpg2 --check-trustdb
ARG CREDSTORE_VERSION=v0.6.0
ARG CREDSTORE_VERSION=v0.6.3
RUN curl -sSL -o /opt/docker-credential-pass.tar.gz \
https://github.com/docker/docker-credential-helpers/releases/download/$CREDSTORE_VERSION/docker-credential-pass-$CREDSTORE_VERSION-amd64.tar.gz && \
tar -xf /opt/docker-credential-pass.tar.gz -O > /usr/local/bin/docker-credential-pass && \
@ -18,11 +26,10 @@ RUN curl -sSL -o /opt/docker-credential-pass.tar.gz \
chmod +x /usr/local/bin/docker-credential-pass
WORKDIR /src
COPY requirements.txt /src/requirements.txt
RUN pip install -r requirements.txt
COPY . .
COPY test-requirements.txt /src/test-requirements.txt
RUN pip install -r test-requirements.txt
COPY . /src
RUN pip install .
ARG VERSION=0.0.0.dev0
RUN --mount=type=cache,target=/cache/pip \
PIP_CACHE_DIR=/cache/pip \
SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \
pip install .[dev,ssh,websockets]

View File

@ -1,4 +1,8 @@
FROM python:2.7
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}
RUN mkdir /tmp/certs
VOLUME /certs

20
tests/Dockerfile-ssh-dind Normal file
View File

@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1
ARG API_VERSION=1.45
ARG ENGINE_VERSION=26.1
FROM docker:${ENGINE_VERSION}-dind
RUN apk add --no-cache --upgrade \
openssh
COPY tests/ssh/config/server /etc/ssh/
# set authorized keys for client paswordless connection
COPY tests/ssh/config/client/id_rsa.pub /root/.ssh/authorized_keys
# RUN echo "root:root" | chpasswd
RUN chmod -R 600 /etc/ssh \
&& chmod -R 600 /root/.ssh \
&& ln -s /usr/local/bin/docker /usr/bin/docker
EXPOSE 22

View File

@ -8,10 +8,10 @@ import tarfile
import tempfile
import time
import docker
import paramiko
import pytest
import six
import docker
def make_tree(dirs, files):
@ -47,6 +47,19 @@ def untar_file(tardata, filename):
return result
def skip_if_desktop():
def fn(f):
@functools.wraps(f)
def wrapped(self, *args, **kwargs):
info = self.client.info()
if info['Name'] == 'docker-desktop':
pytest.skip('Test does not support Docker Desktop')
return f(self, *args, **kwargs)
return wrapped
return fn
def requires_api_version(version):
test_version = os.environ.get(
'DOCKER_TEST_API_VERSION', docker.constants.DEFAULT_DOCKER_API_VERSION
@ -54,7 +67,7 @@ def requires_api_version(version):
return pytest.mark.skipif(
docker.utils.version_lt(test_version, version),
reason="API version is too low (< {0})".format(version)
reason=f"API version is too low (< {version})"
)
@ -81,12 +94,12 @@ def wait_on_condition(condition, delay=0.1, timeout=40):
start_time = time.time()
while not condition():
if time.time() - start_time > timeout:
raise AssertionError("Timeout: %s" % condition)
raise AssertionError(f"Timeout: {condition}")
time.sleep(delay)
def random_name():
return u'dockerpytest_{0:x}'.format(random.getrandbits(64))
return f'dockerpytest_{random.getrandbits(64):x}'
def force_leave_swarm(client):
@ -105,11 +118,11 @@ def force_leave_swarm(client):
def swarm_listen_addr():
return '0.0.0.0:{0}'.format(random.randrange(10000, 25000))
return f'0.0.0.0:{random.randrange(10000, 25000)}'
def assert_cat_socket_detached_with_keys(sock, inputs):
if six.PY3 and hasattr(sock, '_sock'):
if hasattr(sock, '_sock'):
sock = sock._sock
for i in inputs:
@ -128,7 +141,7 @@ def assert_cat_socket_detached_with_keys(sock, inputs):
# of the daemon no longer cause this to raise an error.
try:
sock.sendall(b'make sure the socket is closed\n')
except socket.error:
except OSError:
return
sock.sendall(b"make sure the socket is closed\n")
@ -144,4 +157,4 @@ def ctrl_with(char):
if re.match('[a-z]', char):
return chr(ord(char) - ord('a') + 1).encode('ascii')
else:
raise(Exception('char must be [a-z]'))
raise Exception('char must be [a-z]')

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