forked from toolshed/abra
		
	fix: remove cruft + readme pass + document forks
This commit is contained in:
		
							
								
								
									
										80
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								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 <package>` 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@<commit>` where `<commit>` 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.
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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.
 | 
			
		||||
@ -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).
 | 
			
		||||
@ -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)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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.
 | 
			
		||||
@ -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.
 | 
			
		||||
@ -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))
 | 
			
		||||
}
 | 
			
		||||
@ -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))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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))
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user