diff --git a/README.md b/README.md index 3ff08189..375b6ae7 100644 --- a/README.md +++ b/README.md @@ -7,89 +7,33 @@ The Co-op Cloud utility belt 🎩🐇 -`abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create applications, deploy them, run backup and restore operations and a whole lot of other things. It is the go-to tool for day-to-day operations when managing a Co-op Cloud instance. - -## Install - -### Arch-based Linux Distros - -[abra (coming-soon)](https://aur.archlinux.org/packages/abra/) or for the latest version on git [abra-git](https://aur.archlinux.org/packages/abra-git/) - -```sh -yay -S abra-git # or abra -``` - -### Debian-based Linux Distros - -**Coming Soon** - -### Homebrew - -**Coming Soon** - -### Build from source - -```sh -git clone https://git.coopcloud.tech/coop-cloud/abra -cd abra -go env -w GOPRIVATE=coopcloud.tech -make install -``` - -The abra binary will be in `$GOPATH/bin`. - -## Autocompletion - -**bash** - -Copy `scripts/autocomplete/bash` into `/etc/bash_completion.d/` and rename -it to abra. - -``` -sudo cp scripts/autocomplete/bash /etc/bash_completion.d/abra -source /etc/bash_completion.d/abra -``` - -In development, you can source the script in your git checkout, just make sure -to set `PROG=abra`, otherwise it'll add completion to the wrong command: - -``` -PROG=abra source /path/to/abra/scripts/autocomplete/bash -``` - -**(fi)zsh** - -(fi)zsh doesn't have an autocompletion folder by default but you can create one, then copy `scripts/autocomplete/zsh` into it and add a couple lines to your `~/.zshrc` or `~/.fizsh/.fizshrc` - -``` -sudo mkdir /etc/zsh/completion.d/ -sudo cp scripts/autocomplete/zsh /etc/zsh/completion.d/abra -echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc -``` - -(replace .zshrc with ~/.fizsh/.fizshrc if you use fizsh) +`abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create apps, deploy them, run backup and restore operations and a whole lot of other things. Please see [docs.coopcloud.tech](https://docs.coopcloud.tech) for more extensive documentation. ## Hacking -Install direnv, run `cp .envrc.sample .envrc`, then run `direnv allow` in this directory. This will set coopcloud repos as private due to [this bug.](https://git.coopcloud.tech/coop-cloud/coopcloud.tech/issues/20#issuecomment-8201). Or you can run `go env -w GOPRIVATE=coopcloud.tech` but I'm not sure how persistent this is. +### Getting started + +Install [direnv](https://direnv.net), run `cp .envrc.sample .envrc`, then run `direnv allow` in this directory. This will set coopcloud repos as private due to [this bug.](https://git.coopcloud.tech/coop-cloud/coopcloud.tech/issues/20#issuecomment-8201). Or you can run `go env -w GOPRIVATE=coopcloud.tech` but I'm not sure how persistent this is. Install [Go >= 1.16](https://golang.org/doc/install) and then: - `make build` to build - `./abra` to run commands - `make test` will run tests +- `make install` will install it to `$GOPATH/bin` +- `go get ` and `go mod tidy` to add a new dependency Our [Drone CI configuration](.drone.yml) runs a number of sanity on each pushed commit. See the [Makefile](./Makefile) for more handy targets. Please use the [conventional commit format](https://www.conventionalcommits.org/en/v1.0.0/) for your commits so we can automate our change log. -## Versioning +### Versioning We use [goreleaser](https://goreleaser.com) to help us automate releases. We use [semver](https://semver.org) for versioning all releases of the tool. While we are still in the public alpha release phase, we will maintain a `0.y.z-alpha` format. Change logs are generated from our commit logs. We are still working this out and aim to refine our release praxis as we go. For developers, while using this `-alpha` format, the `y` part is the "major" version part. So, if you make breaking changes, you increment that and _not_ the `x` part. So, if you're on `0.1.0-alpha`, then you'd go to `0.1.1-alpha` for a backwards compatible change and `0.2.0-alpha` for a backwards incompatible change. -## Making a new release +### Making a new release - Change `ABRA_VERSION` to match the new tag in [`scripts`](./scripts/installer/installer) (use [semver](https://semver.org)) - Commit that change (e.g. `git commit -m 'chore: publish next tag 0.3.1-alpha'`) @@ -99,7 +43,9 @@ For developers, while using this `-alpha` format, the `y` part is the "major" ve - Deploy the new installer script (e.g. `cd ./scripts/installer && make`) - Check the release worked, (e.g. `abra upgrade; abra version`) -## Fork maintenance +### Fork maintenance + +#### `godotenv` We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godotenv) for two features: @@ -109,3 +55,7 @@ We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godote You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@` where `` is the latest commit you want to pin to. We are aiming to migrate to YAML format for the environment configuration, so this should only be a temporary thing. + +#### `docker/client` + +A number of modules in [pkg/client](./pkg/client) are copy/pasta'd from the upstream [docker/docker/client](https://pkg.go.dev/github.com/docker/docker/client). We had to do this because upstream are not exposing their API as public. diff --git a/pkg/client/README.md b/pkg/client/README.md deleted file mode 100644 index 6b8e7119..00000000 --- a/pkg/client/README.md +++ /dev/null @@ -1,4 +0,0 @@ -IMPORTANT POINT ABOUT CONTEXTS - -Please use context names starting with `testContext` for testing purposes to ensure that no data is lost. such as `testContext`, `testContext2`, `testContextFail` etc - diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go deleted file mode 100644 index 6651de73..00000000 --- a/pkg/client/client_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package client_test - -import ( - "fmt" - "testing" - - "coopcloud.tech/abra/pkg/client" -) - -// use at the start to ensure testContext[0, 1, ..., amnt-1] exist and -// testContextFail[0, 1, ..., failAmnt-1] don't exist -func ensureTestState(amnt, failAmnt int) error { - for i := 0; i < amnt; i++ { - err := client.CreateContext(fmt.Sprintf("testContext%d", i), "", "") - if err != nil { - return err - } - } - for i := 0; i < failAmnt; i++ { - if _, er := client.GetContext(fmt.Sprintf("testContextFail%d", i)); er == nil { - err := client.DeleteContext(fmt.Sprintf("testContextFail%d", i)) - if err != nil { - return err - } - } - } - return nil -} - -func TestNew(t *testing.T) { - err := ensureTestState(1, 1) - if err != nil { - t.Errorf("Couldn't ensure existence/nonexistence of contexts: %s", err) - } - contextName := "testContext0" - _, err = client.New(contextName) - if err != nil { - t.Errorf("couldn't initialise a new client with context %s: %s", contextName, err) - } - contextName = "testContextFail0" - _, err = client.New(contextName) - if err == nil { - t.Errorf("client.New(\"testContextFail0\") should have failed but didn't return an error") - } - -} diff --git a/pkg/client/container/LICENSE b/pkg/client/container/LICENSE deleted file mode 100644 index 9c8e20ab..00000000 --- a/pkg/client/container/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2013-2017 Docker, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/pkg/client/container/README.md b/pkg/client/container/README.md deleted file mode 100644 index e1472d2d..00000000 --- a/pkg/client/container/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# github.com/docker/cli/cli/command/container - -Due to this literally just being copy-pasted from the lib, the Apache license -will be posted in this folder. Small edits to the source code have been to -function names and parts we don't need deleted. - -Same vibe as [../convert](../convert). diff --git a/pkg/client/context_test.go b/pkg/client/context_test.go deleted file mode 100644 index 1ec36c64..00000000 --- a/pkg/client/context_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package client_test - -import ( - "testing" - - "coopcloud.tech/abra/pkg/client" - dContext "github.com/docker/cli/cli/context" - dCliContextStore "github.com/docker/cli/cli/context/store" -) - -type TestContext struct { - context dCliContextStore.Metadata - expected_endpoint string -} - -func dockerContext(host, key string) TestContext { - dockerContext := dCliContextStore.Metadata{ - Name: "foo", - Metadata: nil, - Endpoints: map[string]interface{}{ - key: dContext.EndpointMetaBase{ - Host: host, - SkipTLSVerify: false, - }, - }, - } - return TestContext{ - context: dockerContext, - expected_endpoint: host, - } -} - -func TestCreateContext(t *testing.T) { - err := client.CreateContext("testContext0", "wronguser", "wrongport") - if err == nil { - t.Error("client.CreateContext(\"testContextCreate\", \"wronguser\", \"wrongport\") should have failed but didn't return an error") - } - err = client.CreateContext("testContext0", "", "") - if err != nil { - t.Errorf("Couldn't create context: %s", err) - } -} - -func TestDeleteContext(t *testing.T) { - ensureTestState(1, 1) - err := client.DeleteContext("default") - if err == nil { - t.Errorf("client.DeleteContext(\"default\") should have failed but didn't return an error") - } - - err = client.DeleteContext("testContext0") - if err != nil { - t.Errorf("client.DeleteContext(\"testContext0\") failed: %s", err) - } - err = client.DeleteContext("testContextFail0") - if err == nil { - t.Errorf("client.DeleteContext(\"testContextFail0\") should have failed (attempt to delete non-existent context) but didn't return an error") - } -} - -func TestGetContextEndpoint(t *testing.T) { - var testDockerContexts = []TestContext{ - dockerContext("ssh://foobar", "docker"), - dockerContext("ssh://foobar", "k8"), - } - for _, context := range testDockerContexts { - endpoint, err := client.GetContextEndpoint(context.context) - if err != nil { - if err.Error() != "context lacks Docker endpoint" { - t.Error(err) - } - } else { - if endpoint != context.expected_endpoint { - t.Errorf("did not get correct context endpoint. Expected: %s, received: %s", context.expected_endpoint, endpoint) - } - } - - } - -} diff --git a/pkg/client/convert/LICENSE b/pkg/client/convert/LICENSE deleted file mode 100644 index 9c8e20ab..00000000 --- a/pkg/client/convert/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2013-2017 Docker, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/pkg/client/convert/README.md b/pkg/client/convert/README.md deleted file mode 100644 index c2132e4f..00000000 --- a/pkg/client/convert/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# github.com/docker/cli/cli/compose/convert - -DISCLAIMER: This is like the entire `github.com/docker/cli/cli/compose/convert` -package. This should be an easy import but importing it creates DEPENDENCY -HELL. I tried for an hour to fix it but it would work. TRY TO FIX AT YOUR OWN -RISK!!! - -Due to this literally just being copy-pasted from the lib, the Apache license -will be posted in this folder. Small edits to the source code have been to -function names and parts we don't need deleted. diff --git a/pkg/client/convert/compose_test.go b/pkg/client/convert/compose_test.go deleted file mode 100644 index 0dfac14e..00000000 --- a/pkg/client/convert/compose_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package convert - -import ( - "testing" - - composetypes "github.com/docker/cli/cli/compose/types" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/network" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/fs" -) - -func TestNamespaceScope(t *testing.T) { - scoped := Namespace{name: "foo"}.Scope("bar") - assert.Check(t, is.Equal("foo_bar", scoped)) -} - -func TestAddStackLabel(t *testing.T) { - labels := map[string]string{ - "something": "labeled", - } - actual := AddStackLabel(Namespace{name: "foo"}, labels) - expected := map[string]string{ - "something": "labeled", - LabelNamespace: "foo", - } - assert.Check(t, is.DeepEqual(expected, actual)) -} - -func TestNetworks(t *testing.T) { - namespace := Namespace{name: "foo"} - serviceNetworks := map[string]struct{}{ - "normal": {}, - "outside": {}, - "default": {}, - "attachablenet": {}, - "named": {}, - } - source := networkMap{ - "normal": composetypes.NetworkConfig{ - Driver: "overlay", - DriverOpts: map[string]string{ - "opt": "value", - }, - Ipam: composetypes.IPAMConfig{ - Driver: "driver", - Config: []*composetypes.IPAMPool{ - { - Subnet: "10.0.0.0", - }, - }, - }, - Labels: map[string]string{ - "something": "labeled", - }, - }, - "outside": composetypes.NetworkConfig{ - External: composetypes.External{External: true}, - Name: "special", - }, - "attachablenet": composetypes.NetworkConfig{ - Driver: "overlay", - Attachable: true, - }, - "named": composetypes.NetworkConfig{ - Name: "othername", - }, - } - expected := map[string]types.NetworkCreate{ - "foo_default": { - Labels: map[string]string{ - LabelNamespace: "foo", - }, - }, - "foo_normal": { - Driver: "overlay", - IPAM: &network.IPAM{ - Driver: "driver", - Config: []network.IPAMConfig{ - { - Subnet: "10.0.0.0", - }, - }, - }, - Options: map[string]string{ - "opt": "value", - }, - Labels: map[string]string{ - LabelNamespace: "foo", - "something": "labeled", - }, - }, - "foo_attachablenet": { - Driver: "overlay", - Attachable: true, - Labels: map[string]string{ - LabelNamespace: "foo", - }, - }, - "othername": { - Labels: map[string]string{LabelNamespace: "foo"}, - }, - } - - networks, externals := Networks(namespace, source, serviceNetworks) - assert.DeepEqual(t, expected, networks) - assert.DeepEqual(t, []string{"special"}, externals) -} - -func TestSecrets(t *testing.T) { - namespace := Namespace{name: "foo"} - - secretText := "this is the first secret" - secretFile := fs.NewFile(t, "convert-secrets", fs.WithContent(secretText)) - defer secretFile.Remove() - - source := map[string]composetypes.SecretConfig{ - "one": { - File: secretFile.Path(), - Labels: map[string]string{"monster": "mash"}, - }, - "ext": { - External: composetypes.External{ - External: true, - }, - }, - } - - specs, err := Secrets(namespace, source) - assert.NilError(t, err) - assert.Assert(t, is.Len(specs, 1)) - secret := specs[0] - assert.Check(t, is.Equal("foo_one", secret.Name)) - assert.Check(t, is.DeepEqual(map[string]string{ - "monster": "mash", - LabelNamespace: "foo", - }, secret.Labels)) - assert.Check(t, is.DeepEqual([]byte(secretText), secret.Data)) -} - -func TestConfigs(t *testing.T) { - namespace := Namespace{name: "foo"} - - configText := "this is the first config" - configFile := fs.NewFile(t, "convert-configs", fs.WithContent(configText)) - defer configFile.Remove() - - source := map[string]composetypes.ConfigObjConfig{ - "one": { - File: configFile.Path(), - Labels: map[string]string{"monster": "mash"}, - }, - "ext": { - External: composetypes.External{ - External: true, - }, - }, - } - - specs, err := Configs(namespace, source) - assert.NilError(t, err) - assert.Assert(t, is.Len(specs, 1)) - config := specs[0] - assert.Check(t, is.Equal("foo_one", config.Name)) - assert.Check(t, is.DeepEqual(map[string]string{ - "monster": "mash", - LabelNamespace: "foo", - }, config.Labels)) - assert.Check(t, is.DeepEqual([]byte(configText), config.Data)) -} diff --git a/pkg/client/convert/service_test.go b/pkg/client/convert/service_test.go deleted file mode 100644 index 0607f395..00000000 --- a/pkg/client/convert/service_test.go +++ /dev/null @@ -1,678 +0,0 @@ -package convert - -import ( - "context" - "os" - "sort" - "strings" - "testing" - "time" - - composetypes "github.com/docker/cli/cli/compose/types" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/client" - "github.com/pkg/errors" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestConvertRestartPolicyFromNone(t *testing.T) { - policy, err := convertRestartPolicy("no", nil) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual((*swarm.RestartPolicy)(nil), policy)) -} - -func TestConvertRestartPolicyFromUnknown(t *testing.T) { - _, err := convertRestartPolicy("unknown", nil) - assert.Error(t, err, "unknown restart policy: unknown") -} - -func TestConvertRestartPolicyFromAlways(t *testing.T) { - policy, err := convertRestartPolicy("always", nil) - expected := &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyConditionAny, - } - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, policy)) -} - -func TestConvertRestartPolicyFromFailure(t *testing.T) { - policy, err := convertRestartPolicy("on-failure:4", nil) - attempts := uint64(4) - expected := &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyConditionOnFailure, - MaxAttempts: &attempts, - } - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, policy)) -} - -func strPtr(val string) *string { - return &val -} - -func TestConvertEnvironment(t *testing.T) { - source := map[string]*string{ - "foo": strPtr("bar"), - "key": strPtr("value"), - } - env := convertEnvironment(source) - sort.Strings(env) - assert.Check(t, is.DeepEqual([]string{"foo=bar", "key=value"}, env)) -} - -func TestConvertExtraHosts(t *testing.T) { - source := composetypes.HostsList{ - "zulu:127.0.0.2", - "alpha:127.0.0.1", - "zulu:ff02::1", - } - assert.Check(t, is.DeepEqual([]string{"127.0.0.2 zulu", "127.0.0.1 alpha", "ff02::1 zulu"}, convertExtraHosts(source))) -} - -func TestConvertResourcesFull(t *testing.T) { - source := composetypes.Resources{ - Limits: &composetypes.ResourceLimit{ - NanoCPUs: "0.003", - MemoryBytes: composetypes.UnitBytes(300000000), - }, - Reservations: &composetypes.Resource{ - NanoCPUs: "0.002", - MemoryBytes: composetypes.UnitBytes(200000000), - }, - } - resources, err := convertResources(source) - assert.NilError(t, err) - - expected := &swarm.ResourceRequirements{ - Limits: &swarm.Limit{ - NanoCPUs: 3000000, - MemoryBytes: 300000000, - }, - Reservations: &swarm.Resources{ - NanoCPUs: 2000000, - MemoryBytes: 200000000, - }, - } - assert.Check(t, is.DeepEqual(expected, resources)) -} - -func TestConvertResourcesOnlyMemory(t *testing.T) { - source := composetypes.Resources{ - Limits: &composetypes.ResourceLimit{ - MemoryBytes: composetypes.UnitBytes(300000000), - }, - Reservations: &composetypes.Resource{ - MemoryBytes: composetypes.UnitBytes(200000000), - }, - } - resources, err := convertResources(source) - assert.NilError(t, err) - - expected := &swarm.ResourceRequirements{ - Limits: &swarm.Limit{ - MemoryBytes: 300000000, - }, - Reservations: &swarm.Resources{ - MemoryBytes: 200000000, - }, - } - assert.Check(t, is.DeepEqual(expected, resources)) -} - -func TestConvertHealthcheck(t *testing.T) { - retries := uint64(10) - timeout := composetypes.Duration(30 * time.Second) - interval := composetypes.Duration(2 * time.Millisecond) - source := &composetypes.HealthCheckConfig{ - Test: []string{"EXEC", "touch", "/foo"}, - Timeout: &timeout, - Interval: &interval, - Retries: &retries, - } - expected := &container.HealthConfig{ - Test: source.Test, - Timeout: time.Duration(timeout), - Interval: time.Duration(interval), - Retries: 10, - } - - healthcheck, err := convertHealthcheck(source) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, healthcheck)) -} - -func TestConvertHealthcheckDisable(t *testing.T) { - source := &composetypes.HealthCheckConfig{Disable: true} - expected := &container.HealthConfig{ - Test: []string{"NONE"}, - } - - healthcheck, err := convertHealthcheck(source) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, healthcheck)) -} - -func TestConvertHealthcheckDisableWithTest(t *testing.T) { - source := &composetypes.HealthCheckConfig{ - Disable: true, - Test: []string{"EXEC", "touch"}, - } - _, err := convertHealthcheck(source) - assert.Error(t, err, "test and disable can't be set at the same time") -} - -func TestConvertEndpointSpec(t *testing.T) { - source := []composetypes.ServicePortConfig{ - { - Protocol: "udp", - Target: 53, - Published: 1053, - Mode: "host", - }, - { - Target: 8080, - Published: 80, - }, - } - endpoint := convertEndpointSpec("vip", source) - - expected := swarm.EndpointSpec{ - Mode: swarm.ResolutionMode(strings.ToLower("vip")), - Ports: []swarm.PortConfig{ - { - TargetPort: 8080, - PublishedPort: 80, - }, - { - Protocol: "udp", - TargetPort: 53, - PublishedPort: 1053, - PublishMode: "host", - }, - }, - } - - assert.Check(t, is.DeepEqual(expected, *endpoint)) -} - -func TestConvertServiceNetworksOnlyDefault(t *testing.T) { - networkConfigs := networkMap{} - - configs, err := convertServiceNetworks( - nil, networkConfigs, NewNamespace("foo"), "service") - - expected := []swarm.NetworkAttachmentConfig{ - { - Target: "foo_default", - Aliases: []string{"service"}, - }, - } - - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, configs)) -} - -func TestConvertServiceNetworks(t *testing.T) { - networkConfigs := networkMap{ - "front": composetypes.NetworkConfig{ - External: composetypes.External{External: true}, - Name: "fronttier", - }, - "back": composetypes.NetworkConfig{}, - } - networks := map[string]*composetypes.ServiceNetworkConfig{ - "front": { - Aliases: []string{"something"}, - }, - "back": { - Aliases: []string{"other"}, - }, - } - - configs, err := convertServiceNetworks( - networks, networkConfigs, NewNamespace("foo"), "service") - - expected := []swarm.NetworkAttachmentConfig{ - { - Target: "foo_back", - Aliases: []string{"other", "service"}, - }, - { - Target: "fronttier", - Aliases: []string{"something", "service"}, - }, - } - - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, configs)) -} - -func TestConvertServiceNetworksCustomDefault(t *testing.T) { - networkConfigs := networkMap{ - "default": composetypes.NetworkConfig{ - External: composetypes.External{External: true}, - Name: "custom", - }, - } - networks := map[string]*composetypes.ServiceNetworkConfig{} - - configs, err := convertServiceNetworks( - networks, networkConfigs, NewNamespace("foo"), "service") - - expected := []swarm.NetworkAttachmentConfig{ - { - Target: "custom", - Aliases: []string{"service"}, - }, - } - - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, configs)) -} - -func TestConvertDNSConfigEmpty(t *testing.T) { - dnsConfig := convertDNSConfig(nil, nil) - assert.Check(t, is.DeepEqual((*swarm.DNSConfig)(nil), dnsConfig)) -} - -var ( - nameservers = []string{"8.8.8.8", "9.9.9.9"} - search = []string{"dc1.example.com", "dc2.example.com"} -) - -func TestConvertDNSConfigAll(t *testing.T) { - dnsConfig := convertDNSConfig(nameservers, search) - assert.Check(t, is.DeepEqual(&swarm.DNSConfig{ - Nameservers: nameservers, - Search: search, - }, dnsConfig)) -} - -func TestConvertDNSConfigNameservers(t *testing.T) { - dnsConfig := convertDNSConfig(nameservers, nil) - assert.Check(t, is.DeepEqual(&swarm.DNSConfig{ - Nameservers: nameservers, - Search: nil, - }, dnsConfig)) -} - -func TestConvertDNSConfigSearch(t *testing.T) { - dnsConfig := convertDNSConfig(nil, search) - assert.Check(t, is.DeepEqual(&swarm.DNSConfig{ - Nameservers: nil, - Search: search, - }, dnsConfig)) -} - -func TestConvertCredentialSpec(t *testing.T) { - tests := []struct { - name string - in composetypes.CredentialSpecConfig - out *swarm.CredentialSpec - configs []*swarm.ConfigReference - expectedErr string - }{ - { - name: "empty", - }, - { - name: "config-and-file", - in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json"}, - expectedErr: `invalid credential spec: cannot specify both "Config" and "File"`, - }, - { - name: "config-and-registry", - in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", Registry: "testing"}, - expectedErr: `invalid credential spec: cannot specify both "Config" and "Registry"`, - }, - { - name: "file-and-registry", - in: composetypes.CredentialSpecConfig{File: "somefile.json", Registry: "testing"}, - expectedErr: `invalid credential spec: cannot specify both "File" and "Registry"`, - }, - { - name: "config-and-file-and-registry", - in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"}, - expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`, - }, - { - name: "missing-config-reference", - in: composetypes.CredentialSpecConfig{Config: "missing"}, - expectedErr: "invalid credential spec: spec specifies config missing, but no such config can be found", - configs: []*swarm.ConfigReference{ - { - ConfigName: "someName", - ConfigID: "missing", - }, - }, - }, - { - name: "namespaced-config", - in: composetypes.CredentialSpecConfig{Config: "name"}, - configs: []*swarm.ConfigReference{ - { - ConfigName: "namespaced-config_name", - ConfigID: "someID", - }, - }, - out: &swarm.CredentialSpec{Config: "someID"}, - }, - { - name: "config", - in: composetypes.CredentialSpecConfig{Config: "someName"}, - configs: []*swarm.ConfigReference{ - { - ConfigName: "someOtherName", - ConfigID: "someOtherID", - }, { - ConfigName: "someName", - ConfigID: "someID", - }, - }, - out: &swarm.CredentialSpec{Config: "someID"}, - }, - { - name: "file", - in: composetypes.CredentialSpecConfig{File: "somefile.json"}, - out: &swarm.CredentialSpec{File: "somefile.json"}, - }, - { - name: "registry", - in: composetypes.CredentialSpecConfig{Registry: "testing"}, - out: &swarm.CredentialSpec{Registry: "testing"}, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - namespace := NewNamespace(tc.name) - swarmSpec, err := convertCredentialSpec(namespace, tc.in, tc.configs) - - if tc.expectedErr != "" { - assert.Error(t, err, tc.expectedErr) - } else { - assert.NilError(t, err) - } - assert.DeepEqual(t, swarmSpec, tc.out) - }) - } -} - -func TestConvertUpdateConfigOrder(t *testing.T) { - // test default behavior - updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{}) - assert.Check(t, is.Equal("", updateConfig.Order)) - - // test start-first - updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{ - Order: "start-first", - }) - assert.Check(t, is.Equal(updateConfig.Order, "start-first")) - - // test stop-first - updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{ - Order: "stop-first", - }) - assert.Check(t, is.Equal(updateConfig.Order, "stop-first")) -} - -func TestConvertFileObject(t *testing.T) { - namespace := NewNamespace("testing") - config := composetypes.FileReferenceConfig{ - Source: "source", - Target: "target", - UID: "user", - GID: "group", - Mode: uint32Ptr(0644), - } - swarmRef, err := convertFileObject(namespace, config, lookupConfig) - assert.NilError(t, err) - - expected := swarmReferenceObject{ - Name: "testing_source", - File: swarmReferenceTarget{ - Name: config.Target, - UID: config.UID, - GID: config.GID, - Mode: os.FileMode(0644), - }, - } - assert.Check(t, is.DeepEqual(expected, swarmRef)) -} - -func lookupConfig(key string) (composetypes.FileObjectConfig, error) { - if key != "source" { - return composetypes.FileObjectConfig{}, errors.New("bad key") - } - return composetypes.FileObjectConfig{}, nil -} - -func TestConvertFileObjectDefaults(t *testing.T) { - namespace := NewNamespace("testing") - config := composetypes.FileReferenceConfig{Source: "source"} - swarmRef, err := convertFileObject(namespace, config, lookupConfig) - assert.NilError(t, err) - - expected := swarmReferenceObject{ - Name: "testing_source", - File: swarmReferenceTarget{ - Name: config.Source, - UID: "0", - GID: "0", - Mode: os.FileMode(0444), - }, - } - assert.Check(t, is.DeepEqual(expected, swarmRef)) -} - -func TestServiceConvertsIsolation(t *testing.T) { - src := composetypes.ServiceConfig{ - Isolation: "hyperv", - } - result, err := Service("1.35", Namespace{name: "foo"}, src, nil, nil, nil, nil) - assert.NilError(t, err) - assert.Check(t, is.Equal(container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation)) -} - -func TestConvertServiceSecrets(t *testing.T) { - namespace := Namespace{name: "foo"} - secrets := []composetypes.ServiceSecretConfig{ - {Source: "foo_secret"}, - {Source: "bar_secret"}, - } - secretSpecs := map[string]composetypes.SecretConfig{ - "foo_secret": { - Name: "foo_secret", - }, - "bar_secret": { - Name: "bar_secret", - }, - } - client := &fakeClient{ - secretListFunc: func(opts types.SecretListOptions) ([]swarm.Secret, error) { - assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_secret")) - assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_secret")) - return []swarm.Secret{ - {Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}}, - {Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}}, - }, nil - }, - } - refs, err := convertServiceSecrets(client, namespace, secrets, secretSpecs) - assert.NilError(t, err) - expected := []*swarm.SecretReference{ - { - SecretName: "bar_secret", - File: &swarm.SecretReferenceFileTarget{ - Name: "bar_secret", - UID: "0", - GID: "0", - Mode: 0444, - }, - }, - { - SecretName: "foo_secret", - File: &swarm.SecretReferenceFileTarget{ - Name: "foo_secret", - UID: "0", - GID: "0", - Mode: 0444, - }, - }, - } - assert.DeepEqual(t, expected, refs) -} - -func TestConvertServiceConfigs(t *testing.T) { - namespace := Namespace{name: "foo"} - service := composetypes.ServiceConfig{ - Configs: []composetypes.ServiceConfigObjConfig{ - {Source: "foo_config"}, - {Source: "bar_config"}, - }, - CredentialSpec: composetypes.CredentialSpecConfig{ - Config: "baz_config", - }, - } - configSpecs := map[string]composetypes.ConfigObjConfig{ - "foo_config": { - Name: "foo_config", - }, - "bar_config": { - Name: "bar_config", - }, - "baz_config": { - Name: "baz_config", - }, - } - client := &fakeClient{ - configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) { - assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config")) - assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config")) - assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config")) - return []swarm.Config{ - {Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}}, - {Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}}, - {Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}}, - }, nil - }, - } - refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs) - assert.NilError(t, err) - expected := []*swarm.ConfigReference{ - { - ConfigName: "bar_config", - File: &swarm.ConfigReferenceFileTarget{ - Name: "bar_config", - UID: "0", - GID: "0", - Mode: 0444, - }, - }, - { - ConfigName: "baz_config", - Runtime: &swarm.ConfigReferenceRuntimeTarget{}, - }, - { - ConfigName: "foo_config", - File: &swarm.ConfigReferenceFileTarget{ - Name: "foo_config", - UID: "0", - GID: "0", - Mode: 0444, - }, - }, - } - assert.DeepEqual(t, expected, refs) -} - -type fakeClient struct { - client.Client - secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error) - configListFunc func(types.ConfigListOptions) ([]swarm.Config, error) -} - -func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { - if c.secretListFunc != nil { - return c.secretListFunc(options) - } - return []swarm.Secret{}, nil -} - -func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { - if c.configListFunc != nil { - return c.configListFunc(options) - } - return []swarm.Config{}, nil -} - -func TestConvertUpdateConfigParallelism(t *testing.T) { - parallel := uint64(4) - - // test default behavior - updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{}) - assert.Check(t, is.Equal(uint64(1), updateConfig.Parallelism)) - - // Non default value - updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{ - Parallelism: ¶llel, - }) - assert.Check(t, is.Equal(parallel, updateConfig.Parallelism)) -} - -func TestConvertServiceCapAddAndCapDrop(t *testing.T) { - tests := []struct { - title string - in, out composetypes.ServiceConfig - }{ - { - title: "default behavior", - }, - { - title: "some values", - in: composetypes.ServiceConfig{ - CapAdd: []string{"SYS_NICE", "CAP_NET_ADMIN"}, - CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"}, - }, - out: composetypes.ServiceConfig{ - CapAdd: []string{"CAP_NET_ADMIN", "CAP_SYS_NICE"}, - CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID"}, - }, - }, - { - title: "adding ALL capabilities", - in: composetypes.ServiceConfig{ - CapAdd: []string{"ALL", "CAP_NET_ADMIN"}, - CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"}, - }, - out: composetypes.ServiceConfig{ - CapAdd: []string{"ALL"}, - CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"}, - }, - }, - { - title: "dropping ALL capabilities", - in: composetypes.ServiceConfig{ - CapAdd: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"}, - CapDrop: []string{"ALL", "CAP_NET_ADMIN", "CAP_FOO"}, - }, - out: composetypes.ServiceConfig{ - CapAdd: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"}, - CapDrop: []string{"ALL"}, - }, - }, - } - for _, tc := range tests { - tc := tc - t.Run(tc.title, func(t *testing.T) { - result, err := Service("1.41", Namespace{name: "foo"}, tc.in, nil, nil, nil, nil) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityAdd, tc.out.CapAdd)) - assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityDrop, tc.out.CapDrop)) - }) - } -} diff --git a/pkg/client/convert/volume_test.go b/pkg/client/convert/volume_test.go deleted file mode 100644 index 2b08357b..00000000 --- a/pkg/client/convert/volume_test.go +++ /dev/null @@ -1,361 +0,0 @@ -package convert - -import ( - "testing" - - composetypes "github.com/docker/cli/cli/compose/types" - "github.com/docker/docker/api/types/mount" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestConvertVolumeToMountAnonymousVolume(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Target: "/foo/bar", - } - expected := mount.Mount{ - Type: mount.TypeVolume, - Target: "/foo/bar", - } - mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountAnonymousBind(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "bind", - Target: "/foo/bar", - Bind: &composetypes.ServiceVolumeBind{ - Propagation: "slave", - }, - } - _, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.Error(t, err, "invalid bind source, source cannot be empty") -} - -func TestConvertVolumeToMountUnapprovedType(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "foo", - Target: "/foo/bar", - } - _, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.Error(t, err, "volume type must be volume, bind, tmpfs or npipe") -} - -func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "foo", - Target: "/target", - Bind: &composetypes.ServiceVolumeBind{ - Propagation: "slave", - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "bind options are incompatible with type volume") -} - -func TestConvertVolumeToMountConflictingOptionsTmpfsInVolume(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "foo", - Target: "/target", - Tmpfs: &composetypes.ServiceVolumeTmpfs{ - Size: 1000, - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "tmpfs options are incompatible with type volume") -} - -func TestConvertVolumeToMountConflictingOptionsVolumeInBind(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: "/foo", - Target: "/target", - Volume: &composetypes.ServiceVolumeVolume{ - NoCopy: true, - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "volume options are incompatible with type bind") -} - -func TestConvertVolumeToMountConflictingOptionsTmpfsInBind(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: "/foo", - Target: "/target", - Tmpfs: &composetypes.ServiceVolumeTmpfs{ - Size: 1000, - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "tmpfs options are incompatible with type bind") -} - -func TestConvertVolumeToMountConflictingOptionsBindInTmpfs(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "tmpfs", - Target: "/target", - Bind: &composetypes.ServiceVolumeBind{ - Propagation: "slave", - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "bind options are incompatible with type tmpfs") -} - -func TestConvertVolumeToMountConflictingOptionsVolumeInTmpfs(t *testing.T) { - namespace := NewNamespace("foo") - - config := composetypes.ServiceVolumeConfig{ - Type: "tmpfs", - Target: "/target", - Volume: &composetypes.ServiceVolumeVolume{ - NoCopy: true, - }, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "volume options are incompatible with type tmpfs") -} - -func TestConvertVolumeToMountNamedVolume(t *testing.T) { - stackVolumes := volumes{ - "normal": composetypes.VolumeConfig{ - Driver: "glusterfs", - DriverOpts: map[string]string{ - "opt": "value", - }, - Labels: map[string]string{ - "something": "labeled", - }, - }, - } - namespace := NewNamespace("foo") - expected := mount.Mount{ - Type: mount.TypeVolume, - Source: "foo_normal", - Target: "/foo", - ReadOnly: true, - VolumeOptions: &mount.VolumeOptions{ - Labels: map[string]string{ - LabelNamespace: "foo", - "something": "labeled", - }, - DriverConfig: &mount.Driver{ - Name: "glusterfs", - Options: map[string]string{ - "opt": "value", - }, - }, - NoCopy: true, - }, - } - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "normal", - Target: "/foo", - ReadOnly: true, - Volume: &composetypes.ServiceVolumeVolume{ - NoCopy: true, - }, - } - mount, err := convertVolumeToMount(config, stackVolumes, namespace) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountNamedVolumeWithNameCustomizd(t *testing.T) { - stackVolumes := volumes{ - "normal": composetypes.VolumeConfig{ - Name: "user_specified_name", - Driver: "vsphere", - DriverOpts: map[string]string{ - "opt": "value", - }, - Labels: map[string]string{ - "something": "labeled", - }, - }, - } - namespace := NewNamespace("foo") - expected := mount.Mount{ - Type: mount.TypeVolume, - Source: "user_specified_name", - Target: "/foo", - ReadOnly: true, - VolumeOptions: &mount.VolumeOptions{ - Labels: map[string]string{ - LabelNamespace: "foo", - "something": "labeled", - }, - DriverConfig: &mount.Driver{ - Name: "vsphere", - Options: map[string]string{ - "opt": "value", - }, - }, - NoCopy: true, - }, - } - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "normal", - Target: "/foo", - ReadOnly: true, - Volume: &composetypes.ServiceVolumeVolume{ - NoCopy: true, - }, - } - mount, err := convertVolumeToMount(config, stackVolumes, namespace) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountNamedVolumeExternal(t *testing.T) { - stackVolumes := volumes{ - "outside": composetypes.VolumeConfig{ - Name: "special", - External: composetypes.External{External: true}, - }, - } - namespace := NewNamespace("foo") - expected := mount.Mount{ - Type: mount.TypeVolume, - Source: "special", - Target: "/foo", - VolumeOptions: &mount.VolumeOptions{NoCopy: false}, - } - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "outside", - Target: "/foo", - } - mount, err := convertVolumeToMount(config, stackVolumes, namespace) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountNamedVolumeExternalNoCopy(t *testing.T) { - stackVolumes := volumes{ - "outside": composetypes.VolumeConfig{ - Name: "special", - External: composetypes.External{External: true}, - }, - } - namespace := NewNamespace("foo") - expected := mount.Mount{ - Type: mount.TypeVolume, - Source: "special", - Target: "/foo", - VolumeOptions: &mount.VolumeOptions{ - NoCopy: true, - }, - } - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "outside", - Target: "/foo", - Volume: &composetypes.ServiceVolumeVolume{ - NoCopy: true, - }, - } - mount, err := convertVolumeToMount(config, stackVolumes, namespace) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountBind(t *testing.T) { - stackVolumes := volumes{} - namespace := NewNamespace("foo") - expected := mount.Mount{ - Type: mount.TypeBind, - Source: "/bar", - Target: "/foo", - ReadOnly: true, - BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared}, - } - config := composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: "/bar", - Target: "/foo", - ReadOnly: true, - Bind: &composetypes.ServiceVolumeBind{Propagation: "shared"}, - } - mount, err := convertVolumeToMount(config, stackVolumes, namespace) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) { - namespace := NewNamespace("foo") - config := composetypes.ServiceVolumeConfig{ - Type: "volume", - Source: "unknown", - Target: "/foo", - ReadOnly: true, - } - _, err := convertVolumeToMount(config, volumes{}, namespace) - assert.Error(t, err, "undefined volume \"unknown\"") -} - -func TestConvertTmpfsToMountVolume(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "tmpfs", - Target: "/foo/bar", - Tmpfs: &composetypes.ServiceVolumeTmpfs{ - Size: 1000, - }, - } - expected := mount.Mount{ - Type: mount.TypeTmpfs, - Target: "/foo/bar", - TmpfsOptions: &mount.TmpfsOptions{SizeBytes: 1000}, - } - mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -} - -func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "tmpfs", - Source: "/bar", - Target: "/foo/bar", - Tmpfs: &composetypes.ServiceVolumeTmpfs{ - Size: 1000, - }, - } - - _, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.Error(t, err, "invalid tmpfs source, source must be empty") -} - -func TestConvertVolumeToMountAnonymousNpipe(t *testing.T) { - config := composetypes.ServiceVolumeConfig{ - Type: "npipe", - Source: `\\.\pipe\foo`, - Target: `\\.\pipe\foo`, - } - expected := mount.Mount{ - Type: mount.TypeNamedPipe, - Source: `\\.\pipe\foo`, - Target: `\\.\pipe\foo`, - } - mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(expected, mount)) -}