forked from LaconicNetwork/kompose
support multiple containers in a pod (#1394)
This commit is contained in:
parent
c51d59566b
commit
deb00f3407
@ -54,6 +54,10 @@ var (
|
||||
// WithKomposeAnnotation decides if we will add metadata about this convert to resource's annotation.
|
||||
// default is true.
|
||||
WithKomposeAnnotation bool
|
||||
|
||||
// MultipleContainerMode which enables creating multi containers in a single pod is a developping function.
|
||||
// default is false
|
||||
MultipleContainerMode bool
|
||||
)
|
||||
|
||||
var convertCmd = &cobra.Command{
|
||||
@ -95,6 +99,7 @@ var convertCmd = &cobra.Command{
|
||||
IsDeploymentConfigFlag: cmd.Flags().Lookup("deployment-config").Changed,
|
||||
YAMLIndent: ConvertYAMLIndent,
|
||||
WithKomposeAnnotation: WithKomposeAnnotation,
|
||||
MultipleContainerMode: MultipleContainerMode,
|
||||
}
|
||||
|
||||
// Validate before doing anything else. Use "bundle" if passed in.
|
||||
@ -124,6 +129,7 @@ func init() {
|
||||
convertCmd.Flags().MarkHidden("daemon-set")
|
||||
convertCmd.Flags().MarkHidden("replication-controller")
|
||||
convertCmd.Flags().MarkHidden("deployment")
|
||||
convertCmd.Flags().BoolVar(&MultipleContainerMode, "multiple-container-mode", false, "Create multiple containers grouped by 'kompose.service.group' label")
|
||||
|
||||
// OpenShift only
|
||||
convertCmd.Flags().BoolVar(&ConvertDeploymentConfig, "deployment-config", true, "Generate an OpenShift deploymentconfig object")
|
||||
|
||||
@ -158,6 +158,7 @@ The currently supported options are:
|
||||
| Key | Value |
|
||||
|----------------------|-------------------------------------|
|
||||
| kompose.service.type | nodeport / clusterip / loadbalancer / headless |
|
||||
| kompose.service.group | name to group the containers contained in a single pod |
|
||||
| kompose.service.expose | true / hostnames (separated by comma) |
|
||||
| kompose.service.nodeport.port | port value (string) |
|
||||
| kompose.service.expose.tls-secret | secret name |
|
||||
@ -195,6 +196,28 @@ services:
|
||||
kompose.service.type: nodeport
|
||||
```
|
||||
|
||||
- `kompose.service.group` defines the group of containers included in a single pod.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx
|
||||
depends_on:
|
||||
- logs
|
||||
labels:
|
||||
- kompose.service.group=sidecar
|
||||
|
||||
logs:
|
||||
image: busybox
|
||||
command: ["tail -f /var/log/nginx/access.log"]
|
||||
labels:
|
||||
- kompose.service.group=sidecar
|
||||
```
|
||||
|
||||
- `kompose.service.expose` defines if the service needs to be made accessible from outside the cluster or not. If the value is set to "true", the provider sets the endpoint automatically, and for any other value, the value is set as the hostname. If multiple ports are defined in a service, the first one is chosen to be the exposed.
|
||||
- For the Kubernetes provider, an ingress resource is created and it is assumed that an ingress controller has already been configured. If the value is set to a comma sepatated list, multiple hostnames are supported.Hostname with path is also supported.
|
||||
- For the OpenShift provider, a route is created.
|
||||
|
||||
10
go.mod
10
go.mod
@ -17,6 +17,7 @@ replace github.com/containerd/containerd => github.com/containerd/containerd v1.
|
||||
replace golang.org/x/sys => golang.org/x/sys v0.0.0-20201029080932-201ba4db2418
|
||||
|
||||
require (
|
||||
github.com/deckarep/golang-set v1.7.1
|
||||
github.com/docker/cli v0.0.0-20190711175710-5b38d82aa076
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/libcompose v0.4.0
|
||||
@ -26,8 +27,11 @@ require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/imdario/mergo v0.3.10 // indirect
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/mattn/goveralls v0.0.9 // indirect
|
||||
github.com/mitchellh/gox v1.0.1 // indirect
|
||||
github.com/moby/sys/mount v0.1.1 // indirect
|
||||
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 // indirect
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect
|
||||
github.com/novln/docker-parser v1.0.0
|
||||
github.com/openshift/api v0.0.0-20200803131051-87466835fcc0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@ -35,7 +39,11 @@ require (
|
||||
github.com/spf13/cast v1.3.1
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/viper v1.7.1
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
|
||||
36
go.sum
36
go.sum
@ -70,6 +70,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible h1:r99CiNpN5pxrSuSH36suYxrbLxFOhBvQ0sEH6624MHs=
|
||||
@ -194,6 +196,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
@ -244,6 +248,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/goveralls v0.0.9 h1:XmIwwrO9a9pqSW6IpI89BSCShzQxx0j/oKnnvELQNME=
|
||||
github.com/mattn/goveralls v0.0.9/go.mod h1:FRbM1PS8oVsOe9JtdzAAXM+DsvDMMHcM1C7drGJD8HY=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
@ -251,6 +257,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
||||
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@ -268,6 +276,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
@ -385,6 +395,7 @@ github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5 h1:ImnGIsrc
|
||||
github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
@ -399,6 +410,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -414,12 +426,16 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -438,8 +454,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -447,12 +464,12 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@ -482,11 +499,16 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0 h1:6txNFSnY+tteYoO+hf01EpdYcYZiurdC9MDIrcUzEu4=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
||||
@ -77,6 +77,8 @@ type ConvertOptions struct {
|
||||
YAMLIndent int
|
||||
|
||||
WithKomposeAnnotation bool
|
||||
|
||||
MultipleContainerMode bool
|
||||
}
|
||||
|
||||
// IsPodController indicate if the user want to use a controller
|
||||
@ -84,8 +86,11 @@ func (opt *ConvertOptions) IsPodController() bool {
|
||||
return opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag || opt.Controller != ""
|
||||
}
|
||||
|
||||
type ServiceConfigGroup []ServiceConfig
|
||||
|
||||
// ServiceConfig holds the basic struct of a container
|
||||
type ServiceConfig struct {
|
||||
Name string
|
||||
ContainerName string
|
||||
Image string `compose:"image"`
|
||||
Environment []EnvVar `compose:"environment"`
|
||||
|
||||
@ -32,6 +32,8 @@ import (
|
||||
const (
|
||||
// LabelServiceType defines the type of service to be created
|
||||
LabelServiceType = "kompose.service.type"
|
||||
// LabelServiceGroup defines the group of services in a single pod
|
||||
LabelServiceGroup = "kompose.service.group"
|
||||
// LabelNodePortPort defines the port value for NodePort service
|
||||
LabelNodePortPort = "kompose.service.nodeport.port"
|
||||
// LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not
|
||||
|
||||
@ -178,6 +178,7 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose
|
||||
// all relevant information as well as avoid the unsupported keys as well.
|
||||
for name, composeServiceConfig := range composeObject.ServiceConfigs.All() {
|
||||
serviceConfig := kobject.ServiceConfig{}
|
||||
serviceConfig.Name = name
|
||||
serviceConfig.Image = composeServiceConfig.Image
|
||||
serviceConfig.Build = composeServiceConfig.Build.Context
|
||||
newName := normalizeContainerNames(composeServiceConfig.ContainerName)
|
||||
|
||||
@ -364,6 +364,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
|
||||
// No need to modify before importation
|
||||
name := composeServiceConfig.Name
|
||||
serviceConfig := kobject.ServiceConfig{}
|
||||
serviceConfig.Name = name
|
||||
serviceConfig.Image = composeServiceConfig.Image
|
||||
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
|
||||
serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels)
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/kubernetes/kompose/pkg/loader/compose"
|
||||
"github.com/kubernetes/kompose/pkg/transformer"
|
||||
deployapi "github.com/openshift/api/apps/v1"
|
||||
"github.com/pkg/errors"
|
||||
@ -440,11 +441,44 @@ func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceC
|
||||
|
||||
return svc
|
||||
}
|
||||
func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object, podSpec PodSpec) error {
|
||||
// Configure annotations
|
||||
annotations := transformer.ConfigAnnotations(service)
|
||||
|
||||
// fillTemplate fills the pod template with the value calculated from config
|
||||
fillTemplate := func(template *api.PodTemplateSpec) error {
|
||||
template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
|
||||
template.Spec = podSpec.Get()
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillObjectMeta fills the metadata with the value calculated from config
|
||||
fillObjectMeta := func(meta *metav1.ObjectMeta) {
|
||||
meta.Annotations = annotations
|
||||
}
|
||||
|
||||
// update supported controller
|
||||
for _, obj := range *objects {
|
||||
err := k.UpdateController(obj, fillTemplate, fillObjectMeta)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "k.UpdateController failed")
|
||||
}
|
||||
if len(service.Volumes) > 0 {
|
||||
switch objType := obj.(type) {
|
||||
case *appsv1.Deployment:
|
||||
objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
|
||||
case *deployapi.DeploymentConfig:
|
||||
objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateKubernetesObjects loads configurations to k8s objects
|
||||
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
|
||||
// Configure the environment variables.
|
||||
envs, err := k.ConfigEnvs(name, service, opt)
|
||||
envs, err := ConfigEnvs(name, service, opt)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to load env variables")
|
||||
}
|
||||
@ -479,10 +513,10 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
||||
}
|
||||
|
||||
// Configure the container ports.
|
||||
ports := k.ConfigPorts(name, service)
|
||||
ports := ConfigPorts(name, service)
|
||||
|
||||
// Configure capabilities
|
||||
capabilities := k.ConfigCapabilities(service)
|
||||
capabilities := ConfigCapabilities(service)
|
||||
|
||||
// Configure annotations
|
||||
annotations := transformer.ConfigAnnotations(service)
|
||||
@ -662,6 +696,20 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
||||
return nil
|
||||
}
|
||||
|
||||
// KomposeObjectToServiceConfigGroupMapping returns the service config group by name
|
||||
func KomposeObjectToServiceConfigGroupMapping(komposeObject kobject.KomposeObject) map[string]kobject.ServiceConfigGroup {
|
||||
serviceConfigGroup := make(map[string]kobject.ServiceConfigGroup)
|
||||
for name, service := range komposeObject.ServiceConfigs {
|
||||
if groupID, ok := service.Labels[compose.LabelServiceGroup]; ok {
|
||||
service.Name = name
|
||||
serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], service)
|
||||
} else {
|
||||
serviceConfigGroup[name] = append(serviceConfigGroup[name], service)
|
||||
}
|
||||
}
|
||||
return serviceConfigGroup
|
||||
}
|
||||
|
||||
// TranslatePodResource config pod resources
|
||||
func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) {
|
||||
// Configure the resource limits
|
||||
|
||||
@ -536,7 +536,7 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
|
||||
}
|
||||
|
||||
// ConfigPorts configures the container ports.
|
||||
func (k *Kubernetes) ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
|
||||
func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
|
||||
ports := []api.ContainerPort{}
|
||||
exist := map[string]bool{}
|
||||
for _, port := range service.Port {
|
||||
@ -641,7 +641,7 @@ func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConf
|
||||
}
|
||||
|
||||
//ConfigCapabilities configure POSIX capabilities that can be added or removed to a container
|
||||
func (k *Kubernetes) ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
|
||||
func ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
|
||||
capsAdd := []api.Capability{}
|
||||
capsDrop := []api.Capability{}
|
||||
for _, capAdd := range service.CapAdd {
|
||||
@ -947,7 +947,7 @@ func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.Volu
|
||||
}
|
||||
|
||||
// ConfigEnvs configures the environment variables.
|
||||
func (k *Kubernetes) ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
|
||||
func ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
|
||||
envs := transformer.EnvSort{}
|
||||
|
||||
keysFromEnvFile := make(map[string]bool)
|
||||
@ -1132,98 +1132,249 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
|
||||
}
|
||||
}
|
||||
|
||||
sortedKeys := SortedKeys(komposeObject)
|
||||
for _, name := range sortedKeys {
|
||||
service := komposeObject.ServiceConfigs[name]
|
||||
var objects []runtime.Object
|
||||
if opt.MultipleContainerMode {
|
||||
komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(komposeObject)
|
||||
for name, group := range komposeObjectToServiceConfigGroupMapping {
|
||||
service := komposeObject.ServiceConfigs[name]
|
||||
var objects []runtime.Object
|
||||
|
||||
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
|
||||
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
|
||||
|
||||
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
|
||||
// Check that --build is set to true
|
||||
// Check to see if there is an InputFile (required!) before we build the container
|
||||
// Check that there's actually a Build key
|
||||
// Lastly, we must have an Image name to continue
|
||||
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
|
||||
// If there's no "image" key, use the name of the container that's built
|
||||
if service.Image == "" {
|
||||
service.Image = name
|
||||
}
|
||||
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
|
||||
// Check that --build is set to true
|
||||
// Check to see if there is an InputFile (required!) before we build the container
|
||||
// Check that there's actually a Build key
|
||||
// Lastly, we must have an Image name to continue
|
||||
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
|
||||
// If there's no "image" key, use the name of the container that's built
|
||||
if service.Image == "" {
|
||||
service.Image = name
|
||||
}
|
||||
|
||||
if service.Image == "" {
|
||||
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
|
||||
}
|
||||
if service.Image == "" {
|
||||
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
|
||||
}
|
||||
|
||||
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
|
||||
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
|
||||
|
||||
// Build the image!
|
||||
err := transformer.BuildDockerImage(service, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
|
||||
}
|
||||
|
||||
// Push the built image to the repo!
|
||||
if opt.PushImage {
|
||||
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
|
||||
err = transformer.PushDockerImage(service, name)
|
||||
// Build the image!
|
||||
err := transformer.BuildDockerImage(service, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
|
||||
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
|
||||
}
|
||||
|
||||
// Push the built image to the repo!
|
||||
if opt.PushImage {
|
||||
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
|
||||
err = transformer.PushDockerImage(service, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate pod only and nothing more
|
||||
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
|
||||
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
|
||||
pod := k.InitPod(name, service)
|
||||
objects = append(objects, pod)
|
||||
} else {
|
||||
objects = k.CreateKubernetesObjects(name, service, opt)
|
||||
}
|
||||
podSpec := PodSpec{}
|
||||
|
||||
if k.PortsExist(service) {
|
||||
if service.ServiceType == "LoadBalancer" {
|
||||
svcs := k.CreateLBService(name, service, objects)
|
||||
for _, svc := range svcs {
|
||||
// added a container
|
||||
for _, service := range group {
|
||||
podSpec.Append(AddContainer(service, opt))
|
||||
|
||||
// Generate pod only and nothing more
|
||||
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
|
||||
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
|
||||
pod := k.InitPod(name, service)
|
||||
objects = append(objects, pod)
|
||||
} else {
|
||||
objects = k.CreateKubernetesObjects(name, service, opt)
|
||||
}
|
||||
|
||||
if k.PortsExist(service) {
|
||||
if service.ServiceType == "LoadBalancer" {
|
||||
svcs := k.CreateLBService(name, service, objects)
|
||||
for _, svc := range svcs {
|
||||
objects = append(objects, svc)
|
||||
}
|
||||
if len(svcs) > 1 {
|
||||
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
|
||||
}
|
||||
} else {
|
||||
svc := k.CreateService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
if service.ExposeService != "" {
|
||||
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if service.ServiceType == "Headless" {
|
||||
svc := k.CreateHeadlessService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
} else {
|
||||
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the container volumes.
|
||||
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "k.ConfigVolumes failed")
|
||||
}
|
||||
podSpec.Append(
|
||||
SetVolumeMounts(volumesMount),
|
||||
SetVolumes(volumes),
|
||||
)
|
||||
|
||||
// Configure Tmpfs
|
||||
if len(service.TmpFs) > 0 {
|
||||
TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
|
||||
|
||||
volumes = append(volumes, TmpVolumes...)
|
||||
|
||||
volumesMount = append(volumesMount, TmpVolumesMount...)
|
||||
}
|
||||
|
||||
if pvc != nil {
|
||||
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
|
||||
// because the type of objects and pvc is different, but when doing append
|
||||
// one element at a time it gets converted to runtime.Object for objects slice
|
||||
for _, p := range pvc {
|
||||
objects = append(objects, p)
|
||||
}
|
||||
}
|
||||
|
||||
if cms != nil {
|
||||
for _, c := range cms {
|
||||
objects = append(objects, c)
|
||||
}
|
||||
}
|
||||
|
||||
podSpec.Append(
|
||||
SetPorts(name, service),
|
||||
ImagePullPolicy(name, service),
|
||||
RestartPolicy(name, service),
|
||||
SecurityContext(name, service),
|
||||
LivenessProbe(service),
|
||||
ReadinessProbe(service),
|
||||
HostName(service),
|
||||
DomainName(service),
|
||||
ResourcesLimits(service),
|
||||
ResourcesRequests(service),
|
||||
TerminationGracePeriodSeconds(name, service),
|
||||
)
|
||||
|
||||
err = k.UpdateKubernetesObjectsMultipleContainers(name, service, opt, &objects, podSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
||||
}
|
||||
}
|
||||
|
||||
if len(service.Network) > 0 {
|
||||
for _, net := range service.Network {
|
||||
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
|
||||
np, err := k.CreateNetworkPolicy(name, net)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
|
||||
}
|
||||
objects = append(objects, np)
|
||||
}
|
||||
}
|
||||
|
||||
allobjects = append(allobjects, objects...)
|
||||
}
|
||||
} else {
|
||||
sortedKeys := SortedKeys(komposeObject)
|
||||
for _, name := range sortedKeys {
|
||||
service := komposeObject.ServiceConfigs[name]
|
||||
var objects []runtime.Object
|
||||
|
||||
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
|
||||
|
||||
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
|
||||
// Check that --build is set to true
|
||||
// Check to see if there is an InputFile (required!) before we build the container
|
||||
// Check that there's actually a Build key
|
||||
// Lastly, we must have an Image name to continue
|
||||
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
|
||||
// If there's no "image" key, use the name of the container that's built
|
||||
if service.Image == "" {
|
||||
service.Image = name
|
||||
}
|
||||
|
||||
if service.Image == "" {
|
||||
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
|
||||
}
|
||||
|
||||
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
|
||||
|
||||
// Build the image!
|
||||
err := transformer.BuildDockerImage(service, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
|
||||
}
|
||||
|
||||
// Push the built image to the repo!
|
||||
if opt.PushImage {
|
||||
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
|
||||
err = transformer.PushDockerImage(service, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate pod only and nothing more
|
||||
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
|
||||
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
|
||||
pod := k.InitPod(name, service)
|
||||
objects = append(objects, pod)
|
||||
} else {
|
||||
objects = k.CreateKubernetesObjects(name, service, opt)
|
||||
}
|
||||
|
||||
if k.PortsExist(service) {
|
||||
if service.ServiceType == "LoadBalancer" {
|
||||
svcs := k.CreateLBService(name, service, objects)
|
||||
for _, svc := range svcs {
|
||||
objects = append(objects, svc)
|
||||
}
|
||||
if len(svcs) > 1 {
|
||||
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
|
||||
}
|
||||
} else {
|
||||
svc := k.CreateService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
}
|
||||
if len(svcs) > 1 {
|
||||
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
|
||||
if service.ExposeService != "" {
|
||||
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
svc := k.CreateService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
if service.ExposeService != "" {
|
||||
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
|
||||
if service.ServiceType == "Headless" {
|
||||
svc := k.CreateHeadlessService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
} else {
|
||||
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if service.ServiceType == "Headless" {
|
||||
svc := k.CreateHeadlessService(name, service, objects)
|
||||
objects = append(objects, svc)
|
||||
} else {
|
||||
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
|
||||
|
||||
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
||||
}
|
||||
}
|
||||
|
||||
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
||||
}
|
||||
if len(service.Network) > 0 {
|
||||
for _, net := range service.Network {
|
||||
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
|
||||
np, err := k.CreateNetworkPolicy(name, net)
|
||||
|
||||
if len(service.Network) > 0 {
|
||||
for _, net := range service.Network {
|
||||
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
|
||||
np, err := k.CreateNetworkPolicy(name, net)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
|
||||
}
|
||||
objects = append(objects, np)
|
||||
}
|
||||
objects = append(objects, np)
|
||||
}
|
||||
}
|
||||
|
||||
allobjects = append(allobjects, objects...)
|
||||
allobjects = append(allobjects, objects...)
|
||||
}
|
||||
}
|
||||
|
||||
// sort all object so Services are first
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/kubernetes/kompose/pkg/loader/compose"
|
||||
"github.com/kubernetes/kompose/pkg/transformer"
|
||||
deployapi "github.com/openshift/api/apps/v1"
|
||||
"github.com/pkg/errors"
|
||||
@ -535,12 +536,106 @@ func TestConfigCapabilities(t *testing.T) {
|
||||
"ConfigCapsNoAddDrop": {kobject.ServiceConfig{CapAdd: nil, CapDrop: nil}, api.Capabilities{Add: []api.Capability{}, Drop: []api.Capability{}}},
|
||||
}
|
||||
|
||||
k := Kubernetes{}
|
||||
for name, test := range testCases {
|
||||
t.Log("Test case:", name)
|
||||
result := k.ConfigCapabilities(test.service)
|
||||
result := ConfigCapabilities(test.service)
|
||||
if !reflect.DeepEqual(result.Add, test.result.Add) || !reflect.DeepEqual(result.Drop, test.result.Drop) {
|
||||
t.Errorf("Not expected result for ConfigCapabilities")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleContainersInPod(t *testing.T) {
|
||||
groupName := "pod_group"
|
||||
containerName := ""
|
||||
|
||||
createConfig := func(name string, containerName *string) kobject.ServiceConfig {
|
||||
config := newServiceConfig()
|
||||
config.Labels = map[string]string{compose.LabelServiceGroup: groupName}
|
||||
config.Name = name
|
||||
if containerName != nil {
|
||||
config.ContainerName = *containerName
|
||||
}
|
||||
config.Volumes = []kobject.Volumes{
|
||||
{
|
||||
VolumeName: "mountVolume",
|
||||
MountPath: "/data",
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
komposeObject kobject.KomposeObject
|
||||
opt kobject.ConvertOptions
|
||||
expectedNumObjs int
|
||||
expectedNames []string
|
||||
}{
|
||||
"Converted multiple containers": {
|
||||
kobject.KomposeObject{
|
||||
ServiceConfigs: map[string]kobject.ServiceConfig{
|
||||
"app1": createConfig("app1", &containerName),
|
||||
"app2": createConfig("app2", &containerName),
|
||||
},
|
||||
}, kobject.ConvertOptions{MultipleContainerMode: true}, 2, []string{"app1", "app2"}},
|
||||
"Converted multiple containers to Deployments (D)": {
|
||||
kobject.KomposeObject{
|
||||
ServiceConfigs: map[string]kobject.ServiceConfig{
|
||||
"app1": createConfig("app1", &containerName),
|
||||
"app2": createConfig("app2", &containerName),
|
||||
},
|
||||
}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"app1", "app2"}},
|
||||
"Converted multiple containers (ContainerName are nil) to Deployments (D)": {
|
||||
kobject.KomposeObject{
|
||||
ServiceConfigs: map[string]kobject.ServiceConfig{
|
||||
"app1": createConfig("app1", nil),
|
||||
"app2": createConfig("app2", nil),
|
||||
},
|
||||
}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"name", "name"}},
|
||||
// TODO: add more tests
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Log("Test case:", name)
|
||||
k := Kubernetes{}
|
||||
// Run Transform
|
||||
objs, err := k.Transform(test.komposeObject, test.opt)
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, "k.Transform failed"))
|
||||
}
|
||||
if len(objs) != test.expectedNumObjs {
|
||||
t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs))
|
||||
}
|
||||
|
||||
// Check results
|
||||
for _, obj := range objs {
|
||||
if svc, ok := obj.(*api.Service); ok {
|
||||
if svc.Name != groupName {
|
||||
t.Errorf("Expected %v returned, got %v", groupName, svc.Name)
|
||||
}
|
||||
}
|
||||
if deployment, ok := obj.(*appsv1.Deployment); ok {
|
||||
if deployment.Name != groupName {
|
||||
t.Errorf("Expected %v returned, got %v", groupName, deployment.Name)
|
||||
}
|
||||
if len(deployment.Spec.Template.Spec.Containers) != 2 {
|
||||
t.Errorf("Expected %d returned, got %d", 2, len(deployment.Spec.Template.Spec.Containers))
|
||||
}
|
||||
nameSet := make(map[string]api.Container)
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
nameSet[container.Name] = container
|
||||
}
|
||||
if container, ok := nameSet[test.expectedNames[0]]; !ok {
|
||||
t.Errorf("Expected %v returned, got %v", test.expectedNames[0], container.Name)
|
||||
} else if len(container.VolumeMounts) != 1 {
|
||||
t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
|
||||
}
|
||||
if container, ok := nameSet[test.expectedNames[1]]; !ok {
|
||||
t.Errorf("Expected %v returned, got %v", test.expectedNames[1], container.Name)
|
||||
} else if len(container.VolumeMounts) != 1 {
|
||||
t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
352
pkg/transformer/kubernetes/podspec.go
Normal file
352
pkg/transformer/kubernetes/podspec.go
Normal file
@ -0,0 +1,352 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
type PodSpec struct {
|
||||
api.PodSpec
|
||||
}
|
||||
|
||||
type PodSpecOption func(*PodSpec)
|
||||
|
||||
func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
name := service.Name
|
||||
image := service.Image
|
||||
|
||||
if image == "" {
|
||||
image = name
|
||||
}
|
||||
|
||||
// do not override in openshift case?
|
||||
if len(service.ContainerName) > 0 {
|
||||
name = FormatContainerName(service.ContainerName)
|
||||
}
|
||||
|
||||
envs, err := ConfigEnvs(name, service, opt)
|
||||
if err != nil {
|
||||
panic("Unable to load env variables")
|
||||
}
|
||||
|
||||
podSpec.Containers = append(podSpec.Containers, api.Container{
|
||||
Name: name,
|
||||
Image: image,
|
||||
Env: envs,
|
||||
Command: service.Command,
|
||||
Args: service.Args,
|
||||
WorkingDir: service.WorkingDir,
|
||||
Stdin: service.Stdin,
|
||||
TTY: service.Tty,
|
||||
})
|
||||
podSpec.NodeSelector = service.Placement
|
||||
}
|
||||
}
|
||||
|
||||
func ImagePullSecrets(pullSecret string) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets,
|
||||
api.LocalObjectReference{
|
||||
Name: pullSecret,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TerminationGracePeriodSeconds(name string, service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
var err error
|
||||
if service.StopGracePeriod != "" {
|
||||
podSpec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod)
|
||||
if err != nil {
|
||||
log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the resource limits
|
||||
func ResourcesLimits(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if service.MemLimit != 0 || service.CPULimit != 0 {
|
||||
resourceLimit := api.ResourceList{}
|
||||
|
||||
if service.MemLimit != 0 {
|
||||
resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
|
||||
}
|
||||
|
||||
if service.CPULimit != 0 {
|
||||
resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI)
|
||||
}
|
||||
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].Resources.Limits = resourceLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the resource requests
|
||||
func ResourcesRequests(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if service.MemReservation != 0 || service.CPUReservation != 0 {
|
||||
resourceRequests := api.ResourceList{}
|
||||
|
||||
if service.MemReservation != 0 {
|
||||
resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
|
||||
}
|
||||
|
||||
if service.CPUReservation != 0 {
|
||||
resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI)
|
||||
}
|
||||
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].Resources.Requests = resourceRequests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure SecurityContext
|
||||
func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
// Configure resource reservations
|
||||
podSecurityContext := &api.PodSecurityContext{}
|
||||
|
||||
//set pid namespace mode
|
||||
if service.Pid != "" {
|
||||
if service.Pid == "host" {
|
||||
// podSecurityContext.HostPID = true
|
||||
} else {
|
||||
log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
//set supplementalGroups
|
||||
if service.GroupAdd != nil {
|
||||
podSecurityContext.SupplementalGroups = service.GroupAdd
|
||||
}
|
||||
|
||||
// Setup security context
|
||||
securityContext := &api.SecurityContext{}
|
||||
if service.Privileged {
|
||||
securityContext.Privileged = &service.Privileged
|
||||
}
|
||||
if service.User != "" {
|
||||
uid, err := strconv.ParseInt(service.User, 10, 64)
|
||||
if err != nil {
|
||||
log.Warn("Ignoring user directive. User to be specified as a UID (numeric).")
|
||||
} else {
|
||||
securityContext.RunAsUser = &uid
|
||||
}
|
||||
}
|
||||
|
||||
// Configure capabilities
|
||||
capabilities := ConfigCapabilities(service)
|
||||
|
||||
//set capabilities if it is not empty
|
||||
if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 {
|
||||
securityContext.Capabilities = capabilities
|
||||
}
|
||||
|
||||
// update template only if securityContext is not empty
|
||||
if *securityContext != (api.SecurityContext{}) {
|
||||
podSpec.Containers[0].SecurityContext = securityContext
|
||||
}
|
||||
if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) {
|
||||
podSpec.SecurityContext = podSecurityContext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetVolumeNames(volumes []api.Volume) mapset.Set {
|
||||
set := mapset.NewSet()
|
||||
for _, volume := range volumes {
|
||||
set.Add(volume.Name)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func SetVolumes(volumes []api.Volume) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
volumesSet := SetVolumeNames(volumes)
|
||||
containerVolumesSet := SetVolumeNames(podSpec.Volumes)
|
||||
for diffVolumeName := range volumesSet.Difference(containerVolumesSet).Iter() {
|
||||
for _, volume := range volumes {
|
||||
if volume.Name == diffVolumeName {
|
||||
podSpec.Volumes = append(podSpec.Volumes, volume)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
|
||||
set := mapset.NewSet()
|
||||
for _, volumeMount := range volumesMount {
|
||||
set.Add(volumeMount.MountPath)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func SetVolumeMounts(volumesMount []api.VolumeMount) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
volumesMountSet := SetVolumeMountPaths(volumesMount)
|
||||
for i := range podSpec.Containers {
|
||||
containerVolumeMountsSet := SetVolumeMountPaths(podSpec.Containers[i].VolumeMounts)
|
||||
for diffVolumeMountPath := range volumesMountSet.Difference(containerVolumeMountsSet).Iter() {
|
||||
for _, volumeMount := range volumesMount {
|
||||
if volumeMount.MountPath == diffVolumeMountPath {
|
||||
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, volumeMount)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure ports
|
||||
func SetPorts(name string, service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
// Configure the container ports.
|
||||
ports := ConfigPorts(name, service)
|
||||
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].Ports = ports
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the image pull policy
|
||||
func ImagePullPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if policy, err := GetImagePullPolicy(name, service.ImagePullPolicy); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].ImagePullPolicy = policy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the container restart policy.
|
||||
func RestartPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if restart, err := GetRestartPolicy(name, service.Restart); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
podSpec.RestartPolicy = restart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HostName(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
// Configure hostname/domain_name settings
|
||||
if service.HostName != "" {
|
||||
podSpec.Hostname = service.HostName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DomainName(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if service.DomainName != "" {
|
||||
podSpec.Subdomain = service.DomainName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LivenessProbe(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
// Configure the HealthCheck
|
||||
// We check to see if it's blank
|
||||
if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) {
|
||||
probe := api.Probe{}
|
||||
|
||||
if len(service.HealthChecks.Liveness.Test) > 0 {
|
||||
probe.Handler = api.Handler{
|
||||
Exec: &api.ExecAction{
|
||||
Command: service.HealthChecks.Liveness.Test,
|
||||
},
|
||||
}
|
||||
} else if !reflect.ValueOf(service.HealthChecks.Liveness.HTTPPath).IsZero() &&
|
||||
!reflect.ValueOf(service.HealthChecks.Liveness.HTTPPort).IsZero() {
|
||||
probe.Handler = api.Handler{
|
||||
HTTPGet: &api.HTTPGetAction{
|
||||
Path: service.HealthChecks.Liveness.HTTPPath,
|
||||
Port: intstr.FromInt(int(service.HealthChecks.Liveness.HTTPPort)),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
panic(errors.New("Health check must contain a command"))
|
||||
}
|
||||
|
||||
probe.TimeoutSeconds = service.HealthChecks.Liveness.Timeout
|
||||
probe.PeriodSeconds = service.HealthChecks.Liveness.Interval
|
||||
probe.FailureThreshold = service.HealthChecks.Liveness.Retries
|
||||
|
||||
// See issue: https://github.com/docker/cli/issues/116
|
||||
// StartPeriod has been added to docker/cli however, it is not yet added
|
||||
// to compose. Once the feature has been implemented, this will automatically work
|
||||
probe.InitialDelaySeconds = service.HealthChecks.Liveness.StartPeriod
|
||||
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].LivenessProbe = &probe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReadinessProbe(service kobject.ServiceConfig) PodSpecOption {
|
||||
return func(podSpec *PodSpec) {
|
||||
if !reflect.DeepEqual(service.HealthChecks.Readiness, kobject.HealthCheck{}) {
|
||||
probeHealthCheckReadiness := api.Probe{}
|
||||
if len(service.HealthChecks.Readiness.Test) > 0 {
|
||||
probeHealthCheckReadiness.Handler = api.Handler{
|
||||
Exec: &api.ExecAction{
|
||||
Command: service.HealthChecks.Readiness.Test,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
panic(errors.New("Health check must contain a command"))
|
||||
}
|
||||
|
||||
probeHealthCheckReadiness.TimeoutSeconds = service.HealthChecks.Readiness.Timeout
|
||||
probeHealthCheckReadiness.PeriodSeconds = service.HealthChecks.Readiness.Interval
|
||||
probeHealthCheckReadiness.FailureThreshold = service.HealthChecks.Readiness.Retries
|
||||
|
||||
// See issue: https://github.com/docker/cli/issues/116
|
||||
// StartPeriod has been added to docker/cli however, it is not yet added
|
||||
// to compose. Once the feature has been implemented, this will automatically work
|
||||
probeHealthCheckReadiness.InitialDelaySeconds = service.HealthChecks.Readiness.StartPeriod
|
||||
|
||||
for i := range podSpec.Containers {
|
||||
podSpec.Containers[i].ReadinessProbe = &probeHealthCheckReadiness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
|
||||
for _, option := range ops {
|
||||
option(podSpec)
|
||||
}
|
||||
return podSpec
|
||||
}
|
||||
|
||||
func (podSpec *PodSpec) Get() api.PodSpec {
|
||||
return podSpec.PodSpec
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user