mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-11-01 03:04:02 +08:00
feat: support more docker options
This commit is contained in:
@@ -4,7 +4,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/cli/command"
|
||||||
|
dockercomp "github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/util/completion"
|
"k8s.io/kubectl/pkg/util/completion"
|
||||||
@@ -18,21 +19,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CmdDev(f cmdutil.Factory) *cobra.Command {
|
func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||||
var devOptions = dev.Options{
|
var devOptions = &dev.Options{
|
||||||
Factory: f,
|
Factory: f,
|
||||||
Entrypoint: "",
|
NoProxy: false,
|
||||||
Publish: opts.NewListOpts(nil),
|
ExtraCIDR: []string{},
|
||||||
Expose: opts.NewListOpts(nil),
|
}
|
||||||
Env: opts.NewListOpts(nil),
|
_, dockerCli, err := dev.GetClient()
|
||||||
Volumes: opts.NewListOpts(nil),
|
if err != nil {
|
||||||
ExtraHosts: opts.NewListOpts(nil),
|
panic(err)
|
||||||
NoProxy: false,
|
|
||||||
ExtraCIDR: []string{},
|
|
||||||
}
|
}
|
||||||
var sshConf = &util.SshConfig{}
|
var sshConf = &util.SshConfig{}
|
||||||
var transferImage bool
|
var transferImage bool
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "dev",
|
Use: "dev [OPTIONS] RESOURCE [COMMAND] [ARG...]",
|
||||||
Short: i18n.T("Startup your kubernetes workloads in local Docker container with same volume、env、and network"),
|
Short: i18n.T("Startup your kubernetes workloads in local Docker container with same volume、env、and network"),
|
||||||
Long: templates.LongDesc(i18n.T(`
|
Long: templates.LongDesc(i18n.T(`
|
||||||
Startup your kubernetes workloads in local Docker container with same volume、env、and network
|
Startup your kubernetes workloads in local Docker container with same volume、env、and network
|
||||||
@@ -51,22 +50,22 @@ Startup your kubernetes workloads in local Docker container with same volume、e
|
|||||||
kubevpn dev service/productpage
|
kubevpn dev service/productpage
|
||||||
|
|
||||||
# Develop workloads with mesh, traffic with header a=1, will hit local PC, otherwise no effect
|
# Develop workloads with mesh, traffic with header a=1, will hit local PC, otherwise no effect
|
||||||
kubevpn dev service/productpage --headers a=1
|
kubevpn dev --headers a=1 service/productpage
|
||||||
|
|
||||||
# Develop workloads without proxy traffic
|
# Develop workloads without proxy traffic
|
||||||
kubevpn dev service/productpage --no-proxy
|
kubevpn dev --no-proxy service/productpage
|
||||||
|
|
||||||
# Develop workloads which api-server behind of bastion host or ssh jump host
|
# Develop workloads which api-server behind of bastion host or ssh jump host
|
||||||
kubevpn dev deployment/productpage --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile /Users/naison/.ssh/ssh.pem
|
kubevpn dev --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile /Users/naison/.ssh/ssh.pem deployment/productpage
|
||||||
|
|
||||||
# it also support ProxyJump, like
|
# it also support ProxyJump, like
|
||||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────────┐
|
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────────┐
|
||||||
│ pc ├────►│ ssh1 ├────►│ ssh2 ├────►│ ssh3 ├─────►... ─────► │ api-server │
|
│ pc ├────►│ ssh1 ├────►│ ssh2 ├────►│ ssh3 ├─────►... ─────► │ api-server │
|
||||||
└──────┘ └──────┘ └──────┘ └──────┘ └────────────┘
|
└──────┘ └──────┘ └──────┘ └──────┘ └────────────┘
|
||||||
kubevpn dev deployment/productpage --ssh-alias <alias>
|
kubevpn dev --ssh-alias <alias> deployment/productpage
|
||||||
|
|
||||||
`)),
|
`)),
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if !util.IsAdmin() {
|
if !util.IsAdmin() {
|
||||||
util.RunWithElevated()
|
util.RunWithElevated()
|
||||||
@@ -80,9 +79,14 @@ Startup your kubernetes workloads in local Docker container with same volume、e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handler.SshJump(sshConf, cmd.Flags())
|
return handler.SshJump(sshConf, cmd.Flags())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return dev.DoDev(devOptions, args, f)
|
devOptions.Workload = args[0]
|
||||||
|
if len(args) > 1 {
|
||||||
|
devOptions.Copts.Args = args[1:]
|
||||||
|
}
|
||||||
|
return dev.DoDev(devOptions, cmd.Flags(), f)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringToStringVarP(&devOptions.Headers, "headers", "H", map[string]string{}, "Traffic with special headers with reverse it to local PC, you should startup your service after reverse workloads successfully, If not special, redirect all traffic to local PC, format is k=v, like: k1=v1,k2=v2")
|
cmd.Flags().StringToStringVarP(&devOptions.Headers, "headers", "H", map[string]string{}, "Traffic with special headers with reverse it to local PC, you should startup your service after reverse workloads successfully, If not special, redirect all traffic to local PC, format is k=v, like: k1=v1,k2=v2")
|
||||||
@@ -96,24 +100,42 @@ Startup your kubernetes workloads in local Docker container with same volume、e
|
|||||||
cmd.Flags().StringVar((*string)(&devOptions.ConnectMode), "connect-mode", string(dev.ConnectModeHost), "Connect to kubernetes network in container or in host, eg: ["+string(dev.ConnectModeContainer)+"|"+string(dev.ConnectModeHost)+"]")
|
cmd.Flags().StringVar((*string)(&devOptions.ConnectMode), "connect-mode", string(dev.ConnectModeHost), "Connect to kubernetes network in container or in host, eg: ["+string(dev.ConnectModeContainer)+"|"+string(dev.ConnectModeHost)+"]")
|
||||||
cmd.Flags().BoolVar(&transferImage, "transfer-image", false, "transfer image to remote registry, it will transfer image "+config.OriginImage+" to flags `--image` special image, default: "+config.Image)
|
cmd.Flags().BoolVar(&transferImage, "transfer-image", false, "transfer image to remote registry, it will transfer image "+config.OriginImage+" to flags `--image` special image, default: "+config.Image)
|
||||||
|
|
||||||
// docker options
|
// diy docker options
|
||||||
cmd.Flags().Var(&devOptions.ExtraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
|
|
||||||
// We allow for both "--net" and "--network", although the latter is the recommended way.
|
|
||||||
cmd.Flags().Var(&devOptions.NetMode, "net", "Connect a container to a network, eg: [default|bridge|host|none|container:$CONTAINER_ID]")
|
|
||||||
cmd.Flags().Var(&devOptions.NetMode, "network", "Connect a container to a network")
|
|
||||||
cmd.Flags().MarkHidden("net")
|
|
||||||
cmd.Flags().VarP(&devOptions.Volumes, "volume", "v", "Bind mount a volume")
|
|
||||||
cmd.Flags().Var(&devOptions.Mounts, "mount", "Attach a filesystem mount to the container")
|
|
||||||
cmd.Flags().Var(&devOptions.Expose, "expose", "Expose a port or a range of ports")
|
|
||||||
cmd.Flags().VarP(&devOptions.Publish, "publish", "p", "Publish a container's port(s) to the host")
|
|
||||||
cmd.Flags().BoolVarP(&devOptions.PublishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
|
|
||||||
cmd.Flags().VarP(&devOptions.Env, "env", "e", "Set environment variables")
|
|
||||||
cmd.Flags().StringVar(&devOptions.Entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
|
|
||||||
cmd.Flags().StringVar(&devOptions.DockerImage, "docker-image", "", "Overwrite the default K8s pod of the image")
|
cmd.Flags().StringVar(&devOptions.DockerImage, "docker-image", "", "Overwrite the default K8s pod of the image")
|
||||||
//cmd.Flags().StringVar(&devOptions.Pull, "pull", container.PullImageMissing, `Pull image before creating ("`+container.PullImageAlways+`"|"`+container.PullImageMissing+`"|"`+container.PullImageNever+`")`)
|
// origin docker options
|
||||||
cmd.Flags().StringVar(&devOptions.Platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
flags := cmd.Flags()
|
||||||
cmd.Flags().StringVar(&devOptions.VolumeDriver, "volume-driver", "", "Optional volume driver for the container")
|
flags.SetInterspersed(false)
|
||||||
_ = cmd.Flags().SetAnnotation("platform", "version", []string{"1.32"})
|
|
||||||
|
// These are flags not stored in Config/HostConfig
|
||||||
|
flags.BoolVarP(&devOptions.Options.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||||
|
flags.StringVar(&devOptions.Options.Name, "name", "", "Assign a name to the container")
|
||||||
|
flags.StringVar(&devOptions.Options.Pull, "pull", dev.PullImageMissing, `Pull image before running ("`+dev.PullImageAlways+`"|"`+dev.PullImageMissing+`"|"`+dev.PullImageNever+`")`)
|
||||||
|
flags.BoolVarP(&devOptions.Options.Quiet, "quiet", "q", false, "Suppress the pull output")
|
||||||
|
|
||||||
|
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
||||||
|
// with hostname
|
||||||
|
flags.Bool("help", false, "Print usage")
|
||||||
|
|
||||||
|
command.AddPlatformFlag(flags, &devOptions.Options.Platform)
|
||||||
|
command.AddTrustVerificationFlags(flags, &devOptions.Options.Untrusted, dockerCli.ContentTrustEnabled())
|
||||||
|
devOptions.Copts = dev.AddFlags(flags)
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc(
|
||||||
|
"env",
|
||||||
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc(
|
||||||
|
"env-file",
|
||||||
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc(
|
||||||
|
"network",
|
||||||
|
dockercomp.NetworkNames(nil),
|
||||||
|
)
|
||||||
|
|
||||||
addSshFlags(cmd, sshConf)
|
addSshFlags(cmd, sshConf)
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
191
pkg/dev/LICENSE
Normal file
191
pkg/dev/LICENSE
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2013-2017 Docker, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -32,19 +32,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RunConfig struct {
|
type RunConfig struct {
|
||||||
|
containerName string
|
||||||
|
k8sContainerName string
|
||||||
|
|
||||||
config *container.Config
|
config *container.Config
|
||||||
hostConfig *container.HostConfig
|
hostConfig *container.HostConfig
|
||||||
networkingConfig *network.NetworkingConfig
|
networkingConfig *network.NetworkingConfig
|
||||||
platform *v12.Platform
|
platform *v12.Platform
|
||||||
containerName string
|
|
||||||
k8sContainerName string
|
Options RunOptions
|
||||||
|
Copts *ContainerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertKubeResourceToContainer(namespace string, temp v1.PodTemplateSpec, envMap map[string][]string, mountVolume map[string][]mount.Mount, dnsConfig *miekgdns.ClientConfig) (runConfigList Run) {
|
func ConvertKubeResourceToContainer(namespace string, temp v1.PodTemplateSpec, envMap map[string][]string, mountVolume map[string][]mount.Mount, dnsConfig *miekgdns.ClientConfig) (runConfigList ConfigList) {
|
||||||
spec := temp.Spec
|
spec := temp.Spec
|
||||||
for _, c := range spec.Containers {
|
for _, c := range spec.Containers {
|
||||||
var r RunConfig
|
var r RunConfig
|
||||||
config := &container.Config{
|
tmpConfig := &container.Config{
|
||||||
Hostname: func() string {
|
Hostname: func() string {
|
||||||
var hostname = spec.Hostname
|
var hostname = spec.Hostname
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
@@ -84,7 +88,7 @@ func ConvertKubeResourceToContainer(namespace string, temp v1.PodTemplateSpec, e
|
|||||||
Shell: nil,
|
Shell: nil,
|
||||||
}
|
}
|
||||||
if temp.DeletionGracePeriodSeconds != nil {
|
if temp.DeletionGracePeriodSeconds != nil {
|
||||||
config.StopTimeout = (*int)(unsafe.Pointer(temp.DeletionGracePeriodSeconds))
|
tmpConfig.StopTimeout = (*int)(unsafe.Pointer(temp.DeletionGracePeriodSeconds))
|
||||||
}
|
}
|
||||||
hostConfig := &container.HostConfig{
|
hostConfig := &container.HostConfig{
|
||||||
Binds: []string{},
|
Binds: []string{},
|
||||||
@@ -138,7 +142,7 @@ func ConvertKubeResourceToContainer(namespace string, temp v1.PodTemplateSpec, e
|
|||||||
portset[port1] = struct{}{}
|
portset[port1] = struct{}{}
|
||||||
}
|
}
|
||||||
hostConfig.PortBindings = portmap
|
hostConfig.PortBindings = portmap
|
||||||
config.ExposedPorts = portset
|
tmpConfig.ExposedPorts = portset
|
||||||
if c.SecurityContext != nil && c.SecurityContext.Capabilities != nil {
|
if c.SecurityContext != nil && c.SecurityContext.Capabilities != nil {
|
||||||
hostConfig.CapAdd = append(hostConfig.CapAdd, *(*strslice.StrSlice)(unsafe.Pointer(&c.SecurityContext.Capabilities.Add))...)
|
hostConfig.CapAdd = append(hostConfig.CapAdd, *(*strslice.StrSlice)(unsafe.Pointer(&c.SecurityContext.Capabilities.Add))...)
|
||||||
hostConfig.CapDrop = *(*strslice.StrSlice)(unsafe.Pointer(&c.SecurityContext.Capabilities.Drop))
|
hostConfig.CapDrop = *(*strslice.StrSlice)(unsafe.Pointer(&c.SecurityContext.Capabilities.Drop))
|
||||||
@@ -150,10 +154,10 @@ func ConvertKubeResourceToContainer(namespace string, temp v1.PodTemplateSpec, e
|
|||||||
}
|
}
|
||||||
r.containerName = fmt.Sprintf("%s_%s_%s_%s", c.Name, namespace, "kubevpn", suffix)
|
r.containerName = fmt.Sprintf("%s_%s_%s_%s", c.Name, namespace, "kubevpn", suffix)
|
||||||
r.k8sContainerName = c.Name
|
r.k8sContainerName = c.Name
|
||||||
r.config = config
|
r.config = tmpConfig
|
||||||
r.hostConfig = hostConfig
|
r.hostConfig = hostConfig
|
||||||
r.networkingConfig = &network.NetworkingConfig{EndpointsConfig: make(map[string]*network.EndpointSettings)}
|
r.networkingConfig = &network.NetworkingConfig{EndpointsConfig: make(map[string]*network.EndpointSettings)}
|
||||||
r.platform = /*&v12.Platform{Architecture: "amd64", OS: "linux"}*/ nil
|
r.platform = nil
|
||||||
|
|
||||||
runConfigList = append(runConfigList, &r)
|
runConfigList = append(runConfigList, &r)
|
||||||
}
|
}
|
||||||
@@ -179,11 +183,11 @@ func GetDNS(ctx context.Context, f util.Factory, ns, pod string) (*miekgdns.Clie
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fromPod, err := dns.GetDNSServiceIPFromPod(clientSet, client, config, pod, ns)
|
clientConfig, err := dns.GetDNSServiceIPFromPod(clientSet, client, config, pod, ns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fromPod, nil
|
return clientConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVolume key format: [container name]-[volume mount name]
|
// GetVolume key format: [container name]-[volume mount name]
|
||||||
|
|||||||
271
pkg/dev/dockercreate.go
Normal file
271
pkg/dev/dockercreate.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package dev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/image"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
apiclient "github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pull constants
|
||||||
|
const (
|
||||||
|
PullImageAlways = "always"
|
||||||
|
PullImageMissing = "missing" // Default (matches previous behavior)
|
||||||
|
PullImageNever = "never"
|
||||||
|
)
|
||||||
|
|
||||||
|
type createOptions struct {
|
||||||
|
Name string
|
||||||
|
Platform string
|
||||||
|
Untrusted bool
|
||||||
|
Pull string // always, missing, never
|
||||||
|
Quiet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error {
|
||||||
|
ref, err := reference.ParseNormalizedNamed(image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
|
||||||
|
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
options := types.ImageCreateOptions{
|
||||||
|
RegistryAuth: encodedAuth,
|
||||||
|
Platform: platform,
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseBody.Close()
|
||||||
|
|
||||||
|
return jsonmessage.DisplayJSONMessagesStream(
|
||||||
|
responseBody,
|
||||||
|
out,
|
||||||
|
dockerCli.Out().FD(),
|
||||||
|
dockerCli.Out().IsTerminal(),
|
||||||
|
nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cidFile struct {
|
||||||
|
path string
|
||||||
|
file *os.File
|
||||||
|
written bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cid *cidFile) Close() error {
|
||||||
|
if cid.file == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cid.file.Close()
|
||||||
|
|
||||||
|
if cid.written {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.Remove(cid.path); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to remove the CID file '%s'", cid.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cid *cidFile) Write(id string) error {
|
||||||
|
if cid.file == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := cid.file.Write([]byte(id)); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to write the container ID to the file")
|
||||||
|
}
|
||||||
|
cid.written = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCIDFile(path string) (*cidFile, error) {
|
||||||
|
if path == "" {
|
||||||
|
return &cidFile{}, nil
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create the container ID file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cidFile{path: path, file: f}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.CreateResponse, error) {
|
||||||
|
config := containerConfig.Config
|
||||||
|
hostConfig := containerConfig.HostConfig
|
||||||
|
networkingConfig := containerConfig.NetworkingConfig
|
||||||
|
stderr := dockerCli.Err()
|
||||||
|
|
||||||
|
warnOnOomKillDisable(*hostConfig, stderr)
|
||||||
|
warnOnLocalhostDNS(*hostConfig, stderr)
|
||||||
|
|
||||||
|
var (
|
||||||
|
trustedRef reference.Canonical
|
||||||
|
namedRef reference.Named
|
||||||
|
)
|
||||||
|
|
||||||
|
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer containerIDFile.Close()
|
||||||
|
|
||||||
|
ref, err := reference.ParseAnyReference(config.Image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if named, ok := ref.(reference.Named); ok {
|
||||||
|
namedRef = reference.TagNameOnly(named)
|
||||||
|
|
||||||
|
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.Untrusted {
|
||||||
|
var err error
|
||||||
|
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Image = reference.FamiliarString(trustedRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pullAndTagImage := func() error {
|
||||||
|
pullOut := stderr
|
||||||
|
if opts.Quiet {
|
||||||
|
pullOut = io.Discard
|
||||||
|
}
|
||||||
|
if err := pullImage(ctx, dockerCli, config.Image, opts.Platform, pullOut); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
|
||||||
|
return image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var platform *specs.Platform
|
||||||
|
// Engine API version 1.41 first introduced the option to specify platform on
|
||||||
|
// create. It will produce an error if you try to set a platform on older API
|
||||||
|
// versions, so check the API version here to maintain backwards
|
||||||
|
// compatibility for CLI users.
|
||||||
|
if opts.Platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
||||||
|
p, err := platforms.Parse(opts.Platform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error parsing specified platform")
|
||||||
|
}
|
||||||
|
platform = &p
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Pull == PullImageAlways {
|
||||||
|
if err := pullAndTagImage(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
|
||||||
|
|
||||||
|
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.Name)
|
||||||
|
if err != nil {
|
||||||
|
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
|
||||||
|
if apiclient.IsErrNotFound(err) && namedRef != nil && opts.Pull == PullImageMissing {
|
||||||
|
if !opts.Quiet {
|
||||||
|
// we don't want to write to stdout anything apart from container.ID
|
||||||
|
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pullAndTagImage(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var retryErr error
|
||||||
|
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.Name)
|
||||||
|
if retryErr != nil {
|
||||||
|
return nil, retryErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, warning := range response.Warnings {
|
||||||
|
fmt.Fprintf(stderr, "WARNING: %s\n", warning)
|
||||||
|
}
|
||||||
|
err = containerIDFile.Write(response.ID)
|
||||||
|
return &response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {
|
||||||
|
if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
|
||||||
|
fmt.Fprintln(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the DNS settings passed via --dns against localhost regexp to warn if
|
||||||
|
// they are trying to set a DNS to a localhost address
|
||||||
|
func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) {
|
||||||
|
for _, dnsIP := range hostConfig.DNS {
|
||||||
|
if isLocalhost(dnsIP) {
|
||||||
|
fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
|
||||||
|
const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
|
||||||
|
|
||||||
|
var localhostIPRegexp = regexp.MustCompile(ipLocalhost)
|
||||||
|
|
||||||
|
// IsLocalhost returns true if ip matches the localhost IP regular expression.
|
||||||
|
// Used for determining if nameserver settings are being passed which are
|
||||||
|
// localhost addresses
|
||||||
|
func isLocalhost(ip string) bool {
|
||||||
|
return localhostIPRegexp.MatchString(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatePullOpt(val string) error {
|
||||||
|
switch val {
|
||||||
|
case PullImageAlways, PullImageMissing, PullImageNever, "":
|
||||||
|
// valid option, but nothing to do yet
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid pull option: '%s': must be one of %q, %q or %q",
|
||||||
|
val,
|
||||||
|
PullImageAlways,
|
||||||
|
PullImageMissing,
|
||||||
|
PullImageNever,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
1076
pkg/dev/dockeropts.go
Normal file
1076
pkg/dev/dockeropts.go
Normal file
File diff suppressed because it is too large
Load Diff
108
pkg/dev/dockerrun.go
Normal file
108
pkg/dev/dockerrun.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package dev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunOptions struct {
|
||||||
|
createOptions
|
||||||
|
Detach bool
|
||||||
|
sigProxy bool
|
||||||
|
detachKeys string
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachContainer(ctx context.Context, dockerCli command.Cli, errCh *chan error, config *container.Config, containerID string) (func(), error) {
|
||||||
|
options := types.ContainerAttachOptions{
|
||||||
|
Stream: true,
|
||||||
|
Stdin: config.AttachStdin,
|
||||||
|
Stdout: config.AttachStdout,
|
||||||
|
Stderr: config.AttachStderr,
|
||||||
|
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
|
||||||
|
if errAttach != nil {
|
||||||
|
return nil, errAttach
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
out, cerr io.Writer
|
||||||
|
in io.ReadCloser
|
||||||
|
)
|
||||||
|
if config.AttachStdin {
|
||||||
|
in = dockerCli.In()
|
||||||
|
}
|
||||||
|
if config.AttachStdout {
|
||||||
|
out = dockerCli.Out()
|
||||||
|
}
|
||||||
|
if config.AttachStderr {
|
||||||
|
if config.Tty {
|
||||||
|
cerr = dockerCli.Out()
|
||||||
|
} else {
|
||||||
|
cerr = dockerCli.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan error, 1)
|
||||||
|
*errCh = ch
|
||||||
|
|
||||||
|
if in != nil && out != nil && cerr != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ch <- func() error {
|
||||||
|
streamer := hijackedIOStreamer{
|
||||||
|
streams: dockerCli,
|
||||||
|
inputStream: in,
|
||||||
|
outputStream: out,
|
||||||
|
errorStream: cerr,
|
||||||
|
resp: resp,
|
||||||
|
tty: config.Tty,
|
||||||
|
detachKeys: options.DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
if errHijack := streamer.stream(ctx); errHijack != nil {
|
||||||
|
return errHijack
|
||||||
|
}
|
||||||
|
return errAttach
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
return resp.Close, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reportError is a utility method that prints a user-friendly message
|
||||||
|
// containing the error that occurred during parsing and a suggestion to get help
|
||||||
|
func reportError(stderr io.Writer, name string, str string, withHelp bool) {
|
||||||
|
str = strings.TrimSuffix(str, ".") + "."
|
||||||
|
if withHelp {
|
||||||
|
str += "\nSee 'docker " + name + " --help'."
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(stderr, "docker:", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if container start fails with 'not found'/'no such' error, return 127
|
||||||
|
// if container start fails with 'permission denied' error, return 126
|
||||||
|
// return 125 for generic docker daemon failures
|
||||||
|
func runStartContainerErr(err error) error {
|
||||||
|
trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
|
||||||
|
statusError := cli.StatusError{StatusCode: 125}
|
||||||
|
if strings.Contains(trimmedErr, "executable file not found") ||
|
||||||
|
strings.Contains(trimmedErr, "no such file or directory") ||
|
||||||
|
strings.Contains(trimmedErr, "system cannot find the file specified") {
|
||||||
|
statusError = cli.StatusError{StatusCode: 127}
|
||||||
|
} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
|
||||||
|
statusError = cli.StatusError{StatusCode: 126}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusError
|
||||||
|
}
|
||||||
207
pkg/dev/hijack.go
Normal file
207
pkg/dev/hijack.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package dev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/moby/term"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The default escape key sequence: ctrl-p, ctrl-q
|
||||||
|
// TODO: This could be moved to `pkg/term`.
|
||||||
|
var defaultEscapeKeys = []byte{16, 17}
|
||||||
|
|
||||||
|
// A hijackedIOStreamer handles copying input to and output from streams to the
|
||||||
|
// connection.
|
||||||
|
type hijackedIOStreamer struct {
|
||||||
|
streams command.Streams
|
||||||
|
inputStream io.ReadCloser
|
||||||
|
outputStream io.Writer
|
||||||
|
errorStream io.Writer
|
||||||
|
|
||||||
|
resp types.HijackedResponse
|
||||||
|
|
||||||
|
tty bool
|
||||||
|
detachKeys string
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream handles setting up the IO and then begins streaming stdin/stdout
|
||||||
|
// to/from the hijacked connection, blocking until it is either done reading
|
||||||
|
// output, the user inputs the detach key sequence when in TTY mode, or when
|
||||||
|
// the given context is cancelled.
|
||||||
|
func (h *hijackedIOStreamer) stream(ctx context.Context) error {
|
||||||
|
restoreInput, err := h.setupInput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to setup input stream: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer restoreInput()
|
||||||
|
|
||||||
|
outputDone := h.beginOutputStream(restoreInput)
|
||||||
|
inputDone, detached := h.beginInputStream(restoreInput)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-outputDone:
|
||||||
|
return err
|
||||||
|
case <-inputDone:
|
||||||
|
// Input stream has closed.
|
||||||
|
if h.outputStream != nil || h.errorStream != nil {
|
||||||
|
// Wait for output to complete streaming.
|
||||||
|
select {
|
||||||
|
case err := <-outputDone:
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case err := <-detached:
|
||||||
|
// Got a detach key sequence.
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||||
|
if h.inputStream == nil || !h.tty {
|
||||||
|
// No need to setup input TTY.
|
||||||
|
// The restore func is a nop.
|
||||||
|
return func() {}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setRawTerminal(h.streams); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use sync.Once so we may call restore multiple times but ensure we
|
||||||
|
// only restore the terminal once.
|
||||||
|
var restoreOnce sync.Once
|
||||||
|
restore = func() {
|
||||||
|
restoreOnce.Do(func() {
|
||||||
|
restoreTerminal(h.streams, h.inputStream)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the input to detect detach escape sequence.
|
||||||
|
// Use default escape keys if an invalid sequence is given.
|
||||||
|
escapeKeys := defaultEscapeKeys
|
||||||
|
if h.detachKeys != "" {
|
||||||
|
customEscapeKeys, err := term.ToBytes(h.detachKeys)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("invalid detach escape keys, using default: %s", err)
|
||||||
|
} else {
|
||||||
|
escapeKeys = customEscapeKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.inputStream = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(h.inputStream, escapeKeys), h.inputStream.Close)
|
||||||
|
|
||||||
|
return restore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error {
|
||||||
|
if h.outputStream == nil && h.errorStream == nil {
|
||||||
|
// There is no need to copy output.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outputDone := make(chan error)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// When TTY is ON, use regular copy
|
||||||
|
if h.outputStream != nil && h.tty {
|
||||||
|
_, err = io.Copy(h.outputStream, h.resp.Reader)
|
||||||
|
// We should restore the terminal as soon as possible
|
||||||
|
// once the connection ends so any following print
|
||||||
|
// messages will be in normal type.
|
||||||
|
restoreInput()
|
||||||
|
} else {
|
||||||
|
_, err = stdcopy.StdCopy(h.outputStream, h.errorStream, h.resp.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debug("[hijack] End of stdout")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error receiveStdout: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputDone <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
return outputDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan struct{}, detachedC <-chan error) {
|
||||||
|
inputDone := make(chan struct{})
|
||||||
|
detached := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if h.inputStream != nil {
|
||||||
|
_, err := io.Copy(h.resp.Conn, h.inputStream)
|
||||||
|
// We should restore the terminal as soon as possible
|
||||||
|
// once the connection ends so any following print
|
||||||
|
// messages will be in normal type.
|
||||||
|
restoreInput()
|
||||||
|
|
||||||
|
logrus.Debug("[hijack] End of stdin")
|
||||||
|
|
||||||
|
if _, ok := err.(term.EscapeError); ok {
|
||||||
|
detached <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// This error will also occur on the receive
|
||||||
|
// side (from stdout) where it will be
|
||||||
|
// propagated back to the caller.
|
||||||
|
logrus.Debugf("Error sendStdin: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.resp.CloseWrite(); err != nil {
|
||||||
|
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(inputDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return inputDone, detached
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRawTerminal(streams command.Streams) error {
|
||||||
|
if err := streams.In().SetRawTerminal(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return streams.Out().SetRawTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreTerminal(streams command.Streams, in io.Closer) error {
|
||||||
|
streams.In().RestoreTerminal()
|
||||||
|
streams.Out().RestoreTerminal()
|
||||||
|
// WARNING: DO NOT REMOVE THE OS CHECKS !!!
|
||||||
|
// For some reason this Close call blocks on darwin..
|
||||||
|
// As the client exits right after, simply discard the close
|
||||||
|
// until we find a better solution.
|
||||||
|
//
|
||||||
|
// This can also cause the client on Windows to get stuck in Win32 CloseHandle()
|
||||||
|
// in some cases. See https://github.com/docker/docker/issues/28267#issuecomment-288237442
|
||||||
|
// Tracked internally at Microsoft by VSO #11352156. In the
|
||||||
|
// Windows case, you hit this if you are using the native/v2 console,
|
||||||
|
// not the "legacy" console, and you start the client in a new window. eg
|
||||||
|
// `start docker run --rm -it microsoft/nanoserver cmd /s /c echo foobar`
|
||||||
|
// will hang. Remove start, and it won't repro.
|
||||||
|
if in != nil && runtime.GOOS != "darwin" && runtime.GOOS != "windows" {
|
||||||
|
return in.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
417
pkg/dev/main.go
417
pkg/dev/main.go
@@ -29,10 +29,12 @@ import (
|
|||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
pkgerr "github.com/pkg/errors"
|
pkgerr "github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@@ -67,22 +69,12 @@ type Options struct {
|
|||||||
ConnectMode ConnectMode
|
ConnectMode ConnectMode
|
||||||
|
|
||||||
// docker options
|
// docker options
|
||||||
Platform string
|
DockerImage string
|
||||||
//Pull string // always, missing, never
|
Options RunOptions
|
||||||
PublishAll bool
|
Copts *ContainerOptions
|
||||||
Entrypoint string
|
|
||||||
DockerImage string
|
|
||||||
Publish opts.ListOpts
|
|
||||||
Expose opts.ListOpts
|
|
||||||
ExtraHosts opts.ListOpts
|
|
||||||
NetMode opts.NetworkOpt
|
|
||||||
Env opts.ListOpts
|
|
||||||
Mounts opts.MountOpt
|
|
||||||
Volumes opts.ListOpts
|
|
||||||
VolumeDriver string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Options) Main(ctx context.Context) error {
|
func (d *Options) Main(ctx context.Context, cli *client.Client, dockerCli *command.DockerCli, tempContainerConfig *containerConfig) error {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
object, err := util.GetUnstructuredObject(d.Factory, d.Namespace, d.Workload)
|
object, err := util.GetUnstructuredObject(d.Factory, d.Namespace, d.Workload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,31 +125,25 @@ func (d Options) Main(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mesh.RemoveContainers(templateSpec)
|
mesh.RemoveContainers(templateSpec)
|
||||||
list := ConvertKubeResourceToContainer(d.Namespace, *templateSpec, env, volume, dns)
|
runConfigList := ConvertKubeResourceToContainer(d.Namespace, *templateSpec, env, volume, dns)
|
||||||
err = fillOptions(list, d)
|
err = mergeDockerOptions(runConfigList, d, tempContainerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can not fill docker options, err: %v", err)
|
return fmt.Errorf("can not fill docker options, err: %v", err)
|
||||||
}
|
}
|
||||||
var dockerCli *command.DockerCli
|
|
||||||
var cli *client.Client
|
|
||||||
cli, dockerCli, err = GetClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// check resource
|
// check resource
|
||||||
var outOfMemory bool
|
var outOfMemory bool
|
||||||
outOfMemory, _ = checkOutOfMemory(templateSpec, cli)
|
outOfMemory, _ = checkOutOfMemory(templateSpec, cli)
|
||||||
if outOfMemory {
|
if outOfMemory {
|
||||||
return fmt.Errorf("your pod resource request is bigger than docker-desktop resource, please adjust your docker-desktop resource")
|
return fmt.Errorf("your pod resource request is bigger than docker-desktop resource, please adjust your docker-desktop resource")
|
||||||
}
|
}
|
||||||
mode := container.NetworkMode(d.NetMode.NetworkMode())
|
mode := container.NetworkMode(d.Copts.netMode.NetworkMode())
|
||||||
if len(d.NetMode.Value()) != 0 {
|
if len(d.Copts.netMode.Value()) != 0 {
|
||||||
for _, runConfig := range list[:] {
|
for _, runConfig := range runConfigList[:] {
|
||||||
// remove expose port
|
// remove expose port
|
||||||
runConfig.config.ExposedPorts = nil
|
runConfig.config.ExposedPorts = nil
|
||||||
runConfig.hostConfig.NetworkMode = mode
|
runConfig.hostConfig.NetworkMode = mode
|
||||||
if mode.IsContainer() {
|
if mode.IsContainer() {
|
||||||
runConfig.hostConfig.PidMode = containertypes.PidMode(d.NetMode.NetworkMode())
|
runConfig.hostConfig.PidMode = containertypes.PidMode(d.Copts.netMode.NetworkMode())
|
||||||
}
|
}
|
||||||
runConfig.hostConfig.PortBindings = nil
|
runConfig.hostConfig.PortBindings = nil
|
||||||
|
|
||||||
@@ -175,15 +161,32 @@ func (d Options) Main(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
list[0].networkingConfig.EndpointsConfig[list[0].containerName] = &network.EndpointSettings{
|
runConfigList[len(runConfigList)-1].networkingConfig.EndpointsConfig[runConfigList[len(runConfigList)-1].containerName] = &network.EndpointSettings{
|
||||||
NetworkID: networkID,
|
NetworkID: networkID,
|
||||||
}
|
}
|
||||||
// skip first
|
var portmap = nat.PortMap{}
|
||||||
for _, runConfig := range list[1:] {
|
var portset = nat.PortSet{}
|
||||||
|
for _, runConfig := range runConfigList {
|
||||||
|
for k, v := range runConfig.hostConfig.PortBindings {
|
||||||
|
if oldValue, ok := portmap[k]; ok {
|
||||||
|
portmap[k] = append(oldValue, v...)
|
||||||
|
} else {
|
||||||
|
portmap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range runConfig.config.ExposedPorts {
|
||||||
|
portset[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runConfigList[len(runConfigList)-1].hostConfig.PortBindings = portmap
|
||||||
|
runConfigList[len(runConfigList)-1].config.ExposedPorts = portset
|
||||||
|
|
||||||
|
// skip last, use last container network
|
||||||
|
for _, runConfig := range runConfigList[:len(runConfigList)-1] {
|
||||||
// remove expose port
|
// remove expose port
|
||||||
runConfig.config.ExposedPorts = nil
|
runConfig.config.ExposedPorts = nil
|
||||||
runConfig.hostConfig.NetworkMode = containertypes.NetworkMode("container:" + list[0].containerName)
|
runConfig.hostConfig.NetworkMode = containertypes.NetworkMode("container:" + runConfigList[len(runConfigList)-1].containerName)
|
||||||
runConfig.hostConfig.PidMode = containertypes.PidMode("container:" + list[0].containerName)
|
runConfig.hostConfig.PidMode = containertypes.PidMode("container:" + runConfigList[len(runConfigList)-1].containerName)
|
||||||
runConfig.hostConfig.PortBindings = nil
|
runConfig.hostConfig.PortBindings = nil
|
||||||
|
|
||||||
// remove dns
|
// remove dns
|
||||||
@@ -196,25 +199,20 @@ func (d Options) Main(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler.RollbackFuncList = append(handler.RollbackFuncList, func() {
|
handler.RollbackFuncList = append(handler.RollbackFuncList, func() {
|
||||||
_ = list.Remove(ctx)
|
_ = runConfigList.Remove(ctx, cli)
|
||||||
})
|
})
|
||||||
err = list.Run(ctx, volume)
|
err = runConfigList.Run(ctx, volume, cli, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return terminal(list[0].containerName, dockerCli)
|
return terminal(runConfigList[0].containerName, dockerCli)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Run []*RunConfig
|
type ConfigList []*RunConfig
|
||||||
|
|
||||||
func (r Run) Remove(ctx context.Context) error {
|
func (l ConfigList) Remove(ctx context.Context, cli *client.Client) error {
|
||||||
cli, _, err := GetClient()
|
for _, runConfig := range l {
|
||||||
if err != nil {
|
err := cli.NetworkDisconnect(ctx, runConfig.containerName, runConfig.containerName, true)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, runConfig := range r {
|
|
||||||
err = cli.NetworkDisconnect(ctx, runConfig.containerName, runConfig.containerName, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
}
|
}
|
||||||
@@ -223,8 +221,7 @@ func (r Run) Remove(ctx context.Context) error {
|
|||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var i types.NetworkResource
|
i, err := cli.NetworkInspect(ctx, config.ConfigMapPodTrafficManager, types.NetworkInspectOptions{})
|
||||||
i, err = cli.NetworkInspect(ctx, config.ConfigMapPodTrafficManager, types.NetworkInspectOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -254,32 +251,35 @@ func GetClient() (*client.Client, *command.DockerCli, error) {
|
|||||||
return cli, dockerCli, nil
|
return cli, dockerCli, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Run) Run(ctx context.Context, volume map[string][]mount.Mount) error {
|
func (l ConfigList) Run(ctx context.Context, volume map[string][]mount.Mount, cli *client.Client, dockerCli *command.DockerCli) error {
|
||||||
cli, c, err := GetClient()
|
for index := len(l) - 1; index >= 0; index-- {
|
||||||
if err != nil {
|
runConfig := l[index]
|
||||||
return err
|
if index == 0 {
|
||||||
}
|
_, err := runFirst(ctx, runConfig, cli, dockerCli)
|
||||||
for _, runConfig := range r {
|
|
||||||
var id string
|
|
||||||
id, err = run(ctx, runConfig, cli, c)
|
|
||||||
if err != nil {
|
|
||||||
// try another way to startup container
|
|
||||||
log.Infof("occur err: %v, try another way to startup container...", err)
|
|
||||||
runConfig.hostConfig.Mounts = nil
|
|
||||||
id, err = run(ctx, runConfig, cli, c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = r.copyToContainer(ctx, volume[runConfig.k8sContainerName], cli, id)
|
} else {
|
||||||
|
id, err := run(ctx, runConfig, cli, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// try another way to startup container
|
||||||
|
log.Infof("occur err: %v, try another way to startup container...", err)
|
||||||
|
runConfig.hostConfig.Mounts = nil
|
||||||
|
id, err = run(ctx, runConfig, cli, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = l.copyToContainer(ctx, volume[runConfig.k8sContainerName], cli, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Run) copyToContainer(ctx context.Context, volume []mount.Mount, cli *client.Client, id string) error {
|
func (l ConfigList) copyToContainer(ctx context.Context, volume []mount.Mount, cli *client.Client, id string) error {
|
||||||
// copy volume into container
|
// copy volume into container
|
||||||
for _, v := range volume {
|
for _, v := range volume {
|
||||||
target, err := createFolder(ctx, cli, id, v.Source, v.Target)
|
target, err := createFolder(ctx, cli, id, v.Source, v.Target)
|
||||||
@@ -367,22 +367,22 @@ func checkOutOfMemory(spec *v1.PodTemplateSpec, cli *client.Client) (outOfMemory
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoDev(devOptions Options, args []string, f cmdutil.Factory) error {
|
func DoDev(devOptions *Options, flags *pflag.FlagSet, f cmdutil.Factory) error {
|
||||||
connect := handler.ConnectOptions{
|
connect := handler.ConnectOptions{
|
||||||
Headers: devOptions.Headers,
|
Headers: devOptions.Headers,
|
||||||
Workloads: args,
|
Workloads: []string{devOptions.Workload},
|
||||||
ExtraCIDR: devOptions.ExtraCIDR,
|
ExtraCIDR: devOptions.ExtraCIDR,
|
||||||
ExtraDomain: devOptions.ExtraDomain,
|
ExtraDomain: devOptions.ExtraDomain,
|
||||||
}
|
}
|
||||||
|
cli, dockerCli, err := GetClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
mode := container.NetworkMode(devOptions.NetMode.NetworkMode())
|
mode := container.NetworkMode(devOptions.Copts.netMode.NetworkMode())
|
||||||
if mode.IsContainer() {
|
if mode.IsContainer() {
|
||||||
client, _, err := GetClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var inspect types.ContainerJSON
|
var inspect types.ContainerJSON
|
||||||
inspect, err = client.ContainerInspect(context.Background(), mode.ConnectedContainer())
|
inspect, err = cli.ContainerInspect(context.Background(), mode.ConnectedContainer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -397,8 +397,7 @@ func DoDev(devOptions Options, args []string, f cmdutil.Factory) error {
|
|||||||
if err := connect.InitClient(f); err != nil {
|
if err := connect.InitClient(f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err := connect.PreCheckResource()
|
if err = connect.PreCheckResource(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,8 +409,8 @@ func DoDev(devOptions Options, args []string, f cmdutil.Factory) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var platform *specs.Platform
|
var platform *specs.Platform
|
||||||
if devOptions.Platform != "" {
|
if devOptions.Options.Platform != "" {
|
||||||
p, err := platforms.Parse(devOptions.Platform)
|
p, err := platforms.Parse(devOptions.Options.Platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pkgerr.Wrap(err, "error parsing specified platform")
|
return pkgerr.Wrap(err, "error parsing specified platform")
|
||||||
}
|
}
|
||||||
@@ -441,117 +440,15 @@ func DoDev(devOptions Options, args []string, f cmdutil.Factory) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case ConnectModeContainer:
|
case ConnectModeContainer:
|
||||||
var dockerCli *command.DockerCli
|
var connectContainer *RunConfig
|
||||||
var cli *client.Client
|
connectContainer, err = createConnectContainer(*devOptions, connect, path, err, cli, platform)
|
||||||
cli, dockerCli, err = GetClient()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var entrypoint []string
|
|
||||||
if devOptions.NoProxy {
|
|
||||||
entrypoint = []string{"kubevpn", "connect", "-n", connect.Namespace, "--kubeconfig", "/root/.kube/config", "--image", config.Image}
|
|
||||||
for _, v := range connect.ExtraCIDR {
|
|
||||||
entrypoint = append(entrypoint, "--extra-cidr", v)
|
|
||||||
}
|
|
||||||
for _, v := range connect.ExtraDomain {
|
|
||||||
entrypoint = append(entrypoint, "--extra-domain", v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entrypoint = []string{"kubevpn", "proxy", connect.Workloads[0], "-n", connect.Namespace, "--kubeconfig", "/root/.kube/config", "--image", config.Image}
|
|
||||||
for k, v := range connect.Headers {
|
|
||||||
entrypoint = append(entrypoint, "--headers", fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
for _, v := range connect.ExtraCIDR {
|
|
||||||
entrypoint = append(entrypoint, "--extra-cidr", v)
|
|
||||||
}
|
|
||||||
for _, v := range connect.ExtraDomain {
|
|
||||||
entrypoint = append(entrypoint, "--extra-domain", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runConfig := &container.Config{
|
|
||||||
User: "root",
|
|
||||||
AttachStdin: false,
|
|
||||||
AttachStdout: false,
|
|
||||||
AttachStderr: false,
|
|
||||||
ExposedPorts: nil,
|
|
||||||
StdinOnce: false,
|
|
||||||
Env: []string{fmt.Sprintf("%s=1", config.EnvStartSudoKubeVPNByKubeVPN)},
|
|
||||||
Cmd: []string{},
|
|
||||||
Healthcheck: nil,
|
|
||||||
ArgsEscaped: false,
|
|
||||||
Image: config.Image,
|
|
||||||
Volumes: nil,
|
|
||||||
Entrypoint: entrypoint,
|
|
||||||
NetworkDisabled: false,
|
|
||||||
MacAddress: "",
|
|
||||||
OnBuild: nil,
|
|
||||||
StopSignal: "",
|
|
||||||
StopTimeout: nil,
|
|
||||||
Shell: nil,
|
|
||||||
}
|
|
||||||
hostConfig := &container.HostConfig{
|
|
||||||
Binds: []string{fmt.Sprintf("%s:%s", path, "/root/.kube/config")},
|
|
||||||
LogConfig: container.LogConfig{},
|
|
||||||
PortBindings: nil,
|
|
||||||
RestartPolicy: container.RestartPolicy{},
|
|
||||||
AutoRemove: true,
|
|
||||||
VolumeDriver: "",
|
|
||||||
VolumesFrom: nil,
|
|
||||||
ConsoleSize: [2]uint{},
|
|
||||||
CapAdd: strslice.StrSlice{"SYS_PTRACE", "SYS_ADMIN"}, // for dlv
|
|
||||||
CgroupnsMode: "",
|
|
||||||
ExtraHosts: nil,
|
|
||||||
GroupAdd: nil,
|
|
||||||
IpcMode: "",
|
|
||||||
Cgroup: "",
|
|
||||||
Links: nil,
|
|
||||||
OomScoreAdj: 0,
|
|
||||||
PidMode: "",
|
|
||||||
Privileged: true,
|
|
||||||
PublishAllPorts: false,
|
|
||||||
ReadonlyRootfs: false,
|
|
||||||
SecurityOpt: []string{"apparmor=unconfined", "seccomp=unconfined"},
|
|
||||||
StorageOpt: nil,
|
|
||||||
Tmpfs: nil,
|
|
||||||
UTSMode: "",
|
|
||||||
UsernsMode: "",
|
|
||||||
ShmSize: 0,
|
|
||||||
Sysctls: nil,
|
|
||||||
Runtime: "",
|
|
||||||
Isolation: "",
|
|
||||||
Resources: container.Resources{},
|
|
||||||
MaskedPaths: nil,
|
|
||||||
ReadonlyPaths: nil,
|
|
||||||
Init: nil,
|
|
||||||
}
|
|
||||||
var suffix string
|
|
||||||
if newUUID, err := uuid.NewUUID(); err == nil {
|
|
||||||
suffix = strings.ReplaceAll(newUUID.String(), "-", "")[:5]
|
|
||||||
}
|
|
||||||
var kubevpnNetwork string
|
|
||||||
kubevpnNetwork, err = createKubevpnNetwork(context.Background(), cli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := fmt.Sprintf("%s_%s_%s", "kubevpn", "local", suffix)
|
|
||||||
c := &RunConfig{
|
|
||||||
config: runConfig,
|
|
||||||
hostConfig: hostConfig,
|
|
||||||
networkingConfig: &network.NetworkingConfig{
|
|
||||||
EndpointsConfig: map[string]*network.EndpointSettings{name: {
|
|
||||||
NetworkID: kubevpnNetwork,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
platform: platform,
|
|
||||||
containerName: name,
|
|
||||||
k8sContainerName: name,
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
var id string
|
var id string
|
||||||
if id, err = run(ctx, c, cli, dockerCli); err != nil {
|
if id, err = run(ctx, connectContainer, cli, dockerCli); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h := interrupt.New(func(signal os.Signal) {
|
h := interrupt.New(func(signal os.Signal) {
|
||||||
@@ -570,21 +467,150 @@ func DoDev(devOptions Options, args []string, f cmdutil.Factory) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = devOptions.NetMode.Set("container:" + id); err != nil {
|
if err = devOptions.Copts.netMode.Set("container:" + id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupport connect mode: %s", devOptions.ConnectMode)
|
return fmt.Errorf("unsupport connect mode: %s", devOptions.ConnectMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tempContainerConfig *containerConfig
|
||||||
|
{
|
||||||
|
if err := validatePullOpt(devOptions.Options.Pull); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(devOptions.Copts.env.GetAll()))
|
||||||
|
newEnv := []string{}
|
||||||
|
for k, v := range proxyConfig {
|
||||||
|
if v == nil {
|
||||||
|
newEnv = append(newEnv, k)
|
||||||
|
} else {
|
||||||
|
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
devOptions.Copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||||
|
tempContainerConfig, err = parse(flags, devOptions.Copts, dockerCli.ServerInfo().OSType)
|
||||||
|
// just in case the parse does not exit
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = validateAPIVersion(tempContainerConfig, dockerCli.Client().ClientVersion()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
devOptions.Namespace = connect.Namespace
|
devOptions.Namespace = connect.Namespace
|
||||||
err = devOptions.Main(context.Background())
|
err = devOptions.Main(context.Background(), cli, dockerCli, tempContainerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createConnectContainer(devOptions Options, connect handler.ConnectOptions, path string, err error, cli *client.Client, platform *specs.Platform) (*RunConfig, error) {
|
||||||
|
var entrypoint []string
|
||||||
|
if devOptions.NoProxy {
|
||||||
|
entrypoint = []string{"kubevpn", "connect", "-n", connect.Namespace, "--kubeconfig", "/root/.kube/config", "--image", config.Image}
|
||||||
|
for _, v := range connect.ExtraCIDR {
|
||||||
|
entrypoint = append(entrypoint, "--extra-cidr", v)
|
||||||
|
}
|
||||||
|
for _, v := range connect.ExtraDomain {
|
||||||
|
entrypoint = append(entrypoint, "--extra-domain", v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entrypoint = []string{"kubevpn", "proxy", connect.Workloads[0], "-n", connect.Namespace, "--kubeconfig", "/root/.kube/config", "--image", config.Image}
|
||||||
|
for k, v := range connect.Headers {
|
||||||
|
entrypoint = append(entrypoint, "--headers", fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
for _, v := range connect.ExtraCIDR {
|
||||||
|
entrypoint = append(entrypoint, "--extra-cidr", v)
|
||||||
|
}
|
||||||
|
for _, v := range connect.ExtraDomain {
|
||||||
|
entrypoint = append(entrypoint, "--extra-domain", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runConfig := &container.Config{
|
||||||
|
User: "root",
|
||||||
|
AttachStdin: false,
|
||||||
|
AttachStdout: false,
|
||||||
|
AttachStderr: false,
|
||||||
|
ExposedPorts: nil,
|
||||||
|
StdinOnce: false,
|
||||||
|
Env: []string{fmt.Sprintf("%s=1", config.EnvStartSudoKubeVPNByKubeVPN)},
|
||||||
|
Cmd: []string{},
|
||||||
|
Healthcheck: nil,
|
||||||
|
ArgsEscaped: false,
|
||||||
|
Image: config.Image,
|
||||||
|
Volumes: nil,
|
||||||
|
Entrypoint: entrypoint,
|
||||||
|
NetworkDisabled: false,
|
||||||
|
MacAddress: "",
|
||||||
|
OnBuild: nil,
|
||||||
|
StopSignal: "",
|
||||||
|
StopTimeout: nil,
|
||||||
|
Shell: nil,
|
||||||
|
}
|
||||||
|
hostConfig := &container.HostConfig{
|
||||||
|
Binds: []string{fmt.Sprintf("%s:%s", path, "/root/.kube/config")},
|
||||||
|
LogConfig: container.LogConfig{},
|
||||||
|
PortBindings: nil,
|
||||||
|
RestartPolicy: container.RestartPolicy{},
|
||||||
|
AutoRemove: true,
|
||||||
|
VolumeDriver: "",
|
||||||
|
VolumesFrom: nil,
|
||||||
|
ConsoleSize: [2]uint{},
|
||||||
|
CapAdd: strslice.StrSlice{"SYS_PTRACE", "SYS_ADMIN"}, // for dlv
|
||||||
|
CgroupnsMode: "",
|
||||||
|
ExtraHosts: nil,
|
||||||
|
GroupAdd: nil,
|
||||||
|
IpcMode: "",
|
||||||
|
Cgroup: "",
|
||||||
|
Links: nil,
|
||||||
|
OomScoreAdj: 0,
|
||||||
|
PidMode: "",
|
||||||
|
Privileged: true,
|
||||||
|
PublishAllPorts: false,
|
||||||
|
ReadonlyRootfs: false,
|
||||||
|
SecurityOpt: []string{"apparmor=unconfined", "seccomp=unconfined"},
|
||||||
|
StorageOpt: nil,
|
||||||
|
Tmpfs: nil,
|
||||||
|
UTSMode: "",
|
||||||
|
UsernsMode: "",
|
||||||
|
ShmSize: 0,
|
||||||
|
Sysctls: nil,
|
||||||
|
Runtime: "",
|
||||||
|
Isolation: "",
|
||||||
|
Resources: container.Resources{},
|
||||||
|
MaskedPaths: nil,
|
||||||
|
ReadonlyPaths: nil,
|
||||||
|
Init: nil,
|
||||||
|
}
|
||||||
|
var suffix string
|
||||||
|
if newUUID, err := uuid.NewUUID(); err == nil {
|
||||||
|
suffix = strings.ReplaceAll(newUUID.String(), "-", "")[:5]
|
||||||
|
}
|
||||||
|
var kubevpnNetwork string
|
||||||
|
kubevpnNetwork, err = createKubevpnNetwork(context.Background(), cli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("%s_%s_%s", "kubevpn", "local", suffix)
|
||||||
|
c := &RunConfig{
|
||||||
|
config: runConfig,
|
||||||
|
hostConfig: hostConfig,
|
||||||
|
networkingConfig: &network.NetworkingConfig{
|
||||||
|
EndpointsConfig: map[string]*network.EndpointSettings{name: {
|
||||||
|
NetworkID: kubevpnNetwork,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
platform: platform,
|
||||||
|
containerName: name,
|
||||||
|
k8sContainerName: name,
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func runLogsWaitRunning(ctx context.Context, dockerCli command.Cli, container string) error {
|
func runLogsWaitRunning(ctx context.Context, dockerCli command.Cli, container string) error {
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, container)
|
c, err := dockerCli.Client().ContainerInspect(ctx, container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -685,37 +711,6 @@ func runKill(dockerCli command.Cli, containers ...string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error {
|
|
||||||
if len(containers) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
const defaultParallel int = 50
|
|
||||||
sem := make(chan struct{}, defaultParallel)
|
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
// make sure result is printed in correct order
|
|
||||||
output := map[string]chan error{}
|
|
||||||
for _, c := range containers {
|
|
||||||
output[c] = make(chan error, 1)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for _, c := range containers {
|
|
||||||
err := <-output[c]
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for _, c := range containers {
|
|
||||||
sem <- struct{}{} // Wait for active queue sem to drain.
|
|
||||||
go func(container string) {
|
|
||||||
output[container] <- op(ctx, container)
|
|
||||||
<-sem
|
|
||||||
}(c)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return errChan
|
|
||||||
}
|
|
||||||
|
|
||||||
func createKubevpnNetwork(ctx context.Context, cli *client.Client) (string, error) {
|
func createKubevpnNetwork(ctx context.Context, cli *client.Client) (string, error) {
|
||||||
by := map[string]string{"owner": config.ConfigMapPodTrafficManager}
|
by := map[string]string{"owner": config.ConfigMapPodTrafficManager}
|
||||||
|
|||||||
@@ -1,26 +1,13 @@
|
|||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/containerd/containerd/platforms"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
|
||||||
"github.com/docker/cli/opts"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
|
||||||
"github.com/docker/docker/errdefs"
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
v12 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fillOptions(r Run, copts Options) error {
|
func mergeDockerOptions(r ConfigList, copts *Options, tempContainerConfig *containerConfig) error {
|
||||||
if copts.ContainerName != "" {
|
if copts.ContainerName != "" {
|
||||||
var index = -1
|
var index = -1
|
||||||
for i, config := range r {
|
for i, config := range r {
|
||||||
@@ -35,226 +22,49 @@ func fillOptions(r Run, copts Options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := r[0]
|
config := r[0]
|
||||||
config.hostConfig.PublishAllPorts = copts.PublishAll
|
config.Options = copts.Options
|
||||||
|
config.Copts = copts.Copts
|
||||||
|
|
||||||
if copts.DockerImage != "" {
|
if copts.DockerImage != "" {
|
||||||
config.config.Image = copts.DockerImage
|
config.config.Image = copts.DockerImage
|
||||||
}
|
}
|
||||||
|
if copts.Options.Name != "" {
|
||||||
if copts.Entrypoint != "" {
|
config.containerName = copts.Options.Name
|
||||||
if strings.Count(copts.Entrypoint, " ") != 0 {
|
} else {
|
||||||
split := strings.Split(copts.Entrypoint, " ")
|
config.Options.Name = config.containerName
|
||||||
config.config.Entrypoint = split
|
|
||||||
} else {
|
|
||||||
config.config.Entrypoint = strslice.StrSlice{copts.Entrypoint}
|
|
||||||
}
|
|
||||||
config.config.Cmd = []string{}
|
|
||||||
}
|
}
|
||||||
if copts.Platform != "" {
|
if copts.Options.Platform != "" {
|
||||||
split := strings.Split(copts.Platform, "/")
|
p, err := platforms.Parse(copts.Options.Platform)
|
||||||
if len(split) != 2 {
|
|
||||||
return errors.Errorf("invalid port format for --platform: %s", copts.Platform)
|
|
||||||
}
|
|
||||||
config.platform = &v12.Platform{
|
|
||||||
OS: split[0],
|
|
||||||
Architecture: split[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect all the environment variables for the container
|
|
||||||
envVariables, err := opts.ReadKVEnvStrings([]string{}, copts.Env.GetAll())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.config.Env = append(config.config.Env, envVariables...)
|
|
||||||
|
|
||||||
publishOpts := copts.Publish.GetAll()
|
|
||||||
var (
|
|
||||||
ports map[nat.Port]struct{}
|
|
||||||
portBindings map[nat.Port][]nat.PortBinding
|
|
||||||
convertedOpts []string
|
|
||||||
)
|
|
||||||
|
|
||||||
convertedOpts, err = convertToStandardNotation(publishOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ports, portBindings, err = nat.ParsePortSpecs(convertedOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge in exposed ports to the map of published ports
|
|
||||||
for _, e := range copts.Expose.GetAll() {
|
|
||||||
if strings.Contains(e, ":") {
|
|
||||||
return errors.Errorf("invalid port format for --expose: %s", e)
|
|
||||||
}
|
|
||||||
// support two formats for expose, original format <portnum>/[<proto>]
|
|
||||||
// or <startport-endport>/[<proto>]
|
|
||||||
proto, port := nat.SplitProtoPort(e)
|
|
||||||
// parse the start and end port and create a sequence of ports to expose
|
|
||||||
// if expose a port, the start and end port are the same
|
|
||||||
start, end, err := nat.ParsePortRange(port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
|
return errors.Wrap(err, "error parsing specified platform")
|
||||||
}
|
}
|
||||||
for i := start; i <= end; i++ {
|
config.platform = &p
|
||||||
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, exists := ports[p]; !exists {
|
|
||||||
ports[p] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for port, bindings := range portBindings {
|
|
||||||
config.hostConfig.PortBindings[port] = bindings
|
|
||||||
}
|
|
||||||
for port, s := range ports {
|
|
||||||
config.config.ExposedPorts[port] = s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mounts := copts.Mounts.Value()
|
config.hostConfig = tempContainerConfig.HostConfig
|
||||||
if len(mounts) > 0 && copts.VolumeDriver != "" {
|
config.networkingConfig.EndpointsConfig = util.Merge[string, *network.EndpointSettings](tempContainerConfig.NetworkingConfig.EndpointsConfig, config.networkingConfig.EndpointsConfig)
|
||||||
logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
|
|
||||||
|
c := tempContainerConfig.Config
|
||||||
|
var entrypoint = config.config.Entrypoint
|
||||||
|
var args = config.config.Cmd
|
||||||
|
// if special --entrypoint, then use it
|
||||||
|
if len(c.Entrypoint) != 0 {
|
||||||
|
entrypoint = c.Entrypoint
|
||||||
|
args = c.Cmd
|
||||||
}
|
}
|
||||||
var binds []string
|
if len(c.Cmd) != 0 {
|
||||||
volumes := copts.Volumes.GetMap()
|
args = c.Cmd
|
||||||
// add any bind targets to the list of container volumes
|
|
||||||
for bind := range copts.Volumes.GetMap() {
|
|
||||||
parsed, _ := loader.ParseVolume(bind)
|
|
||||||
|
|
||||||
if parsed.Source != "" {
|
|
||||||
toBind := bind
|
|
||||||
|
|
||||||
if parsed.Type == string(mounttypes.TypeBind) {
|
|
||||||
if arr := strings.SplitN(bind, ":", 2); len(arr) == 2 {
|
|
||||||
hostPart := arr[0]
|
|
||||||
if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." {
|
|
||||||
if absHostPart, err := filepath.Abs(hostPart); err == nil {
|
|
||||||
hostPart = absHostPart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toBind = hostPart + ":" + arr[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// after creating the bind mount we want to delete it from the copts.volumes values because
|
|
||||||
// we do not want bind mounts being committed to image configs
|
|
||||||
binds = append(binds, toBind)
|
|
||||||
// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
|
|
||||||
// there are duplicates entries.
|
|
||||||
delete(volumes, bind)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
c.Entrypoint = entrypoint
|
||||||
config.hostConfig.Binds = binds
|
c.Cmd = args
|
||||||
networkOpts, err := parseNetworkOpts(copts)
|
c.Env = append(config.config.Env, c.Env...)
|
||||||
if err != nil {
|
c.Image = config.config.Image
|
||||||
return err
|
if c.User == "" {
|
||||||
|
c.User = config.config.User
|
||||||
}
|
}
|
||||||
config.networkingConfig = &network.NetworkingConfig{EndpointsConfig: networkOpts}
|
c.Volumes = util.Merge[string, struct{}](c.Volumes, config.config.Volumes)
|
||||||
|
|
||||||
|
config.config = c
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToStandardNotation(ports []string) ([]string, error) {
|
|
||||||
optsList := []string{}
|
|
||||||
for _, publish := range ports {
|
|
||||||
if strings.Contains(publish, "=") {
|
|
||||||
params := map[string]string{"protocol": "tcp"}
|
|
||||||
for _, param := range strings.Split(publish, ",") {
|
|
||||||
opt := strings.Split(param, "=")
|
|
||||||
if len(opt) < 2 {
|
|
||||||
return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
|
|
||||||
}
|
|
||||||
|
|
||||||
params[opt[0]] = opt[1]
|
|
||||||
}
|
|
||||||
optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"]))
|
|
||||||
} else {
|
|
||||||
optsList = append(optsList, publish)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return optsList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseNetworkOpts converts --network advanced options to endpoint-specs, and combines
|
|
||||||
// them with the old --network-alias and --links. If returns an error if conflicting options
|
|
||||||
// are found.
|
|
||||||
//
|
|
||||||
// this function may return _multiple_ endpoints, which is not currently supported
|
|
||||||
// by the daemon, but may be in future; it's up to the daemon to produce an error
|
|
||||||
// in case that is not supported.
|
|
||||||
func parseNetworkOpts(copts Options) (map[string]*network.EndpointSettings, error) {
|
|
||||||
var (
|
|
||||||
endpoints = make(map[string]*network.EndpointSettings, len(copts.NetMode.Value()))
|
|
||||||
hasUserDefined, hasNonUserDefined bool
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, n := range copts.NetMode.Value() {
|
|
||||||
n := n
|
|
||||||
if container.NetworkMode(n.Target).IsUserDefined() {
|
|
||||||
hasUserDefined = true
|
|
||||||
} else {
|
|
||||||
hasNonUserDefined = true
|
|
||||||
}
|
|
||||||
ep, err := parseNetworkAttachmentOpt(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, ok := endpoints[n.Target]; ok {
|
|
||||||
return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target))
|
|
||||||
}
|
|
||||||
|
|
||||||
// For backward compatibility: if no custom options are provided for the network,
|
|
||||||
// and only a single network is specified, omit the endpoint-configuration
|
|
||||||
// on the client (the daemon will still create it when creating the container)
|
|
||||||
if i == 0 && len(copts.NetMode.Value()) == 1 {
|
|
||||||
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endpoints[n.Target] = ep
|
|
||||||
}
|
|
||||||
if hasUserDefined && hasNonUserDefined {
|
|
||||||
return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes"))
|
|
||||||
}
|
|
||||||
return endpoints, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.EndpointSettings, error) {
|
|
||||||
if strings.TrimSpace(ep.Target) == "" {
|
|
||||||
return nil, errors.New("no name set for network")
|
|
||||||
}
|
|
||||||
if !container.NetworkMode(ep.Target).IsUserDefined() {
|
|
||||||
if len(ep.Aliases) > 0 {
|
|
||||||
return nil, errors.New("network-scoped aliases are only supported for user-defined networks")
|
|
||||||
}
|
|
||||||
if len(ep.Links) > 0 {
|
|
||||||
return nil, errors.New("links are only supported for user-defined networks")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
epConfig := &network.EndpointSettings{
|
|
||||||
NetworkID: ep.Target,
|
|
||||||
}
|
|
||||||
epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
|
|
||||||
if len(ep.DriverOpts) > 0 {
|
|
||||||
epConfig.DriverOpts = make(map[string]string)
|
|
||||||
epConfig.DriverOpts = ep.DriverOpts
|
|
||||||
}
|
|
||||||
if len(ep.Links) > 0 {
|
|
||||||
epConfig.Links = ep.Links
|
|
||||||
}
|
|
||||||
if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
|
|
||||||
epConfig.IPAMConfig = &network.EndpointIPAMConfig{
|
|
||||||
IPv4Address: ep.IPv4Address,
|
|
||||||
IPv6Address: ep.IPv6Address,
|
|
||||||
LinkLocalIPs: ep.LinkLocalIPs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return epConfig, nil
|
|
||||||
}
|
|
||||||
|
|||||||
179
pkg/dev/run.go
179
pkg/dev/run.go
@@ -2,11 +2,13 @@ package dev
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,10 +22,13 @@ import (
|
|||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
typescommand "github.com/docker/docker/api/types/container"
|
typescommand "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
apiclient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/moby/term"
|
||||||
dockerterm "github.com/moby/term"
|
dockerterm "github.com/moby/term"
|
||||||
v12 "github.com/opencontainers/image-spec/specs-go/v1"
|
v12 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
@@ -90,6 +95,11 @@ func run(ctx context.Context, runConfig *RunConfig, cli *client.Client, c *comma
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if inspect.State != nil && (inspect.State.Status == "exited" || inspect.State.Status == "dead" || inspect.State.Dead) {
|
||||||
|
once.Do(func() { close(chanStop) })
|
||||||
|
err = errors.New(fmt.Sprintf("container status: %s", inspect.State.Status))
|
||||||
|
return
|
||||||
|
}
|
||||||
if inspect.State != nil && inspect.State.Running {
|
if inspect.State != nil && inspect.State.Running {
|
||||||
once.Do(func() { close(chanStop) })
|
once.Do(func() { close(chanStop) })
|
||||||
return
|
return
|
||||||
@@ -125,6 +135,175 @@ func run(ctx context.Context, runConfig *RunConfig, cli *client.Client, c *comma
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runFirst(ctx context.Context, runConfig *RunConfig, cli *apiclient.Client, dockerCli *command.DockerCli) (id string, err error) {
|
||||||
|
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = cli.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
stdout, stderr := dockerCli.Out(), dockerCli.Err()
|
||||||
|
client := dockerCli.Client()
|
||||||
|
|
||||||
|
runConfig.config.ArgsEscaped = false
|
||||||
|
|
||||||
|
if err := dockerCli.In().CheckTty(runConfig.config.AttachStdin, runConfig.config.Tty); err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runConfig.Options.Detach {
|
||||||
|
if err := dockerCli.In().CheckTty(runConfig.config.AttachStdin, runConfig.config.Tty); err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if runConfig.Copts.attach.Len() != 0 {
|
||||||
|
return id, errors.New("Conflicting options: -a and -d")
|
||||||
|
}
|
||||||
|
|
||||||
|
runConfig.config.AttachStdin = false
|
||||||
|
runConfig.config.AttachStdout = false
|
||||||
|
runConfig.config.AttachStderr = false
|
||||||
|
runConfig.config.StdinOnce = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelFun := context.WithCancel(context.Background())
|
||||||
|
defer cancelFun()
|
||||||
|
|
||||||
|
createResponse, err := createContainer(ctx, dockerCli, &containerConfig{
|
||||||
|
Config: runConfig.config,
|
||||||
|
HostConfig: runConfig.hostConfig,
|
||||||
|
NetworkingConfig: runConfig.networkingConfig,
|
||||||
|
}, &runConfig.Options.createOptions)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Infof("Created container: %s", runConfig.containerName)
|
||||||
|
|
||||||
|
var (
|
||||||
|
waitDisplayID chan struct{}
|
||||||
|
errCh chan error
|
||||||
|
)
|
||||||
|
if !runConfig.config.AttachStdout && !runConfig.config.AttachStderr {
|
||||||
|
// Make this asynchronous to allow the client to write to stdin before having to read the ID
|
||||||
|
waitDisplayID = make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(waitDisplayID)
|
||||||
|
fmt.Fprintln(stdout, createResponse.ID)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
attach := runConfig.config.AttachStdin || runConfig.config.AttachStdout || runConfig.config.AttachStderr
|
||||||
|
if attach {
|
||||||
|
close, err := attachContainer(ctx, dockerCli, &errCh, runConfig.config, createResponse.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
defer close()
|
||||||
|
}
|
||||||
|
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, runConfig.Copts.autoRemove)
|
||||||
|
// start the container
|
||||||
|
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
|
// If we have hijackedIOStreamer, we should notify
|
||||||
|
// hijackedIOStreamer we are going to exit and wait
|
||||||
|
// to avoid the terminal are not restored.
|
||||||
|
if attach {
|
||||||
|
cancelFun()
|
||||||
|
<-errCh
|
||||||
|
}
|
||||||
|
|
||||||
|
reportError(stderr, "run", err.Error(), false)
|
||||||
|
if runConfig.Copts.autoRemove {
|
||||||
|
// wait container to be removed
|
||||||
|
<-statusChan
|
||||||
|
}
|
||||||
|
return id, runStartContainerErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runConfig.config.AttachStdin || runConfig.config.AttachStdout || runConfig.config.AttachStderr) && runConfig.config.Tty && dockerCli.Out().IsTerminal() {
|
||||||
|
if err := container.MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
|
||||||
|
fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errCh != nil {
|
||||||
|
if err := <-errCh; err != nil {
|
||||||
|
if _, ok := err.(term.EscapeError); ok {
|
||||||
|
// The user entered the detach escape sequence.
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Error hijack: %s", err)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detached mode: wait for the id to be displayed and return.
|
||||||
|
if !runConfig.config.AttachStdout && !runConfig.config.AttachStderr {
|
||||||
|
// Detached mode
|
||||||
|
<-waitDisplayID
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status := <-statusChan
|
||||||
|
if status != 0 {
|
||||||
|
return id, errors.New(strconv.Itoa(status))
|
||||||
|
}
|
||||||
|
log.Infof("Wait container %s to be running...", runConfig.containerName)
|
||||||
|
chanStop := make(chan struct{})
|
||||||
|
var inspect types.ContainerJSON
|
||||||
|
var once = &sync.Once{}
|
||||||
|
wait.Until(func() {
|
||||||
|
inspect, err = cli.ContainerInspect(ctx, createResponse.ID)
|
||||||
|
if err != nil && errdefs.IsNotFound(err) {
|
||||||
|
once.Do(func() { close(chanStop) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if inspect.State != nil && (inspect.State.Status == "exited" || inspect.State.Status == "dead" || inspect.State.Dead) {
|
||||||
|
once.Do(func() { close(chanStop) })
|
||||||
|
err = errors.New(fmt.Sprintf("container status: %s", inspect.State.Status))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if inspect.State != nil && inspect.State.Running {
|
||||||
|
once.Do(func() { close(chanStop) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, time.Second, chanStop)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to wait container to be ready: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// print port mapping to host
|
||||||
|
var empty = true
|
||||||
|
var str string
|
||||||
|
if inspect.NetworkSettings != nil && inspect.NetworkSettings.Ports != nil {
|
||||||
|
var list []string
|
||||||
|
for port, bindings := range inspect.NetworkSettings.Ports {
|
||||||
|
var p []string
|
||||||
|
for _, binding := range bindings {
|
||||||
|
if binding.HostPort != "" {
|
||||||
|
p = append(p, binding.HostPort)
|
||||||
|
empty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = append(list, fmt.Sprintf("%s:%s", port, strings.Join(p, ",")))
|
||||||
|
}
|
||||||
|
str = fmt.Sprintf("Container %s is running on port %s now", runConfig.containerName, strings.Join(list, " "))
|
||||||
|
}
|
||||||
|
if !empty {
|
||||||
|
log.Infoln(str)
|
||||||
|
} else {
|
||||||
|
log.Infof("Container %s is running now", runConfig.containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func PullImage(ctx context.Context, platform *v12.Platform, cli *client.Client, c *command.DockerCli, img string) error {
|
func PullImage(ctx context.Context, platform *v12.Platform, cli *client.Client, c *command.DockerCli, img string) error {
|
||||||
var readCloser io.ReadCloser
|
var readCloser io.ReadCloser
|
||||||
var plat string
|
var plat string
|
||||||
|
|||||||
162
pkg/dev/utils.go
Normal file
162
pkg/dev/utils.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package dev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/events"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||||
|
if len(containerID) == 0 {
|
||||||
|
// containerID can never be empty
|
||||||
|
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older versions used the Events API, and even older versions did not
|
||||||
|
// support server-side removal. This legacyWaitExitOrRemoved method
|
||||||
|
// preserves that old behavior and any issues it may have.
|
||||||
|
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
|
||||||
|
return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := container.WaitConditionNextExit
|
||||||
|
if waitRemove {
|
||||||
|
condition = container.WaitConditionRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition)
|
||||||
|
|
||||||
|
statusC := make(chan int)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case result := <-resultC:
|
||||||
|
if result.Error != nil {
|
||||||
|
logrus.Errorf("Error waiting for container: %v", result.Error.Message)
|
||||||
|
statusC <- 125
|
||||||
|
} else {
|
||||||
|
statusC <- int(result.StatusCode)
|
||||||
|
}
|
||||||
|
case err := <-errC:
|
||||||
|
logrus.Errorf("error waiting for container: %v", err)
|
||||||
|
statusC <- 125
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return statusC
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||||
|
var removeErr error
|
||||||
|
statusChan := make(chan int)
|
||||||
|
exitCode := 125
|
||||||
|
|
||||||
|
// Get events via Events API
|
||||||
|
f := filters.NewArgs()
|
||||||
|
f.Add("type", "container")
|
||||||
|
f.Add("container", containerID)
|
||||||
|
options := types.EventsOptions{
|
||||||
|
Filters: f,
|
||||||
|
}
|
||||||
|
eventCtx, cancel := context.WithCancel(ctx)
|
||||||
|
eventq, errq := dockerCli.Client().Events(eventCtx, options)
|
||||||
|
|
||||||
|
eventProcessor := func(e events.Message) bool {
|
||||||
|
stopProcessing := false
|
||||||
|
switch e.Status {
|
||||||
|
case "die":
|
||||||
|
if v, ok := e.Actor.Attributes["exitCode"]; ok {
|
||||||
|
code, cerr := strconv.Atoi(v)
|
||||||
|
if cerr != nil {
|
||||||
|
logrus.Errorf("failed to convert exitcode '%q' to int: %v", v, cerr)
|
||||||
|
} else {
|
||||||
|
exitCode = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !waitRemove {
|
||||||
|
stopProcessing = true
|
||||||
|
} else {
|
||||||
|
// If we are talking to an older daemon, `AutoRemove` is not supported.
|
||||||
|
// We need to fall back to the old behavior, which is client-side removal
|
||||||
|
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") {
|
||||||
|
go func() {
|
||||||
|
removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true})
|
||||||
|
if removeErr != nil {
|
||||||
|
logrus.Errorf("error removing container: %v", removeErr)
|
||||||
|
cancel() // cancel the event Q
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "detach":
|
||||||
|
exitCode = 0
|
||||||
|
stopProcessing = true
|
||||||
|
case "destroy":
|
||||||
|
stopProcessing = true
|
||||||
|
}
|
||||||
|
return stopProcessing
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
statusChan <- exitCode // must always send an exit code or the caller will block
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-eventCtx.Done():
|
||||||
|
if removeErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case evt := <-eventq:
|
||||||
|
if eventProcessor(evt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case err := <-errq:
|
||||||
|
logrus.Errorf("error getting events from daemon: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return statusChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error {
|
||||||
|
if len(containers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
const defaultParallel int = 50
|
||||||
|
sem := make(chan struct{}, defaultParallel)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
// make sure result is printed in correct order
|
||||||
|
output := map[string]chan error{}
|
||||||
|
for _, c := range containers {
|
||||||
|
output[c] = make(chan error, 1)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for _, c := range containers {
|
||||||
|
err := <-output[c]
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for _, c := range containers {
|
||||||
|
sem <- struct{}{} // Wait for active queue sem to drain.
|
||||||
|
go func(container string) {
|
||||||
|
output[container] <- op(ctx, container)
|
||||||
|
<-sem
|
||||||
|
}(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return errChan
|
||||||
|
}
|
||||||
16
pkg/test/2pod.yaml
Normal file
16
pkg/test/2pod.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
labels:
|
||||||
|
app: test
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 0
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
- name: tomcat
|
||||||
|
image: tomcat
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
restartPolicy: Always
|
||||||
@@ -749,3 +749,15 @@ func MoveToTemp() {
|
|||||||
log.Debugln(err)
|
log.Debugln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Merge[K comparable, V any](fromMap, ToMap map[K]V) map[K]V {
|
||||||
|
for keyToMap, valueToMap := range ToMap {
|
||||||
|
fromMap[keyToMap] = valueToMap
|
||||||
|
}
|
||||||
|
if fromMap == nil {
|
||||||
|
// merge(nil, map[string]interface{...}) -> map[string]interface{...}
|
||||||
|
return ToMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromMap
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user