initial docker-compose support - wip

This commit is contained in:
Kyle Quest 2021-09-22 12:51:22 -07:00
parent 15d2bb599c
commit 5175e48ed0
875 changed files with 104544 additions and 18383 deletions

27
go.mod
View File

@ -3,7 +3,7 @@ module github.com/docker-slim/docker-slim
go 1.13
require (
github.com/Microsoft/hcsshim v0.8.9 // indirect
github.com/Microsoft/hcsshim v0.8.22 // indirect
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/antchfx/htmlquery v1.2.3 // indirect
@ -14,42 +14,41 @@ require (
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/c-bata/go-prompt v0.2.3
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c
github.com/containerd/containerd v1.3.4 // indirect
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/docker-slim/go-update v0.0.0-20190422071557-ed40247aff59
github.com/docker-slim/uilive v0.0.2 // indirect
github.com/docker-slim/uiprogress v0.0.0-20190505193231-9d4396e6d40b
github.com/docker/docker v1.4.2-0.20191101170500-ac7306503d23
github.com/docker/docker v20.10.8+incompatible
github.com/docker/go-connections v0.4.0
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.10.0
github.com/fsouza/go-dockerclient v1.6.5
github.com/fsouza/go-dockerclient v1.7.4
github.com/getkin/kin-openapi v0.19.0
github.com/ghodss/yaml v1.0.0
github.com/gocolly/colly/v2 v2.0.1
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/websocket v1.4.2
github.com/gosuri/uilive v0.0.3 // indirect
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect
github.com/klauspost/compress v1.10.6 // indirect
github.com/klauspost/pgzip v1.2.4 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/pkg/errors v0.9.1
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect
github.com/sirupsen/logrus v1.6.0
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/sirupsen/logrus v1.8.1
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/ulikunitz/xz v0.5.7 // indirect
github.com/urfave/cli v1.22.4
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sys v0.0.0-20210921065528-437939a70204
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7 // indirect
google.golang.org/grpc v1.29.1 // indirect
github.com/compose-spec/compose-go v0.0.0-20210916141509-a7e1bc322970
)
replace github.com/compose-spec/compose-go => ./pkg/third_party/compose-go

769
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,12 @@ var CLI = cli.Command{
commands.Cflag(commands.FlagTarget),
commands.Cflag(commands.FlagPull),
commands.Cflag(commands.FlagShowPullLogs),
commands.Cflag(commands.FlagComposeFile),
commands.Cflag(commands.FlagTargetComposeSvc),
commands.Cflag(commands.FlagDepIncludeComposeSvc),
commands.Cflag(commands.FlagDepExcludeComposeSvc),
commands.Cflag(commands.FlagDepIncludeComposeSvcDeps),
commands.Cflag(commands.FlagComposeNet),
commands.Cflag(commands.FlagHTTPProbeOff),
commands.Cflag(commands.FlagHTTPProbe),
commands.Cflag(commands.FlagHTTPProbeCmd),
@ -170,29 +176,41 @@ var CLI = cli.Command{
xc.Exit(-1)
}
composeFile := ctx.String(commands.FlagComposeFile)
//todo: load/parse compose file and then use it to validate the related compose params
targetComposeSvc := ctx.String(commands.FlagTargetComposeSvc)
depIncludeComposeSvcDeps := ctx.String(commands.FlagDepIncludeComposeSvcDeps)
depIncludeComposeSvcs := ctx.StringSlice(commands.FlagDepIncludeComposeSvc)
depExcludeComposeSvcs := ctx.StringSlice(commands.FlagDepExcludeComposeSvc)
composeNets := ctx.StringSlice(commands.FlagComposeNet)
var targetRef string
if cbOpts.Dockerfile == "" {
targetRef = ctx.String(commands.FlagTarget)
if targetRef == "" {
if len(ctx.Args()) < 1 {
xc.Out.Error("param.target", "missing image ID/name")
cli.ShowCommandHelp(ctx, Name)
return nil
} else {
targetRef = ctx.Args().First()
}
}
if composeFile != "" && targetComposeSvc != "" {
targetRef = targetComposeSvc
} else {
targetRef = cbOpts.DockerfileContext
if targetRef == "" {
if len(ctx.Args()) < 1 {
xc.Out.Error("param.target", "missing image ID/name")
cli.ShowCommandHelp(ctx, Name)
return nil
} else {
targetRef = ctx.Args().First()
if cbOpts.Dockerfile == "" {
targetRef = ctx.String(commands.FlagTarget)
if targetRef == "" {
if len(ctx.Args()) < 1 {
xc.Out.Error("param.target", "missing image ID/name")
cli.ShowCommandHelp(ctx, Name)
return nil
} else {
targetRef = ctx.Args().First()
}
}
} else {
targetRef = cbOpts.DockerfileContext
if targetRef == "" {
if len(ctx.Args()) < 1 {
xc.Out.Error("param.target", "missing Dockerfile build context directory")
cli.ShowCommandHelp(ctx, Name)
return nil
} else {
targetRef = ctx.Args().First()
}
}
}
}
@ -568,6 +586,12 @@ var CLI = cli.Command{
targetRef,
doPull,
doShowPullLogs,
composeFile,
targetComposeSvc,
depIncludeComposeSvcDeps,
depIncludeComposeSvcs,
depExcludeComposeSvcs,
composeNets,
cbOpts,
crOpts,
outputTags,

View File

@ -15,6 +15,7 @@ import (
"github.com/docker-slim/docker-slim/pkg/app"
"github.com/docker-slim/docker-slim/pkg/app/master/builder"
"github.com/docker-slim/docker-slim/pkg/app/master/commands"
"github.com/docker-slim/docker-slim/pkg/app/master/compose"
"github.com/docker-slim/docker-slim/pkg/app/master/config"
"github.com/docker-slim/docker-slim/pkg/app/master/docker/dockerclient"
"github.com/docker-slim/docker-slim/pkg/app/master/inspectors/container"
@ -53,6 +54,12 @@ func OnCommand(
targetRef string,
doPull bool,
doShowPullLogs bool,
composeFile string,
targetComposeSvc string,
depIncludeComposeSvcDeps string,
depIncludeComposeSvcs []string,
depExcludeComposeSvcs []string,
composeNets []string,
cbOpts *config.ContainerBuildOptions,
crOpts *config.ContainerRunOptions,
outputTags []string,
@ -150,9 +157,10 @@ func OnCommand(
xc.Out.State("started")
if cbOpts.Dockerfile == "" {
if composeFile != "" && targetComposeSvc != "" {
xc.Out.Info("params",
ovars{
"target.type": "compose.service",
"target": targetRef,
"continue.mode": continueAfter.Mode,
"rt.as.user": doRunTargetAsUser,
@ -160,14 +168,27 @@ func OnCommand(
"tags": strings.Join(outputTags, ","),
})
} else {
xc.Out.Info("params",
ovars{
"context": targetRef,
"file": cbOpts.Dockerfile,
"continue.mode": continueAfter.Mode,
"rt.as.user": doRunTargetAsUser,
"keep.perms": doKeepPerms,
})
if cbOpts.Dockerfile == "" {
xc.Out.Info("params",
ovars{
"target.type": "image",
"target": targetRef,
"continue.mode": continueAfter.Mode,
"rt.as.user": doRunTargetAsUser,
"keep.perms": doKeepPerms,
"tags": strings.Join(outputTags, ","),
})
} else {
xc.Out.Info("params",
ovars{
"target.type": "dockerfile",
"context": targetRef,
"file": cbOpts.Dockerfile,
"continue.mode": continueAfter.Mode,
"rt.as.user": doRunTargetAsUser,
"keep.perms": doKeepPerms,
})
}
}
if cbOpts.Dockerfile != "" {
@ -261,6 +282,22 @@ func OnCommand(
//todo: remove the temporary fat image (should have a flag for it in case users want the fat image too)
}
if composeFile != "" {
composeSelectors := compose.NewServiceSelectors(depIncludeComposeSvcDeps,
depIncludeComposeSvcs,
depExcludeComposeSvcs)
logger.Debugf("compose: file='%s' selectors='%+v'\n",
composeFile, composeSelectors)
/*
cx, err := compose.NewExecution(xc,
logger,
client,
composeFile
)
*/
}
logger.Infof("image=%v http-probe=%v remove-file-artifacts=%v image-overrides=%+v entrypoint=%+v (%v) cmd=%+v (%v) workdir='%v' env=%+v expose=%+v",
targetRef, doHTTPProbe, doRmFileArtifacts,
imageOverrideSelectors,

View File

@ -14,6 +14,12 @@ var CommandSuggestion = prompt.Suggest{
var CommandFlagSuggestions = &commands.FlagSuggestions{
Names: []prompt.Suggest{
{Text: commands.FullFlagName(commands.FlagTarget), Description: commands.FlagTargetUsage},
{Text: commands.FullFlagName(commands.FlagComposeFile), Description: commands.FlagComposeFileUsage},
{Text: commands.FullFlagName(commands.FlagTargetComposeSvc), Description: commands.FlagTargetComposeSvcUsage},
{Text: commands.FullFlagName(commands.FlagDepIncludeComposeSvc), Description: commands.FlagDepIncludeComposeSvcUsage},
{Text: commands.FullFlagName(commands.FlagDepExcludeComposeSvc), Description: commands.FlagDepExcludeComposeSvcUsage},
{Text: commands.FullFlagName(commands.FlagDepIncludeComposeSvcDeps), Description: commands.FlagDepIncludeComposeSvcDepsUsage},
{Text: commands.FullFlagName(commands.FlagComposeNet), Description: commands.FlagComposeNetUsage},
{Text: commands.FullFlagName(commands.FlagPull), Description: commands.FlagPullUsage},
{Text: commands.FullFlagName(commands.FlagShowPullLogs), Description: commands.FlagShowPullLogsUsage},
{Text: commands.FullFlagName(FlagShowBuildLogs), Description: FlagShowBuildLogsUsage},
@ -110,6 +116,7 @@ var CommandFlagSuggestions = &commands.FlagSuggestions{
commands.FullFlagName(commands.FlagPull): commands.CompleteBool,
commands.FullFlagName(commands.FlagShowPullLogs): commands.CompleteBool,
commands.FullFlagName(commands.FlagTarget): commands.CompleteTarget,
commands.FullFlagName(commands.FlagComposeFile): commands.CompleteFile,
commands.FullFlagName(FlagShowBuildLogs): commands.CompleteBool,
commands.FullFlagName(commands.FlagShowContainerLogs): commands.CompleteBool,
commands.FullFlagName(commands.FlagPublishExposedPorts): commands.CompleteBool,

View File

@ -53,6 +53,14 @@ const (
FlagPull = "pull"
FlagShowPullLogs = "show-plogs"
//Compose-related flags
FlagComposeFile = "compose-file"
FlagTargetComposeSvc = "target-compose-svc"
FlagDepIncludeComposeSvc = "dep-include-compose-svc"
FlagDepExcludeComposeSvc = "dep-exclude-compose-svc"
FlagDepIncludeComposeSvcDeps = "dep-include-compose-svc-deps"
FlagComposeNet = "compose-net"
FlagRemoveFileArtifacts = "remove-file-artifacts"
FlagCopyMetaArtifacts = "copy-meta-artifacts"
@ -119,6 +127,14 @@ const (
FlagPullUsage = "Try pulling target if it's not available locally"
FlagShowPullLogsUsage = "Show image pull logs"
//Compose-related flags
FlagComposeFileUsage = "Load container info from selected compose file"
FlagTargetComposeSvcUsage = "Target service from compose file"
FlagDepIncludeComposeSvcUsage = "Include specific compose service as a target dependency (only selected services will be started)"
FlagDepExcludeComposeSvcUsage = "Exclude specific service from the compose services that will be started as target dependencies"
FlagDepIncludeComposeSvcDepsUsage = "Include all dependencies for the selected compose service (excluding the service itself) as target dependencies"
FlagComposeNetUsage = "Attach target to the selected compose network(s) otherwise all networks will be attached"
FlagRemoveFileArtifactsUsage = "remove file artifacts when command is done"
FlagCopyMetaArtifactsUsage = "copy metadata artifacts to the selected location when command is done"
@ -270,6 +286,42 @@ var CommonFlags = map[string]cli.Flag{
Usage: FlagShowPullLogsUsage,
EnvVar: "DSLIM_PLOG",
},
FlagComposeFile: cli.StringFlag{
Name: FlagComposeFile,
Value: "",
Usage: FlagComposeFileUsage,
EnvVar: "DSLIM_COMPOSE_FILE",
},
FlagTargetComposeSvc: cli.StringFlag{
Name: FlagTargetComposeSvc,
Value: "",
Usage: FlagTargetComposeSvcUsage,
EnvVar: "DSLIM_TARGET_COMPOSE_SVC",
},
FlagDepIncludeComposeSvcDeps: cli.StringFlag{
Name: FlagDepIncludeComposeSvcDeps,
Value: "",
Usage: FlagDepIncludeComposeSvcDepsUsage,
EnvVar: "DSLIM_DEP_INCLUDE_COMPOSE_SVC_DEPS",
},
FlagDepIncludeComposeSvc: cli.StringSliceFlag{
Name: FlagDepIncludeComposeSvc,
Value: &cli.StringSlice{},
Usage: FlagDepIncludeComposeSvcUsage,
EnvVar: "DSLIM_DEP_INCLUDE_COMPOSE_SVC",
},
FlagDepExcludeComposeSvc: cli.StringSliceFlag{
Name: FlagDepExcludeComposeSvc,
Value: &cli.StringSlice{},
Usage: FlagDepExcludeComposeSvcUsage,
EnvVar: "DSLIM_DEP_EXCLUDE_COMPOSE_SVC",
},
FlagComposeNet: cli.StringSliceFlag{
Name: FlagComposeNet,
Value: &cli.StringSlice{},
Usage: FlagComposeNetUsage,
EnvVar: "DSLIM_COMPOSE_NET",
},
FlagRemoveFileArtifacts: cli.BoolFlag{
Name: FlagRemoveFileArtifacts,
Usage: FlagRemoveFileArtifactsUsage,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
* @ndeloof

View File

@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
ignore:
- dependency-name: github.com/sirupsen/logrus
versions:
- 1.8.0
- 1.8.1
- dependency-name: github.com/google/go-cmp
versions:
- 0.5.5

View File

@ -0,0 +1,31 @@
on: [push, pull_request]
name: Continuous integration
jobs:
validate:
name: validate
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Lint code
run: DOCKER_BUILDKIT=1 make lint
- name: Check license
run: DOCKER_BUILDKIT=1 make check-license
test:
strategy:
matrix:
go-version: [1.16.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
timeout-minutes: 5
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test ./...

View File

@ -0,0 +1,13 @@
name: Release
on:
push:
tags:
- "v1*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

2
pkg/third_party/compose-go/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
### IDEs ###
.idea/*

View File

@ -0,0 +1,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-yaml
exclude: '^vendor/'
- id: end-of-file-fixer
exclude: '^vendor/'
- id: trailing-whitespace
exclude: '^vendor/'
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.3.5
hooks:
- id: go-fmt
- id: golangci-lint
- id: go-imports

127
pkg/third_party/compose-go/CODE_OF_CONDUCT.md vendored Executable file
View File

@ -0,0 +1,127 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

88
pkg/third_party/compose-go/CONTRIBUTING.md vendored Executable file
View File

@ -0,0 +1,88 @@
# Contributing
Contributions should be made via pull requests. Pull requests will be reviewed
by one or more maintainers and merged when acceptable.
The goal of the Compose Go library is to facilitate parsing and loading Compose
files.
## Commit Messages
Commit messages should follow best practices and explain the context of the
problem and how it was solved including any caveats or follow up changes
required. They should tell the story of the change and provide readers an
understanding of what led to it.
[How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/)
provides a good guide for how to do so.
In practice, the best approach to maintaining a nice commit message is to
leverage a `git add -p` and `git commit --amend` to formulate a solid
change set. This allows one to piece together a change, as information becomes
available.
If you squash a series of commits, don't just submit that. Re-write the commit
message, as if the series of commits was a single stroke of brilliance.
That said, there is no requirement to have a single commit for a pull request,
as long as each commit tells the story. For example, if there is a feature that
requires a package, it might make sense to have the package in a separate commit
then have a subsequent commit that uses it.
Remember, you're telling part of the story with the commit message. Don't make
your chapter weird.
## Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

View File

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright The containerd Authors
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.

9
pkg/third_party/compose-go/MAINTENANCE.md vendored Executable file
View File

@ -0,0 +1,9 @@
# Maintenance
The compose-go library has to be kept up-to-date with approved changes in the [Compose specification](https://github.com/compose-spec/compose-spec).
As we define new attributes to be added to the spec, this typically requires:
1. Updating `schema` to latest version from compose-spec
1. Creating the matching struct/field in `types`
1. Creating the matching `CheckXX` method in `compatibility`
1. If the new attribute replaces a legacy one we want to deprecate, creating the adequate logic in `normalize.go`

44
pkg/third_party/compose-go/Makefile vendored Executable file
View File

@ -0,0 +1,44 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.
IMAGE_PREFIX=composespec/conformance-tests-
.PHONY: build
build: ## Run tests
go build ./...
.PHONY: test
test: ## Run tests
gotestsum ./...
.PHONY: fmt
fmt: ## Format go files
go fmt ./...
.PHONY: build-validate-image
build-validate-image:
docker build . -f ci/Dockerfile -t $(IMAGE_PREFIX)validate
.PHONY: lint
lint: build-validate-image
docker run --rm $(IMAGE_PREFIX)validate bash -c "golangci-lint run --config ./golangci.yml ./..."
.PHONY: check-license
check-license: build-validate-image
docker run --rm $(IMAGE_PREFIX)validate bash -c "./scripts/validate/fileheader"
.PHONY: setup
setup: ## Setup the precommit hook
@which pre-commit > /dev/null 2>&1 || (echo "pre-commit not installed see README." && false)
@pre-commit install

2
pkg/third_party/compose-go/NOTICE vendored Executable file
View File

@ -0,0 +1,2 @@
The Compose Specification
Copyright 2020 The Compose Specification Authors

11
pkg/third_party/compose-go/README.md vendored Executable file
View File

@ -0,0 +1,11 @@
# compose-go
![Continuous integration](https://github.com/compose-spec/compose-go/workflows/Continuous%20integration/badge.svg)
Go reference library for parsing and loading Compose files as specified by the
[Compose specification](https://github.com/compose-spec/compose-spec).
## Used by
* [compose-ref](https://github.com/compose-spec/compose-ref)
* [containerd/nerdctl](https://github.com/containerd/nerdctl)
* [compose-cli](https://github.com/docker/compose-cli)

23
pkg/third_party/compose-go/ci/Dockerfile vendored Executable file
View File

@ -0,0 +1,23 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.
FROM golang:1.16
WORKDIR /go/src
ARG GOLANGCILINT_VERSION=v1.24.0
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCILINT_VERSION}
RUN go get -v -u github.com/kunalkushwaha/ltag && rm -rf /go/src/github.com/kunalkushwaha
COPY . .

411
pkg/third_party/compose-go/cli/options.go vendored Executable file
View File

@ -0,0 +1,411 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package cli
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/ulyssessouza/godotenv"
)
// ProjectOptions groups the command line options recommended for a Compose implementation
type ProjectOptions struct {
Name string
WorkingDir string
ConfigPaths []string
Environment map[string]string
EnvFile string
loadOptions []func(*loader.Options)
}
type ProjectOptionsFn func(*ProjectOptions) error
// NewProjectOptions creates ProjectOptions
func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOptions, error) {
options := &ProjectOptions{
ConfigPaths: configs,
Environment: map[string]string{},
}
for _, o := range opts {
err := o(options)
if err != nil {
return nil, err
}
}
return options, nil
}
// WithName defines ProjectOptions' name
func WithName(name string) ProjectOptionsFn {
return func(o *ProjectOptions) error {
o.Name = name
return nil
}
}
// WithWorkingDirectory defines ProjectOptions' working directory
func WithWorkingDirectory(wd string) ProjectOptionsFn {
return func(o *ProjectOptions) error {
if wd == "" {
return nil
}
abs, err := filepath.Abs(wd)
if err != nil {
return err
}
o.WorkingDir = abs
return nil
}
}
// WithConfigFileEnv allow to set compose config file paths by COMPOSE_FILE environment variable
func WithConfigFileEnv(o *ProjectOptions) error {
if len(o.ConfigPaths) > 0 {
return nil
}
sep := o.Environment[ComposePathSeparator]
if sep == "" {
sep = string(os.PathListSeparator)
}
f, ok := o.Environment[ComposeFilePath]
if ok {
paths, err := absolutePaths(strings.Split(f, sep))
o.ConfigPaths = paths
return err
}
return nil
}
// WithDefaultConfigPath searches for default config files from working directory
func WithDefaultConfigPath(o *ProjectOptions) error {
if len(o.ConfigPaths) > 0 {
return nil
}
pwd, err := o.GetWorkingDir()
if err != nil {
return err
}
for {
candidates := findFiles(DefaultFileNames, pwd)
if len(candidates) > 0 {
winner := candidates[0]
if len(candidates) > 1 {
logrus.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
logrus.Warnf("Using %s", winner)
}
o.ConfigPaths = append(o.ConfigPaths, winner)
overrides := findFiles(DefaultOverrideFileNames, pwd)
if len(overrides) > 0 {
if len(overrides) > 1 {
logrus.Warnf("Found multiple override files with supported names: %s", strings.Join(overrides, ", "))
logrus.Warnf("Using %s", overrides[0])
}
o.ConfigPaths = append(o.ConfigPaths, overrides[0])
}
return nil
}
parent := filepath.Dir(pwd)
if parent == pwd {
return errors.Wrap(errdefs.ErrNotFound, "can't find a suitable configuration file in this directory or any parent")
}
pwd = parent
}
}
// WithEnv defines a key=value set of variables used for compose file interpolation
func WithEnv(env []string) ProjectOptionsFn {
return func(o *ProjectOptions) error {
for k, v := range getAsEqualsMap(env) {
o.Environment[k] = v
}
return nil
}
}
// WithDiscardEnvFiles sets discards the `env_file` section after resolving to
// the `environment` section
func WithDiscardEnvFile(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, loader.WithDiscardEnvFiles)
return nil
}
// WithLoadOptions provides a hook to control how compose files are loaded
func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn {
return func(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, loadOptions...)
return nil
}
}
// WithOsEnv imports environment variables from OS
func WithOsEnv(o *ProjectOptions) error {
for k, v := range getAsEqualsMap(os.Environ()) {
o.Environment[k] = v
}
return nil
}
// WithEnvFile set an alternate env file
func WithEnvFile(file string) ProjectOptionsFn {
return func(options *ProjectOptions) error {
options.EnvFile = file
return nil
}
}
// WithDotEnv imports environment variables from .env file
func WithDotEnv(o *ProjectOptions) error {
dotEnvFile := o.EnvFile
if dotEnvFile == "" {
wd, err := o.GetWorkingDir()
if err != nil {
return err
}
dotEnvFile = filepath.Join(wd, ".env")
}
abs, err := filepath.Abs(dotEnvFile)
if err != nil {
return err
}
dotEnvFile = abs
s, err := os.Stat(dotEnvFile)
if os.IsNotExist(err) {
if o.EnvFile != "" {
return errors.Errorf("Couldn't find env file: %s", o.EnvFile)
}
return nil
}
if err != nil {
return err
}
if s.IsDir() {
if o.EnvFile != "" {
return errors.Errorf("%s is a directory", dotEnvFile)
}
}
file, err := os.Open(dotEnvFile)
if err != nil {
return err
}
defer file.Close()
notInEnvSet := make(map[string]interface{})
env, err := godotenv.ParseWithLookup(file, func(k string) (string, bool) {
v, ok := os.LookupEnv(k)
if !ok {
notInEnvSet[k] = nil
return "", true
}
return v, true
})
if err != nil {
return err
}
for k, v := range env {
if _, ok := notInEnvSet[k]; ok {
continue
}
o.Environment[k] = v
}
return nil
}
// WithInterpolation set ProjectOptions to enable/skip interpolation
func WithInterpolation(interpolation bool) ProjectOptionsFn {
return func(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
options.SkipInterpolation = !interpolation
})
return nil
}
}
// WithResolvedPaths set ProjectOptions to enable paths resolution
func WithResolvedPaths(resolve bool) ProjectOptionsFn {
return func(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
options.ResolvePaths = resolve
})
return nil
}
}
// DefaultFileNames defines the Compose file names for auto-discovery (in order of preference)
var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
// DefaultOverrideFileNames defines the Compose override file names for auto-discovery (in order of preference)
var DefaultOverrideFileNames = []string{"compose.override.yml", "compose.override.yaml", "docker-compose.override.yml", "docker-compose.override.yaml"}
const (
ComposeProjectName = "COMPOSE_PROJECT_NAME"
ComposePathSeparator = "COMPOSE_PATH_SEPARATOR"
ComposeFilePath = "COMPOSE_FILE"
)
func (o ProjectOptions) GetWorkingDir() (string, error) {
if o.WorkingDir != "" {
return o.WorkingDir, nil
}
for _, path := range o.ConfigPaths {
if path != "-" {
absPath, err := filepath.Abs(path)
if err != nil {
return "", err
}
return filepath.Dir(absPath), nil
}
}
return os.Getwd()
}
// ProjectFromOptions load a compose project based on command line options
func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
configPaths, err := getConfigPathsFromOptions(options)
if err != nil {
return nil, err
}
var configs []types.ConfigFile
for _, f := range configPaths {
var b []byte
if f == "-" {
b, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
} else {
f, err := filepath.Abs(f)
if err != nil {
return nil, err
}
b, err = ioutil.ReadFile(f)
if err != nil {
return nil, err
}
}
configs = append(configs, types.ConfigFile{
Filename: f,
Content: b,
})
}
workingDir, err := options.GetWorkingDir()
if err != nil {
return nil, err
}
absWorkingDir, err := filepath.Abs(workingDir)
if err != nil {
return nil, err
}
var nameLoadOpt = func(opts *loader.Options) {
if options.Name != "" {
opts.Name = options.Name
} else if nameFromEnv, ok := options.Environment[ComposeProjectName]; ok && nameFromEnv != "" {
opts.Name = nameFromEnv
} else {
opts.Name = regexp.MustCompile(`(?m)[a-z]+[-_a-z0-9]*`).FindString(strings.ToLower(filepath.Base(absWorkingDir)))
}
opts.Name = strings.ToLower(opts.Name)
}
options.loadOptions = append(options.loadOptions, nameLoadOpt)
project, err := loader.Load(types.ConfigDetails{
ConfigFiles: configs,
WorkingDir: workingDir,
Environment: options.Environment,
}, options.loadOptions...)
if err != nil {
return nil, err
}
project.ComposeFiles = configPaths
return project, nil
}
// getConfigPathsFromOptions retrieves the config files for project based on project options
func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) {
if len(options.ConfigPaths) != 0 {
return absolutePaths(options.ConfigPaths)
}
return nil, errors.New("no configuration file provided")
}
func findFiles(names []string, pwd string) []string {
candidates := []string{}
for _, n := range names {
f := filepath.Join(pwd, n)
if _, err := os.Stat(f); err == nil {
candidates = append(candidates, f)
}
}
return candidates
}
func absolutePaths(p []string) ([]string, error) {
var paths []string
for _, f := range p {
if f == "-" {
paths = append(paths, f)
continue
}
abs, err := filepath.Abs(f)
if err != nil {
return nil, err
}
f = abs
if _, err := os.Stat(f); err != nil {
return nil, err
}
paths = append(paths, f)
}
return paths, nil
}
// getAsEqualsMap split key=value formatted strings into a key : value map
func getAsEqualsMap(em []string) map[string]string {
m := make(map[string]string)
for _, v := range em {
kv := strings.SplitN(v, "=", 2)
m[kv[0]] = kv[1]
}
return m
}
// getAsEqualsMap format a key : value map into key=value strings
func getAsStringList(em map[string]string) []string {
m := make([]string, 0, len(em))
for k, v := range em {
m = append(m, fmt.Sprintf("%s=%s", k, v))
}
return m
}

168
pkg/third_party/compose-go/cli/options_test.go vendored Executable file
View File

@ -0,0 +1,168 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package cli
import (
"os"
"path/filepath"
"testing"
"gotest.tools/v3/assert"
)
func TestProjectName(t *testing.T) {
t.Run("by name", func(t *testing.T) {
opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithName("my_project"))
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "my_project")
})
t.Run("by working dir", func(t *testing.T) {
opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithWorkingDirectory("."))
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "cli")
})
t.Run("by compose file parent dir", func(t *testing.T) {
opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"})
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "simple")
})
t.Run("by COMPOSE_PROJECT_NAME", func(t *testing.T) {
os.Setenv("COMPOSE_PROJECT_NAME", "my_project_from_env")
defer os.Unsetenv("COMPOSE_PROJECT_NAME")
opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithOsEnv)
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "my_project_from_env")
})
t.Run("by .env", func(t *testing.T) {
wd, err := os.Getwd()
assert.NilError(t, err)
err = os.Chdir("testdata/env-file")
assert.NilError(t, err)
defer os.Chdir(wd)
opts, err := NewProjectOptions(nil, WithDotEnv, WithConfigFileEnv)
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "my_project_from_dot_env")
})
}
func TestProjectFromSetOfFiles(t *testing.T) {
opts, err := NewProjectOptions([]string{
"testdata/simple/compose.yaml",
"testdata/simple/compose-with-overrides.yaml",
}, WithName("my_project"))
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
service, err := p.GetService("simple")
assert.NilError(t, err)
assert.Equal(t, service.Image, "haproxy")
}
func TestProjectComposefilesFromSetOfFiles(t *testing.T) {
opts, err := NewProjectOptions([]string{},
WithWorkingDirectory("testdata/simple/"),
WithName("my_project"),
WithDefaultConfigPath,
)
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
absPath, _ := filepath.Abs(filepath.Join("testdata", "simple", "compose.yaml"))
assert.DeepEqual(t, p.ComposeFiles, []string{absPath})
}
func TestProjectComposefilesFromWorkingDir(t *testing.T) {
opts, err := NewProjectOptions([]string{
"testdata/simple/compose.yaml",
"testdata/simple/compose-with-overrides.yaml",
}, WithName("my_project"))
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
currentDir, _ := os.Getwd()
assert.DeepEqual(t, p.ComposeFiles, []string{
filepath.Join(currentDir, "testdata/simple/compose.yaml"),
filepath.Join(currentDir, "testdata/simple/compose-with-overrides.yaml"),
})
}
func TestProjectWithDotEnv(t *testing.T) {
wd, err := os.Getwd()
assert.NilError(t, err)
err = os.Chdir("testdata/simple")
assert.NilError(t, err)
defer os.Chdir(wd)
opts, err := NewProjectOptions([]string{
"compose-with-variables.yaml",
}, WithName("my_project"), WithDotEnv)
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
service, err := p.GetService("simple")
assert.NilError(t, err)
assert.Equal(t, service.Ports[0].Published, uint32(8000))
}
func TestProjectWithDiscardEnvFile(t *testing.T) {
opts, err := NewProjectOptions([]string{
"testdata/env-file/compose-with-env-file.yaml",
}, WithDiscardEnvFile)
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
service, err := p.GetService("simple")
assert.NilError(t, err)
assert.Equal(t, *service.Environment["DEFAULT_PORT"], "8080")
assert.Assert(t, service.EnvFile == nil)
}
func TestProjectNameFromWorkingDir(t *testing.T) {
opts, err := NewProjectOptions([]string{
"testdata/env-file/compose-with-env-file.yaml",
})
assert.NilError(t, err)
p, err := ProjectFromOptions(opts)
assert.NilError(t, err)
assert.Equal(t, p.Name, "env-file")
}
func TestEnvMap(t *testing.T) {
m := map[string]string{}
m["foo"] = "bar"
l := getAsStringList(m)
assert.Equal(t, l[0], "foo=bar")
m = getAsEqualsMap(l)
assert.Equal(t, m["foo"], "bar")
}

View File

@ -0,0 +1,2 @@
COMPOSE_FILE=compose-with-env-file.yaml
COMPOSE_PROJECT_NAME=my_project_from_dot_env

View File

@ -0,0 +1,8 @@
version: "3"
services:
simple:
image: nginx
env_file:
- ./simple-env
ports:
- 8000:80

View File

@ -0,0 +1 @@
DEFAULT_PORT=8080

View File

@ -0,0 +1 @@
PUBLIC_PORT=8000

View File

@ -0,0 +1,3 @@
services:
simple:
image: haproxy

View File

@ -0,0 +1,5 @@
services:
simple:
image: nginx
ports:
- ${PUBLIC_PORT}:80

View File

@ -0,0 +1,3 @@
services:
simple:
image: nginx

View File

@ -0,0 +1,54 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"fmt"
"github.com/compose-spec/compose-go/errdefs"
"github.com/pkg/errors"
)
// AllowList implements the Checker interface by rejecting all attributes that are not listed as "supported".
type AllowList struct {
Supported []string
errors []error
}
// Errors returns the list of errors encountered when checking against the allow list
func (c *AllowList) Errors() []error {
return c.errors
}
func (c *AllowList) supported(attributes ...string) bool {
for _, a := range attributes {
for _, s := range c.Supported {
if s == a {
return true
}
}
}
return false
}
func (c *AllowList) Unsupported(message string, args ...interface{}) {
c.errors = append(c.errors, errors.Wrap(errdefs.ErrUnsupported, fmt.Sprintf(message, args...)))
}
func (c *AllowList) Incompatible(message string, args ...interface{}) {
c.errors = append(c.errors, errors.Wrap(errdefs.ErrIncompatible, fmt.Sprintf(message, args...)))
}

View File

@ -0,0 +1,78 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"testing"
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"gotest.tools/v3/assert"
)
func TestAllowList(t *testing.T) {
var checker Checker = customChecker{
&AllowList{
Supported: []string{
"services.image",
"services.network_mode",
"services.privileged",
"services.networks",
"services.scale",
},
},
}
dict := []byte(`
services:
foo:
image: busybox
network_mode: host
privileged: true
mac_address: "a:b:c:d"
`)
project, err := loader.Load(types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{Filename: "filename.yml", Content: dict},
},
})
assert.NilError(t, err)
Check(project, checker)
errors := checker.Errors()
assert.Check(t, len(errors) == 2)
assert.Check(t, errdefs.IsUnsupportedError(errors[0]))
assert.Equal(t, errors[0].Error(), "services.mac_address: unsupported attribute")
assert.Check(t, errdefs.IsUnsupportedError(errors[1]))
assert.Equal(t, errors[1].Error(), "services.network_mode=host: unsupported attribute")
service, err := project.GetService("foo")
assert.NilError(t, err)
assert.Check(t, service.MacAddress == "")
}
type customChecker struct {
*AllowList
}
func (c customChecker) CheckNetworkMode(service *types.ServiceConfig) {
if service.NetworkMode == "host" {
c.Unsupported("services.network_mode=host")
}
}

View File

@ -0,0 +1,77 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import "github.com/compose-spec/compose-go/types"
func (c *AllowList) CheckBuild(service *types.ServiceConfig) bool {
if !c.supported("services.build") && service.Build != nil {
service.Build = nil
c.Unsupported("services.build")
return false
}
return true
}
func (c *AllowList) CheckBuildArgs(build *types.BuildConfig) {
if !c.supported("services.build.args") && len(build.Args) != 0 {
build.Args = nil
c.Unsupported("services.build.args")
}
}
func (c *AllowList) CheckBuildLabels(build *types.BuildConfig) {
if !c.supported("services.build.labels") && len(build.Labels) != 0 {
build.Labels = nil
c.Unsupported("services.build.labels")
}
}
func (c *AllowList) CheckBuildCacheFrom(build *types.BuildConfig) {
if !c.supported("services.build.cache_from") && len(build.CacheFrom) != 0 {
build.CacheFrom = nil
c.Unsupported("services.build.cache_from")
}
}
func (c *AllowList) CheckBuildExtraHosts(build *types.BuildConfig) {
if !c.supported("services.build.extra_hosts") && len(build.ExtraHosts) != 0 {
build.ExtraHosts = nil
c.Unsupported("services.build.extra_hosts")
}
}
func (c *AllowList) CheckBuildIsolation(build *types.BuildConfig) {
if !c.supported("services.build.isolation") && build.Isolation != "" {
build.Isolation = ""
c.Unsupported("services.build.isolation")
}
}
func (c *AllowList) CheckBuildNetwork(build *types.BuildConfig) {
if !c.supported("services.build.network") && build.Network != "" {
build.Network = ""
c.Unsupported("services.build.network")
}
}
func (c *AllowList) CheckBuildTarget(build *types.BuildConfig) {
if !c.supported("services.build.target") && build.Target != "" {
build.Target = ""
c.Unsupported("services.build.target")
}
}

View File

@ -0,0 +1,437 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/types"
)
type Checker interface {
Errors() []error
CheckBlkioConfig(build *types.ServiceConfig)
CheckBlkioWeight(build *types.BlkioConfig)
CheckBlkioWeightDevice(build *types.BlkioConfig)
CheckBlkioDeviceReadBps(build *types.BlkioConfig)
CheckBlkioDeviceReadIOps(build *types.BlkioConfig)
CheckBlkioDeviceWriteBps(build *types.BlkioConfig)
CheckBlkioDeviceWriteIOps(build *types.BlkioConfig)
CheckBuild(build *types.ServiceConfig) bool
CheckBuildArgs(build *types.BuildConfig)
CheckBuildLabels(build *types.BuildConfig)
CheckBuildCacheFrom(build *types.BuildConfig)
CheckBuildExtraHosts(build *types.BuildConfig)
CheckBuildIsolation(build *types.BuildConfig)
CheckBuildNetwork(build *types.BuildConfig)
CheckBuildTarget(build *types.BuildConfig)
CheckCapAdd(service *types.ServiceConfig)
CheckCapDrop(service *types.ServiceConfig)
CheckCgroupParent(service *types.ServiceConfig)
CheckCPUCount(service *types.ServiceConfig)
CheckCPUPercent(service *types.ServiceConfig)
CheckCPUPeriod(service *types.ServiceConfig)
CheckCPUQuota(service *types.ServiceConfig)
CheckCPURTRuntime(service *types.ServiceConfig)
CheckCPURTPeriod(service *types.ServiceConfig)
CheckCPUs(service *types.ServiceConfig)
CheckCPUSet(service *types.ServiceConfig)
CheckCPUShares(service *types.ServiceConfig)
CheckCommand(service *types.ServiceConfig)
CheckConfigs(service *types.ServiceConfig)
CheckContainerName(service *types.ServiceConfig)
CheckCredentialSpec(service *types.ServiceConfig)
CheckDependsOn(service *types.ServiceConfig)
CheckDevices(service *types.ServiceConfig)
CheckDNS(service *types.ServiceConfig)
CheckDNSOpts(service *types.ServiceConfig)
CheckDNSSearch(service *types.ServiceConfig)
CheckDomainName(service *types.ServiceConfig)
CheckEntrypoint(service *types.ServiceConfig)
CheckEnvironment(service *types.ServiceConfig)
CheckEnvFile(service *types.ServiceConfig)
CheckExpose(service *types.ServiceConfig)
CheckExtends(service *types.ServiceConfig)
CheckExternalLinks(service *types.ServiceConfig)
CheckExtraHosts(service *types.ServiceConfig)
CheckGroupAdd(service *types.ServiceConfig)
CheckHostname(service *types.ServiceConfig)
CheckHealthCheckTest(h *types.HealthCheckConfig)
CheckHealthCheckTimeout(h *types.HealthCheckConfig)
CheckHealthCheckInterval(h *types.HealthCheckConfig)
CheckHealthCheckRetries(h *types.HealthCheckConfig)
CheckHealthCheckStartPeriod(h *types.HealthCheckConfig)
CheckImage(service *types.ServiceConfig)
CheckInit(service *types.ServiceConfig)
CheckIpc(service *types.ServiceConfig)
CheckIsolation(service *types.ServiceConfig)
CheckLabels(service *types.ServiceConfig)
CheckLinks(service *types.ServiceConfig)
CheckLoggingDriver(logging *types.LoggingConfig)
CheckLoggingOptions(logging *types.LoggingConfig)
CheckMemLimit(service *types.ServiceConfig)
CheckMemReservation(service *types.ServiceConfig)
CheckMemSwapLimit(service *types.ServiceConfig)
CheckMemSwappiness(service *types.ServiceConfig)
CheckMacAddress(service *types.ServiceConfig)
CheckNet(service *types.ServiceConfig)
CheckNetworkMode(service *types.ServiceConfig)
CheckNetworkAliases(n *types.ServiceNetworkConfig)
CheckNetworkIpv4Address(n *types.ServiceNetworkConfig)
CheckNetworkIpv6Address(n *types.ServiceNetworkConfig)
CheckOomKillDisable(service *types.ServiceConfig)
CheckOomScoreAdj(service *types.ServiceConfig)
CheckPid(service *types.ServiceConfig)
CheckPidsLimit(service *types.ServiceConfig)
CheckPlatform(service *types.ServiceConfig)
CheckPortsMode(p *types.ServicePortConfig)
CheckPortsTarget(p *types.ServicePortConfig)
CheckPortsPublished(p *types.ServicePortConfig)
CheckPortsProtocol(p *types.ServicePortConfig)
CheckPrivileged(service *types.ServiceConfig)
CheckPullPolicy(service *types.ServiceConfig)
CheckReadOnly(service *types.ServiceConfig)
CheckRestart(service *types.ServiceConfig)
CheckRuntime(service *types.ServiceConfig)
CheckScale(service *types.ServiceConfig)
CheckSecrets(service *types.ServiceConfig)
CheckFileReferenceSource(s string, config *types.FileReferenceConfig)
CheckFileReferenceTarget(s string, config *types.FileReferenceConfig)
CheckFileReferenceUID(s string, config *types.FileReferenceConfig)
CheckFileReferenceGID(s string, config *types.FileReferenceConfig)
CheckFileReferenceMode(s string, config *types.FileReferenceConfig)
CheckSecurityOpt(service *types.ServiceConfig)
CheckShmSize(service *types.ServiceConfig)
CheckStdinOpen(service *types.ServiceConfig)
CheckStopGracePeriod(service *types.ServiceConfig)
CheckStopSignal(service *types.ServiceConfig)
CheckSysctls(service *types.ServiceConfig)
CheckTmpfs(service *types.ServiceConfig)
CheckTty(service *types.ServiceConfig)
CheckUlimits(service *types.ServiceConfig)
CheckUser(service *types.ServiceConfig)
CheckUserNSMode(service *types.ServiceConfig)
CheckUts(service *types.ServiceConfig)
CheckVolumeDriver(service *types.ServiceConfig)
CheckVolumesSource(config *types.ServiceVolumeConfig)
CheckVolumesTarget(config *types.ServiceVolumeConfig)
CheckVolumesReadOnly(config *types.ServiceVolumeConfig)
CheckVolumesConsistency(config *types.ServiceVolumeConfig)
CheckVolumesBind(config *types.ServiceVolumeBind)
CheckVolumesVolume(config *types.ServiceVolumeVolume)
CheckVolumesTmpfs(config *types.ServiceVolumeTmpfs)
CheckVolumesFrom(service *types.ServiceConfig)
CheckWorkingDir(service *types.ServiceConfig)
CheckVolumeConfigDriver(config *types.VolumeConfig)
CheckVolumeConfigDriverOpts(config *types.VolumeConfig)
CheckVolumeConfigExternal(config *types.VolumeConfig)
CheckVolumeConfigLabels(config *types.VolumeConfig)
CheckFileObjectConfigFile(s string, config *types.FileObjectConfig)
CheckFileObjectConfigExternal(s string, config *types.FileObjectConfig)
CheckFileObjectConfigLabels(s string, config *types.FileObjectConfig)
CheckFileObjectConfigDriver(s string, config *types.FileObjectConfig)
CheckFileObjectConfigDriverOpts(s string, config *types.FileObjectConfig)
CheckFileObjectConfigTemplateDriver(s string, config *types.FileObjectConfig)
CheckDeploy(deploy *types.ServiceConfig) bool
CheckDeployEndpointMode(deploy *types.DeployConfig)
CheckDeployLabels(deploy *types.DeployConfig)
CheckDeployMode(deploy *types.DeployConfig)
CheckDeployReplicas(deploy *types.DeployConfig)
CheckDeployRestartPolicy(deploy *types.DeployConfig) bool
CheckDeployRollbackConfig(deploy *types.DeployConfig) bool
CheckDeployUpdateConfig(deploy *types.DeployConfig) bool
CheckPlacementConstraints(p *types.Placement)
CheckPlacementMaxReplicas(p *types.Placement)
CheckPlacementPreferences(p *types.Placement)
CheckRestartPolicyDelay(policy *types.RestartPolicy)
CheckRestartPolicyCondition(policy *types.RestartPolicy)
CheckRestartPolicyMaxAttempts(policy *types.RestartPolicy)
CheckRestartPolicyWindow(policy *types.RestartPolicy)
CheckUpdateConfigDelay(rollback string, config *types.UpdateConfig)
CheckUpdateConfigFailureAction(rollback string, config *types.UpdateConfig)
CheckUpdateConfigMaxFailureRatio(rollback string, config *types.UpdateConfig)
CheckUpdateConfigMonitor(rollback string, config *types.UpdateConfig)
CheckUpdateConfigOrder(rollback string, config *types.UpdateConfig)
CheckUpdateConfigParallelism(rollback string, config *types.UpdateConfig)
CheckDeployResourcesNanoCPUs(s string, resource *types.Resource)
CheckDeployResourcesMemoryBytes(s string, resource *types.Resource)
CheckDeployResourcesDevices(s string, resource *types.Resource)
CheckDeployResourcesDevicesCapabilities(s string, r types.DeviceRequest)
CheckDeployResourcesDevicesCount(s string, r types.DeviceRequest)
CheckDeployResourcesDevicesIDs(s string, r types.DeviceRequest)
CheckDeployResourcesDevicesDriver(s string, r types.DeviceRequest)
CheckDeployResourcesGenericResources(s string, resource *types.Resource)
CheckDeployResourcesLimits(deploy *types.DeployConfig) bool
CheckDeployResourcesReservations(deploy *types.DeployConfig) bool
CheckHealthCheck(service *types.ServiceConfig) bool
CheckLogging(service *types.ServiceConfig) bool
CheckNetworks(service *types.ServiceConfig) bool
CheckPorts(service *types.ServiceConfig) bool
CheckServiceVolumes(service *types.ServiceConfig) bool
CheckNetworkConfigIpam(network *types.NetworkConfig)
CheckNetworkConfigIpamSubnet(config *types.IPAMPool)
CheckNetworkConfigIpamGateway(config *types.IPAMPool)
CheckNetworkConfigIpamIPRange(config *types.IPAMPool)
CheckNetworkConfigIpamAuxiliaryAddresses(config *types.IPAMPool)
CheckNetworkConfigDriver(network *types.NetworkConfig)
CheckNetworkConfigDriverOpts(network *types.NetworkConfig)
CheckNetworkConfigExternal(network *types.NetworkConfig)
CheckNetworkConfigInternal(network *types.NetworkConfig)
CheckNetworkConfigAttachable(network *types.NetworkConfig)
CheckNetworkConfigLabels(network *types.NetworkConfig)
}
func Check(project *types.Project, c Checker) {
for i, service := range project.Services {
CheckServiceConfig(&service, c)
project.Services[i] = service
}
for i, network := range project.Networks {
CheckNetworkConfig(&network, c)
project.Networks[i] = network
}
for i, volume := range project.Volumes {
CheckVolumeConfig(&volume, c)
project.Volumes[i] = volume
}
for i, config := range project.Configs {
CheckConfigsConfig(&config, c)
project.Configs[i] = config
}
for i, secret := range project.Secrets {
CheckSecretsConfig(&secret, c)
project.Secrets[i] = secret
}
}
// IsCompatible return true if the checker didn't reported any incompatibility error
func IsCompatible(c Checker) bool {
for _, err := range c.Errors() {
if errdefs.IsIncompatibleError(err) {
return false
}
}
return true
}
func CheckServiceConfig(service *types.ServiceConfig, c Checker) {
c.CheckBlkioConfig(service)
if service.Build != nil && c.CheckBuild(service) {
c.CheckBuildArgs(service.Build)
c.CheckBuildLabels(service.Build)
c.CheckBuildCacheFrom(service.Build)
c.CheckBuildNetwork(service.Build)
c.CheckBuildTarget(service.Build)
}
c.CheckCapAdd(service)
c.CheckCapDrop(service)
c.CheckCgroupParent(service)
c.CheckCPUCount(service)
c.CheckCPUPercent(service)
c.CheckCPUPeriod(service)
c.CheckCPUQuota(service)
c.CheckCPURTPeriod(service)
c.CheckCPURTRuntime(service)
c.CheckCPUs(service)
c.CheckCPUSet(service)
c.CheckCPUShares(service)
c.CheckCommand(service)
c.CheckConfigs(service)
c.CheckContainerName(service)
c.CheckCredentialSpec(service)
c.CheckDependsOn(service)
if service.Deploy != nil && c.CheckDeploy(service) {
c.CheckDeployEndpointMode(service.Deploy)
c.CheckDeployLabels(service.Deploy)
c.CheckDeployMode(service.Deploy)
c.CheckPlacementConstraints(&service.Deploy.Placement)
c.CheckPlacementMaxReplicas(&service.Deploy.Placement)
c.CheckPlacementPreferences(&service.Deploy.Placement)
c.CheckDeployReplicas(service.Deploy)
if service.Deploy.Resources.Limits != nil && c.CheckDeployResourcesLimits(service.Deploy) {
c.CheckDeployResourcesNanoCPUs(ResourceLimits, service.Deploy.Resources.Limits)
c.CheckDeployResourcesMemoryBytes(ResourceLimits, service.Deploy.Resources.Limits)
c.CheckDeployResourcesGenericResources(ResourceLimits, service.Deploy.Resources.Limits)
}
if service.Deploy.Resources.Reservations != nil && c.CheckDeployResourcesReservations(service.Deploy) {
c.CheckDeployResourcesNanoCPUs(ResourceReservations, service.Deploy.Resources.Reservations)
c.CheckDeployResourcesMemoryBytes(ResourceReservations, service.Deploy.Resources.Reservations)
c.CheckDeployResourcesGenericResources(ResourceReservations, service.Deploy.Resources.Reservations)
c.CheckDeployResourcesDevices(ResourceReservations, service.Deploy.Resources.Reservations)
}
if service.Deploy.RestartPolicy != nil && c.CheckDeployRestartPolicy(service.Deploy) {
c.CheckRestartPolicyCondition(service.Deploy.RestartPolicy)
c.CheckRestartPolicyDelay(service.Deploy.RestartPolicy)
c.CheckRestartPolicyMaxAttempts(service.Deploy.RestartPolicy)
c.CheckRestartPolicyWindow(service.Deploy.RestartPolicy)
}
if service.Deploy.UpdateConfig != nil && c.CheckDeployUpdateConfig(service.Deploy) {
c.CheckUpdateConfigDelay(UpdateConfigUpdate, service.Deploy.UpdateConfig)
c.CheckUpdateConfigFailureAction(UpdateConfigUpdate, service.Deploy.UpdateConfig)
c.CheckUpdateConfigMaxFailureRatio(UpdateConfigUpdate, service.Deploy.UpdateConfig)
c.CheckUpdateConfigMonitor(UpdateConfigUpdate, service.Deploy.UpdateConfig)
c.CheckUpdateConfigOrder(UpdateConfigUpdate, service.Deploy.UpdateConfig)
c.CheckUpdateConfigParallelism(UpdateConfigUpdate, service.Deploy.UpdateConfig)
}
if service.Deploy.RollbackConfig != nil && c.CheckDeployRollbackConfig(service.Deploy) {
c.CheckUpdateConfigDelay(UpdateConfigRollback, service.Deploy.RollbackConfig)
c.CheckUpdateConfigFailureAction(UpdateConfigRollback, service.Deploy.RollbackConfig)
c.CheckUpdateConfigMaxFailureRatio(UpdateConfigRollback, service.Deploy.RollbackConfig)
c.CheckUpdateConfigMonitor(UpdateConfigRollback, service.Deploy.RollbackConfig)
c.CheckUpdateConfigOrder(UpdateConfigRollback, service.Deploy.RollbackConfig)
c.CheckUpdateConfigParallelism(UpdateConfigRollback, service.Deploy.RollbackConfig)
}
}
c.CheckDevices(service)
c.CheckDNS(service)
c.CheckDNSOpts(service)
c.CheckDNSSearch(service)
c.CheckDomainName(service)
c.CheckEntrypoint(service)
c.CheckEnvironment(service)
c.CheckEnvFile(service)
c.CheckExpose(service)
c.CheckExtends(service)
c.CheckExternalLinks(service)
c.CheckExtraHosts(service)
c.CheckGroupAdd(service)
c.CheckHostname(service)
if service.HealthCheck != nil && c.CheckHealthCheck(service) {
c.CheckHealthCheckInterval(service.HealthCheck)
c.CheckHealthCheckRetries(service.HealthCheck)
c.CheckHealthCheckStartPeriod(service.HealthCheck)
c.CheckHealthCheckTest(service.HealthCheck)
c.CheckHealthCheckTimeout(service.HealthCheck)
}
c.CheckImage(service)
c.CheckInit(service)
c.CheckIpc(service)
c.CheckIsolation(service)
c.CheckLabels(service)
c.CheckLinks(service)
if service.Logging != nil && c.CheckLogging(service) {
c.CheckLoggingDriver(service.Logging)
c.CheckLoggingOptions(service.Logging)
}
c.CheckMemLimit(service)
c.CheckMemReservation(service)
c.CheckMemSwapLimit(service)
c.CheckMemSwappiness(service)
c.CheckMacAddress(service)
c.CheckNet(service)
c.CheckNetworkMode(service)
if len(service.Networks) > 0 && c.CheckNetworks(service) {
for _, n := range service.Networks {
if n != nil {
c.CheckNetworkAliases(n)
c.CheckNetworkIpv4Address(n)
c.CheckNetworkIpv6Address(n)
}
}
}
c.CheckOomKillDisable(service)
c.CheckOomScoreAdj(service)
c.CheckPid(service)
c.CheckPidsLimit(service)
c.CheckPlatform(service)
if len(service.Ports) > 0 && c.CheckPorts(service) {
for i, p := range service.Ports {
c.CheckPortsMode(&p)
c.CheckPortsTarget(&p)
c.CheckPortsProtocol(&p)
c.CheckPortsPublished(&p)
service.Ports[i] = p
}
}
c.CheckPrivileged(service)
c.CheckPullPolicy(service)
c.CheckReadOnly(service)
c.CheckRestart(service)
c.CheckRuntime(service)
c.CheckScale(service)
c.CheckSecrets(service)
c.CheckSecurityOpt(service)
c.CheckShmSize(service)
c.CheckStdinOpen(service)
c.CheckStopGracePeriod(service)
c.CheckStopSignal(service)
c.CheckSysctls(service)
c.CheckTmpfs(service)
c.CheckTty(service)
c.CheckUlimits(service)
c.CheckUser(service)
c.CheckUserNSMode(service)
c.CheckUts(service)
c.CheckVolumeDriver(service)
if len(service.Volumes) > 0 && c.CheckServiceVolumes(service) {
for i, v := range service.Volumes {
c.CheckVolumesSource(&v)
c.CheckVolumesTarget(&v)
c.CheckVolumesReadOnly(&v)
switch v.Type {
case types.VolumeTypeBind:
c.CheckVolumesBind(v.Bind)
case types.VolumeTypeVolume:
c.CheckVolumesVolume(v.Volume)
case types.VolumeTypeTmpfs:
c.CheckVolumesTmpfs(v.Tmpfs)
}
service.Volumes[i] = v
}
}
c.CheckVolumesFrom(service)
c.CheckWorkingDir(service)
}
func CheckNetworkConfig(network *types.NetworkConfig, c Checker) {
c.CheckNetworkConfigDriver(network)
c.CheckNetworkConfigDriverOpts(network)
c.CheckNetworkConfigIpam(network)
c.CheckNetworkConfigExternal(network)
c.CheckNetworkConfigInternal(network)
c.CheckNetworkConfigAttachable(network)
c.CheckNetworkConfigLabels(network)
}
func CheckVolumeConfig(config *types.VolumeConfig, c Checker) {
c.CheckVolumeConfigDriver(config)
c.CheckVolumeConfigDriverOpts(config)
c.CheckVolumeConfigExternal(config)
c.CheckVolumeConfigLabels(config)
}
func CheckConfigsConfig(config *types.ConfigObjConfig, c Checker) {
ref := types.FileObjectConfig(*config)
CheckFileObjectConfig("configs", &ref, c)
}
func CheckSecretsConfig(config *types.SecretConfig, c Checker) {
ref := types.FileObjectConfig(*config)
CheckFileObjectConfig("secrets", &ref, c)
}
func CheckFileObjectConfig(s string, config *types.FileObjectConfig, c Checker) {
c.CheckFileObjectConfigDriver(s, config)
c.CheckFileObjectConfigDriverOpts(s, config)
c.CheckFileObjectConfigExternal(s, config)
c.CheckFileObjectConfigFile(s, config)
c.CheckFileObjectConfigLabels(s, config)
c.CheckFileObjectConfigTemplateDriver(s, config)
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"fmt"
"github.com/compose-spec/compose-go/types"
)
func (c *AllowList) CheckFileObjectConfigFile(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.file", s)
if !c.supported(k) && config.File != "" {
config.File = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileObjectConfigExternal(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.external", s)
if !c.supported(k) && config.External.External {
config.External.External = false
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileObjectConfigLabels(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.labels", s)
if !c.supported(k) && len(config.Labels) != 0 {
config.Labels = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileObjectConfigDriver(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.driver", s)
if !c.supported(k) && config.Driver != "" {
config.Driver = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileObjectConfigDriverOpts(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.driver_opts", s)
if !c.supported(k) && len(config.DriverOpts) != 0 {
config.DriverOpts = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileObjectConfigTemplateDriver(s string, config *types.FileObjectConfig) {
k := fmt.Sprintf("%s.template_driver", s)
if !c.supported(k) && config.TemplateDriver != "" {
config.TemplateDriver = ""
c.Unsupported(k)
}
}

View File

@ -0,0 +1,275 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"fmt"
"github.com/compose-spec/compose-go/types"
)
func (c *AllowList) CheckDeploy(service *types.ServiceConfig) bool {
if !c.supported("services.deploy") && service.Deploy != nil {
service.Deploy = nil
c.Unsupported("services.deploy")
return false
}
return true
}
func (c *AllowList) CheckDeployMode(config *types.DeployConfig) {
if !c.supported("services.deploy.mode") && config.Mode != "" {
config.Mode = ""
c.Unsupported("services.deploy.mode")
}
}
func (c *AllowList) CheckDeployReplicas(config *types.DeployConfig) {
if !c.supported("services.deploy.replicas") && config.Replicas != nil {
config.Replicas = nil
c.Unsupported("services.deploy.replicas")
}
}
func (c *AllowList) CheckDeployLabels(config *types.DeployConfig) {
if !c.supported("services.deploy.labels") && len(config.Labels) != 0 {
config.Labels = nil
c.Unsupported("services.deploy.labels")
}
}
const (
UpdateConfigUpdate = "update_config"
UpdateConfigRollback = "rolback_config"
)
func (c *AllowList) CheckDeployUpdateConfig(config *types.DeployConfig) bool {
if !c.supported("services.deploy.update_config") {
config.UpdateConfig = nil
c.Unsupported("services.deploy.update_config")
return false
}
return true
}
func (c *AllowList) CheckDeployRollbackConfig(config *types.DeployConfig) bool {
if !c.supported("services.deploy.rollback_config") {
config.RollbackConfig = nil
c.Unsupported("services.deploy.rollback_config")
return false
}
return true
}
func (c *AllowList) CheckUpdateConfigParallelism(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.parallelism", s)
if !c.supported(k) && config.Parallelism != nil {
config.Parallelism = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckUpdateConfigDelay(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.delay", s)
if !c.supported(k) && config.Delay != 0 {
config.Delay = 0
c.Unsupported(k)
}
}
func (c *AllowList) CheckUpdateConfigFailureAction(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.failure_action", s)
if !c.supported(k) && config.FailureAction != "" {
config.FailureAction = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckUpdateConfigMonitor(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.monitor", s)
if !c.supported(k) && config.Monitor != 0 {
config.Monitor = 0
c.Unsupported(k)
}
}
func (c *AllowList) CheckUpdateConfigMaxFailureRatio(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.max_failure_ratio", s)
if !c.supported(k) && config.MaxFailureRatio != 0 {
config.MaxFailureRatio = 0
c.Unsupported(k)
}
}
func (c *AllowList) CheckUpdateConfigOrder(s string, config *types.UpdateConfig) {
k := fmt.Sprintf("services.deploy.%s.order", s)
if !c.supported(k) && config.Order != "" {
config.Order = ""
c.Unsupported(k)
}
}
const (
ResourceLimits = "limits"
ResourceReservations = "reservations"
)
func (c *AllowList) CheckDeployResourcesLimits(config *types.DeployConfig) bool {
if !c.supported("services.deploy.resources.limits") {
config.Resources.Limits = nil
c.Unsupported("services.deploy.resources.limits")
return false
}
return true
}
func (c *AllowList) CheckDeployResourcesReservations(config *types.DeployConfig) bool {
if !c.supported("services.deploy.resources.reservations") {
config.Resources.Reservations = nil
c.Unsupported("services.deploy.resources.reservations")
return false
}
return true
}
func (c *AllowList) CheckDeployResourcesNanoCPUs(s string, r *types.Resource) {
k := fmt.Sprintf("services.deploy.resources.%s.cpus", s)
if !c.supported(k) && r.NanoCPUs != "" {
r.NanoCPUs = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesMemoryBytes(s string, r *types.Resource) {
k := fmt.Sprintf("services.deploy.resources.%s.memory", s)
if !c.supported(k) && r.MemoryBytes != 0 {
r.MemoryBytes = 0
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesDevices(s string, r *types.Resource) {
if len(r.Devices) == 0 {
return
}
k := fmt.Sprintf("services.deploy.resources.%s.devices", s)
if !c.supported(k) {
r.Devices = nil
c.Unsupported(k)
return
}
for _, d := range r.Devices {
c.CheckDeployResourcesDevicesCapabilities(s, d)
c.CheckDeployResourcesDevicesCount(s, d)
c.CheckDeployResourcesDevicesIDs(s, d)
c.CheckDeployResourcesDevicesDriver(s, d)
}
}
func (c *AllowList) CheckDeployResourcesDevicesCapabilities(s string, r types.DeviceRequest) {
k := fmt.Sprintf("services.deploy.resources.%s.devices.capabilities", s)
if !c.supported(k) && len(r.Capabilities) != 0 {
r.Capabilities = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesDevicesCount(s string, r types.DeviceRequest) {
k := fmt.Sprintf("services.deploy.resources.%s.devices.count", s)
if !c.supported(k) && r.Count != 0 {
r.Count = 0
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesDevicesIDs(s string, r types.DeviceRequest) {
k := fmt.Sprintf("services.deploy.resources.%s.devices.device_ids", s)
if !c.supported(k) && len(r.IDs) != 0 {
r.IDs = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesDevicesDriver(s string, r types.DeviceRequest) {
k := fmt.Sprintf("services.deploy.resources.%s.devices.driver", s)
if !c.supported(k) && r.Driver != "" {
r.Driver = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployResourcesGenericResources(s string, r *types.Resource) {
k := fmt.Sprintf("services.deploy.resources.%s.generic_resources", s)
if !c.supported(k) && len(r.GenericResources) != 0 {
r.GenericResources = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckDeployRestartPolicy(config *types.DeployConfig) bool {
if !c.supported("services.deploy.restart_policy") {
config.RestartPolicy = nil
c.Unsupported("services.deploy.restart_policy")
return false
}
return true
}
func (c *AllowList) CheckRestartPolicyCondition(p *types.RestartPolicy) {
if !c.supported("services.deploy.restart_policy.condition") && p.Condition != "" {
p.Condition = ""
c.Unsupported("services.deploy.restart_policy.condition")
}
}
func (c *AllowList) CheckRestartPolicyDelay(p *types.RestartPolicy) {
if !c.supported("services.deploy.restart_policy.delay") && p.Delay != nil {
p.Delay = nil
c.Unsupported("services.deploy.restart_policy.delay")
}
}
func (c *AllowList) CheckRestartPolicyMaxAttempts(p *types.RestartPolicy) {
if !c.supported("services.deploy.restart_policy.max_attempts") && p.MaxAttempts != nil {
p.MaxAttempts = nil
c.Unsupported("services.deploy.restart_policy.max_attempts")
}
}
func (c *AllowList) CheckRestartPolicyWindow(p *types.RestartPolicy) {
if !c.supported("services.deploy.restart_policy.window") && p.Window != nil {
p.Window = nil
c.Unsupported("services.deploy.restart_policy.window")
}
}
func (c *AllowList) CheckPlacementConstraints(p *types.Placement) {
if !c.supported("services.deploy.placement", "services.deploy.placement.constraints") && len(p.Constraints) != 0 {
p.Constraints = nil
c.Unsupported("services.deploy.restart_policy.constraints")
}
}
func (c *AllowList) CheckPlacementPreferences(p *types.Placement) {
if !c.supported("services.deploy.placement", "services.deploy.placement.preferences") && p.Preferences != nil {
p.Preferences = nil
c.Unsupported("services.deploy.restart_policy.preferences")
}
}
func (c *AllowList) CheckPlacementMaxReplicas(p *types.Placement) {
if !c.supported("services.deploy.placement", "services.deploy.placement.max_replicas_per_node") && p.MaxReplicas != 0 {
p.MaxReplicas = 0
c.Unsupported("services.deploy.restart_policy.max_replicas_per_node")
}
}
func (c *AllowList) CheckDeployEndpointMode(config *types.DeployConfig) {
if !c.supported("services.deploy.endpoint_mode") && config.EndpointMode != "" {
config.EndpointMode = ""
c.Unsupported("services.deploy.endpoint_mode")
}
}

View File

@ -0,0 +1,122 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import "github.com/compose-spec/compose-go/types"
func (c *AllowList) CheckNetworkConfig(network *types.NetworkConfig) {
c.CheckNetworkConfigDriver(network)
c.CheckNetworkConfigDriverOpts(network)
c.CheckNetworkConfigIpam(network)
c.CheckNetworkConfigExternal(network)
c.CheckNetworkConfigInternal(network)
c.CheckNetworkConfigAttachable(network)
c.CheckNetworkConfigLabels(network)
}
func (c *AllowList) CheckNetworkConfigDriver(config *types.NetworkConfig) {
if !c.supported("networks.driver") && config.Driver != "" {
config.Driver = ""
c.Unsupported("networks.driver")
}
}
func (c *AllowList) CheckNetworkConfigDriverOpts(config *types.NetworkConfig) {
if !c.supported("networks.driver_opts") && len(config.DriverOpts) != 0 {
config.DriverOpts = nil
c.Unsupported("networks.driver_opts")
}
}
func (c *AllowList) CheckNetworkConfigIpam(config *types.NetworkConfig) {
c.CheckNetworkConfigIpamDriver(&config.Ipam)
if len(config.Ipam.Config) != 0 {
if !c.supported("networks.ipam.config") {
c.Unsupported("networks.ipam.config")
return
}
for _, p := range config.Ipam.Config {
c.CheckNetworkConfigIpamSubnet(p)
c.CheckNetworkConfigIpamGateway(p)
c.CheckNetworkConfigIpamIPRange(p)
c.CheckNetworkConfigIpamAuxiliaryAddresses(p)
}
}
}
func (c *AllowList) CheckNetworkConfigIpamDriver(config *types.IPAMConfig) {
if !c.supported("networks.ipam.driver") && config.Driver != "" {
config.Driver = ""
c.Unsupported("networks.ipam.driver")
}
}
func (c *AllowList) CheckNetworkConfigIpamSubnet(config *types.IPAMPool) {
if !c.supported("networks.ipam.config.subnet") && config.Subnet != "" {
config.Subnet = ""
c.Unsupported("networks.ipam.config.subnet")
}
}
func (c *AllowList) CheckNetworkConfigIpamGateway(config *types.IPAMPool) {
if !c.supported("networks.ipam.config.gateway") && config.Gateway != "" {
config.Gateway = ""
c.Unsupported("networks.ipam.config.gateway")
}
}
func (c *AllowList) CheckNetworkConfigIpamIPRange(config *types.IPAMPool) {
if !c.supported("networks.ipam.config.ip_range") && config.IPRange != "" {
config.IPRange = ""
c.Unsupported("networks.ipam.config.ip_range")
}
}
func (c *AllowList) CheckNetworkConfigIpamAuxiliaryAddresses(config *types.IPAMPool) {
if !c.supported("networks.ipam.config.aux_addresses") && len(config.AuxiliaryAddresses) > 0 {
config.AuxiliaryAddresses = nil
c.Unsupported("networks.ipam.config.aux_addresses")
}
}
func (c *AllowList) CheckNetworkConfigExternal(config *types.NetworkConfig) {
if !c.supported("networks.external") && config.External.External {
config.External.External = false
c.Unsupported("networks.external")
}
}
func (c *AllowList) CheckNetworkConfigInternal(config *types.NetworkConfig) {
if !c.supported("networks.internal") && config.Internal {
config.Internal = false
c.Unsupported("networks.internal")
}
}
func (c *AllowList) CheckNetworkConfigAttachable(config *types.NetworkConfig) {
if !c.supported("networks.attachable") && config.Attachable {
config.Attachable = false
c.Unsupported("networks.attachable")
}
}
func (c *AllowList) CheckNetworkConfigLabels(config *types.NetworkConfig) {
if !c.supported("networks.labels") && len(config.Labels) != 0 {
config.Labels = nil
c.Unsupported("networks.labels")
}
}

View File

@ -0,0 +1,833 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import (
"fmt"
"github.com/compose-spec/compose-go/types"
)
func (c *AllowList) CheckBlkioConfig(service *types.ServiceConfig) {
if !c.supported("services.blkio_config") && service.BlkioConfig != nil {
service.BlkioConfig = nil
c.Unsupported("services.blkio_config")
}
}
func (c *AllowList) CheckBlkioWeight(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.weight") && config.Weight != 0 {
config.Weight = 0
c.Unsupported("services.blkio_config.weight")
}
}
func (c *AllowList) CheckBlkioWeightDevice(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.weight_device") && len(config.WeightDevice) != 0 {
config.WeightDevice = nil
c.Unsupported("services.blkio_config.weight_device")
}
}
func (c *AllowList) CheckBlkioDeviceReadBps(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.device_read_bps") && len(config.DeviceWriteBps) != 0 {
config.DeviceWriteBps = nil
c.Unsupported("services.blkio_config.device_read_bps")
}
}
func (c *AllowList) CheckBlkioDeviceReadIOps(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.device_read_iops") && len(config.DeviceReadIOps) != 0 {
config.DeviceReadIOps = nil
c.Unsupported("services.blkio_config.device_read_iops")
}
}
func (c *AllowList) CheckBlkioDeviceWriteBps(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.device_write_bps") && len(config.DeviceWriteBps) != 0 {
config.DeviceWriteBps = nil
c.Unsupported("services.blkio_config.device_write_bps")
}
}
func (c *AllowList) CheckBlkioDeviceWriteIOps(config *types.BlkioConfig) {
if !c.supported("services.blkio_config.device_write_iops") && len(config.DeviceWriteIOps) != 0 {
config.DeviceWriteIOps = nil
c.Unsupported("services.blkio_config.device_write_iops")
}
}
func (c *AllowList) CheckCapAdd(service *types.ServiceConfig) {
if !c.supported("services.cap_add") && len(service.CapAdd) != 0 {
service.CapAdd = nil
c.Unsupported("services.cap_add")
}
}
func (c *AllowList) CheckCapDrop(service *types.ServiceConfig) {
if !c.supported("services.cap_drop") && len(service.CapDrop) != 0 {
service.CapDrop = nil
c.Unsupported("services.cap_drop")
}
}
func (c *AllowList) CheckCgroupParent(service *types.ServiceConfig) {
if !c.supported("services.cgroup_parent") && service.CgroupParent != "" {
service.CgroupParent = ""
c.Unsupported("services.cgroup_parent")
}
}
func (c *AllowList) CheckCPUQuota(service *types.ServiceConfig) {
if !c.supported("services.cpu_quota") && service.CPUQuota != 0 {
service.CPUQuota = 0
c.Unsupported("services.cpu_quota")
}
}
func (c *AllowList) CheckCPUCount(service *types.ServiceConfig) {
if !c.supported("services.cpu_count") && service.CPUCount != 0 {
service.CPUCount = 0
c.Unsupported("services.cpu_count")
}
}
func (c *AllowList) CheckCPUPercent(service *types.ServiceConfig) {
if !c.supported("services.cpu_percent") && service.CPUPercent != 0 {
service.CPUPercent = 0
c.Unsupported("services.cpu_percent")
}
}
func (c *AllowList) CheckCPUPeriod(service *types.ServiceConfig) {
if !c.supported("services.cpu_period") && service.CPUPeriod != 0 {
service.CPUPeriod = 0
c.Unsupported("services.cpu_period")
}
}
func (c *AllowList) CheckCPURTRuntime(service *types.ServiceConfig) {
if !c.supported("services.cpu_rt_runtime") && service.CPURTRuntime != 0 {
service.CPURTRuntime = 0
c.Unsupported("services.cpu_rt_period")
}
}
func (c *AllowList) CheckCPURTPeriod(service *types.ServiceConfig) {
if !c.supported("services.cpu_rt_period") && service.CPURTPeriod != 0 {
service.CPURTPeriod = 0
c.Unsupported("services.cpu_rt_period")
}
}
func (c *AllowList) CheckCPUs(service *types.ServiceConfig) {
if !c.supported("services.cpus") && service.CPUS != 0 {
service.CPUS = 0
c.Unsupported("services.cpus")
}
}
func (c *AllowList) CheckCPUSet(service *types.ServiceConfig) {
if !c.supported("services.cpuset") && service.CPUSet != "" {
service.CPUSet = ""
c.Unsupported("services.cpuset")
}
}
func (c *AllowList) CheckCPUShares(service *types.ServiceConfig) {
if !c.supported("services.cpu_shares") && service.CPUShares != 0 {
service.CPUShares = 0
c.Unsupported("services.cpu_shares")
}
}
func (c *AllowList) CheckCommand(service *types.ServiceConfig) {
if !c.supported("services.command") && len(service.Command) != 0 {
service.Command = nil
c.Unsupported("services.command")
}
}
func (c *AllowList) CheckConfigs(service *types.ServiceConfig) {
if len(service.Configs) != 0 {
if !c.supported("services.configs") {
service.Configs = nil
c.Unsupported("services.configs")
return
}
for i, s := range service.Secrets {
ref := types.FileReferenceConfig(s)
c.CheckFileReference("configs", &ref)
service.Secrets[i] = s
}
}
}
func (c *AllowList) CheckContainerName(service *types.ServiceConfig) {
if !c.supported("services.container_name") && service.ContainerName != "" {
service.ContainerName = ""
c.Unsupported("services.container_name")
}
}
func (c *AllowList) CheckCredentialSpec(service *types.ServiceConfig) {
if !c.supported("services.credential_spec") && service.CredentialSpec != nil {
service.CredentialSpec = nil
c.Unsupported("services.credential_spec")
}
}
func (c *AllowList) CheckDependsOn(service *types.ServiceConfig) {
if !c.supported("services.depends_on") && len(service.DependsOn) != 0 {
service.DependsOn = nil
c.Unsupported("services.depends_on")
}
}
func (c *AllowList) CheckDevices(service *types.ServiceConfig) {
if !c.supported("services.devices") && len(service.Devices) != 0 {
service.Devices = nil
c.Unsupported("services.devices")
}
}
func (c *AllowList) CheckDNS(service *types.ServiceConfig) {
if !c.supported("services.dns") && service.DNS != nil {
service.DNS = nil
c.Unsupported("services.dns")
}
}
func (c *AllowList) CheckDNSOpts(service *types.ServiceConfig) {
if !c.supported("services.dns_opt") && len(service.DNSOpts) != 0 {
service.DNSOpts = nil
c.Unsupported("services.dns_opt")
}
}
func (c *AllowList) CheckDNSSearch(service *types.ServiceConfig) {
if !c.supported("services.dns_search") && len(service.DNSSearch) != 0 {
service.DNSSearch = nil
c.Unsupported("services.dns_search")
}
}
func (c *AllowList) CheckDomainName(service *types.ServiceConfig) {
if !c.supported("services.domainname") && service.DomainName != "" {
service.DomainName = ""
c.Unsupported("services.domainname")
}
}
func (c *AllowList) CheckEntrypoint(service *types.ServiceConfig) {
if !c.supported("services.entrypoint") && len(service.Entrypoint) != 0 {
service.Entrypoint = nil
c.Unsupported("services.entrypoint")
}
}
func (c *AllowList) CheckEnvironment(service *types.ServiceConfig) {
if !c.supported("services.environment") && len(service.Environment) != 0 {
service.Environment = nil
c.Unsupported("services.environment")
}
}
func (c *AllowList) CheckEnvFile(service *types.ServiceConfig) {
if !c.supported("services.env_file") && len(service.EnvFile) != 0 {
service.EnvFile = nil
c.Unsupported("services.env_file")
}
}
func (c *AllowList) CheckExpose(service *types.ServiceConfig) {
if !c.supported("services.expose") && len(service.Expose) != 0 {
service.Expose = nil
c.Unsupported("services.expose")
}
}
func (c *AllowList) CheckExtends(service *types.ServiceConfig) {
if !c.supported("services.extends") && len(service.Extends) != 0 {
service.Extends = nil
c.Unsupported("services.extends")
}
}
func (c *AllowList) CheckExternalLinks(service *types.ServiceConfig) {
if !c.supported("services.external_links") && len(service.ExternalLinks) != 0 {
service.ExternalLinks = nil
c.Unsupported("services.external_links")
}
}
func (c *AllowList) CheckExtraHosts(service *types.ServiceConfig) {
if !c.supported("services.extra_hosts") && len(service.ExtraHosts) != 0 {
service.ExtraHosts = nil
c.Unsupported("services.extra_hosts")
}
}
func (c *AllowList) CheckGroupAdd(service *types.ServiceConfig) {
if !c.supported("services.group_app") && len(service.GroupAdd) != 0 {
service.GroupAdd = nil
c.Unsupported("services.group_app")
}
}
func (c *AllowList) CheckHostname(service *types.ServiceConfig) {
if !c.supported("services.hostname") && service.Hostname != "" {
service.Hostname = ""
c.Unsupported("services.hostname")
}
}
func (c *AllowList) CheckHealthCheck(service *types.ServiceConfig) bool {
if !c.supported("services.healthcheck") {
service.HealthCheck = nil
c.Unsupported("services.healthcheck")
return false
}
return true
}
func (c *AllowList) CheckHealthCheckTest(h *types.HealthCheckConfig) {
if !c.supported("services.healthcheck.test") && len(h.Test) != 0 {
h.Test = nil
c.Unsupported("services.healthcheck.test")
}
}
func (c *AllowList) CheckHealthCheckTimeout(h *types.HealthCheckConfig) {
if !c.supported("services.healthcheck.timeout") && h.Timeout != nil {
h.Timeout = nil
c.Unsupported("services.healthcheck.timeout")
}
}
func (c *AllowList) CheckHealthCheckInterval(h *types.HealthCheckConfig) {
if !c.supported("services.healthcheck.interval") && h.Interval != nil {
h.Interval = nil
c.Unsupported("services.healthcheck.interval")
}
}
func (c *AllowList) CheckHealthCheckRetries(h *types.HealthCheckConfig) {
if !c.supported("services.healthcheck.retries") && h.Retries != nil {
h.Retries = nil
c.Unsupported("services.healthcheck.retries")
}
}
func (c *AllowList) CheckHealthCheckStartPeriod(h *types.HealthCheckConfig) {
if !c.supported("services.healthcheck.start_period") && h.StartPeriod != nil {
h.StartPeriod = nil
c.Unsupported("services.healthcheck.start_period")
}
}
func (c *AllowList) CheckImage(service *types.ServiceConfig) {
if !c.supported("services.image") && service.Image != "" {
service.Image = ""
c.Unsupported("services.image")
}
}
func (c *AllowList) CheckInit(service *types.ServiceConfig) {
if !c.supported("services.init") && service.Init != nil {
service.Init = nil
c.Unsupported("services.init")
}
}
func (c *AllowList) CheckIpc(service *types.ServiceConfig) {
if !c.supported("services.ipc") && service.Ipc != "" {
service.Ipc = ""
c.Unsupported("services.ipc")
}
}
func (c *AllowList) CheckIsolation(service *types.ServiceConfig) {
if !c.supported("services.isolation") && service.Isolation != "" {
service.Isolation = ""
c.Unsupported("services.isolation")
}
}
func (c *AllowList) CheckLabels(service *types.ServiceConfig) {
if !c.supported("services.labels") && len(service.Labels) != 0 {
service.Labels = nil
c.Unsupported("services.labels")
}
}
func (c *AllowList) CheckLinks(service *types.ServiceConfig) {
if !c.supported("services.links") && len(service.Links) != 0 {
service.Links = nil
c.Unsupported("services.links")
}
}
func (c *AllowList) CheckLogging(service *types.ServiceConfig) bool {
if !c.supported("services.logging") {
service.Logging = nil
c.Unsupported("services.logging")
return false
}
return true
}
func (c *AllowList) CheckLoggingDriver(logging *types.LoggingConfig) {
if !c.supported("services.logging.driver") && logging.Driver != "" {
logging.Driver = ""
c.Unsupported("services.logging.driver")
}
}
func (c *AllowList) CheckLoggingOptions(logging *types.LoggingConfig) {
if !c.supported("services.logging.options") && len(logging.Options) != 0 {
logging.Options = nil
c.Unsupported("services.logging.options")
}
}
func (c *AllowList) CheckMemLimit(service *types.ServiceConfig) {
if !c.supported("services.mem_limit") && service.MemLimit != 0 {
service.MemLimit = 0
c.Unsupported("services.mem_limit")
}
}
func (c *AllowList) CheckMemReservation(service *types.ServiceConfig) {
if !c.supported("services.mem_reservation") && service.MemReservation != 0 {
service.MemReservation = 0
c.Unsupported("services.mem_reservation")
}
}
func (c *AllowList) CheckMemSwapLimit(service *types.ServiceConfig) {
if !c.supported("services.memswap_limit") && service.MemSwapLimit != 0 {
service.MemSwapLimit = 0
c.Unsupported("services.memswap_limit")
}
}
func (c *AllowList) CheckMemSwappiness(service *types.ServiceConfig) {
if !c.supported("services.mem_swappiness") && service.MemSwappiness != 0 {
service.MemSwappiness = 0
c.Unsupported("services.mem_swappiness")
}
}
func (c *AllowList) CheckMacAddress(service *types.ServiceConfig) {
if !c.supported("services.mac_address") && service.MacAddress != "" {
service.MacAddress = ""
c.Unsupported("services.mac_address")
}
}
func (c *AllowList) CheckNet(service *types.ServiceConfig) {
if !c.supported("services.net") && service.Net != "" {
service.Net = ""
c.Unsupported("services.net")
}
}
func (c *AllowList) CheckNetworkMode(service *types.ServiceConfig) {
if !c.supported("services.network_mode") && service.NetworkMode != "" {
service.NetworkMode = ""
c.Unsupported("services.network_mode")
}
}
func (c *AllowList) CheckNetworks(service *types.ServiceConfig) bool {
if !c.supported("services.networks") {
service.Networks = nil
c.Unsupported("services.networks")
return false
}
return true
}
func (c *AllowList) CheckNetworkAliases(n *types.ServiceNetworkConfig) {
if !c.supported("services.networks.aliases") && len(n.Aliases) != 0 {
n.Aliases = nil
c.Unsupported("services.networks.aliases")
}
}
func (c *AllowList) CheckNetworkIpv4Address(n *types.ServiceNetworkConfig) {
if !c.supported("services.networks.ipv4_address") && n.Ipv4Address != "" {
n.Ipv4Address = ""
c.Unsupported("services.networks.ipv4_address")
}
}
func (c *AllowList) CheckNetworkIpv6Address(n *types.ServiceNetworkConfig) {
if !c.supported("services.networks.ipv6_address") && n.Ipv6Address != "" {
n.Ipv6Address = ""
c.Unsupported("services.networks.ipv6_address")
}
}
func (c *AllowList) CheckOomKillDisable(service *types.ServiceConfig) {
if !c.supported("services.oom_kill_disable") && service.OomKillDisable {
service.OomKillDisable = false
c.Unsupported("services.oom_kill_disable")
}
}
func (c *AllowList) CheckOomScoreAdj(service *types.ServiceConfig) {
if !c.supported("services.oom_score_adj") && service.OomScoreAdj != 0 {
service.OomScoreAdj = 0
c.Unsupported("services.oom_score_adj")
}
}
func (c *AllowList) CheckPid(service *types.ServiceConfig) {
if !c.supported("services.pid") && service.Pid != "" {
service.Pid = ""
c.Unsupported("services.pid")
}
}
func (c *AllowList) CheckPidsLimit(service *types.ServiceConfig) {
if !c.supported("services.pids_limit") && service.PidsLimit != 0 {
service.PidsLimit = 0
c.Unsupported("services.pids_limit")
}
}
func (c *AllowList) CheckPlatform(service *types.ServiceConfig) {
if !c.supported("services.platform") && service.Platform != "" {
service.Platform = ""
c.Unsupported("services.platform")
}
}
func (c *AllowList) CheckPorts(service *types.ServiceConfig) bool {
if !c.supported("services.ports") {
service.Ports = nil
c.Unsupported("services.ports")
return false
}
return true
}
func (c *AllowList) CheckPortsMode(p *types.ServicePortConfig) {
if !c.supported("services.ports.mode") && p.Mode != "" {
p.Mode = ""
c.Unsupported("services.ports.mode")
}
}
func (c *AllowList) CheckPortsTarget(p *types.ServicePortConfig) {
if !c.supported("services.ports.target") && p.Target != 0 {
p.Target = 0
c.Unsupported("services.ports.target")
}
}
func (c *AllowList) CheckPortsPublished(p *types.ServicePortConfig) {
if !c.supported("services.ports.published") && p.Published != 0 {
p.Published = 0
c.Unsupported("services.ports.published")
}
}
func (c *AllowList) CheckPortsProtocol(p *types.ServicePortConfig) {
if !c.supported("services.ports.protocol") && p.Protocol != "" {
p.Protocol = ""
c.Unsupported("services.ports.protocol")
}
}
func (c *AllowList) CheckPrivileged(service *types.ServiceConfig) {
if !c.supported("services.privileged") && service.Privileged {
service.Privileged = false
c.Unsupported("services.privileged")
}
}
func (c *AllowList) CheckPullPolicy(service *types.ServiceConfig) {
if !c.supported("services.pull_policy") && service.PullPolicy != "" {
service.PullPolicy = "false"
c.Unsupported("services.pull_policy")
}
}
func (c *AllowList) CheckReadOnly(service *types.ServiceConfig) {
if !c.supported("services.read_only") && service.ReadOnly {
service.ReadOnly = false
c.Unsupported("services.read_only")
}
}
func (c *AllowList) CheckRestart(service *types.ServiceConfig) {
if !c.supported("services.restart") && service.Restart != "" {
service.Restart = ""
c.Unsupported("services.restart")
}
}
func (c *AllowList) CheckRuntime(service *types.ServiceConfig) {
if !c.supported("services.runtime") && service.Runtime != "" {
service.Runtime = ""
c.Unsupported("services.runtime")
}
}
func (c *AllowList) CheckScale(service *types.ServiceConfig) {
if !c.supported("services.scale") && service.Scale != 0 {
service.Scale = 0
c.Unsupported("services.scale")
}
}
func (c *AllowList) CheckSecrets(service *types.ServiceConfig) {
if len(service.Secrets) != 0 {
if !c.supported("services.secrets") {
service.Secrets = nil
c.Unsupported("services.secrets")
}
for i, s := range service.Secrets {
ref := types.FileReferenceConfig(s)
c.CheckFileReference("services.secrets", &ref)
service.Secrets[i] = s
}
}
}
func (c *AllowList) CheckFileReference(s string, config *types.FileReferenceConfig) {
c.CheckFileReferenceSource(s, config)
c.CheckFileReferenceTarget(s, config)
c.CheckFileReferenceGID(s, config)
c.CheckFileReferenceUID(s, config)
c.CheckFileReferenceMode(s, config)
}
func (c *AllowList) CheckFileReferenceSource(s string, config *types.FileReferenceConfig) {
k := fmt.Sprintf("%s.source", s)
if !c.supported(k) && config.Source != "" {
config.Source = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileReferenceTarget(s string, config *types.FileReferenceConfig) {
k := fmt.Sprintf("%s.target", s)
if !c.supported(k) && config.Target == "" {
config.Target = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileReferenceUID(s string, config *types.FileReferenceConfig) {
k := fmt.Sprintf("%s.uid", s)
if !c.supported(k) && config.UID != "" {
config.UID = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileReferenceGID(s string, config *types.FileReferenceConfig) {
k := fmt.Sprintf("%s.gid", s)
if !c.supported(k) && config.GID != "" {
config.GID = ""
c.Unsupported(k)
}
}
func (c *AllowList) CheckFileReferenceMode(s string, config *types.FileReferenceConfig) {
k := fmt.Sprintf("%s.mode", s)
if !c.supported(k) && config.Mode != nil {
config.Mode = nil
c.Unsupported(k)
}
}
func (c *AllowList) CheckSecurityOpt(service *types.ServiceConfig) {
if !c.supported("services.security_opt") && len(service.SecurityOpt) != 0 {
service.SecurityOpt = nil
c.Unsupported("services.security_opt")
}
}
func (c *AllowList) CheckShmSize(service *types.ServiceConfig) {
if !c.supported("services.shm_size") && service.ShmSize != 0 {
service.ShmSize = 0
c.Unsupported("services.shm_size")
}
}
func (c *AllowList) CheckStdinOpen(service *types.ServiceConfig) {
if !c.supported("services.stdin_open") && service.StdinOpen {
service.StdinOpen = true
c.Unsupported("services.stdin_open")
}
}
func (c *AllowList) CheckStopGracePeriod(service *types.ServiceConfig) {
if !c.supported("services.stop_grace_period") && service.StopGracePeriod != nil {
service.StopGracePeriod = nil
c.Unsupported("services.stop_grace_period")
}
}
func (c *AllowList) CheckStopSignal(service *types.ServiceConfig) {
if !c.supported("services.stop_signal") && service.StopSignal != "" {
service.StopSignal = ""
c.Unsupported("services.stop_signal")
}
}
func (c *AllowList) CheckSysctls(service *types.ServiceConfig) {
if !c.supported("services.sysctls") && len(service.Sysctls) != 0 {
service.Sysctls = nil
c.Unsupported("services.sysctls")
}
}
func (c *AllowList) CheckTmpfs(service *types.ServiceConfig) {
if !c.supported("services.tmpfs") && len(service.Tmpfs) != 0 {
service.Tmpfs = nil
c.Unsupported("services.tmpfs")
}
}
func (c *AllowList) CheckTty(service *types.ServiceConfig) {
if !c.supported("services.tty") && service.Tty {
service.Tty = false
c.Unsupported("services.tty")
}
}
func (c *AllowList) CheckUlimits(service *types.ServiceConfig) {
if !c.supported("services.ulimits") && len(service.Ulimits) != 0 {
service.Ulimits = nil
c.Unsupported("services.ulimits")
}
}
func (c *AllowList) CheckUser(service *types.ServiceConfig) {
if !c.supported("services.user") && service.User != "" {
service.User = ""
c.Unsupported("services.user")
}
}
func (c *AllowList) CheckUserNSMode(service *types.ServiceConfig) {
if !c.supported("services.userns_mode") && service.UserNSMode != "" {
service.UserNSMode = ""
c.Unsupported("services.userns_mode")
}
}
func (c *AllowList) CheckUts(service *types.ServiceConfig) {
if !c.supported("services.build") && service.Uts != "" {
service.Uts = ""
c.Unsupported("services.uts")
}
}
func (c *AllowList) CheckVolumeDriver(service *types.ServiceConfig) {
if !c.supported("services.volume_driver") && service.VolumeDriver != "" {
service.VolumeDriver = ""
c.Unsupported("services.volume_driver")
}
}
func (c *AllowList) CheckServiceVolumes(service *types.ServiceConfig) bool {
if !c.supported("services.volumes") {
service.Volumes = nil
c.Unsupported("services.volumes")
return false
}
return true
}
func (c *AllowList) CheckVolumesSource(config *types.ServiceVolumeConfig) {
if !c.supported("services.volumes.source") && config.Source != "" {
config.Source = ""
c.Unsupported("services.volumes.source")
}
}
func (c *AllowList) CheckVolumesTarget(config *types.ServiceVolumeConfig) {
if !c.supported("services.volumes.target") && config.Target != "" {
config.Target = ""
c.Unsupported("services.volumes.target")
}
}
func (c *AllowList) CheckVolumesReadOnly(config *types.ServiceVolumeConfig) {
if !c.supported("services.volumes.read_only") && config.ReadOnly {
config.ReadOnly = false
c.Unsupported("services.volumes.read_only")
}
}
func (c *AllowList) CheckVolumesConsistency(config *types.ServiceVolumeConfig) {
if !c.supported("services.volumes.consistency") && config.Consistency != "" {
config.Consistency = ""
c.Unsupported("services.volumes.consistency")
}
}
func (c *AllowList) CheckVolumesBind(config *types.ServiceVolumeBind) {
if config == nil {
return
}
if !c.supported("services.volumes.bind.propagation") && config.Propagation != "" {
config.Propagation = ""
c.Unsupported("services.volumes.bind.propagation")
}
}
func (c *AllowList) CheckVolumesVolume(config *types.ServiceVolumeVolume) {
if config == nil {
return
}
if !c.supported("services.volumes.nocopy") && config.NoCopy {
config.NoCopy = false
c.Unsupported("services.volumes.nocopy")
}
}
func (c *AllowList) CheckVolumesTmpfs(config *types.ServiceVolumeTmpfs) {
if config == nil {
return
}
if !c.supported("services.volumes.tmpfs.size") && config.Size != 0 {
config.Size = 0
c.Unsupported("services.volumes.tmpfs.size")
}
}
func (c *AllowList) CheckVolumesFrom(service *types.ServiceConfig) {
if !c.supported("services.volumes_from") && len(service.VolumesFrom) != 0 {
service.VolumesFrom = nil
c.Unsupported("services.volumes_from")
}
}
func (c *AllowList) CheckWorkingDir(service *types.ServiceConfig) {
if !c.supported("services.working_dir") && service.WorkingDir != "" {
service.WorkingDir = ""
c.Unsupported("services.working_dir")
}
}

View File

@ -0,0 +1,47 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package compatibility
import "github.com/compose-spec/compose-go/types"
func (c *AllowList) CheckVolumeConfigDriver(config *types.VolumeConfig) {
if !c.supported("volumes.driver") && config.Driver != "" {
config.Driver = ""
c.Unsupported("volumes.driver")
}
}
func (c *AllowList) CheckVolumeConfigDriverOpts(config *types.VolumeConfig) {
if !c.supported("volumes.driver_opts") && len(config.DriverOpts) != 0 {
config.DriverOpts = nil
c.Unsupported("volumes.driver_opts")
}
}
func (c *AllowList) CheckVolumeConfigExternal(config *types.VolumeConfig) {
if !c.supported("volumes.external") && config.External.External {
config.External.External = false
c.Unsupported("volumes.external")
}
}
func (c *AllowList) CheckVolumeConfigLabels(config *types.VolumeConfig) {
if !c.supported("volumes.labels") && len(config.Labels) != 0 {
config.Labels = nil
c.Unsupported("volumes.labels")
}
}

53
pkg/third_party/compose-go/errdefs/errors.go vendored Executable file
View File

@ -0,0 +1,53 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package errdefs
import "errors"
var (
// ErrNotFound is returned when an object is not found
ErrNotFound = errors.New("not found")
// ErrInvalid is returned when a compose project is invalid
ErrInvalid = errors.New("invalid compose project")
// ErrUnsupported is returned when a compose project uses an unsupported attribute
ErrUnsupported = errors.New("unsupported attribute")
// ErrIncompatible is returned when a compose project uses an incompatible attribute
ErrIncompatible = errors.New("incompatible attribute")
)
// IsNotFoundError returns true if the unwrapped error is ErrNotFound
func IsNotFoundError(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsInvalidError returns true if the unwrapped error is ErrInvalid
func IsInvalidError(err error) bool {
return errors.Is(err, ErrInvalid)
}
// IsUnsupportedError returns true if the unwrapped error is ErrUnsupported
func IsUnsupportedError(err error) bool {
return errors.Is(err, ErrUnsupported)
}
// IsUnsupportedError returns true if the unwrapped error is ErrIncompatible
func IsIncompatibleError(err error) bool {
return errors.Is(err, ErrIncompatible)
}

22
pkg/third_party/compose-go/go.mod vendored Executable file
View File

@ -0,0 +1,22 @@
module github.com/compose-spec/compose-go
go 1.16
require (
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/google/go-cmp v0.5.5
github.com/imdario/mergo v0.3.12
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/mapstructure v1.4.1
github.com/opencontainers/go-digest v1.0.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/ulyssessouza/godotenv v1.3.1-0.20210806120901-e417b721114e
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
gopkg.in/yaml.v2 v2.4.0
gotest.tools/v3 v3.0.3
)

163
pkg/third_party/compose-go/go.sum vendored Executable file
View File

@ -0,0 +1,163 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU=
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/ulyssessouza/godotenv v1.3.1-0.20210806120901-e417b721114e h1:byEYm3QADv5mDUesYKstWwRodf2RoxxC/YuGOxtdqJw=
github.com/ulyssessouza/godotenv v1.3.1-0.20210806120901-e417b721114e/go.mod h1:9JN/BuU6Agy5aHyEoA5EIPkBsYbk0+2R42zJgYi/SlI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 h1:NMiUjDZiD6qDVeBOzpImftxXzQHCp2Y2QLdmaqU9MRk=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=

13
pkg/third_party/compose-go/golangci.yml vendored Executable file
View File

@ -0,0 +1,13 @@
run:
deadline: 2m
linters:
disable-all: true
enable:
- gofmt
- goimports
- golint
- gosimple
- ineffassign
- misspell
- govet

View File

@ -0,0 +1,177 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package interpolation
import (
"os"
"strings"
"github.com/compose-spec/compose-go/template"
"github.com/pkg/errors"
)
// Options supported by Interpolate
type Options struct {
// LookupValue from a key
LookupValue LookupValue
// TypeCastMapping maps key paths to functions to cast to a type
TypeCastMapping map[Path]Cast
// Substitution function to use
Substitute func(string, template.Mapping) (string, error)
}
// LookupValue is a function which maps from variable names to values.
// Returns the value as a string and a bool indicating whether
// the value is present, to distinguish between an empty string
// and the absence of a value.
type LookupValue func(key string) (string, bool)
// Cast a value to a new type, or return an error if the value can't be cast
type Cast func(value string) (interface{}, error)
// Interpolate replaces variables in a string with the values from a mapping
func Interpolate(config map[string]interface{}, opts Options) (map[string]interface{}, error) {
if opts.LookupValue == nil {
opts.LookupValue = os.LookupEnv
}
if opts.TypeCastMapping == nil {
opts.TypeCastMapping = make(map[Path]Cast)
}
if opts.Substitute == nil {
opts.Substitute = template.Substitute
}
out := map[string]interface{}{}
for key, value := range config {
interpolatedValue, err := recursiveInterpolate(value, NewPath(key), opts)
if err != nil {
return out, err
}
out[key] = interpolatedValue
}
return out, nil
}
func recursiveInterpolate(value interface{}, path Path, opts Options) (interface{}, error) {
switch value := value.(type) {
case string:
newValue, err := opts.Substitute(value, template.Mapping(opts.LookupValue))
if err != nil || newValue == value {
return value, newPathError(path, err)
}
caster, ok := opts.getCasterForPath(path)
if !ok {
return newValue, nil
}
casted, err := caster(newValue)
return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type"))
case map[string]interface{}:
out := map[string]interface{}{}
for key, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, path.Next(key), opts)
if err != nil {
return nil, err
}
out[key] = interpolatedElem
}
return out, nil
case []interface{}:
out := make([]interface{}, len(value))
for i, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, path.Next(PathMatchList), opts)
if err != nil {
return nil, err
}
out[i] = interpolatedElem
}
return out, nil
default:
return value, nil
}
}
func newPathError(path Path, err error) error {
switch err := err.(type) {
case nil:
return nil
case *template.InvalidTemplateError:
return errors.Errorf(
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
path, err.Template)
default:
return errors.Wrapf(err, "error while interpolating %s", path)
}
}
const pathSeparator = "."
// PathMatchAll is a token used as part of a Path to match any key at that level
// in the nested structure
const PathMatchAll = "*"
// PathMatchList is a token used as part of a Path to match items in a list
const PathMatchList = "[]"
// Path is a dotted path of keys to a value in a nested mapping structure. A *
// section in a path will match any key in the mapping structure.
type Path string
// NewPath returns a new Path
func NewPath(items ...string) Path {
return Path(strings.Join(items, pathSeparator))
}
// Next returns a new path by append part to the current path
func (p Path) Next(part string) Path {
return Path(string(p) + pathSeparator + part)
}
func (p Path) parts() []string {
return strings.Split(string(p), pathSeparator)
}
func (p Path) matches(pattern Path) bool {
patternParts := pattern.parts()
parts := p.parts()
if len(patternParts) != len(parts) {
return false
}
for index, part := range parts {
switch patternParts[index] {
case PathMatchAll, part:
continue
default:
return false
}
}
return true
}
func (o Options) getCasterForPath(path Path) (Cast, bool) {
for pattern, caster := range o.TypeCastMapping {
if path.matches(pattern) {
return caster, true
}
}
return nil, false
}

View File

@ -0,0 +1,163 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package interpolation
import (
"testing"
"strconv"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/env"
)
var defaults = map[string]string{
"USER": "jenny",
"FOO": "bar",
"count": "5",
}
func defaultMapping(name string) (string, bool) {
val, ok := defaults[name]
return val, ok
}
func TestInterpolate(t *testing.T) {
services := map[string]interface{}{
"servicea": map[string]interface{}{
"image": "example:${USER}",
"volumes": []interface{}{"$FOO:/target"},
"logging": map[string]interface{}{
"driver": "${FOO}",
"options": map[string]interface{}{
"user": "$USER",
},
},
},
}
expected := map[string]interface{}{
"servicea": map[string]interface{}{
"image": "example:jenny",
"volumes": []interface{}{"bar:/target"},
"logging": map[string]interface{}{
"driver": "bar",
"options": map[string]interface{}{
"user": "jenny",
},
},
},
}
result, err := Interpolate(services, Options{LookupValue: defaultMapping})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, result))
}
func TestInvalidInterpolation(t *testing.T) {
services := map[string]interface{}{
"servicea": map[string]interface{}{
"image": "${",
},
}
_, err := Interpolate(services, Options{LookupValue: defaultMapping})
assert.Error(t, err, `invalid interpolation format for servicea.image: "${". You may need to escape any $ with another $.`)
}
func TestInterpolateWithDefaults(t *testing.T) {
defer env.Patch(t, "FOO", "BARZ")()
config := map[string]interface{}{
"networks": map[string]interface{}{
"foo": "thing_${FOO}",
},
}
expected := map[string]interface{}{
"networks": map[string]interface{}{
"foo": "thing_BARZ",
},
}
result, err := Interpolate(config, Options{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, result))
}
func TestInterpolateWithCast(t *testing.T) {
config := map[string]interface{}{
"foo": map[string]interface{}{
"replicas": "$count",
},
}
toInt := func(value string) (interface{}, error) {
return strconv.Atoi(value)
}
result, err := Interpolate(config, Options{
LookupValue: defaultMapping,
TypeCastMapping: map[Path]Cast{NewPath(PathMatchAll, "replicas"): toInt},
})
assert.NilError(t, err)
expected := map[string]interface{}{
"foo": map[string]interface{}{
"replicas": 5,
},
}
assert.Check(t, is.DeepEqual(expected, result))
}
func TestPathMatches(t *testing.T) {
var testcases = []struct {
doc string
path Path
pattern Path
expected bool
}{
{
doc: "pattern too short",
path: NewPath("one", "two", "three"),
pattern: NewPath("one", "two"),
},
{
doc: "pattern too long",
path: NewPath("one", "two"),
pattern: NewPath("one", "two", "three"),
},
{
doc: "pattern mismatch",
path: NewPath("one", "three", "two"),
pattern: NewPath("one", "two", "three"),
},
{
doc: "pattern mismatch with match-all part",
path: NewPath("one", "three", "two"),
pattern: NewPath(PathMatchAll, "two", "three"),
},
{
doc: "pattern match with match-all part",
path: NewPath("one", "two", "three"),
pattern: NewPath("one", "*", "three"),
expected: true,
},
{
doc: "pattern match",
path: NewPath("one", "two", "three"),
pattern: NewPath("one", "two", "three"),
expected: true,
},
}
for _, testcase := range testcases {
assert.Check(t, is.Equal(testcase.expected, testcase.path.matches(testcase.pattern)))
}
}

View File

@ -0,0 +1,8 @@
# passed through
FOO=foo_from_env_file
# overridden in example2.env
BAR=bar_from_env_file
# overridden in full-example.yml
BAZ=baz_from_env_file

View File

@ -0,0 +1,4 @@
BAR=bar_from_env_file_2
# overridden in configDetails.Environment
QUX=quz_from_env_file_2

View File

@ -0,0 +1,412 @@
services:
foo:
build:
context: ./dir
dockerfile: Dockerfile
args:
foo: bar
target: foo
network: foo
cache_from:
- foo
- bar
labels: [FOO=BAR]
cap_add:
- ALL
cap_drop:
- NET_ADMIN
- SYS_ADMIN
cgroup_parent: m-executor-abcd
# String or list
command: bundle exec thin -p 3000
# command: ["bundle", "exec", "thin", "-p", "3000"]
configs:
- config1
- source: config2
target: /my_config
uid: '103'
gid: '103'
mode: 0440
container_name: my-web-container
depends_on:
- db
- redis
deploy:
mode: replicated
replicas: 6
labels: [FOO=BAR]
rollback_config:
parallelism: 3
delay: 10s
failure_action: continue
monitor: 60s
max_failure_ratio: 0.3
order: start-first
update_config:
parallelism: 3
delay: 10s
failure_action: continue
monitor: 60s
max_failure_ratio: 0.3
order: start-first
resources:
limits:
cpus: '0.001'
memory: 50M
reservations:
cpus: '0.0001'
memory: 20M
generic_resources:
- discrete_resource_spec:
kind: 'gpu'
value: 2
- discrete_resource_spec:
kind: 'ssd'
value: 1
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
placement:
constraints: [node=foo]
max_replicas_per_node: 5
preferences:
- spread: node.labels.az
endpoint_mode: dnsrr
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
# String or list
# dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9
# String or list
# dns_search: example.com
dns_search:
- dc1.example.com
- dc2.example.com
domainname: foo.com
# String or list
# entrypoint: /code/entrypoint.sh -p 3000
entrypoint: ["/code/entrypoint.sh", "-p", "3000"]
# String or list
# env_file: .env
env_file:
- ./example1.env
- ./example2.env
# Mapping or list
# Mapping values can be strings, numbers or null
# Booleans are not allowed - must be quoted
environment:
BAZ: baz_from_service_def
QUX:
# environment:
# - RACK_ENV=development
# - SHOW=true
# - SESSION_SECRET
# Items can be strings or numbers
expose:
- "3000"
- 8000
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
# Mapping or list
# Mapping values must be strings
# extra_hosts:
# somehost: "162.242.195.82"
# otherhost: "50.31.209.229"
extra_hosts:
- "somehost:162.242.195.82"
- "otherhost:50.31.209.229"
hostname: foo
healthcheck:
test: echo "hello world"
interval: 10s
timeout: 1s
retries: 5
start_period: 15s
# Any valid image reference - repo, tag, id, sha
image: redis
# image: ubuntu:14.04
# image: tutum/influxdb
# image: example-registry.com:4000/postgresql
# image: a4bc65fd
# image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
ipc: host
# Mapping or list
# Mapping values can be strings, numbers or null
labels:
com.example.description: "Accounting webapp"
com.example.number: 42
com.example.empty-label:
# labels:
# - "com.example.description=Accounting webapp"
# - "com.example.number=42"
# - "com.example.empty-label"
links:
- db
- db:database
- redis
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
mac_address: 02:42:ac:11:65:43
# network_mode: "bridge"
# network_mode: "host"
# network_mode: "none"
# Use the network mode of an arbitrary container from another service
# network_mode: "service:db"
# Use the network mode of another container, specified by name or id
# network_mode: "container:some-container"
network_mode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b"
networks:
some-network:
aliases:
- alias1
- alias3
other-network:
ipv4_address: 172.16.238.10
ipv6_address: 2001:3984:3989::10
other-other-network:
pid: "host"
ports:
- 3000
- "3001-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
privileged: true
read_only: true
restart: always
secrets:
- secret1
- source: secret2
target: my_secret
uid: '103'
gid: '103'
mode: 0440
security_opt:
- label=level:s0:c100,c200
- label=type:svirt_apache_t
stdin_open: true
stop_grace_period: 20s
stop_signal: SIGUSR1
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
# String or list
# tmpfs: /run
tmpfs:
- /run
- /tmp
tty: true
ulimits:
# Single number or mapping with soft + hard limits
nproc: 65535
nofile:
soft: 20000
hard: 40000
user: someone
volumes:
# Just specify a path and let the Engine create a volume
- /var/lib/mysql
# Specify an absolute path mapping
- /opt/data:/var/lib/mysql
# Path on the host, relative to the Compose file
- .:/code
- ./static:/var/www/html
# User-relative path
- ~/configs:/etc/configs/:ro
# Named volume
- datavolume:/var/lib/mysql
- type: bind
source: ./opt
target: /opt
consistency: cached
- type: tmpfs
target: /opt
tmpfs:
size: 10000
working_dir: /code
x-bar: baz
x-foo: bar
networks:
# Entries can be null, which specifies simply that a network
# called "{project name}_some-network" should be created and
# use the default driver
some-network:
other-network:
driver: overlay
driver_opts:
# Values can be strings or numbers
foo: "bar"
baz: 1
ipam:
driver: overlay
# driver_opts:
# # Values can be strings or numbers
# com.docker.network.enable_ipv6: "true"
# com.docker.network.numeric_value: 1
config:
- subnet: 172.28.0.0/16
ip_range: 172.28.5.0/24
gateway: 172.28.5.254
aux_addresses:
host1: 172.28.1.5
host2: 172.28.1.6
host3: 172.28.1.7
- subnet: 2001:3984:3989::/64
gateway: 2001:3984:3989::1
labels:
foo: bar
external-network:
# Specifies that a pre-existing network called "external-network"
# can be referred to within this file as "external-network"
external: true
other-external-network:
# Specifies that a pre-existing network called "my-cool-network"
# can be referred to within this file as "other-external-network"
external:
name: my-cool-network
x-bar: baz
x-foo: bar
volumes:
# Entries can be null, which specifies simply that a volume
# called "{project name}_some-volume" should be created and
# use the default driver
some-volume:
other-volume:
driver: flocker
driver_opts:
# Values can be strings or numbers
foo: "bar"
baz: 1
labels:
foo: bar
another-volume:
name: "user_specified_name"
driver: vsphere
driver_opts:
# Values can be strings or numbers
foo: "bar"
baz: 1
external-volume:
# Specifies that a pre-existing volume called "external-volume"
# can be referred to within this file as "external-volume"
external: true
other-external-volume:
# Specifies that a pre-existing volume called "my-cool-volume"
# can be referred to within this file as "other-external-volume"
# This example uses the deprecated "volume.external.name" (replaced by "volume.name")
external:
name: my-cool-volume
external-volume3:
# Specifies that a pre-existing volume called "this-is-volume3"
# can be referred to within this file as "external-volume3"
name: this-is-volume3
external: true
x-bar: baz
x-foo: bar
configs:
config1:
file: ./config_data
labels:
foo: bar
config2:
external:
name: my_config
config3:
external: true
config4:
name: foo
x-bar: baz
x-foo: bar
secrets:
secret1:
file: ./secret_data
labels:
foo: bar
secret2:
external:
name: my_secret
secret3:
external: true
secret4:
name: bar
x-bar: baz
x-foo: bar
x-bar: baz
x-foo: bar
x-nested:
bar: baz
foo: bar

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"strconv"
"strings"
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
)
var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
servicePath("configs", interp.PathMatchList, "mode"): toInt,
servicePath("cpu_count"): toInt64,
servicePath("cpu_percent"): toFloat,
servicePath("cpu_period"): toInt64,
servicePath("cpu_quota"): toInt64,
servicePath("cpu_rt_period"): toInt64,
servicePath("cpu_rt_runtime"): toInt64,
servicePath("cpus"): toFloat32,
servicePath("cpu_shares"): toInt64,
servicePath("init"): toBoolean,
servicePath("deploy", "replicas"): toInt,
servicePath("deploy", "update_config", "parallelism"): toInt,
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "rollback_config", "parallelism"): toInt,
servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
servicePath("deploy", "placement", "max_replicas_per_node"): toInt,
servicePath("healthcheck", "retries"): toInt,
servicePath("healthcheck", "disable"): toBoolean,
servicePath("mem_limit"): toUnitBytes,
servicePath("mem_reservation"): toUnitBytes,
servicePath("memswap_limit"): toUnitBytes,
servicePath("mem_swappiness"): toUnitBytes,
servicePath("oom_kill_disable"): toBoolean,
servicePath("oom_score_adj"): toInt64,
servicePath("pids_limit"): toInt64,
servicePath("ports", interp.PathMatchList, "target"): toInt,
servicePath("ports", interp.PathMatchList, "published"): toInt,
servicePath("privileged"): toBoolean,
servicePath("read_only"): toBoolean,
servicePath("scale"): toInt,
servicePath("secrets", interp.PathMatchList, "mode"): toInt,
servicePath("shm_size"): toUnitBytes,
servicePath("stdin_open"): toBoolean,
servicePath("stop_grace_period"): toDuration,
servicePath("tty"): toBoolean,
servicePath("ulimits", interp.PathMatchAll): toInt,
servicePath("ulimits", interp.PathMatchAll, "hard"): toInt,
servicePath("ulimits", interp.PathMatchAll, "soft"): toInt,
servicePath("volumes", interp.PathMatchList, "read_only"): toBoolean,
servicePath("volumes", interp.PathMatchList, "volume", "nocopy"): toBoolean,
iPath("networks", interp.PathMatchAll, "external"): toBoolean,
iPath("networks", interp.PathMatchAll, "internal"): toBoolean,
iPath("networks", interp.PathMatchAll, "attachable"): toBoolean,
iPath("volumes", interp.PathMatchAll, "external"): toBoolean,
iPath("secrets", interp.PathMatchAll, "external"): toBoolean,
iPath("configs", interp.PathMatchAll, "external"): toBoolean,
}
func iPath(parts ...string) interp.Path {
return interp.NewPath(parts...)
}
func servicePath(parts ...string) interp.Path {
return iPath(append([]string{"services", interp.PathMatchAll}, parts...)...)
}
func toInt(value string) (interface{}, error) {
return strconv.Atoi(value)
}
func toInt64(value string) (interface{}, error) {
return strconv.ParseInt(value, 10, 64)
}
func toUnitBytes(value string) (interface{}, error) {
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, err
}
return types.UnitBytes(i), nil
}
func toDuration(value string) (interface{}, error) {
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, err
}
return types.Duration(i), nil
}
func toFloat(value string) (interface{}, error) {
return strconv.ParseFloat(value, 64)
}
func toFloat32(value string) (interface{}, error) {
f, err := strconv.ParseFloat(value, 32)
if err != nil {
return nil, err
}
return float32(f), nil
}
// should match http://yaml.org/type/bool.html
func toBoolean(value string) (interface{}, error) {
switch strings.ToLower(value) {
case "y", "yes", "true", "on":
return true, nil
case "n", "no", "false", "off":
return false, nil
default:
return nil, errors.Errorf("invalid boolean: %s", value)
}
}

1062
pkg/third_party/compose-go/loader/loader.go vendored Executable file

File diff suppressed because it is too large Load Diff

1809
pkg/third_party/compose-go/loader/loader_test.go vendored Executable file

File diff suppressed because it is too large Load Diff

349
pkg/third_party/compose-go/loader/merge.go vendored Executable file
View File

@ -0,0 +1,349 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"reflect"
"sort"
"github.com/compose-spec/compose-go/types"
"github.com/imdario/mergo"
"github.com/pkg/errors"
)
type specials struct {
m map[reflect.Type]func(dst, src reflect.Value) error
}
var serviceSpecials = &specials{
m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig),
reflect.TypeOf(&types.UlimitsConfig{}): safelyMerge(mergeUlimitsConfig),
reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig,
reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig,
},
}
func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
if fn, ok := s.m[t]; ok {
return fn
}
return nil
}
func merge(configs []*types.Config) (*types.Config, error) {
base := configs[0]
for _, override := range configs[1:] {
var err error
base.Services, err = mergeServices(base.Services, override.Services)
if err != nil {
return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename)
}
base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes)
if err != nil {
return base, errors.Wrapf(err, "cannot merge volumes from %s", override.Filename)
}
base.Networks, err = mergeNetworks(base.Networks, override.Networks)
if err != nil {
return base, errors.Wrapf(err, "cannot merge networks from %s", override.Filename)
}
base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets)
if err != nil {
return base, errors.Wrapf(err, "cannot merge secrets from %s", override.Filename)
}
base.Configs, err = mergeConfigs(base.Configs, override.Configs)
if err != nil {
return base, errors.Wrapf(err, "cannot merge configs from %s", override.Filename)
}
base.Extensions, err = mergeExtensions(base.Extensions, override.Extensions)
if err != nil {
return base, errors.Wrapf(err, "cannot merge extensions from %s", override.Filename)
}
}
return base, nil
}
func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
baseServices := mapByName(base)
overrideServices := mapByName(override)
for name, overrideService := range overrideServices {
overrideService := overrideService
if baseService, ok := baseServices[name]; ok {
merged, err := _merge(&baseService, &overrideService)
if err != nil {
return nil, errors.Wrapf(err, "cannot merge service %s", name)
}
baseServices[name] = *merged
continue
}
baseServices[name] = overrideService
}
services := []types.ServiceConfig{}
for _, baseService := range baseServices {
services = append(services, baseService)
}
sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
return services, nil
}
func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConfig) (*types.ServiceConfig, error) {
if err := mergo.Merge(baseService, overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil {
return nil, err
}
if overrideService.Command != nil {
baseService.Command = overrideService.Command
}
if overrideService.Entrypoint != nil {
baseService.Entrypoint = overrideService.Entrypoint
}
return baseService, nil
}
func toServiceSecretConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
secrets, ok := s.([]types.ServiceSecretConfig)
if !ok {
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
}
m := map[interface{}]interface{}{}
for _, secret := range secrets {
m[secret.Source] = secret
}
return m, nil
}
func toServiceConfigObjConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
secrets, ok := s.([]types.ServiceConfigObjConfig)
if !ok {
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
}
m := map[interface{}]interface{}{}
for _, secret := range secrets {
m[secret.Source] = secret
}
return m, nil
}
func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
ports, ok := s.([]types.ServicePortConfig)
if !ok {
return nil, errors.Errorf("not a servicePortConfig slice: %v", s)
}
m := map[interface{}]interface{}{}
type port struct {
target uint32
published uint32
ip string
protocol string
}
for _, p := range ports {
mergeKey := port{
target: p.Target,
published: p.Published,
ip: p.HostIP,
protocol: p.Protocol,
}
m[mergeKey] = p
}
return m, nil
}
func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
volumes, ok := s.([]types.ServiceVolumeConfig)
if !ok {
return nil, errors.Errorf("not a ServiceVolumeConfig slice: %v", s)
}
m := map[interface{}]interface{}{}
for _, v := range volumes {
m[v.Target] = v
}
return m, nil
}
func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceSecretConfig{}
for _, v := range m {
s = append(s, v.(types.ServiceSecretConfig))
}
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
dst.Set(reflect.ValueOf(s))
return nil
}
func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceConfigObjConfig{}
for _, v := range m {
s = append(s, v.(types.ServiceConfigObjConfig))
}
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
dst.Set(reflect.ValueOf(s))
return nil
}
func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServicePortConfig{}
for _, v := range m {
s = append(s, v.(types.ServicePortConfig))
}
sort.Slice(s, func(i, j int) bool {
if s[i].Target != s[j].Target {
return s[i].Target < s[j].Target
}
if s[i].Published != s[j].Published {
return s[i].Published < s[j].Published
}
if s[i].HostIP != s[j].HostIP {
return s[i].HostIP < s[j].HostIP
}
return s[i].Protocol < s[j].Protocol
})
dst.Set(reflect.ValueOf(s))
return nil
}
func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceVolumeConfig{}
for _, v := range m {
s = append(s, v.(types.ServiceVolumeConfig))
}
sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target })
dst.Set(reflect.ValueOf(s))
return nil
}
type tomapFn func(s interface{}) (map[interface{}]interface{}, error)
type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error
func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error {
return func(dst, src reflect.Value) error {
if src.IsNil() {
return nil
}
if dst.IsNil() {
dst.Set(src)
return nil
}
return mergeFn(dst, src)
}
}
func mergeSlice(tomap tomapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error {
return func(dst, src reflect.Value) error {
dstMap, err := sliceToMap(tomap, dst)
if err != nil {
return err
}
srcMap, err := sliceToMap(tomap, src)
if err != nil {
return err
}
if err := mergo.Map(&dstMap, srcMap, mergo.WithOverride); err != nil {
return err
}
return writeValue(dst, dstMap)
}
}
func sliceToMap(tomap tomapFn, v reflect.Value) (map[interface{}]interface{}, error) {
// check if valid
if !v.IsValid() {
return nil, errors.Errorf("invalid value : %+v", v)
}
return tomap(v.Interface())
}
func mergeLoggingConfig(dst, src reflect.Value) error {
// Same driver, merging options
if getLoggingDriver(dst.Elem()) == getLoggingDriver(src.Elem()) ||
getLoggingDriver(dst.Elem()) == "" || getLoggingDriver(src.Elem()) == "" {
if getLoggingDriver(dst.Elem()) == "" {
dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem()))
}
dstOptions := dst.Elem().FieldByName("Options").Interface().(map[string]string)
srcOptions := src.Elem().FieldByName("Options").Interface().(map[string]string)
return mergo.Merge(&dstOptions, srcOptions, mergo.WithOverride)
}
// Different driver, override with src
dst.Set(src)
return nil
}
//nolint: unparam
func mergeUlimitsConfig(dst, src reflect.Value) error {
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
dst.Elem().Set(src.Elem())
}
return nil
}
//nolint: unparam
func mergeServiceNetworkConfig(dst, src reflect.Value) error {
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases"))
if ipv4 := src.Elem().FieldByName("Ipv4Address").Interface().(string); ipv4 != "" {
dst.Elem().FieldByName("Ipv4Address").SetString(ipv4)
}
if ipv6 := src.Elem().FieldByName("Ipv6Address").Interface().(string); ipv6 != "" {
dst.Elem().FieldByName("Ipv6Address").SetString(ipv6)
}
}
return nil
}
func getLoggingDriver(v reflect.Value) string {
return v.FieldByName("Driver").String()
}
func mapByName(services []types.ServiceConfig) map[string]types.ServiceConfig {
m := map[string]types.ServiceConfig{}
for _, service := range services {
m[service.Name] = service
}
return m
}
func mergeVolumes(base, override map[string]types.VolumeConfig) (map[string]types.VolumeConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeNetworks(base, override map[string]types.NetworkConfig) (map[string]types.NetworkConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeSecrets(base, override map[string]types.SecretConfig) (map[string]types.SecretConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeConfigs(base, override map[string]types.ConfigObjConfig) (map[string]types.ConfigObjConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeExtensions(base, override map[string]interface{}) (map[string]interface{}, error) {
if base == nil {
base = map[string]interface{}{}
}
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}

1226
pkg/third_party/compose-go/loader/merge_test.go vendored Executable file

File diff suppressed because it is too large Load Diff

261
pkg/third_party/compose-go/loader/normalize.go vendored Executable file
View File

@ -0,0 +1,261 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"fmt"
"os"
"path/filepath"
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
func normalize(project *types.Project, resolvePaths bool) error {
absWorkingDir, err := filepath.Abs(project.WorkingDir)
if err != nil {
return err
}
project.WorkingDir = absWorkingDir
absComposeFiles, err := absComposeFiles(project.ComposeFiles)
if err != nil {
return err
}
project.ComposeFiles = absComposeFiles
// If not declared explicitly, Compose model involves an implicit "default" network
if _, ok := project.Networks["default"]; !ok {
project.Networks["default"] = types.NetworkConfig{}
}
err = relocateExternalName(project)
if err != nil {
return err
}
for i, s := range project.Services {
if len(s.Networks) == 0 && s.NetworkMode == "" {
// Service without explicit network attachment are implicitly exposed on default network
s.Networks = map[string]*types.ServiceNetworkConfig{"default": nil}
}
if s.PullPolicy == types.PullPolicyIfNotPresent {
s.PullPolicy = types.PullPolicyMissing
}
fn := func(s string) (string, bool) {
v, ok := project.Environment[s]
return v, ok
}
if s.Build != nil {
if s.Build.Dockerfile == "" {
s.Build.Dockerfile = "Dockerfile"
}
localContext := absPath(project.WorkingDir, s.Build.Context)
if _, err := os.Stat(localContext); err == nil {
if resolvePaths {
s.Build.Context = localContext
s.Build.Dockerfile = absPath(localContext, s.Build.Dockerfile)
}
} else {
// might be a remote http/git context. Unfortunately supported "remote" syntax is highly ambiguous
// in moby/moby and not defined by compose-spec, so let's assume runtime will check
}
s.Build.Args = s.Build.Args.Resolve(fn)
}
s.Environment = s.Environment.Resolve(fn)
err := relocateLogDriver(&s)
if err != nil {
return err
}
err = relocateLogOpt(&s)
if err != nil {
return err
}
err = relocateDockerfile(&s)
if err != nil {
return err
}
err = relocateScale(&s)
if err != nil {
return err
}
project.Services[i] = s
}
setNameFromKey(project)
return nil
}
func relocateScale(s *types.ServiceConfig) error {
scale := uint64(s.Scale)
if scale != 1 {
logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element")
if s.Deploy == nil {
s.Deploy = &types.DeployConfig{}
}
if s.Deploy.Replicas != nil && *s.Deploy.Replicas != scale {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'scale' (deprecated) and 'deploy.replicas'")
}
s.Deploy.Replicas = &scale
}
return nil
}
func absComposeFiles(composeFiles []string) ([]string, error) {
absComposeFiles := make([]string, len(composeFiles))
for i, composeFile := range composeFiles {
absComposefile, err := filepath.Abs(composeFile)
if err != nil {
return nil, err
}
absComposeFiles[i] = absComposefile
}
return absComposeFiles, nil
}
// Resources with no explicit name are actually named by their key in map
func setNameFromKey(project *types.Project) {
for i, n := range project.Networks {
if n.Name == "" {
n.Name = fmt.Sprintf("%s_%s", project.Name, i)
project.Networks[i] = n
}
}
for i, v := range project.Volumes {
if v.Name == "" {
v.Name = fmt.Sprintf("%s_%s", project.Name, i)
project.Volumes[i] = v
}
}
for i, c := range project.Configs {
if c.Name == "" {
c.Name = fmt.Sprintf("%s_%s", project.Name, i)
project.Configs[i] = c
}
}
for i, s := range project.Secrets {
if s.Name == "" {
s.Name = fmt.Sprintf("%s_%s", project.Name, i)
project.Secrets[i] = s
}
}
}
func relocateExternalName(project *types.Project) error {
for i, n := range project.Networks {
if n.External.Name != "" {
if n.Name != "" {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'networks.external.name' (deprecated) and 'networks.name'")
}
n.Name = n.External.Name
}
project.Networks[i] = n
}
for i, v := range project.Volumes {
if v.External.Name != "" {
if v.Name != "" {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'volumes.external.name' (deprecated) and 'volumes.name'")
}
v.Name = v.External.Name
}
project.Volumes[i] = v
}
for i, s := range project.Secrets {
if s.External.Name != "" {
if s.Name != "" {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'secrets.external.name' (deprecated) and 'secrets.name'")
}
s.Name = s.External.Name
}
project.Secrets[i] = s
}
for i, c := range project.Configs {
if c.External.Name != "" {
if c.Name != "" {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'configs.external.name' (deprecated) and 'configs.name'")
}
c.Name = c.External.Name
}
project.Configs[i] = c
}
return nil
}
func relocateLogOpt(s *types.ServiceConfig) error {
if len(s.LogOpt) != 0 {
logrus.Warn("`log_opts` is deprecated. Use the `logging` element")
if s.Logging == nil {
s.Logging = &types.LoggingConfig{}
}
for k, v := range s.LogOpt {
if _, ok := s.Logging.Options[k]; !ok {
s.Logging.Options[k] = v
} else {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_opt' (deprecated) and 'logging.options'")
}
}
}
return nil
}
func relocateLogDriver(s *types.ServiceConfig) error {
if s.LogDriver != "" {
logrus.Warn("`log_driver` is deprecated. Use the `logging` element")
if s.Logging == nil {
s.Logging = &types.LoggingConfig{}
}
if s.Logging.Driver == "" {
s.Logging.Driver = s.LogDriver
} else {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_driver' (deprecated) and 'logging.driver'")
}
}
return nil
}
func relocateDockerfile(s *types.ServiceConfig) error {
if s.Dockerfile != "" {
logrus.Warn("`dockerfile` is deprecated. Use the `build` element")
if s.Build == nil {
s.Build = &types.BuildConfig{}
}
if s.Dockerfile == "" {
s.Build.Dockerfile = s.Dockerfile
} else {
return errors.Wrap(errdefs.ErrInvalid, "can't use both 'dockerfile' (deprecated) and 'build.dockerfile'")
}
}
return nil
}

View File

@ -0,0 +1,148 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"os"
"path/filepath"
"testing"
"github.com/compose-spec/compose-go/types"
"gopkg.in/yaml.v2"
"gotest.tools/v3/assert"
)
func TestNormalizeNetworkNames(t *testing.T) {
wd, _ := os.Getwd()
project := types.Project{
Name: "myProject",
WorkingDir: wd,
Environment: map[string]string{
"FOO": "BAR",
},
Networks: types.Networks{
"myExternalnet": {
Name: "myExternalnet", // this is automaticaly setup by loader for externa networks before reaching normalization
External: types.External{External: true},
},
"mynet": {},
"myNamedNet": {
Name: "CustomName",
},
},
Services: []types.ServiceConfig{
{
Name: "foo",
Build: &types.BuildConfig{
Context: "./testdata",
Args: map[string]*string{
"FOO": nil,
"ZOT": nil,
},
},
Scale: 1,
},
},
}
expected := `services:
foo:
build:
context: ./testdata
dockerfile: Dockerfile
args:
FOO: BAR
ZOT: null
networks:
default: null
networks:
default:
name: myProject_default
myExternalnet:
name: myExternalnet
external: true
myNamedNet:
name: CustomName
mynet:
name: myProject_mynet
`
err := normalize(&project, false)
assert.NilError(t, err)
marshal, err := yaml.Marshal(project)
assert.NilError(t, err)
assert.DeepEqual(t, expected, string(marshal))
}
func TestNormalizeAbsolutePaths(t *testing.T) {
project := types.Project{
Name: "myProject",
WorkingDir: "testdata",
Networks: types.Networks{},
ComposeFiles: []string{filepath.Join("testdata", "simple", "compose.yaml"), filepath.Join("testdata", "simple", "compose-with-overrides.yaml")},
}
absWorkingDir, _ := filepath.Abs("testdata")
absComposeFile, _ := filepath.Abs(filepath.Join("testdata", "simple", "compose.yaml"))
absOverrideFile, _ := filepath.Abs(filepath.Join("testdata", "simple", "compose-with-overrides.yaml"))
expected := types.Project{
Name: "myProject",
Networks: types.Networks{"default": {Name: "myProject_default"}},
WorkingDir: absWorkingDir,
ComposeFiles: []string{absComposeFile, absOverrideFile},
}
err := normalize(&project, false)
assert.NilError(t, err)
assert.DeepEqual(t, expected, project)
}
func TestNormalizeVolumes(t *testing.T) {
project := types.Project{
Name: "myProject",
Networks: types.Networks{},
Volumes: types.Volumes{
"myExternalVol": {
Name: "myExternalVol", // this is automaticaly setup by loader for externa networks before reaching normalization
External: types.External{External: true},
},
"myvol": {},
"myNamedVol": {
Name: "CustomName",
},
},
}
absCwd, _ := filepath.Abs(".")
expected := types.Project{
Name: "myProject",
Networks: types.Networks{"default": {Name: "myProject_default"}},
Volumes: types.Volumes{
"myExternalVol": {
Name: "myExternalVol",
External: types.External{External: true},
},
"myvol": {Name: "myProject_myvol"},
"myNamedVol": {
Name: "CustomName",
},
},
WorkingDir: absCwd,
ComposeFiles: []string{},
}
err := normalize(&project, false)
assert.NilError(t, err)
assert.DeepEqual(t, expected, project)
}

View File

@ -0,0 +1,17 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.
FROM busybox:1.31.0-uclibc
RUN echo something
CMD top

View File

@ -0,0 +1,3 @@
services:
imported:
image: nginx

View File

@ -0,0 +1,5 @@
services:
importer:
extends:
file: compose-test-extends-imported.yaml
service: imported

View File

@ -0,0 +1,23 @@
version: "2"
volumes:
data:
driver: local
networks:
front: {}
services:
web:
build: ./Dockerfile
networks:
- front
- default
volumes_from:
- other
other:
image: busybox:1.31.0-uclibc
command: top
volumes:
- /data

View File

@ -0,0 +1,67 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"encoding/json"
"os"
"testing"
yaml "gopkg.in/yaml.v2"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestMarshallConfig(t *testing.T) {
workingDir, err := os.Getwd()
assert.NilError(t, err)
homeDir, err := os.UserHomeDir()
assert.NilError(t, err)
cfg := fullExampleConfig(workingDir, homeDir)
expected := fullExampleYAML(workingDir, homeDir)
actual, err := yaml.Marshal(cfg)
assert.NilError(t, err)
assert.Check(t, is.Equal(expected, string(actual)))
// Make sure the expected still
_, err = Load(buildConfigDetails(expected, map[string]string{}), func(options *Options) {
options.SkipNormalization = true
options.SkipConsistencyCheck = true
})
assert.NilError(t, err)
}
func TestJSONMarshallConfig(t *testing.T) {
workingDir, err := os.Getwd()
assert.NilError(t, err)
homeDir, err := os.UserHomeDir()
assert.NilError(t, err)
cfg := fullExampleConfig(workingDir, homeDir)
expected := fullExampleJSON(workingDir, homeDir)
actual, err := json.MarshalIndent(cfg, "", " ")
assert.NilError(t, err)
assert.Check(t, is.Equal(expected, string(actual)))
_, err = Load(buildConfigDetails(expected, map[string]string{}), func(options *Options) {
options.SkipNormalization = true
options.SkipConsistencyCheck = true
})
assert.NilError(t, err)
}

70
pkg/third_party/compose-go/loader/validate.go vendored Executable file
View File

@ -0,0 +1,70 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"fmt"
"strings"
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
)
// checkConsistency validate a compose model is consistent
func checkConsistency(project *types.Project) error {
for _, s := range project.Services {
if s.Build == nil && s.Image == "" {
return errors.Wrapf(errdefs.ErrInvalid, "service %q has neither an image nor a build context specified", s.Name)
}
for network := range s.Networks {
if _, ok := project.Networks[network]; !ok {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined network %s", s.Name, network))
}
}
if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) {
serviceName := s.NetworkMode[len(types.ServicePrefix):]
if _, err := project.GetServices(serviceName); err != nil {
return fmt.Errorf("service %q not found for network_mode 'service:%s'", serviceName, serviceName)
}
}
for _, volume := range s.Volumes {
switch volume.Type {
case types.VolumeTypeVolume:
if volume.Source != "" { // non anonymous volumes
if _, ok := project.Volumes[volume.Source]; !ok {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined volume %s", s.Name, volume.Source))
}
}
}
}
for _, secret := range s.Secrets {
if _, ok := project.Secrets[secret.Source]; !ok {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined secret %s", s.Name, secret.Source))
}
}
for _, config := range s.Configs {
if _, ok := project.Configs[config.Source]; !ok {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined config %s", s.Name, config.Source))
}
}
}
return nil
}

View File

@ -0,0 +1,141 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"testing"
"gotest.tools/v3/assert"
"github.com/compose-spec/compose-go/types"
)
func TestValidateAnonymousVolume(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice",
Image: "my/service",
Volumes: []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeVolume,
Target: "/use/local",
},
},
},
}),
}
err := checkConsistency(project)
assert.NilError(t, err)
}
func TestValidateNamedVolume(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice",
Image: "my/service",
Volumes: []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeVolume,
Source: "myVolume",
Target: "/use/local",
},
},
},
}),
}
err := checkConsistency(project)
assert.Error(t, err, `service "myservice" refers to undefined volume myVolume: invalid compose project`)
project.Volumes = types.Volumes(map[string]types.VolumeConfig{
"myVolume": {
Name: "myVolume",
},
})
err = checkConsistency(project)
assert.NilError(t, err)
}
func TestValidateNoBuildNoImage(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice",
},
}),
}
err := checkConsistency(project)
assert.Error(t, err, `service "myservice" has neither an image nor a build context specified: invalid compose project`)
}
func TestValidateNetworkMode(t *testing.T) {
t.Run("network_mode service fail", func(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice1",
Image: "scratch",
},
{
Name: "myservice2",
Image: "scratch",
NetworkMode: "service:myservice1",
},
}),
}
err := checkConsistency(project)
assert.NilError(t, err)
})
t.Run("network_mode service fail", func(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice1",
Image: "scratch",
},
{
Name: "myservice2",
Image: "scratch",
NetworkMode: "service:nonexistentservice",
},
}),
}
err := checkConsistency(project)
assert.Error(t, err, `service "nonexistentservice" not found for network_mode 'service:nonexistentservice'`)
})
t.Run("network_mode container", func(t *testing.T) {
project := &types.Project{
Services: types.Services([]types.ServiceConfig{
{
Name: "myservice1",
ContainerName: "mycontainer_name",
Image: "scratch",
},
{
Name: "myservice2",
Image: "scratch",
NetworkMode: "container:mycontainer_name",
},
}),
}
err := checkConsistency(project)
assert.NilError(t, err)
})
}

154
pkg/third_party/compose-go/loader/volume.go vendored Executable file
View File

@ -0,0 +1,154 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"strings"
"unicode"
"unicode/utf8"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
)
const endOfSpec = rune(0)
// ParseVolume parses a volume spec without any knowledge of the target platform
func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
volume := types.ServiceVolumeConfig{}
switch len(spec) {
case 0:
return volume, errors.New("invalid empty volume spec")
case 1, 2:
volume.Target = spec
volume.Type = string(types.VolumeTypeVolume)
return volume, nil
}
buffer := []rune{}
for _, char := range spec + string(endOfSpec) {
switch {
case isWindowsDrive(buffer, char):
buffer = append(buffer, char)
case char == ':' || char == endOfSpec:
if err := populateFieldFromBuffer(char, buffer, &volume); err != nil {
populateType(&volume)
return volume, errors.Wrapf(err, "invalid spec: %s", spec)
}
buffer = []rune{}
default:
buffer = append(buffer, char)
}
}
populateType(&volume)
return volume, nil
}
func isWindowsDrive(buffer []rune, char rune) bool {
return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0])
}
func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error {
strBuffer := string(buffer)
switch {
case len(buffer) == 0:
return errors.New("empty section between colons")
// Anonymous volume
case volume.Source == "" && char == endOfSpec:
volume.Target = strBuffer
return nil
case volume.Source == "":
volume.Source = strBuffer
return nil
case volume.Target == "":
volume.Target = strBuffer
return nil
case char == ':':
return errors.New("too many colons")
}
for _, option := range strings.Split(strBuffer, ",") {
switch option {
case "ro":
volume.ReadOnly = true
case "rw":
volume.ReadOnly = false
case "nocopy":
volume.Volume = &types.ServiceVolumeVolume{NoCopy: true}
default:
if isBindOption(option) {
volume.Bind = &types.ServiceVolumeBind{Propagation: option}
}
// ignore unknown options
}
}
return nil
}
var Propagations = []string{
types.PropagationRPrivate,
types.PropagationPrivate,
types.PropagationRShared,
types.PropagationShared,
types.PropagationRSlave,
types.PropagationSlave,
}
func isBindOption(option string) bool {
for _, propagation := range Propagations {
if option == propagation {
return true
}
}
return false
}
func populateType(volume *types.ServiceVolumeConfig) {
if isFilePath(volume.Source) {
volume.Type = types.VolumeTypeBind
if volume.Bind == nil {
volume.Bind = &types.ServiceVolumeBind{}
}
// For backward compatibility with docker-compose legacy, using short notation involves
// bind will create missing host path
volume.Bind.CreateHostPath = true
} else {
volume.Type = types.VolumeTypeVolume
if volume.Volume == nil {
volume.Volume = &types.ServiceVolumeVolume{}
}
}
}
func isFilePath(source string) bool {
if source == "" {
return false
}
switch source[0] {
case '.', '/', '~':
return true
}
// windows named pipes
if strings.HasPrefix(source, `\\`) {
return true
}
first, nextIndex := utf8.DecodeRuneInString(source)
return isWindowsDrive([]rune{first}, rune(source[nextIndex]))
}

View File

@ -0,0 +1,250 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"fmt"
"testing"
"github.com/compose-spec/compose-go/types"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestParseVolumeAnonymousVolume(t *testing.T) {
for _, path := range []string{"/path", "/path/foo"} {
volume, err := ParseVolume(path)
expected := types.ServiceVolumeConfig{Type: "volume", Target: path, Volume: &types.ServiceVolumeVolume{}}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeAnonymousVolumeWindows(t *testing.T) {
for _, path := range []string{"C:\\path", "Z:\\path\\foo"} {
volume, err := ParseVolume(path)
expected := types.ServiceVolumeConfig{Type: "volume", Target: path, Volume: &types.ServiceVolumeVolume{}}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeTooManyColons(t *testing.T) {
_, err := ParseVolume("/foo:/foo:ro:foo")
assert.Error(t, err, "invalid spec: /foo:/foo:ro:foo: too many colons")
}
func TestParseVolumeShortVolumes(t *testing.T) {
for _, path := range []string{".", "/a"} {
volume, err := ParseVolume(path)
expected := types.ServiceVolumeConfig{Type: "volume", Target: path}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeMissingSource(t *testing.T) {
for _, spec := range []string{":foo", "/foo::ro"} {
_, err := ParseVolume(spec)
assert.ErrorContains(t, err, "empty section between colons")
}
}
func TestParseVolumeBindMount(t *testing.T) {
for _, path := range []string{"./foo", "~/thing", "../other", "/foo", "/home/user"} {
volume, err := ParseVolume(path + ":/target")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: path,
Target: "/target",
Bind: &types.ServiceVolumeBind{CreateHostPath: true},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeRelativeBindMountWindows(t *testing.T) {
for _, path := range []string{
"./foo",
"~/thing",
"../other",
"D:\\path", "/home/user",
} {
volume, err := ParseVolume(path + ":d:\\target")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: path,
Target: "d:\\target",
Bind: &types.ServiceVolumeBind{CreateHostPath: true},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeWithBindOptions(t *testing.T) {
volume, err := ParseVolume("/source:/target:slave")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: "/source",
Target: "/target",
Bind: &types.ServiceVolumeBind{
CreateHostPath: true,
Propagation: "slave",
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
func TestParseVolumeWithBindOptionsWindows(t *testing.T) {
volume, err := ParseVolume("C:\\source\\foo:D:\\target:ro,rprivate")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: "C:\\source\\foo",
Target: "D:\\target",
ReadOnly: true,
Bind: &types.ServiceVolumeBind{
CreateHostPath: true,
Propagation: "rprivate",
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
func TestParseVolumeWithInvalidVolumeOptions(t *testing.T) {
_, err := ParseVolume("name:/target:bogus")
assert.NilError(t, err)
}
func TestParseVolumeWithVolumeOptions(t *testing.T) {
volume, err := ParseVolume("name:/target:nocopy")
expected := types.ServiceVolumeConfig{
Type: "volume",
Source: "name",
Target: "/target",
Volume: &types.ServiceVolumeVolume{NoCopy: true},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
func TestParseVolumeWithReadOnly(t *testing.T) {
for _, path := range []string{"./foo", "/home/user"} {
volume, err := ParseVolume(path + ":/target:ro")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: path,
Target: "/target",
ReadOnly: true,
Bind: &types.ServiceVolumeBind{CreateHostPath: true},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeWithRW(t *testing.T) {
for _, path := range []string{"./foo", "/home/user"} {
volume, err := ParseVolume(path + ":/target:rw")
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: path,
Target: "/target",
ReadOnly: false,
Bind: &types.ServiceVolumeBind{CreateHostPath: true},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, volume))
}
}
func TestParseVolumeWindowsNamedPipe(t *testing.T) {
volume, err := ParseVolume(`\\.\pipe\docker_engine:\\.\pipe\inside`)
assert.NilError(t, err)
expected := types.ServiceVolumeConfig{
Type: "bind",
Source: `\\.\pipe\docker_engine`,
Target: `\\.\pipe\inside`,
Bind: &types.ServiceVolumeBind{CreateHostPath: true},
}
assert.Check(t, is.DeepEqual(expected, volume))
}
func TestIsFilePath(t *testing.T) {
assert.Check(t, !isFilePath("a界"))
}
// Preserve the test cases for VolumeSplitN
func TestParseVolumeSplitCases(t *testing.T) {
for casenumber, x := range []struct {
input string
n int
expected []string
}{
{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
{`:C:\foo:d:`, -1, nil},
{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
{`d:\`, -1, []string{`d:\`}},
{`d:`, -1, []string{`d:`}},
{`d:\path`, -1, []string{`d:\path`}},
{`d:\path with space`, -1, []string{`d:\path with space`}},
{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
{`name:D:`, -1, []string{`name`, `D:`}},
{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
{`c:\Windows`, -1, []string{`c:\Windows`}},
{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
{``, -1, nil},
{`.`, -1, []string{`.`}},
{`..\`, -1, []string{`..\`}},
{`c:\:..\`, -1, []string{`c:\`, `..\`}},
{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
// Cover directories with one-character name
{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
} {
parsed, _ := ParseVolume(x.input)
expected := len(x.expected) > 1
msg := fmt.Sprintf("Case %d: %s", casenumber, x.input)
assert.Check(t, is.Equal(expected, parsed.Source != ""), msg)
}
}
func TestParseVolumeInvalidEmptySpec(t *testing.T) {
_, err := ParseVolume("")
assert.ErrorContains(t, err, "invalid empty volume spec")
}
func TestParseVolumeInvalidSections(t *testing.T) {
_, err := ParseVolume("/foo::rw")
assert.ErrorContains(t, err, "invalid spec")
}

View File

@ -0,0 +1,82 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// https://github.com/golang/go/blob/master/LICENSE
// This file contains utilities to check for Windows absolute paths on Linux.
// The code in this file was largely copied from the Golang filepath package
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_windows.go#L12-L65
func isSlash(c uint8) bool {
return c == '\\' || c == '/'
}
// isAbs reports whether the path is a Windows absolute path.
func isAbs(path string) (b bool) {
l := volumeNameLen(path)
if l == 0 {
return false
}
path = path[l:]
if path == "" {
return false
}
return isSlash(path[0])
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
// nolint: gocyclo
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}

View File

@ -0,0 +1,78 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// https://github.com/golang/go/blob/master/LICENSE
// The code in this file was copied from the Golang filepath package with some
// small modifications to run it on non-Windows platforms.
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_test.go#L711-L763
import "testing"
type IsAbsTest struct {
path string
isAbs bool
}
var isabstests = []IsAbsTest{
{"", false},
{"/", true},
{"/usr/bin/gcc", true},
{"..", false},
{"/a/../bb", true},
{".", false},
{"./", false},
{"lala", false},
}
var winisabstests = []IsAbsTest{
{`C:\`, true},
{`c\`, false},
{`c::`, false},
{`c:`, false},
{`/`, false},
{`\`, false},
{`\Windows`, false},
{`c:a\b`, false},
{`c:\a\b`, true},
{`c:/a/b`, true},
{`\\host\share\foo`, true},
{`//host/share/foo/bar`, true},
}
func TestIsAbs(t *testing.T) {
tests := winisabstests
// All non-windows tests should fail, because they have no volume letter.
for _, test := range isabstests {
tests = append(tests, IsAbsTest{test.path, false})
}
// All non-windows test should work as intended if prefixed with volume letter.
for _, test := range isabstests {
tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
}
for _, test := range tests {
if r := isAbs(test.path); r != test.isAbs {
t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
}
}
}

View File

@ -0,0 +1,73 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package loader
import (
"github.com/compose-spec/compose-go/types"
)
func withVersionExampleConfig(workingDir, homeDir string) *types.Config {
return &types.Config{
Services: withVersionServices(workingDir, homeDir),
Networks: withVersionNetworks(),
Volumes: withVersionVolumes(),
}
}
func withVersionServices(workingDir, homeDir string) []types.ServiceConfig {
return []types.ServiceConfig{
{
Name: "web",
Build: &types.BuildConfig{
Context: "./Dockerfile",
},
Environment: types.MappingWithEquals{},
Networks: map[string]*types.ServiceNetworkConfig{
"front": nil,
"default": nil,
},
VolumesFrom: []string{"other"},
Scale: 1,
},
{
Name: "other",
Image: "busybox:1.31.0-uclibc",
Command: []string{"top"},
Environment: types.MappingWithEquals{},
Volumes: []types.ServiceVolumeConfig{
{Target: "/data", Type: "volume", Volume: &types.ServiceVolumeVolume{}},
},
Scale: 1,
},
}
}
func withVersionNetworks() map[string]types.NetworkConfig {
return map[string]types.NetworkConfig{
"front": {},
}
}
func withVersionVolumes() map[string]types.VolumeConfig {
return map[string]types.VolumeConfig{
"data": {
Driver: "local",
},
}
}

View File

@ -0,0 +1,813 @@
{
"$schema": "http://json-schema.org/draft/2019-09/schema#",
"id": "compose_spec.json",
"type": "object",
"title": "Compose Specification",
"description": "The Compose file is a YAML file defining a multi-containers based application.",
"properties": {
"version": {
"type": "string",
"description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
},
"configs": {
"id": "#/properties/configs",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/config"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"type": "array", "items": {"type": "string"}},
"network": {"type": "string"},
"target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"isolation": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
},
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"configs": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
}
},
"container_name": {"type": "string"},
"cpu_count": {"type": "integer", "minimum": 0},
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpu_rt_period": {"type": ["number", "string"]},
"cpu_rt_runtime": {"type": ["number", "string"]},
"cpus": {"type": ["number", "string"]},
"cpuset": {"type": "string"},
"credential_spec": {
"type": "object",
"properties": {
"config": {"type": "string"},
"file": {"type": "string"},
"registry": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy", "service_completed_successfully"]
}
},
"required": ["condition"]
}
}
}
]
},
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"init": {"type": "boolean"},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"pid": {"type": ["string", "null"]},
"pids_limit": {"type": ["number", "string"]},
"platform": {"type": "string"},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"host_ip": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"profiles": {"$ref": "#/definitions/list_of_strings"},
"pull_policy": {"type": "string", "enum": [
"always", "never", "if_not_present", "build"
]},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"runtime": {
"type": "string"
},
"scale": {
"type": "integer"
},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type": "object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"},
"create_host_path": {"type": "boolean"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"tmpfs": {
"type": "object",
"properties": {
"size": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
},
"uniqueItems": true
},
"volumes_from": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"working_dir": {"type": "string"}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string", "format": "duration"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"rollback_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpus": {"type": ["number", "string"]},
"memory": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"reservations": {
"type": "object",
"properties": {
"cpus": {"type": ["number", "string"]},
"memory": {"type": "string"},
"generic_resources": {"$ref": "#/definitions/generic_resources"},
"devices": {"$ref": "#/definitions/devices"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}},
"preferences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"spread": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"max_replicas_per_node": {"type": "integer"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"generic_resources": {
"id": "#/definitions/generic_resources",
"type": "array",
"items": {
"type": "object",
"properties": {
"discrete_resource_spec": {
"type": "object",
"properties": {
"kind": {"type": "string"},
"value": {"type": "number"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"devices": {
"id": "#/definitions/devices",
"type": "array",
"items": {
"type": "object",
"properties": {
"capabilities": {"$ref": "#/definitions/list_of_strings"},
"count": {"type": ["string", "integer"]},
"device_ids": {"$ref": "#/definitions/list_of_strings"},
"driver":{"type": "string"},
"options":{"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"additionalProperties": false,
"patternProperties": {"^.+$": {"type": "string"}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
},
"options": {
"type": "object",
"additionalProperties": false,
"patternProperties": {"^.+$": {"type": "string"}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {
"deprecated": true,
"type": "string"
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"internal": {"type": "boolean"},
"enable_ipv6": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {
"deprecated": true,
"type": "string"
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/list_or_dict"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"template_driver": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"config": {
"id": "#/definitions/config",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {
"deprecated": true,
"type": "string"
}
}
},
"labels": {"$ref": "#/definitions/list_or_dict"},
"template_driver": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "boolean", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

169
pkg/third_party/compose-go/schema/schema.go vendored Executable file
View File

@ -0,0 +1,169 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package schema
import (
"fmt"
"strings"
"time"
"github.com/xeipuuv/gojsonschema"
// Enable support for embedded static resources
_ "embed"
)
const (
defaultVersion = "1.0"
versionField = "version"
)
type portsFormatChecker struct{}
func (checker portsFormatChecker) IsFormat(input interface{}) bool {
// TODO: implement this
return true
}
type durationFormatChecker struct{}
func (checker durationFormatChecker) IsFormat(input interface{}) bool {
value, ok := input.(string)
if !ok {
return false
}
_, err := time.ParseDuration(value)
return err == nil
}
func init() {
gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{})
gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
}
// Schema is the compose-spec JSON schema
//go:embed compose-spec.json
var Schema string
// Validate uses the jsonschema to validate the configuration
func Validate(config map[string]interface{}) error {
schemaLoader := gojsonschema.NewStringLoader(Schema)
dataLoader := gojsonschema.NewGoLoader(config)
result, err := gojsonschema.Validate(schemaLoader, dataLoader)
if err != nil {
return err
}
if !result.Valid() {
return toError(result)
}
return nil
}
func toError(result *gojsonschema.Result) error {
err := getMostSpecificError(result.Errors())
return err
}
const (
jsonschemaOneOf = "number_one_of"
jsonschemaAnyOf = "number_any_of"
)
func getDescription(err validationError) string {
switch err.parent.Type() {
case "invalid_type":
if expectedType, ok := err.parent.Details()["expected"].(string); ok {
return fmt.Sprintf("must be a %s", humanReadableType(expectedType))
}
case jsonschemaOneOf, jsonschemaAnyOf:
if err.child == nil {
return err.parent.Description()
}
return err.child.Description()
}
return err.parent.Description()
}
func humanReadableType(definition string) string {
if definition[0:1] == "[" {
allTypes := strings.Split(definition[1:len(definition)-1], ",")
for i, t := range allTypes {
allTypes[i] = humanReadableType(t)
}
return fmt.Sprintf(
"%s or %s",
strings.Join(allTypes[0:len(allTypes)-1], ", "),
allTypes[len(allTypes)-1],
)
}
if definition == "object" {
return "mapping"
}
if definition == "array" {
return "list"
}
return definition
}
type validationError struct {
parent gojsonschema.ResultError
child gojsonschema.ResultError
}
func (err validationError) Error() string {
description := getDescription(err)
return fmt.Sprintf("%s %s", err.parent.Field(), description)
}
func getMostSpecificError(errors []gojsonschema.ResultError) validationError {
mostSpecificError := 0
for i, err := range errors {
if specificity(err) > specificity(errors[mostSpecificError]) {
mostSpecificError = i
continue
}
if specificity(err) == specificity(errors[mostSpecificError]) {
// Invalid type errors win in a tie-breaker for most specific field name
if err.Type() == "invalid_type" && errors[mostSpecificError].Type() != "invalid_type" {
mostSpecificError = i
}
}
}
if mostSpecificError+1 == len(errors) {
return validationError{parent: errors[mostSpecificError]}
}
switch errors[mostSpecificError].Type() {
case "number_one_of", "number_any_of":
return validationError{
parent: errors[mostSpecificError],
child: errors[mostSpecificError+1],
}
default:
return validationError{parent: errors[mostSpecificError]}
}
}
func specificity(err gojsonschema.ResultError) int {
return len(strings.Split(err.Field(), "."))
}

View File

@ -0,0 +1,235 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package schema
import (
"testing"
"gotest.tools/v3/assert"
)
type dict map[string]interface{}
func TestValidate(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
},
},
}
assert.NilError(t, Validate(config))
}
func TestValidateUndefinedTopLevelOption(t *testing.T) {
config := dict{
"helicopters": dict{
"foo": dict{
"image": "busybox",
},
},
}
err := Validate(config)
assert.ErrorContains(t, err, "Additional property helicopters is not allowed")
}
func TestValidateAllowsXTopLevelFields(t *testing.T) {
config := dict{
"x-extra-stuff": dict{},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateAllowsXFields(t *testing.T) {
config := dict{
"services": dict{
"bar": dict{
"x-extra-stuff": dict{},
},
},
"volumes": dict{
"bar": dict{
"x-extra-stuff": dict{},
},
},
"networks": dict{
"bar": dict{
"x-extra-stuff": dict{},
},
},
"configs": dict{
"bar": dict{
"x-extra-stuff": dict{},
},
},
"secrets": dict{
"bar": dict{
"x-extra-stuff": dict{},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateSecretConfigNames(t *testing.T) {
config := dict{
"configs": dict{
"bar": dict{
"name": "foobar",
},
},
"secrets": dict{
"baz": dict{
"name": "foobaz",
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
type array []interface{}
func TestValidatePlacement(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"deploy": dict{
"placement": dict{
"preferences": array{
dict{
"spread": "node.labels.az",
},
},
},
},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateIsolation(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"isolation": "some-isolation-value",
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateRollbackConfig(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"deploy": dict{
"rollback_config": dict{
"parallelism": 1,
},
},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateRollbackConfigWithOrder(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"deploy": dict{
"rollback_config": dict{
"parallelism": 1,
"order": "start-first",
},
},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateRollbackConfigWithUpdateConfig(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"deploy": dict{
"update_config": dict{
"parallelism": 1,
"order": "start-first",
},
"rollback_config": dict{
"parallelism": 1,
"order": "start-first",
},
},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}
func TestValidateRollbackConfigWithUpdateConfigFull(t *testing.T) {
config := dict{
"services": dict{
"foo": dict{
"image": "busybox",
"deploy": dict{
"update_config": dict{
"parallelism": 1,
"order": "start-first",
"delay": "10s",
"failure_action": "pause",
"monitor": "10s",
},
"rollback_config": dict{
"parallelism": 1,
"order": "start-first",
"delay": "10s",
"failure_action": "pause",
"monitor": "10s",
},
},
},
},
}
assert.NilError(t, Validate(config))
assert.NilError(t, Validate(config))
}

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright The Compose Specification Authors.
# 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
# http://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.
set -eu -o pipefail
if ! command -v ltag; then
>&2 echo "ERROR: ltag not found. Install with:"
>&2 echo " go get -u github.com/kunalkushwaha/ltag"
exit 1
fi
BASEPATH="${1-}"
ltag -t "${BASEPATH}scripts/validate/template" --excludes "validate" --check -v

View File

@ -0,0 +1,14 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.

View File

@ -0,0 +1,14 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.

View File

@ -1,7 +1,5 @@
// +build darwin freebsd openbsd
/*
Copyright The containerd Authors.
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,10 +14,3 @@
limitations under the License.
*/
package sysx
import (
"syscall"
)
const ENODATA = syscall.ENOATTR

View File

@ -0,0 +1,14 @@
# Copyright 2020 The Compose Specification Authors.
# 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
# http://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.

View File

@ -0,0 +1,279 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package template
import (
"fmt"
"regexp"
"strings"
"github.com/sirupsen/logrus"
)
var delimiter = "\\$"
var substitutionNamed = "[_a-z][_a-z0-9]*"
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
var patternString = fmt.Sprintf(
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
delimiter, delimiter, substitutionNamed, substitutionBraced,
)
var defaultPattern = regexp.MustCompile(patternString)
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
var DefaultSubstituteFuncs = []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
// InvalidTemplateError is returned when a variable template is not in a valid
// format
type InvalidTemplateError struct {
Template string
}
func (e InvalidTemplateError) Error() string {
return fmt.Sprintf("Invalid template: %#v", e.Template)
}
// Mapping is a user-supplied function which maps from variable names to values.
// Returns the value as a string and a bool indicating whether
// the value is present, to distinguish between an empty string
// and the absence of a value.
type Mapping func(string) (string, bool)
// SubstituteFunc is a user-supplied function that apply substitution.
// Returns the value as a string, a bool indicating if the function could apply
// the substitution and an error.
type SubstituteFunc func(string, Mapping) (string, bool, error)
// SubstituteWith substitute variables in the string with their values.
// It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
matches := pattern.FindStringSubmatch(substring)
groups := matchGroups(matches, pattern)
if escaped := groups["escaped"]; escaped != "" {
return escaped
}
braced := false
substitution := groups["named"]
if substitution == "" {
substitution = groups["braced"]
braced = true
}
if substitution == "" {
err = &InvalidTemplateError{Template: template}
return ""
}
if braced {
for _, f := range subsFuncs {
var (
value string
applied bool
)
value, applied, err = f(substitution, mapping)
if err != nil {
return ""
}
if !applied {
continue
}
return value
}
}
value, ok := mapping(substitution)
if !ok {
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
}
return value
})
return result, err
}
// Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
}
// ExtractVariables returns a map of all the variables defined in the specified
// composefile (dict representation) and their default value if any.
func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable {
if pattern == nil {
pattern = defaultPattern
}
return recurseExtract(configDict, pattern)
}
func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variable {
m := map[string]Variable{}
switch value := value.(type) {
case string:
if values, is := extractVariable(value, pattern); is {
for _, v := range values {
m[v.Name] = v
}
}
case map[string]interface{}:
for _, elem := range value {
submap := recurseExtract(elem, pattern)
for key, value := range submap {
m[key] = value
}
}
case []interface{}:
for _, elem := range value {
if values, is := extractVariable(elem, pattern); is {
for _, v := range values {
m[v.Name] = v
}
}
}
}
return m
}
type Variable struct {
Name string
DefaultValue string
Required bool
}
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) {
sValue, ok := value.(string)
if !ok {
return []Variable{}, false
}
matches := pattern.FindAllStringSubmatch(sValue, -1)
if len(matches) == 0 {
return []Variable{}, false
}
values := []Variable{}
for _, match := range matches {
groups := matchGroups(match, pattern)
if escaped := groups["escaped"]; escaped != "" {
continue
}
val := groups["named"]
if val == "" {
val = groups["braced"]
}
name := val
var defaultValue string
var required bool
switch {
case strings.Contains(val, ":?"):
name, _ = partition(val, ":?")
required = true
case strings.Contains(val, "?"):
name, _ = partition(val, "?")
required = true
case strings.Contains(val, ":-"):
name, defaultValue = partition(val, ":-")
case strings.Contains(val, "-"):
name, defaultValue = partition(val, "-")
}
values = append(values, Variable{
Name: name,
DefaultValue: defaultValue,
Required: required,
})
}
return values, len(values) > 0
}
// Soft default (fall back if unset or empty)
func softDefault(substitution string, mapping Mapping) (string, bool, error) {
sep := ":-"
if !strings.Contains(substitution, sep) {
return "", false, nil
}
name, defaultValue := partition(substitution, sep)
value, ok := mapping(name)
if !ok || value == "" {
return defaultValue, true, nil
}
return value, true, nil
}
// Hard default (fall back if-and-only-if empty)
func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
sep := "-"
if !strings.Contains(substitution, sep) {
return "", false, nil
}
name, defaultValue := partition(substitution, sep)
value, ok := mapping(name)
if !ok {
return defaultValue, true, nil
}
return value, true, nil
}
func requiredNonEmpty(substitution string, mapping Mapping) (string, bool, error) {
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
}
func required(substitution string, mapping Mapping) (string, bool, error) {
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
}
func withRequired(substitution string, mapping Mapping, sep string, valid func(string) bool) (string, bool, error) {
if !strings.Contains(substitution, sep) {
return "", false, nil
}
name, errorMessage := partition(substitution, sep)
value, ok := mapping(name)
if !ok || !valid(value) {
return "", true, &InvalidTemplateError{
Template: fmt.Sprintf("required variable %s is missing a value: %s", name, errorMessage),
}
}
return value, true, nil
}
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
groups := make(map[string]string)
for i, name := range pattern.SubexpNames()[1:] {
groups[name] = matches[i+1]
}
return groups
}
// Split the string at the first occurrence of sep, and return the part before the separator,
// and the part after the separator.
//
// If the separator is not found, return the string itself, followed by an empty string.
func partition(s, sep string) (string, string) {
if strings.Contains(s, sep) {
parts := strings.SplitN(s, sep, 2)
return parts[0], parts[1]
}
return s, ""
}

View File

@ -0,0 +1,307 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package template
import (
"fmt"
"reflect"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
var defaults = map[string]string{
"FOO": "first",
"BAR": "",
}
func defaultMapping(name string) (string, bool) {
val, ok := defaults[name]
return val, ok
}
func TestEscaped(t *testing.T) {
result, err := Substitute("$${foo}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("${foo}", result))
}
func TestSubstituteNoMatch(t *testing.T) {
result, err := Substitute("foo", defaultMapping)
assert.NilError(t, err)
assert.Equal(t, "foo", result)
}
func TestInvalid(t *testing.T) {
invalidTemplates := []string{
"${",
"$}",
"${}",
"${ }",
"${ foo}",
"${foo }",
"${foo!}",
}
for _, template := range invalidTemplates {
_, err := Substitute(template, defaultMapping)
assert.ErrorContains(t, err, "Invalid template")
}
}
// see https://github.com/docker/compose/issues/8601
func TestNonBraced(t *testing.T) {
substituted, err := Substitute("$FOO-bar", defaultMapping)
assert.NilError(t, err)
assert.Equal(t, substituted, "first-bar")
}
func TestNoValueNoDefault(t *testing.T) {
for _, template := range []string{"This ${missing} var", "This ${BAR} var"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("This var", result))
}
}
func TestValueNoDefault(t *testing.T) {
for _, template := range []string{"This $FOO var", "This ${FOO} var"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("This first var", result))
}
}
func TestNoValueWithDefault(t *testing.T) {
for _, template := range []string{"ok ${missing:-def}", "ok ${missing-def}"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok def", result))
}
}
func TestEmptyValueWithSoftDefault(t *testing.T) {
result, err := Substitute("ok ${BAR:-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok def", result))
}
func TestValueWithSoftDefault(t *testing.T) {
result, err := Substitute("ok ${FOO:-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
}
func TestEmptyValueWithHardDefault(t *testing.T) {
result, err := Substitute("ok ${BAR-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))
}
func TestNonAlphanumericDefault(t *testing.T) {
result, err := Substitute("ok ${BAR:-/non:-alphanumeric}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok /non:-alphanumeric", result))
}
func TestMandatoryVariableErrors(t *testing.T) {
testCases := []struct {
template string
expectedError string
}{
{
template: "not ok ${UNSET_VAR:?Mandatory Variable Unset}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
},
{
template: "not ok ${BAR:?Mandatory Variable Empty}",
expectedError: "required variable BAR is missing a value: Mandatory Variable Empty",
},
{
template: "not ok ${UNSET_VAR:?}",
expectedError: "required variable UNSET_VAR is missing a value",
},
{
template: "not ok ${UNSET_VAR?Mandatory Variable Unset}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
},
{
template: "not ok ${UNSET_VAR?}",
expectedError: "required variable UNSET_VAR is missing a value",
},
}
for _, tc := range testCases {
_, err := Substitute(tc.template, defaultMapping)
assert.ErrorContains(t, err, tc.expectedError)
assert.ErrorType(t, err, reflect.TypeOf(&InvalidTemplateError{}))
}
}
func TestDefaultsForMandatoryVariables(t *testing.T) {
testCases := []struct {
template string
expected string
}{
{
template: "ok ${FOO:?err}",
expected: "ok first",
},
{
template: "ok ${FOO?err}",
expected: "ok first",
},
{
template: "ok ${BAR?err}",
expected: "ok ",
},
}
for _, tc := range testCases {
result, err := Substitute(tc.template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, result))
}
}
func TestSubstituteWithCustomFunc(t *testing.T) {
errIsMissing := func(substitution string, mapping Mapping) (string, bool, error) {
value, found := mapping(substitution)
if !found {
return "", true, &InvalidTemplateError{
Template: fmt.Sprintf("required variable %s is missing a value", substitution),
}
}
return value, true, nil
}
result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
assert.Check(t, is.ErrorContains(err, "required variable"))
}
func TestExtractVariables(t *testing.T) {
testCases := []struct {
name string
dict map[string]interface{}
expected map[string]Variable
}{
{
name: "empty",
dict: map[string]interface{}{},
expected: map[string]Variable{},
},
{
name: "no-variables",
dict: map[string]interface{}{
"foo": "bar",
},
expected: map[string]Variable{},
},
{
name: "variable-without-curly-braces",
dict: map[string]interface{}{
"foo": "$bar",
},
expected: map[string]Variable{
"bar": {Name: "bar"},
},
},
{
name: "variable",
dict: map[string]interface{}{
"foo": "${bar}",
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: ""},
},
},
{
name: "required-variable",
dict: map[string]interface{}{
"foo": "${bar?:foo}",
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: "", Required: true},
},
},
{
name: "required-variable2",
dict: map[string]interface{}{
"foo": "${bar?foo}",
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: "", Required: true},
},
},
{
name: "default-variable",
dict: map[string]interface{}{
"foo": "${bar:-foo}",
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: "foo"},
},
},
{
name: "default-variable2",
dict: map[string]interface{}{
"foo": "${bar-foo}",
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: "foo"},
},
},
{
name: "multiple-values",
dict: map[string]interface{}{
"foo": "${bar:-foo}",
"bar": map[string]interface{}{
"foo": "${fruit:-banana}",
"bar": "vegetable",
},
"baz": []interface{}{
"foo",
"$docker:${project:-cli}",
"$toto",
},
},
expected: map[string]Variable{
"bar": {Name: "bar", DefaultValue: "foo"},
"fruit": {Name: "fruit", DefaultValue: "banana"},
"toto": {Name: "toto", DefaultValue: ""},
"docker": {Name: "docker", DefaultValue: ""},
"project": {Name: "project", DefaultValue: "cli"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
actual := ExtractVariables(tc.dict, defaultPattern)
assert.Check(t, is.DeepEqual(actual, tc.expected))
})
}
}

105
pkg/third_party/compose-go/types/config.go vendored Executable file
View File

@ -0,0 +1,105 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
"encoding/json"
"github.com/mitchellh/mapstructure"
)
// ConfigDetails are the details about a group of ConfigFiles
type ConfigDetails struct {
Version string
WorkingDir string
ConfigFiles []ConfigFile
Environment map[string]string
}
// LookupEnv provides a lookup function for environment variables
func (cd ConfigDetails) LookupEnv(key string) (string, bool) {
v, ok := cd.Environment[key]
return v, ok
}
// ConfigFile is a filename and the contents of the file as a Dict
type ConfigFile struct {
// Filename is the name of the yaml configuration file
Filename string
// Content is the raw yaml content. Will be loaded from Filename if not set
Content []byte
// Config if the yaml tree for this config file. Will be parsed from Content if not set
Config map[string]interface{}
}
// Config is a full compose file configuration and model
type Config struct {
Filename string `yaml:"-" json:"-"`
Services Services `json:"services"`
Networks Networks `yaml:",omitempty" json:"networks,omitempty"`
Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"`
Secrets Secrets `yaml:",omitempty" json:"secrets,omitempty"`
Configs Configs `yaml:",omitempty" json:"configs,omitempty"`
Extensions Extensions `yaml:",inline" json:"-"`
}
// Volumes is a map of VolumeConfig
type Volumes map[string]VolumeConfig
// Networks is a map of NetworkConfig
type Networks map[string]NetworkConfig
// Secrets is a map of SecretConfig
type Secrets map[string]SecretConfig
// Configs is a map of ConfigObjConfig
type Configs map[string]ConfigObjConfig
// Extensions is a map of custom extension
type Extensions map[string]interface{}
// MarshalJSON makes Config implement json.Marshaler
func (c Config) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{
"services": c.Services,
}
if len(c.Networks) > 0 {
m["networks"] = c.Networks
}
if len(c.Volumes) > 0 {
m["volumes"] = c.Volumes
}
if len(c.Secrets) > 0 {
m["secrets"] = c.Secrets
}
if len(c.Configs) > 0 {
m["configs"] = c.Configs
}
for k, v := range c.Extensions {
m[k] = v
}
return json.Marshal(m)
}
func (e Extensions) Get(name string, target interface{}) (bool, error) {
if v, ok := e[name]; ok {
err := mapstructure.Decode(v, target)
return true, err
}
return false, nil
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
"testing"
"gotest.tools/v3/assert"
)
func Test_WithServices(t *testing.T) {
p := Project{
Services: append(Services{},
ServiceConfig{
Name: "service_1",
DependsOn: map[string]ServiceDependency{
"service_3": {
Condition: ServiceConditionStarted,
},
},
}, ServiceConfig{
Name: "service_2",
}, ServiceConfig{
Name: "service_3",
DependsOn: map[string]ServiceDependency{
"service_2": {
Condition: ServiceConditionStarted,
},
},
}),
}
order := []string{}
fn := func(service ServiceConfig) error {
order = append(order, service.Name)
return nil
}
err := p.WithServices(nil, fn)
assert.NilError(t, err)
assert.DeepEqual(t, order, []string{"service_2", "service_3", "service_1"})
}

344
pkg/third_party/compose-go/types/project.go vendored Executable file
View File

@ -0,0 +1,344 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
"fmt"
"os"
"path/filepath"
"sort"
"github.com/distribution/distribution/v3/reference"
"github.com/opencontainers/go-digest"
"golang.org/x/sync/errgroup"
)
// Project is the result of loading a set of compose files
type Project struct {
Name string `yaml:"-" json:"-"`
WorkingDir string `yaml:"-" json:"-"`
Services Services `json:"services"`
Networks Networks `yaml:",omitempty" json:"networks,omitempty"`
Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"`
Secrets Secrets `yaml:",omitempty" json:"secrets,omitempty"`
Configs Configs `yaml:",omitempty" json:"configs,omitempty"`
Extensions Extensions `yaml:",inline" json:"-"` // https://github.com/golang/go/issues/6213
ComposeFiles []string `yaml:"-" json:"-"`
Environment map[string]string `yaml:"-" json:"-"`
// DisabledServices track services which have been disable as profile is not active
DisabledServices Services `yaml:"-" json:"-"`
}
// ServiceNames return names for all services in this Compose config
func (p Project) ServiceNames() []string {
names := []string{}
for _, s := range p.Services {
names = append(names, s.Name)
}
sort.Strings(names)
return names
}
// VolumeNames return names for all volumes in this Compose config
func (p Project) VolumeNames() []string {
names := []string{}
for k := range p.Volumes {
names = append(names, k)
}
sort.Strings(names)
return names
}
// NetworkNames return names for all volumes in this Compose config
func (p Project) NetworkNames() []string {
names := []string{}
for k := range p.Networks {
names = append(names, k)
}
sort.Strings(names)
return names
}
// SecretNames return names for all secrets in this Compose config
func (p Project) SecretNames() []string {
names := []string{}
for k := range p.Secrets {
names = append(names, k)
}
sort.Strings(names)
return names
}
// ConfigNames return names for all configs in this Compose config
func (p Project) ConfigNames() []string {
names := []string{}
for k := range p.Configs {
names = append(names, k)
}
sort.Strings(names)
return names
}
// GetServices retrieve services by names, or return all services if no name specified
func (p Project) GetServices(names ...string) (Services, error) {
if len(names) == 0 {
return p.Services, nil
}
services := Services{}
for _, name := range names {
var serviceConfig *ServiceConfig
for _, s := range p.Services {
if s.Name == name {
serviceConfig = &s
break
}
}
if serviceConfig == nil {
return services, fmt.Errorf("no such service: %s", name)
}
services = append(services, *serviceConfig)
}
return services, nil
}
// GetService retrieve a specific service by name
func (p Project) GetService(name string) (ServiceConfig, error) {
services, err := p.GetServices(name)
if err != nil {
return ServiceConfig{}, err
}
if len(services) == 0 {
return ServiceConfig{}, fmt.Errorf("no such service: %s", name)
}
return services[0], nil
}
func (p Project) AllServices() Services {
var all Services
all = append(all, p.Services...)
all = append(all, p.DisabledServices...)
return all
}
type ServiceFunc func(service ServiceConfig) error
// WithServices run ServiceFunc on each service and dependencies in dependency order
func (p Project) WithServices(names []string, fn ServiceFunc) error {
return p.withServices(names, fn, map[string]bool{})
}
func (p Project) withServices(names []string, fn ServiceFunc, done map[string]bool) error {
services, err := p.GetServices(names...)
if err != nil {
return err
}
for _, service := range services {
if done[service.Name] {
continue
}
//KCQ
done[service.Name] = true
dependencies := service.GetDependencies()
if len(dependencies) > 0 {
err := p.withServices(dependencies, fn, done)
if err != nil {
return err
}
}
if err := fn(service); err != nil {
return err
}
}
return nil
}
// RelativePath resolve a relative path based project's working directory
func (p *Project) RelativePath(path string) string {
if path[0] == '~' {
home, _ := os.UserHomeDir()
path = filepath.Join(home, path[1:])
}
if filepath.IsAbs(path) {
return path
}
return filepath.Join(p.WorkingDir, path)
}
// HasProfile return true if service has no profile declared or has at least one profile matching
func (service ServiceConfig) HasProfile(profiles []string) bool {
if len(service.Profiles) == 0 {
return true
}
for _, p := range profiles {
for _, sp := range service.Profiles {
if sp == p {
return true
}
}
}
return false
}
// GetProfiles retrieve the profiles implicitly enabled by explicitly targeting selected services
func (s Services) GetProfiles() []string {
set := map[string]struct{}{}
for _, service := range s {
for _, p := range service.Profiles {
set[p] = struct{}{}
}
}
var profiles []string
for k := range set {
profiles = append(profiles, k)
}
return profiles
}
// ApplyProfiles disables service which don't match selected profiles
func (p *Project) ApplyProfiles(profiles []string) {
for _, p := range profiles {
if p == "*" {
return
}
}
var enabled, disabled Services
for _, service := range p.Services {
if service.HasProfile(profiles) {
enabled = append(enabled, service)
} else {
disabled = append(disabled, service)
}
}
p.Services = enabled
p.DisabledServices = disabled
}
// WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services
func (p *Project) WithoutUnnecessaryResources() {
requiredNetworks := map[string]struct{}{}
requiredVolumes := map[string]struct{}{}
requiredSecrets := map[string]struct{}{}
requiredConfigs := map[string]struct{}{}
for _, s := range p.Services {
for k := range s.Networks {
requiredNetworks[k] = struct{}{}
}
for _, v := range s.Volumes {
if v.Type != VolumeTypeVolume || v.Source == "" {
continue
}
requiredVolumes[v.Source] = struct{}{}
}
for _, v := range s.Secrets {
requiredSecrets[v.Source] = struct{}{}
}
for _, v := range s.Configs {
requiredConfigs[v.Source] = struct{}{}
}
}
networks := Networks{}
for k := range requiredNetworks {
networks[k] = p.Networks[k]
}
p.Networks = networks
volumes := Volumes{}
for k := range requiredVolumes {
volumes[k] = p.Volumes[k]
}
p.Volumes = volumes
secrets := Secrets{}
for k := range requiredSecrets {
secrets[k] = p.Secrets[k]
}
p.Secrets = secrets
configs := Configs{}
for k := range requiredConfigs {
configs[k] = p.Configs[k]
}
p.Configs = configs
}
// ForServices restrict the project model to a subset of services
func (p *Project) ForServices(names []string) error {
if len(names) == 0 {
// All services
return nil
}
set := map[string]struct{}{}
err := p.WithServices(names, func(service ServiceConfig) error {
set[service.Name] = struct{}{}
return nil
})
if err != nil {
return err
}
// Disable all services which are not explicit target or dependencies
var enabled Services
for _, s := range p.Services {
if _, ok := set[s.Name]; ok {
enabled = append(enabled, s)
} else {
p.DisabledServices = append(p.DisabledServices, s)
}
}
p.Services = enabled
return nil
}
// ResolveImages updates services images to include digest computed by a resolver function
func (p *Project) ResolveImages(resolver func(named reference.Named) (digest.Digest, error)) error {
eg := errgroup.Group{}
for i, s := range p.Services {
idx := i
service := s
if service.Image == "" {
continue
}
eg.Go(func() error {
named, err := reference.ParseDockerRef(service.Image)
if err != nil {
return err
}
if _, ok := named.(reference.Canonical); !ok {
// image is named but not digested reference
digest, err := resolver(named)
if err != nil {
return err
}
named, err = reference.WithDigest(named, digest)
if err != nil {
return err
}
}
service.Image = named.String()
p.Services[idx] = service
return nil
})
}
return eg.Wait()
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
_ "crypto/sha256"
"testing"
"github.com/distribution/distribution/v3/reference"
"github.com/opencontainers/go-digest"
"gotest.tools/v3/assert"
)
func Test_ApplyProfiles(t *testing.T) {
p := makeProject()
p.ApplyProfiles([]string{"foo"})
assert.Equal(t, len(p.Services), 2)
assert.Equal(t, p.Services[0].Name, "service_1")
assert.Equal(t, p.Services[1].Name, "service_2")
assert.Equal(t, len(p.DisabledServices), 1)
assert.Equal(t, p.DisabledServices[0].Name, "service_3")
}
func Test_WithoutUnnecessaryResources(t *testing.T) {
p := makeProject()
p.Networks["unused"] = NetworkConfig{}
p.Volumes["unused"] = VolumeConfig{}
p.Secrets["unused"] = SecretConfig{}
p.Configs["unused"] = ConfigObjConfig{}
p.WithoutUnnecessaryResources()
if _, ok := p.Networks["unused"]; ok {
t.Fail()
}
if _, ok := p.Volumes["unused"]; ok {
t.Fail()
}
if _, ok := p.Secrets["unused"]; ok {
t.Fail()
}
if _, ok := p.Configs["unused"]; ok {
t.Fail()
}
}
func Test_NoProfiles(t *testing.T) {
p := makeProject()
p.ApplyProfiles(nil)
assert.Equal(t, len(p.Services), 1)
assert.Equal(t, len(p.DisabledServices), 2)
assert.Equal(t, p.Services[0].Name, "service_1")
}
func Test_ServiceProfiles(t *testing.T) {
p := makeProject()
services, err := p.GetServices("service_1", "service_2")
assert.NilError(t, err)
profiles := services.GetProfiles()
assert.Equal(t, len(profiles), 1)
assert.Equal(t, profiles[0], "foo")
}
func Test_ForServices(t *testing.T) {
p := makeProject()
err := p.ForServices([]string{"service_2"})
assert.NilError(t, err)
assert.Equal(t, len(p.DisabledServices), 1)
assert.Equal(t, p.DisabledServices[0].Name, "service_3")
}
func makeProject() Project {
return Project{
Services: append(Services{},
ServiceConfig{
Name: "service_1",
}, ServiceConfig{
Name: "service_2",
Profiles: []string{"foo"},
DependsOn: map[string]ServiceDependency{"service_1": {}},
}, ServiceConfig{
Name: "service_3",
Profiles: []string{"bar"},
}),
Networks: Networks{},
Volumes: Volumes{},
Secrets: Secrets{},
Configs: Configs{},
}
}
func Test_ResolveImages(t *testing.T) {
p := makeProject()
resolver := func(named reference.Named) (digest.Digest, error) {
return "sha256:1234567890123456789012345678901234567890123456789012345678901234", nil
}
tests := []struct {
image string
resolved string
}{
{
image: "com.acme/long:tag",
resolved: "com.acme/long:tag@sha256:1234567890123456789012345678901234567890123456789012345678901234",
},
{
image: "com.acme/long",
resolved: "com.acme/long:latest@sha256:1234567890123456789012345678901234567890123456789012345678901234",
},
{
image: "short",
resolved: "docker.io/library/short:latest@sha256:1234567890123456789012345678901234567890123456789012345678901234",
},
{
image: "com.acme/digested:tag@sha256:1234567890123456789012345678901234567890123456789012345678901234",
resolved: "com.acme/digested@sha256:1234567890123456789012345678901234567890123456789012345678901234",
},
{
image: "com.acme/digested@sha256:1234567890123456789012345678901234567890123456789012345678901234",
resolved: "com.acme/digested@sha256:1234567890123456789012345678901234567890123456789012345678901234",
},
}
for _, test := range tests {
p.Services[0].Image = test.image
err := p.ResolveImages(resolver)
assert.NilError(t, err)
assert.Equal(t, p.Services[0].Image, test.resolved)
}
}

849
pkg/third_party/compose-go/types/types.go vendored Executable file
View File

@ -0,0 +1,849 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"github.com/docker/go-connections/nat"
)
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
type Duration time.Duration
func (d Duration) String() string {
return time.Duration(d).String()
}
// ConvertDurationPtr converts a typedefined Duration pointer to a time.Duration pointer with the same value.
func ConvertDurationPtr(d *Duration) *time.Duration {
if d == nil {
return nil
}
res := time.Duration(*d)
return &res
}
// MarshalJSON makes Duration implement json.Marshaler
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// MarshalYAML makes Duration implement yaml.Marshaler
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
timeDuration, err := time.ParseDuration(s)
if err != nil {
return err
}
*d = Duration(timeDuration)
return nil
}
// Services is a list of ServiceConfig
type Services []ServiceConfig
// MarshalYAML makes Services implement yaml.Marshaller
func (s Services) MarshalYAML() (interface{}, error) {
services := map[string]ServiceConfig{}
for _, service := range s {
services[service.Name] = service
}
return services, nil
}
// MarshalJSON makes Services implement json.Marshaler
func (s Services) MarshalJSON() ([]byte, error) {
data, err := s.MarshalYAML()
if err != nil {
return nil, err
}
return json.MarshalIndent(data, "", " ")
}
// ServiceConfig is the configuration of one service
type ServiceConfig struct {
Name string `yaml:"-" json:"-"`
Profiles []string `mapstructure:"profiles" yaml:"profiles,omitempty" json:"profiles,omitempty"`
Build *BuildConfig `yaml:",omitempty" json:"build,omitempty"`
BlkioConfig *BlkioConfig `yaml:",omitempty" json:"blkio_config,omitempty"`
CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"`
CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"`
CPUCount int64 `mapstructure:"cpu_count" yaml:"cpu_count,omitempty" json:"cpu_count,omitempty"`
CPUPercent float32 `mapstructure:"cpu_percent" yaml:"cpu_percent,omitempty" json:"cpu_percent,omitempty"`
CPUPeriod int64 `mapstructure:"cpu_period" yaml:"cpu_period,omitempty" json:"cpu_period,omitempty"`
CPUQuota int64 `mapstructure:"cpu_quota" yaml:"cpu_quota,omitempty" json:"cpu_quota,omitempty"`
CPURTPeriod int64 `mapstructure:"cpu_rt_period" yaml:"cpu_rt_period,omitempty" json:"cpu_rt_period,omitempty"`
CPURTRuntime int64 `mapstructure:"cpu_rt_runtime" yaml:"cpu_rt_runtime,omitempty" json:"cpu_rt_runtime,omitempty"`
CPUS float32 `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
CPUSet string `mapstructure:"cpuset" yaml:"cpuset,omitempty" json:"cpuset,omitempty"`
CPUShares int64 `mapstructure:"cpu_shares" yaml:"cpu_shares,omitempty" json:"cpu_shares,omitempty"`
Command ShellCommand `yaml:",omitempty" json:"command,omitempty"`
Configs []ServiceConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"`
ContainerName string `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"`
CredentialSpec *CredentialSpecConfig `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
DependsOn DependsOnConfig `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
Deploy *DeployConfig `yaml:",omitempty" json:"deploy,omitempty"`
Devices []string `yaml:",omitempty" json:"devices,omitempty"`
DNS StringList `yaml:",omitempty" json:"dns,omitempty"`
DNSOpts []string `mapstructure:"dns_opt" yaml:"dns_opt,omitempty" json:"dns_opt,omitempty"`
DNSSearch StringList `mapstructure:"dns_search" yaml:"dns_search,omitempty" json:"dns_search,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
DomainName string `mapstructure:"domainname" yaml:"domainname,omitempty" json:"domainname,omitempty"`
Entrypoint ShellCommand `yaml:",omitempty" json:"entrypoint,omitempty"`
Environment MappingWithEquals `yaml:",omitempty" json:"environment,omitempty"`
EnvFile StringList `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"`
Expose StringOrNumberList `yaml:",omitempty" json:"expose,omitempty"`
Extends ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"`
ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
GroupAdd []string `mapstructure:"group_app" yaml:"group_add,omitempty" json:"group_add,omitempty"`
Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"`
Image string `yaml:",omitempty" json:"image,omitempty"`
Init *bool `yaml:",omitempty" json:"init,omitempty"`
Ipc string `yaml:",omitempty" json:"ipc,omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty" json:"isolation,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Links []string `yaml:",omitempty" json:"links,omitempty"`
Logging *LoggingConfig `yaml:",omitempty" json:"logging,omitempty"`
LogDriver string `mapstructure:"log_driver" yaml:"log_driver,omitempty" json:"log_driver,omitempty"`
LogOpt map[string]string `mapstructure:"log_opt" yaml:"log_opt,omitempty" json:"log_opt,omitempty"`
MemLimit UnitBytes `mapstructure:"mem_limit" yaml:"mem_limit,omitempty" json:"mem_limit,omitempty"`
MemReservation UnitBytes `mapstructure:"mem_reservation" yaml:"mem_reservation,omitempty" json:"mem_reservation,omitempty"`
MemSwapLimit UnitBytes `mapstructure:"memswap_limit" yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"`
MemSwappiness UnitBytes `mapstructure:"mem_swappiness" yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"`
MacAddress string `mapstructure:"mac_address" yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
Net string `yaml:"net,omitempty" json:"net,omitempty"`
NetworkMode string `mapstructure:"network_mode" yaml:"network_mode,omitempty" json:"network_mode,omitempty"`
Networks map[string]*ServiceNetworkConfig `yaml:",omitempty" json:"networks,omitempty"`
OomKillDisable bool `mapstructure:"oom_kill_disable" yaml:"oom_kill_disable,omitempty" json:"oom_kill_disable,omitempty"`
OomScoreAdj int64 `mapstructure:"oom_score_adj" yaml:"oom_score_adj,omitempty" json:"oom_score_adj,omitempty"`
Pid string `yaml:",omitempty" json:"pid,omitempty"`
PidsLimit int64 `mapstructure:"pids_limit" yaml:"pids_limit,omitempty" json:"pids_limit,omitempty"`
Platform string `yaml:",omitempty" json:"platform,omitempty"`
Ports []ServicePortConfig `yaml:",omitempty" json:"ports,omitempty"`
Privileged bool `yaml:",omitempty" json:"privileged,omitempty"`
PullPolicy string `mapstructure:"pull_policy" yaml:"pull_policy,omitempty" json:"pull_policy,omitempty"`
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
Restart string `yaml:",omitempty" json:"restart,omitempty"`
Runtime string `yaml:",omitempty" json:"runtime,omitempty"`
Scale int `yaml:"-" json:"-"`
Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"`
SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"`
ShmSize UnitBytes `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"`
StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
StopGracePeriod *Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
Sysctls Mapping `yaml:",omitempty" json:"sysctls,omitempty"`
Tmpfs StringList `yaml:",omitempty" json:"tmpfs,omitempty"`
Tty bool `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"`
Ulimits map[string]*UlimitsConfig `yaml:",omitempty" json:"ulimits,omitempty"`
User string `yaml:",omitempty" json:"user,omitempty"`
UserNSMode string `mapstructure:"userns_mode" yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"`
Uts string `yaml:"uts,omitempty" json:"uts,omitempty"`
VolumeDriver string `mapstructure:"volume_driver" yaml:"volume_driver,omitempty" json:"volume_driver,omitempty"`
Volumes []ServiceVolumeConfig `yaml:",omitempty" json:"volumes,omitempty"`
VolumesFrom []string `mapstructure:"volumes_from" yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// NetworksByPriority return the service networks IDs sorted according to Priority
func (s *ServiceConfig) NetworksByPriority() []string {
type key struct {
name string
priority int
}
var keys []key
for k, v := range s.Networks {
priority := 0
if v != nil {
priority = v.Priority
}
keys = append(keys, key{
name: k,
priority: priority,
})
}
sort.Slice(keys, func(i, j int) bool {
return keys[i].priority > keys[j].priority
})
var sorted []string
for _, k := range keys {
sorted = append(sorted, k.name)
}
return sorted
}
const (
//PullPolicyAlways always pull images
PullPolicyAlways = "always"
//PullPolicyNever never pull images
PullPolicyNever = "never"
//PullPolicyIfNotPresent pull missing images
PullPolicyIfNotPresent = "if_not_present"
//PullPolicyIfNotPresent pull missing images
PullPolicyMissing = "missing"
//PullPolicyBuild force building images
PullPolicyBuild = "build"
)
const (
//RestartPolicyAlways always restart the container if it stops
RestartPolicyAlways = "always"
//RestartPolicyOnFailure restart the container if it exits due to an error
RestartPolicyOnFailure = "on-failure"
//RestartPolicyNo do not automatically restart the container
RestartPolicyNo = "no"
//RestartPolicyUnlessStopped always restart the container unless the container is stopped (manually or otherwise)
RestartPolicyUnlessStopped = "unless-stopped"
)
const (
// ServicePrefix is the prefix for references pointing to a service
ServicePrefix = "service:"
// ContainerPrefix is the prefix for references pointing to a container
ContainerPrefix = "container:"
// NetworkModeServicePrefix is the prefix for network_mode pointing to a service
// Deprecated prefer ServicePrefix
NetworkModeServicePrefix = ServicePrefix
// NetworkModeContainerPrefix is the prefix for network_mode pointing to a container
// Deprecated prefer ContainerPrefix
NetworkModeContainerPrefix = ContainerPrefix
)
// GetDependencies retrieve all services this service depends on
func (s ServiceConfig) GetDependencies() []string {
dependencies := make(set)
for dependency := range s.DependsOn {
dependencies.append(dependency)
}
for _, link := range s.Links {
parts := strings.Split(link, ":")
if len(parts) == 2 {
dependencies.append(parts[0])
} else {
dependencies.append(link)
}
}
if strings.HasPrefix(s.NetworkMode, ServicePrefix) {
dependencies.append(s.NetworkMode[len(ServicePrefix):])
}
if strings.HasPrefix(s.Ipc, ServicePrefix) {
dependencies.append(s.Ipc[len(ServicePrefix):])
}
if strings.HasPrefix(s.Pid, ServicePrefix) {
dependencies.append(s.Pid[len(ServicePrefix):])
}
for _, vol := range s.VolumesFrom {
if !strings.HasPrefix(s.Pid, ContainerPrefix) {
dependencies.append(vol)
}
}
return dependencies.toSlice()
}
type set map[string]struct{}
func (s set) append(strings ...string) {
for _, str := range strings {
s[str] = struct{}{}
}
}
func (s set) toSlice() []string {
slice := make([]string, 0, len(s))
for v := range s {
slice = append(slice, v)
}
return slice
}
// BuildConfig is a type for build
type BuildConfig struct {
Context string `yaml:",omitempty" json:"context,omitempty"`
Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
Isolation string `yaml:",omitempty" json:"isolation,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"`
Target string `yaml:",omitempty" json:"target,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// BlkioConfig define blkio config
type BlkioConfig struct {
Weight uint16 `yaml:",omitempty" json:"weight,omitempty"`
WeightDevice []WeightDevice `yaml:",omitempty" json:"weight_device,omitempty"`
DeviceReadBps []ThrottleDevice `yaml:",omitempty" json:"device_read_bps,omitempty"`
DeviceReadIOps []ThrottleDevice `yaml:",omitempty" json:"device_read_iops,omitempty"`
DeviceWriteBps []ThrottleDevice `yaml:",omitempty" json:"device_write_bps,omitempty"`
DeviceWriteIOps []ThrottleDevice `yaml:",omitempty" json:"device_write_iops,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// WeightDevice is a structure that holds device:weight pair
type WeightDevice struct {
Path string
Weight uint16
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ThrottleDevice is a structure that holds device:rate_per_second pair
type ThrottleDevice struct {
Path string
Rate uint64
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ShellCommand is a string or list of string args
type ShellCommand []string
// StringList is a type for fields that can be a string or list of strings
type StringList []string
// StringOrNumberList is a type for fields that can be a list of strings or
// numbers
type StringOrNumberList []string
// MappingWithEquals is a mapping type that can be converted from a list of
// key[=value] strings.
// For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`.
// For the key without value (`key`), the mapped value is set to nil.
type MappingWithEquals map[string]*string
// NewMappingWithEquals build a new Mapping from a set of KEY=VALUE strings
func NewMappingWithEquals(values []string) MappingWithEquals {
mapping := MappingWithEquals{}
for _, env := range values {
tokens := strings.SplitN(env, "=", 2)
if len(tokens) > 1 {
mapping[tokens[0]] = &tokens[1]
} else {
mapping[env] = nil
}
}
return mapping
}
// OverrideBy update MappingWithEquals with values from another MappingWithEquals
func (e MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals {
for k, v := range other {
e[k] = v
}
return e
}
// Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`)
func (e MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals {
for k, v := range e {
if v == nil {
if value, ok := lookupFn(k); ok {
e[k] = &value
}
}
}
return e
}
// RemoveEmpty excludes keys that are not associated with a value
func (e MappingWithEquals) RemoveEmpty() MappingWithEquals {
for k, v := range e {
if v == nil {
delete(e, k)
}
}
return e
}
// Mapping is a mapping type that can be converted from a list of
// key[=value] strings.
// For the key with an empty value (`key=`), or key without value (`key`), the
// mapped value is set to an empty string `""`.
type Mapping map[string]string
// NewMapping build a new Mapping from a set of KEY=VALUE strings
func NewMapping(values []string) Mapping {
mapping := Mapping{}
for _, value := range values {
parts := strings.SplitN(value, "=", 2)
key := parts[0]
switch {
case len(parts) == 1:
mapping[key] = ""
default:
mapping[key] = parts[1]
}
}
return mapping
}
// Labels is a mapping type for labels
type Labels map[string]string
func (l Labels) Add(key, value string) Labels {
if l == nil {
l = Labels{}
}
l[key] = value
return l
}
// MappingWithColon is a mapping type that can be converted from a list of
// 'key: value' strings
type MappingWithColon map[string]string
// HostsList is a list of colon-separated host-ip mappings
type HostsList []string
// LoggingConfig the logging configuration for a service
type LoggingConfig struct {
Driver string `yaml:",omitempty" json:"driver,omitempty"`
Options map[string]string `yaml:",omitempty" json:"options,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// DeployConfig the deployment configuration for a service
type DeployConfig struct {
Mode string `yaml:",omitempty" json:"mode,omitempty"`
Replicas *uint64 `yaml:",omitempty" json:"replicas,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
UpdateConfig *UpdateConfig `mapstructure:"update_config" yaml:"update_config,omitempty" json:"update_config,omitempty"`
RollbackConfig *UpdateConfig `mapstructure:"rollback_config" yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"`
Resources Resources `yaml:",omitempty" json:"resources,omitempty"`
RestartPolicy *RestartPolicy `mapstructure:"restart_policy" yaml:"restart_policy,omitempty" json:"restart_policy,omitempty"`
Placement Placement `yaml:",omitempty" json:"placement,omitempty"`
EndpointMode string `mapstructure:"endpoint_mode" yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// HealthCheckConfig the healthcheck configuration for a service
type HealthCheckConfig struct {
Test HealthCheckTest `yaml:",omitempty" json:"test,omitempty"`
Timeout *Duration `yaml:",omitempty" json:"timeout,omitempty"`
Interval *Duration `yaml:",omitempty" json:"interval,omitempty"`
Retries *uint64 `yaml:",omitempty" json:"retries,omitempty"`
StartPeriod *Duration `mapstructure:"start_period" yaml:"start_period,omitempty" json:"start_period,omitempty"`
Disable bool `yaml:",omitempty" json:"disable,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// HealthCheckTest is the command run to test the health of a service
type HealthCheckTest []string
// UpdateConfig the service update configuration
type UpdateConfig struct {
Parallelism *uint64 `yaml:",omitempty" json:"parallelism,omitempty"`
Delay Duration `yaml:",omitempty" json:"delay,omitempty"`
FailureAction string `mapstructure:"failure_action" yaml:"failure_action,omitempty" json:"failure_action,omitempty"`
Monitor Duration `yaml:",omitempty" json:"monitor,omitempty"`
MaxFailureRatio float32 `mapstructure:"max_failure_ratio" yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"`
Order string `yaml:",omitempty" json:"order,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// Resources the resource limits and reservations
type Resources struct {
Limits *Resource `yaml:",omitempty" json:"limits,omitempty"`
Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// Resource is a resource to be limited or reserved
type Resource struct {
// TODO: types to convert from units and ratios
NanoCPUs string `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
MemoryBytes UnitBytes `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"`
Devices []DeviceRequest `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"`
GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
type DeviceRequest struct {
Capabilities []string `mapstructure:"capabilities" yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
Driver string `mapstructure:"driver" yaml:"driver,omitempty" json:"driver,omitempty"`
Count int64 `mapstructure:"count" yaml:"count,omitempty" json:"count,omitempty"`
IDs []string `mapstructure:"device_ids" yaml:"device_ids,omitempty" json:"device_ids,omitempty"`
}
// GenericResource represents a "user defined" resource which can
// only be an integer (e.g: SSD=3) for a service
type GenericResource struct {
DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec" yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// DiscreteGenericResource represents a "user defined" resource which is defined
// as an integer
// "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...)
// Value is used to count the resource (SSD=5, HDD=3, ...)
type DiscreteGenericResource struct {
Kind string `json:"kind"`
Value int64 `json:"value"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// UnitBytes is the bytes type
type UnitBytes int64
// MarshalYAML makes UnitBytes implement yaml.Marshaller
func (u UnitBytes) MarshalYAML() (interface{}, error) {
return fmt.Sprintf("%d", u), nil
}
// MarshalJSON makes UnitBytes implement json.Marshaler
func (u UnitBytes) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d"`, u)), nil
}
// RestartPolicy the service restart policy
type RestartPolicy struct {
Condition string `yaml:",omitempty" json:"condition,omitempty"`
Delay *Duration `yaml:",omitempty" json:"delay,omitempty"`
MaxAttempts *uint64 `mapstructure:"max_attempts" yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
Window *Duration `yaml:",omitempty" json:"window,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// Placement constraints for the service
type Placement struct {
Constraints []string `yaml:",omitempty" json:"constraints,omitempty"`
Preferences []PlacementPreferences `yaml:",omitempty" json:"preferences,omitempty"`
MaxReplicas uint64 `mapstructure:"max_replicas_per_node" yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// PlacementPreferences is the preferences for a service placement
type PlacementPreferences struct {
Spread string `yaml:",omitempty" json:"spread,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ServiceNetworkConfig is the network configuration for a service
type ServiceNetworkConfig struct {
Priority int `yaml:",omitempty" json:"priotirt,omitempty"`
Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ServicePortConfig is the port configuration for a service
type ServicePortConfig struct {
Mode string `yaml:",omitempty" json:"mode,omitempty"`
HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
Target uint32 `yaml:",omitempty" json:"target,omitempty"`
Published uint32 `yaml:",omitempty" json:"published,omitempty"`
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ParsePortConfig parse short syntax for service port configuration
func ParsePortConfig(value string) ([]ServicePortConfig, error) {
var portConfigs []ServicePortConfig
ports, portBindings, err := nat.ParsePortSpecs([]string{value})
if err != nil {
return nil, err
}
// We need to sort the key of the ports to make sure it is consistent
keys := []string{}
for port := range ports {
keys = append(keys, string(port))
}
sort.Strings(keys)
for _, key := range keys {
port := nat.Port(key)
converted, err := convertPortToPortConfig(port, portBindings)
if err != nil {
return nil, err
}
portConfigs = append(portConfigs, converted...)
}
return portConfigs, nil
}
func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
portConfigs := []ServicePortConfig{}
for _, binding := range portBindings[port] {
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
if err != nil && binding.HostPort != "" {
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
}
for i := startHostPort; i <= endHostPort; i++ {
portConfigs = append(portConfigs, ServicePortConfig{
HostIP: binding.HostIP,
Protocol: strings.ToLower(port.Proto()),
Target: uint32(port.Int()),
Published: uint32(i),
Mode: "ingress",
})
}
}
return portConfigs, nil
}
// ServiceVolumeConfig are references to a volume used by a service
type ServiceVolumeConfig struct {
Type string `yaml:",omitempty" json:"type,omitempty"`
Source string `yaml:",omitempty" json:"source,omitempty"`
Target string `yaml:",omitempty" json:"target,omitempty"`
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
Consistency string `yaml:",omitempty" json:"consistency,omitempty"`
Bind *ServiceVolumeBind `yaml:",omitempty" json:"bind,omitempty"`
Volume *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"`
Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty" json:"tmpfs,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
const (
// TypeBind is the type for mounting host dir
VolumeTypeBind = "bind"
// TypeVolume is the type for remote storage volumes
VolumeTypeVolume = "volume"
// TypeTmpfs is the type for mounting tmpfs
VolumeTypeTmpfs = "tmpfs"
// TypeNamedPipe is the type for mounting Windows named pipes
VolumeTypeNamedPipe = "npipe"
)
// ServiceVolumeBind are options for a service volume of type bind
type ServiceVolumeBind struct {
Propagation string `yaml:",omitempty" json:"propagation,omitempty"`
CreateHostPath bool `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// Propagation represents the propagation of a mount.
const (
// PropagationRPrivate RPRIVATE
PropagationRPrivate string = "rprivate"
// PropagationPrivate PRIVATE
PropagationPrivate string = "private"
// PropagationRShared RSHARED
PropagationRShared string = "rshared"
// PropagationShared SHARED
PropagationShared string = "shared"
// PropagationRSlave RSLAVE
PropagationRSlave string = "rslave"
// PropagationSlave SLAVE
PropagationSlave string = "slave"
)
// ServiceVolumeVolume are options for a service volume of type volume
type ServiceVolumeVolume struct {
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ServiceVolumeTmpfs are options for a service volume of type tmpfs
type ServiceVolumeTmpfs struct {
Size int64 `yaml:",omitempty" json:"size,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// FileReferenceConfig for a reference to a swarm file object
type FileReferenceConfig struct {
Source string `yaml:",omitempty" json:"source,omitempty"`
Target string `yaml:",omitempty" json:"target,omitempty"`
UID string `yaml:",omitempty" json:"uid,omitempty"`
GID string `yaml:",omitempty" json:"gid,omitempty"`
Mode *uint32 `yaml:",omitempty" json:"mode,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// ServiceConfigObjConfig is the config obj configuration for a service
type ServiceConfigObjConfig FileReferenceConfig
// ServiceSecretConfig is the secret configuration for a service
type ServiceSecretConfig FileReferenceConfig
// UlimitsConfig the ulimit configuration
type UlimitsConfig struct {
Single int `yaml:",omitempty" json:"single,omitempty"`
Soft int `yaml:",omitempty" json:"soft,omitempty"`
Hard int `yaml:",omitempty" json:"hard,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// MarshalYAML makes UlimitsConfig implement yaml.Marshaller
func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
if u.Single != 0 {
return u.Single, nil
}
return u, nil
}
// MarshalJSON makes UlimitsConfig implement json.Marshaller
func (u *UlimitsConfig) MarshalJSON() ([]byte, error) {
if u.Single != 0 {
return json.Marshal(u.Single)
}
// Pass as a value to avoid re-entering this method and use the default implementation
return json.Marshal(*u)
}
// NetworkConfig for a network
type NetworkConfig struct {
Name string `yaml:",omitempty" json:"name,omitempty"`
Driver string `yaml:",omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
Ipam IPAMConfig `yaml:",omitempty" json:"ipam,omitempty"`
External External `yaml:",omitempty" json:"external,omitempty"`
Internal bool `yaml:",omitempty" json:"internal,omitempty"`
Attachable bool `yaml:",omitempty" json:"attachable,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// IPAMConfig for a network
type IPAMConfig struct {
Driver string `yaml:",omitempty" json:"driver,omitempty"`
Config []*IPAMPool `yaml:",omitempty" json:"config,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// IPAMPool for a network
type IPAMPool struct {
Subnet string `yaml:",omitempty" json:"subnet,omitempty"`
Gateway string `yaml:",omitempty" json:"gateway,omitempty"`
IPRange string `mapstructure:"ip_range" yaml:"ip_range,omitempty" json:"ip_range,omitempty"`
AuxiliaryAddresses map[string]string `mapstructure:"aux_addresses" yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// VolumeConfig for a volume
type VolumeConfig struct {
Name string `yaml:",omitempty" json:"name,omitempty"`
Driver string `yaml:",omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
External External `yaml:",omitempty" json:"external,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// External identifies a Volume or Network as a reference to a resource that is
// not managed, and should already exist.
// External.name is deprecated and replaced by Volume.name
type External struct {
Name string `yaml:",omitempty" json:"name,omitempty"`
External bool `yaml:",omitempty" json:"external,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// MarshalYAML makes External implement yaml.Marshaller
func (e External) MarshalYAML() (interface{}, error) {
if e.Name == "" {
return e.External, nil
}
return External{Name: e.Name}, nil
}
// MarshalJSON makes External implement json.Marshaller
func (e External) MarshalJSON() ([]byte, error) {
if e.Name == "" {
return []byte(fmt.Sprintf("%v", e.External)), nil
}
return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil
}
// CredentialSpecConfig for credential spec on Windows
type CredentialSpecConfig struct {
Config string `yaml:",omitempty" json:"config,omitempty"` // Config was added in API v1.40
File string `yaml:",omitempty" json:"file,omitempty"`
Registry string `yaml:",omitempty" json:"registry,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// FileObjectConfig is a config type for a file used by a service
type FileObjectConfig struct {
Name string `yaml:",omitempty" json:"name,omitempty"`
File string `yaml:",omitempty" json:"file,omitempty"`
External External `yaml:",omitempty" json:"external,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Driver string `yaml:",omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
TemplateDriver string `mapstructure:"template_driver" yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
const (
// ServiceConditionCompletedSuccessfully is the type for waiting until a service has completed successfully (exit code 0).
ServiceConditionCompletedSuccessfully = "service_completed_successfully"
// ServiceConditionHealthy is the type for waiting until a service is healthy.
ServiceConditionHealthy = "service_healthy"
// ServiceConditionStarted is the type for waiting until a service has started (default).
ServiceConditionStarted = "service_started"
)
type DependsOnConfig map[string]ServiceDependency
type ServiceDependency struct {
Condition string `yaml:",omitempty" json:"condition,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
type ExtendsConfig MappingWithEquals
// SecretConfig for a secret
type SecretConfig FileObjectConfig
// ConfigObjConfig is the config for the swarm "Config" object
type ConfigObjConfig FileObjectConfig

274
pkg/third_party/compose-go/types/types_test.go vendored Executable file
View File

@ -0,0 +1,274 @@
/*
Copyright 2020 The Compose Specification Authors.
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
http://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.
*/
package types
import (
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestParsePortConfig(t *testing.T) {
testCases := []struct {
value string
expectedError string
expected []ServicePortConfig
}{
{
value: "80",
expected: []ServicePortConfig{
{
Protocol: "tcp",
Target: 80,
Mode: "ingress",
},
},
},
{
value: "80:8080",
expected: []ServicePortConfig{
{
Protocol: "tcp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
},
},
{
value: "8080:80/tcp",
expected: []ServicePortConfig{
{
Protocol: "tcp",
Target: 80,
Published: 8080,
Mode: "ingress",
},
},
},
{
value: "80:8080/udp",
expected: []ServicePortConfig{
{
Protocol: "udp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
},
},
{
value: "80-81:8080-8081/tcp",
expected: []ServicePortConfig{
{
Protocol: "tcp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
{
Protocol: "tcp",
Target: 8081,
Published: 81,
Mode: "ingress",
},
},
},
{
value: "80-82:8080-8082/udp",
expected: []ServicePortConfig{
{
Protocol: "udp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
{
Protocol: "udp",
Target: 8081,
Published: 81,
Mode: "ingress",
},
{
Protocol: "udp",
Target: 8082,
Published: 82,
Mode: "ingress",
},
},
},
{
value: "80-82:8080/udp",
expected: []ServicePortConfig{
{
Protocol: "udp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
{
Protocol: "udp",
Target: 8080,
Published: 81,
Mode: "ingress",
},
{
Protocol: "udp",
Target: 8080,
Published: 82,
Mode: "ingress",
},
},
},
{
value: "80-80:8080/tcp",
expected: []ServicePortConfig{
{
Protocol: "tcp",
Target: 8080,
Published: 80,
Mode: "ingress",
},
},
},
{
value: "9999999",
expectedError: "Invalid containerPort: 9999999",
},
{
value: "80/xyz",
expectedError: "Invalid proto: xyz",
},
{
value: "tcp",
expectedError: "Invalid containerPort: tcp",
},
{
value: "udp",
expectedError: "Invalid containerPort: udp",
},
{
value: "",
expectedError: "No port specified: <empty>",
},
{
value: "1.1.1.1:80:80",
expected: []ServicePortConfig{
{
HostIP: "1.1.1.1",
Protocol: "tcp",
Target: 80,
Published: 80,
Mode: "ingress",
},
},
},
}
for _, tc := range testCases {
ports, err := ParsePortConfig(tc.value)
if tc.expectedError != "" {
assert.Error(t, err, tc.expectedError)
continue
}
assert.NilError(t, err)
assert.Check(t, is.Len(ports, len(tc.expected)))
for _, expectedPortConfig := range tc.expected {
assertContains(t, ports, expectedPortConfig)
}
}
}
func assertContains(t *testing.T, portConfigs []ServicePortConfig, expected ServicePortConfig) {
var contains = false
for _, portConfig := range portConfigs {
if is.DeepEqual(portConfig, expected)().Success() {
contains = true
break
}
}
if !contains {
t.Errorf("expected %v to contain %v, did not", portConfigs, expected)
}
}
func TestSet(t *testing.T) {
s := make(set)
s.append("one")
s.append("two")
s.append("three")
s.append("two")
assert.Equal(t, len(s.toSlice()), 3)
}
type foo struct {
Bar string
}
func TestExtension(t *testing.T) {
x := Extensions{
"foo": map[string]interface{}{
"bar": "zot",
},
}
var foo foo
ok, err := x.Get("foo", &foo)
assert.NilError(t, err)
assert.Check(t, ok == true)
assert.Check(t, foo.Bar == "zot")
ok, err = x.Get("qiz", &foo)
assert.NilError(t, err)
assert.Check(t, ok == false)
}
func TestNewMapping(t *testing.T) {
m := NewMapping([]string{
"FOO=BAR",
"ZOT=",
"QIX",
})
mw := NewMappingWithEquals([]string{
"FOO=BAR",
"ZOT=",
"QIX",
})
assert.Check(t, m["FOO"] == "BAR")
assert.Check(t, m["ZOT"] == "")
assert.Check(t, m["QIX"] == "")
assert.Check(t, *mw["FOO"] == "BAR")
assert.Check(t, *mw["ZOT"] == "")
assert.Check(t, mw["QIX"] == nil)
}
func TestNetworksByPriority(t *testing.T) {
s := ServiceConfig{
Networks: map[string]*ServiceNetworkConfig{
"foo": nil,
"bar": {
Priority: 10,
},
"zot": {
Priority: 100,
},
"qix": {
Priority: 1000,
},
},
}
assert.DeepEqual(t, s.NetworksByPriority(), []string{"qix", "zot", "bar", "foo"})
}

1
vendor/github.com/Microsoft/go-winio/CODEOWNERS generated vendored Normal file
View File

@ -0,0 +1 @@
* @microsoft/containerplat

View File

@ -1,4 +1,4 @@
# go-winio
# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
This repository contains utilities for efficiently performing Win32 IO operations in
Go. Currently, this is focused on accessing named pipes and other file handles, and

View File

@ -5,21 +5,14 @@ package winio
import (
"os"
"runtime"
"syscall"
"unsafe"
)
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
"golang.org/x/sys/windows"
)
// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
FileAttributes uint32
pad uint32 // padding
}
@ -27,7 +20,7 @@ type FileBasicInfo struct {
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
@ -36,13 +29,32 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return nil
}
// FileStandardInfo contains extended information for the file.
// FILE_STANDARD_INFO in WinBase.h
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
type FileStandardInfo struct {
AllocationSize, EndOfFile int64
NumberOfLinks uint32
DeletePending, Directory bool
}
// GetFileStandardInfo retrieves ended information for the file.
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
si := &FileStandardInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return si, nil
}
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system.
type FileIDInfo struct {
@ -53,7 +65,7 @@ type FileIDInfo struct {
// GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)

View File

@ -3,7 +3,7 @@ module github.com/Microsoft/go-winio
go 1.12
require (
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.1
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.7.0
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
)

View File

@ -1,18 +1,14 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -1,3 +1,5 @@
// +build windows
package winio
import (

View File

@ -429,10 +429,10 @@ type PipeConfig struct {
// when the pipe is in message mode.
MessageMode bool
// InputBufferSize specifies the size the input buffer, in bytes.
// InputBufferSize specifies the size of the input buffer, in bytes.
InputBufferSize int32
// OutputBufferSize specifies the size the input buffer, in bytes.
// OutputBufferSize specifies the size of the output buffer, in bytes.
OutputBufferSize int32
}

View File

@ -1,3 +1,5 @@
// +build windows
// Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding,

View File

@ -0,0 +1,161 @@
// +build windows
package security
import (
"os"
"syscall"
"unsafe"
"github.com/pkg/errors"
)
type (
accessMask uint32
accessMode uint32
desiredAccess uint32
inheritMode uint32
objectType uint32
shareMode uint32
securityInformation uint32
trusteeForm uint32
trusteeType uint32
explicitAccess struct {
accessPermissions accessMask
accessMode accessMode
inheritance inheritMode
trustee trustee
}
trustee struct {
multipleTrustee *trustee
multipleTrusteeOperation int32
trusteeForm trusteeForm
trusteeType trusteeType
name uintptr
}
)
const (
accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ
accessModeGrant accessMode = 1
desiredAccessReadControl desiredAccess = 0x20000
desiredAccessWriteDac desiredAccess = 0x40000
gvmga = "GrantVmGroupAccess:"
inheritModeNoInheritance inheritMode = 0x0
inheritModeSubContainersAndObjectsInherit inheritMode = 0x3
objectTypeFileObject objectType = 0x1
securityInformationDACL securityInformation = 0x4
shareModeRead shareMode = 0x1
shareModeWrite shareMode = 0x2
sidVmGroup = "S-1-5-83-0"
trusteeFormIsSid trusteeForm = 0
trusteeTypeWellKnownGroup trusteeType = 5
)
// GrantVMGroupAccess sets the DACL for a specified file or directory to
// include Grant ACE entries for the VM Group SID. This is a golang re-
// implementation of the same function in vmcompute, just not exported in
// RS5. Which kind of sucks. Sucks a lot :/
func GrantVmGroupAccess(name string) error {
// Stat (to determine if `name` is a directory).
s, err := os.Stat(name)
if err != nil {
return errors.Wrapf(err, "%s os.Stat %s", gvmga, name)
}
// Get a handle to the file/directory. Must defer Close on success.
fd, err := createFile(name, s.IsDir())
if err != nil {
return err // Already wrapped
}
defer syscall.CloseHandle(fd)
// Get the current DACL and Security Descriptor. Must defer LocalFree on success.
ot := objectTypeFileObject
si := securityInformationDACL
sd := uintptr(0)
origDACL := uintptr(0)
if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil {
return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name)
}
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd)))
// Generate a new DACL which is the current DACL with the required ACEs added.
// Must defer LocalFree on success.
newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL)
if err != nil {
return err // Already wrapped
}
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL)))
// And finally use SetSecurityInfo to apply the updated DACL.
if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil {
return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name)
}
return nil
}
// createFile is a helper function to call [Nt]CreateFile to get a handle to
// the file or directory.
func createFile(name string, isDir bool) (syscall.Handle, error) {
namep := syscall.StringToUTF16(name)
da := uint32(desiredAccessReadControl | desiredAccessWriteDac)
sm := uint32(shareModeRead | shareModeWrite)
fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
if isDir {
fa = uint32(fa | syscall.FILE_FLAG_BACKUP_SEMANTICS)
}
fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0)
if err != nil {
return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name)
}
return fd, nil
}
// generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added.
// The caller is responsible for LocalFree of the returned DACL on success.
func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) {
// Generate pointers to the SIDs based on the string SIDs
sid, err := syscall.StringToSid(sidVmGroup)
if err != nil {
return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup)
}
inheritance := inheritModeNoInheritance
if isDir {
inheritance = inheritModeSubContainersAndObjectsInherit
}
eaArray := []explicitAccess{
explicitAccess{
accessPermissions: accessMaskDesiredPermission,
accessMode: accessModeGrant,
inheritance: inheritance,
trustee: trustee{
trusteeForm: trusteeFormIsSid,
trusteeType: trusteeTypeWellKnownGroup,
name: uintptr(unsafe.Pointer(sid)),
},
},
}
modifiedDACL := uintptr(0)
if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil {
return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name)
}
return modifiedDACL, nil
}

View File

@ -0,0 +1,7 @@
package security
//go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo
//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo
//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW

View File

@ -0,0 +1,70 @@
// Code generated by 'go generate'; DO NOT EDIT.
package security
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo")
procSetEntriesInAclW = modadvapi32.NewProc("SetEntriesInAclW")
procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo")
)
func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) {
r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) {
r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) {
r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@ -28,8 +28,9 @@ const (
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
SeSecurityPrivilege = "SeSecurityPrivilege"
)
const (

View File

@ -1,3 +1,3 @@
package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go

323
vendor/github.com/Microsoft/go-winio/vhd/vhd.go generated vendored Normal file
View File

@ -0,0 +1,323 @@
// +build windows
package vhd
import (
"fmt"
"syscall"
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
//go:generate go run mksyscall_windows.go -output zvhd_windows.go vhd.go
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.CreateVirtualDisk
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.OpenVirtualDisk
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) [failretval != 0] = virtdisk.AttachVirtualDisk
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = virtdisk.DetachVirtualDisk
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) [failretval != 0] = virtdisk.GetVirtualDiskPhysicalPath
type (
CreateVirtualDiskFlag uint32
VirtualDiskFlag uint32
AttachVirtualDiskFlag uint32
DetachVirtualDiskFlag uint32
VirtualDiskAccessMask uint32
)
type VirtualStorageType struct {
DeviceID uint32
VendorID guid.GUID
}
type CreateVersion2 struct {
UniqueID guid.GUID
MaximumSize uint64
BlockSizeInBytes uint32
SectorSizeInBytes uint32
PhysicalSectorSizeInByte uint32
ParentPath *uint16 // string
SourcePath *uint16 // string
OpenFlags uint32
ParentVirtualStorageType VirtualStorageType
SourceVirtualStorageType VirtualStorageType
ResiliencyGUID guid.GUID
}
type CreateVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 CreateVersion2
}
type OpenVersion2 struct {
GetInfoOnly bool
ReadOnly bool
ResiliencyGUID guid.GUID
}
type OpenVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 OpenVersion2
}
type AttachVersion2 struct {
RestrictedOffset uint64
RestrictedLength uint64
}
type AttachVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 AttachVersion2
}
const (
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
// Access Mask for opening a VHD
VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
// Flags for creating a VHD
CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40
CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100
CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
// Flags for opening a VHD
OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
// Flags for attaching a VHD
AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
// Flags for detaching a VHD
DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
)
// CreateVhdx is a helper function to create a simple vhdx file at the given path using
// default values.
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
params := CreateVirtualDiskParameters{
Version: 2,
Version2: CreateVersion2{
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
},
}
handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, &params)
if err != nil {
return err
}
if err := syscall.CloseHandle(handle); err != nil {
return err
}
return nil
}
// DetachVirtualDisk detaches a virtual hard disk by handle.
func DetachVirtualDisk(handle syscall.Handle) (err error) {
if err := detachVirtualDisk(handle, 0, 0); err != nil {
return errors.Wrap(err, "failed to detach virtual disk")
}
return nil
}
// DetachVhd detaches a vhd found at `path`.
func DetachVhd(path string) error {
handle, err := OpenVirtualDisk(
path,
VirtualDiskAccessNone,
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
)
if err != nil {
return err
}
defer syscall.CloseHandle(handle)
return DetachVirtualDisk(handle)
}
// AttachVirtualDisk attaches a virtual hard disk for use.
func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtualDiskFlag, parameters *AttachVirtualDiskParameters) (err error) {
// Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
if err := attachVirtualDisk(
handle,
nil,
uint32(attachVirtualDiskFlag),
0,
parameters,
nil,
); err != nil {
return errors.Wrap(err, "failed to attach virtual disk")
}
return nil
}
// AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
// of the ATTACH_VIRTUAL_DISK_PARAMETERS.
func AttachVhd(path string) (err error) {
handle, err := OpenVirtualDisk(
path,
VirtualDiskAccessNone,
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
)
if err != nil {
return err
}
defer syscall.CloseHandle(handle)
params := AttachVirtualDiskParameters{Version: 2}
if err := AttachVirtualDisk(
handle,
AttachVirtualDiskFlagNone,
&params,
); err != nil {
return errors.Wrap(err, "failed to attach virtual disk")
}
return nil
}
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
func OpenVirtualDisk(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag) (syscall.Handle, error) {
parameters := OpenVirtualDiskParameters{Version: 2}
handle, err := OpenVirtualDiskWithParameters(
vhdPath,
virtualDiskAccessMask,
openVirtualDiskFlags,
&parameters,
)
if err != nil {
return 0, err
}
return handle, nil
}
// OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag, parameters *OpenVirtualDiskParameters) (syscall.Handle, error) {
var (
handle syscall.Handle
defaultType VirtualStorageType
)
if parameters.Version != 2 {
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
}
if err := openVirtualDisk(
&defaultType,
vhdPath,
uint32(virtualDiskAccessMask),
uint32(openVirtualDiskFlags),
parameters,
&handle,
); err != nil {
return 0, errors.Wrap(err, "failed to open virtual disk")
}
return handle, nil
}
// CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, createVirtualDiskFlags CreateVirtualDiskFlag, parameters *CreateVirtualDiskParameters) (syscall.Handle, error) {
var (
handle syscall.Handle
defaultType VirtualStorageType
)
if parameters.Version != 2 {
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
}
if err := createVirtualDisk(
&defaultType,
path,
uint32(virtualDiskAccessMask),
nil,
uint32(createVirtualDiskFlags),
0,
parameters,
nil,
&handle,
); err != nil {
return handle, errors.Wrap(err, "failed to create virtual disk")
}
return handle, nil
}
// GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
// path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
// that represents the particular enumeration of the physical disk on the caller's system.
func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
var (
diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
diskPhysicalPathBuf [256]uint16
)
if err := getVirtualDiskPhysicalPath(
handle,
&diskPathSizeInBytes,
&diskPhysicalPathBuf[0],
); err != nil {
return "", errors.Wrap(err, "failed to get disk physical path")
}
return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
}
// CreateDiffVhd is a helper function to create a differencing virtual disk.
func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
// Setting `ParentPath` is how to signal to create a differencing disk.
createParams := &CreateVirtualDiskParameters{
Version: 2,
Version2: CreateVersion2{
ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
},
}
vhdHandle, err := CreateVirtualDisk(
diffVhdPath,
VirtualDiskAccessNone,
CreateVirtualDiskFlagNone,
createParams,
)
if err != nil {
return fmt.Errorf("failed to create differencing vhd: %s", err)
}
if err := syscall.CloseHandle(vhdHandle); err != nil {
return fmt.Errorf("failed to close differencing vhd handle: %s", err)
}
return nil
}

View File

@ -0,0 +1,106 @@
// Code generated by 'go generate'; DO NOT EDIT.
package vhd
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modvirtdisk = windows.NewLazySystemDLL("virtdisk.dll")
procAttachVirtualDisk = modvirtdisk.NewProc("AttachVirtualDisk")
procCreateVirtualDisk = modvirtdisk.NewProc("CreateVirtualDisk")
procDetachVirtualDisk = modvirtdisk.NewProc("DetachVirtualDisk")
procGetVirtualDiskPhysicalPath = modvirtdisk.NewProc("GetVirtualDiskPhysicalPath")
procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk")
)
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle)
}
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) {
r1, _, e1 := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle)
}
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@ -19,6 +19,7 @@ const (
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
@ -26,7 +27,7 @@ var (
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
@ -37,243 +38,62 @@ func errnoErr(e syscall.Errno) error {
}
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procLocalFree = modkernel32.NewProc("LocalFree")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procBackupRead = modkernel32.NewProc("BackupRead")
procBackupWrite = modkernel32.NewProc("BackupWrite")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procLocalFree = modkernel32.NewProc("LocalFree")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procbind = modws2_32.NewProc("bind")
)
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if wait {
if releaseAll {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
err = errnoErr(e1)
}
return
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
return
}
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)
if err != nil {
return
}
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
@ -281,11 +101,7 @@ func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidS
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
@ -302,126 +118,73 @@ func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision ui
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0)
return
}
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if releaseAll {
_p0 = 1
} else {
_p0 = 0
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)
if err != nil {
return
}
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
} else {
_p0 = 0
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
@ -442,53 +205,27 @@ func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err err
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
@ -501,22 +238,14 @@ func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, proce
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
@ -529,22 +258,162 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
err = errnoErr(e1)
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
return
}
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
var _p0 uint32
if wait {
_p0 = 1
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
@ -552,11 +421,7 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
err = errnoErr(e1)
}
return
}

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