mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-01 10:20:35 +00:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
017c4ae0aa | ||
|
|
7b2baad390 | ||
|
|
bc2d69f4f9 | ||
|
|
e913a71fad | ||
|
|
7406dda074 | ||
|
|
0b4df3d414 | ||
|
|
7d38916d63 | ||
|
|
79d1abe573 | ||
|
|
9563052094 | ||
|
|
f881c19bb6 | ||
|
|
8d7b5f82c4 | ||
|
|
7554406c61 | ||
|
|
cf17050170 | ||
|
|
3857a67701 | ||
|
|
10665c6fc9 | ||
|
|
e0a09f4755 | ||
|
|
31c6a55747 | ||
|
|
8332a70d19 | ||
|
|
7fe2338acd | ||
|
|
43d4dbc07a | ||
|
|
f0cf4579d2 | ||
|
|
68ba37f139 | ||
|
|
bf73633cda | ||
|
|
55f8828ba1 | ||
|
|
0e1307dccf | ||
|
|
4471b75912 | ||
|
|
75c6204337 | ||
|
|
1b7171ac9e | ||
|
|
5193d6b4a8 | ||
|
|
6a834b6262 | ||
|
|
083d3cbb65 | ||
|
|
e68411b71e | ||
|
|
664774576c | ||
|
|
37e97084f9 | ||
|
|
de4d8b7dfa | ||
|
|
7f97108686 | ||
|
|
71f069cf95 | ||
|
|
3dbe732cb5 | ||
|
|
e5aea4423b | ||
|
|
100f05260e | ||
|
|
02f9329747 | ||
|
|
b6abd7600c | ||
|
|
2e7093e67f | ||
|
|
3b3a272d27 | ||
|
|
36115a7fa3 | ||
|
|
4d9d54e2c7 | ||
|
|
88aec95628 | ||
|
|
e30401489d | ||
|
|
58bc4b14a2 | ||
|
|
2824c28e08 | ||
|
|
d7cbb95d9c | ||
|
|
e771ec1169 | ||
|
|
9e5374e725 | ||
|
|
4569a09d54 | ||
|
|
25d3ad7522 | ||
|
|
77e18724db | ||
|
|
12d1771bb3 | ||
|
|
a78aa22399 | ||
|
|
05a91893bf | ||
|
|
8d420ec3f7 | ||
|
|
838a766d12 | ||
|
|
50d79e4d3e | ||
|
|
4d2d450f6e | ||
|
|
fdc46fb0b1 | ||
|
|
92ac9b5a0e | ||
|
|
857a9df70f | ||
|
|
969f4f28fa | ||
|
|
58aa45c50a | ||
|
|
5715f4bab4 | ||
|
|
c8502c78f5 | ||
|
|
909de5c94a | ||
|
|
2eaeb83ec3 | ||
|
|
03b9c2a3a3 | ||
|
|
59b98727ec | ||
|
|
5851f96524 | ||
|
|
08be3f061e | ||
|
|
5906aaba19 | ||
|
|
4b6f180d0c | ||
|
|
7f22f187f8 | ||
|
|
fa3a64e352 | ||
|
|
82f2cf9124 | ||
|
|
276693cf0e | ||
|
|
0197c019cc | ||
|
|
9576a81787 | ||
|
|
ff4a1c0b4f | ||
|
|
7dd28b1fd9 | ||
|
|
b754557418 | ||
|
|
f305c0d791 | ||
|
|
3fdaa2e903 | ||
|
|
964c74fb46 | ||
|
|
f14988ff80 | ||
|
|
f1adbfdbff | ||
|
|
072bf992b0 | ||
|
|
2d0d09e178 | ||
|
|
564b0d6827 | ||
|
|
5edae84a9e | ||
|
|
9432671887 | ||
|
|
8fda0f87ab | ||
|
|
08bc8637c8 | ||
|
|
9645f397ef | ||
|
|
ed9f716361 | ||
|
|
9986b65326 | ||
|
|
94dab9ddc4 | ||
|
|
81f246ed60 | ||
|
|
30ed50eb27 | ||
|
|
4325401fe7 | ||
|
|
65af5c13f1 | ||
|
|
9674fd12b2 | ||
|
|
2377902a0b | ||
|
|
1dbde0b085 | ||
|
|
5920563bbd | ||
|
|
23201c27f0 | ||
|
|
d4c7131f8f | ||
|
|
d2b189874b | ||
|
|
98a38eb290 | ||
|
|
aa729229e2 | ||
|
|
afbc1b0401 | ||
|
|
3305be9589 | ||
|
|
36772aac89 | ||
|
|
7755d6cac2 | ||
|
|
0c260ef804 |
102
Gopkg.lock
generated
102
Gopkg.lock
generated
@@ -2,175 +2,224 @@
|
|||||||
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7"
|
||||||
name = "github.com/PuerkitoBio/purell"
|
name = "github.com/PuerkitoBio/purell"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
|
||||||
name = "github.com/PuerkitoBio/urlesc"
|
name = "github.com/PuerkitoBio/urlesc"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
packages = ["spew"]
|
packages = ["spew"]
|
||||||
|
pruneopts = ""
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8"
|
||||||
name = "github.com/emicklei/go-restful"
|
name = "github.com/emicklei/go-restful"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
"log"
|
"log",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "3658237ded108b4134956c1b3050349d93e7b895"
|
revision = "3658237ded108b4134956c1b3050349d93e7b895"
|
||||||
version = "v2.7.1"
|
version = "v2.7.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:dcefbadf4534c5ecac8573698fba6e6e601157bfa8f96aafe29df31ae582ef2a"
|
||||||
name = "github.com/evanphx/json-patch"
|
name = "github.com/evanphx/json-patch"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
|
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
|
||||||
version = "v3.0.0"
|
version = "v3.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
|
||||||
name = "github.com/ghodss/yaml"
|
name = "github.com/ghodss/yaml"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
||||||
name = "github.com/go-openapi/jsonpointer"
|
name = "github.com/go-openapi/jsonpointer"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:3830527ef0f4f9b268d9286661c0f52f9115f8aefd9f45ee7352516f93489ac9"
|
||||||
name = "github.com/go-openapi/jsonreference"
|
name = "github.com/go-openapi/jsonreference"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:238a056875c4b053b4b29984765ee335bf8c539fdf17e527fd9b7aa72521c8dd"
|
||||||
name = "github.com/go-openapi/spec"
|
name = "github.com/go-openapi/spec"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
|
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:7b067ca8b94982960860d18c42e29f15bbd0e8d9ae8145a83a218296e75393cf"
|
||||||
name = "github.com/go-openapi/swag"
|
name = "github.com/go-openapi/swag"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
|
||||||
name = "github.com/gogo/protobuf"
|
name = "github.com/gogo/protobuf"
|
||||||
packages = [
|
packages = [
|
||||||
"proto",
|
"proto",
|
||||||
"sortkeys"
|
"sortkeys",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
|
||||||
name = "github.com/golang/glog"
|
name = "github.com/golang/glog"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
||||||
name = "github.com/golang/protobuf"
|
name = "github.com/golang/protobuf"
|
||||||
packages = [
|
packages = [
|
||||||
"proto",
|
"proto",
|
||||||
"ptypes",
|
"ptypes",
|
||||||
"ptypes/any",
|
"ptypes/any",
|
||||||
"ptypes/duration",
|
"ptypes/duration",
|
||||||
"ptypes/timestamp"
|
"ptypes/timestamp",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
|
||||||
name = "github.com/google/gofuzz"
|
name = "github.com/google/gofuzz"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16"
|
||||||
name = "github.com/googleapis/gnostic"
|
name = "github.com/googleapis/gnostic"
|
||||||
packages = [
|
packages = [
|
||||||
"OpenAPIv2",
|
"OpenAPIv2",
|
||||||
"compiler",
|
"compiler",
|
||||||
"extensions"
|
"extensions",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||||
version = "v0.1.0"
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||||
version = "v1.0"
|
version = "v1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
||||||
name = "github.com/json-iterator/go"
|
name = "github.com/json-iterator/go"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
|
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
|
||||||
name = "github.com/mailru/easyjson"
|
name = "github.com/mailru/easyjson"
|
||||||
packages = [
|
packages = [
|
||||||
"buffer",
|
"buffer",
|
||||||
"jlexer",
|
"jlexer",
|
||||||
"jwriter"
|
"jwriter",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
||||||
name = "github.com/modern-go/concurrent"
|
name = "github.com/modern-go/concurrent"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:420f9231f816eeca3ff5aab070caac3ed7f27e4d37ded96ce9de3d7a7a2e31ad"
|
||||||
name = "github.com/modern-go/reflect2"
|
name = "github.com/modern-go/reflect2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
version = "v0.8.0"
|
version = "v0.8.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:74c32990510c9f188556aa17600313e867d1d06f5a9db244056a95d144ec34ce"
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||||
version = "v0.0.2"
|
version = "v0.0.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
|
||||||
name = "github.com/spf13/pflag"
|
name = "github.com/spf13/pflag"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||||
version = "v1.0.1"
|
version = "v1.0.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = [
|
packages = [
|
||||||
"http/httpguts",
|
"http/httpguts",
|
||||||
"http2",
|
"http2",
|
||||||
"http2/hpack",
|
"http2/hpack",
|
||||||
"idna"
|
"idna",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
|
||||||
name = "golang.org/x/text"
|
name = "golang.org/x/text"
|
||||||
packages = [
|
packages = [
|
||||||
"collate",
|
"collate",
|
||||||
@@ -187,25 +236,31 @@
|
|||||||
"unicode/cldr",
|
"unicode/cldr",
|
||||||
"unicode/norm",
|
"unicode/norm",
|
||||||
"unicode/rangetable",
|
"unicode/rangetable",
|
||||||
"width"
|
"width",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
version = "v0.3.0"
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6"
|
||||||
name = "gopkg.in/inf.v0"
|
name = "gopkg.in/inf.v0"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||||
version = "v0.9.1"
|
version = "v0.9.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
|
||||||
name = "gopkg.in/yaml.v2"
|
name = "gopkg.in/yaml.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||||
version = "v2.2.1"
|
version = "v2.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:663df6da5560210fc39194a0a2c4fceba09ead717c330f1174bb15597cf18ce8"
|
||||||
name = "k8s.io/api"
|
name = "k8s.io/api"
|
||||||
packages = [
|
packages = [
|
||||||
"admissionregistration/v1alpha1",
|
"admissionregistration/v1alpha1",
|
||||||
@@ -235,12 +290,14 @@
|
|||||||
"settings/v1alpha1",
|
"settings/v1alpha1",
|
||||||
"storage/v1",
|
"storage/v1",
|
||||||
"storage/v1alpha1",
|
"storage/v1alpha1",
|
||||||
"storage/v1beta1"
|
"storage/v1beta1",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:bcb2285bb525712de7903a5d254c2789df65c8b58d2cfac5a26d950ad94c2079"
|
||||||
name = "k8s.io/apimachinery"
|
name = "k8s.io/apimachinery"
|
||||||
packages = [
|
packages = [
|
||||||
"pkg/api/resource",
|
"pkg/api/resource",
|
||||||
@@ -274,28 +331,51 @@
|
|||||||
"pkg/util/yaml",
|
"pkg/util/yaml",
|
||||||
"pkg/watch",
|
"pkg/watch",
|
||||||
"third_party/forked/golang/json",
|
"third_party/forked/golang/json",
|
||||||
"third_party/forked/golang/reflect"
|
"third_party/forked/golang/reflect",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f"
|
||||||
name = "k8s.io/client-go"
|
name = "k8s.io/client-go"
|
||||||
packages = ["kubernetes/scheme"]
|
packages = ["kubernetes/scheme"]
|
||||||
|
pruneopts = ""
|
||||||
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
||||||
version = "v7.0.0"
|
version = "v7.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
digest = "1:386c5d69077ce740614e8309ddf107dde91a5db25d3d779143f452fb4fbdfd1e"
|
||||||
name = "k8s.io/kube-openapi"
|
name = "k8s.io/kube-openapi"
|
||||||
packages = [
|
packages = [
|
||||||
"pkg/common",
|
"pkg/common",
|
||||||
"pkg/util/proto"
|
"pkg/util/proto",
|
||||||
]
|
]
|
||||||
|
pruneopts = ""
|
||||||
revision = "b3f03f55328800731ce03a164b80973014ecd455"
|
revision = "b3f03f55328800731ce03a164b80973014ecd455"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "586d4cb9094e9b5c0731f16e931b953e9c0f709b7125a7537ae3625ada179eee"
|
input-imports = [
|
||||||
|
"github.com/evanphx/json-patch",
|
||||||
|
"github.com/ghodss/yaml",
|
||||||
|
"github.com/golang/glog",
|
||||||
|
"github.com/pkg/errors",
|
||||||
|
"github.com/spf13/cobra",
|
||||||
|
"k8s.io/api/core/v1",
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||||
|
"k8s.io/apimachinery/pkg/runtime",
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
|
"k8s.io/apimachinery/pkg/util/mergepatch",
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets",
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation",
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml",
|
||||||
|
"k8s.io/client-go/kubernetes/scheme",
|
||||||
|
"k8s.io/kube-openapi/pkg/common",
|
||||||
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ function testGoMetalinter {
|
|||||||
--enable=misspell \
|
--enable=misspell \
|
||||||
--enable=structcheck \
|
--enable=structcheck \
|
||||||
--enable=deadcode \
|
--enable=deadcode \
|
||||||
--enable=goimports \
|
# Disabling 'goimports' because it reports hyphens in imported package \
|
||||||
|
# names as errors, and we have to vendor them in regardless. \
|
||||||
|
# --enable=goimports \
|
||||||
--enable=varcheck \
|
--enable=varcheck \
|
||||||
--enable=goconst \
|
--enable=goconst \
|
||||||
--enable=unparam \
|
--enable=unparam \
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[releases page]: https://github.com/kubernetes-sigs/kustomize/releases
|
[releases page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||||
[`container-builder-local`]: https://github.com/GoogleCloudPlatform/container-builder-local
|
[`cloud-build-local`]: https://github.com/GoogleCloudPlatform/cloud-build-local
|
||||||
[Google Container Builder]: https://cloud.google.com/container-builder
|
[Google Cloud Build]: https://cloud.google.com/cloud-build
|
||||||
|
|
||||||
Scripts and configuration files for publishing a
|
Scripts and configuration files for publishing a
|
||||||
`kustomize` release on the [releases page].
|
`kustomize` release on the [releases page].
|
||||||
|
|
||||||
### Build a release locally
|
### Build a release locally
|
||||||
|
|
||||||
Install [`container-builder-local`], then run
|
Install [`cloud-build-local`], then run
|
||||||
|
|
||||||
```
|
```
|
||||||
container-builder-local \
|
cloud-build-local \
|
||||||
--config=build/cloudbuild_local.yaml \
|
--config=build/cloudbuild_local.yaml \
|
||||||
--dryrun=false --write-workspace=/tmp/w .
|
--dryrun=false --write-workspace=/tmp/w .
|
||||||
```
|
```
|
||||||
@@ -41,5 +41,5 @@ Push the tag upstream:
|
|||||||
git push upstream $version
|
git push upstream $version
|
||||||
```
|
```
|
||||||
|
|
||||||
The new tag will trigger a job in [Google Container
|
The new tag will trigger a job in [Google Cloud
|
||||||
Builder] to put a new release on the [releases page].
|
Build] to put a new release on the [releases page].
|
||||||
|
|||||||
@@ -56,4 +56,4 @@ case $key in
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
/goreleaser release --config=build/goreleaser.yml --rm-dist --skip-validate ${SNAPSHOT}
|
/goreleaser release --config=build/goreleaser.yaml --rm-dist --skip-validate ${SNAPSHOT}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ project_name: kustomize
|
|||||||
builds:
|
builds:
|
||||||
- main: ./kustomize.go
|
- main: ./kustomize.go
|
||||||
binary: kustomize
|
binary: kustomize
|
||||||
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
|
ldflags: -s -X github.com/kubernetes-sigs/kustomize/pkg/commands.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.buildDate={{.Date}}
|
||||||
goos:
|
goos:
|
||||||
- darwin
|
- darwin
|
||||||
- linux
|
- linux
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# This is an example goreleaser.yaml file with some sane defaults.
|
|
||||||
# Make sure to check the documentation at http://goreleaser.com
|
|
||||||
project_name: kustomize
|
|
||||||
builds:
|
|
||||||
- main: ./kustomize.go
|
|
||||||
binary: kustomize
|
|
||||||
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
- linux
|
|
||||||
- windows
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
checksum:
|
|
||||||
name_template: 'checksums.txt'
|
|
||||||
archive:
|
|
||||||
format: binary
|
|
||||||
snapshot:
|
|
||||||
name_template: "master"
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- '^docs:'
|
|
||||||
- '^test:'
|
|
||||||
- Merge pull request
|
|
||||||
- Merge branch
|
|
||||||
release:
|
|
||||||
github:
|
|
||||||
owner: kubernetes-sigs
|
|
||||||
name: kustomize
|
|
||||||
80
docs/eschewedFeatures.md
Normal file
80
docs/eschewedFeatures.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Eschewed Features
|
||||||
|
|
||||||
|
## Removal directives
|
||||||
|
|
||||||
|
`kustomize` supports configurations that can be reasoned about as
|
||||||
|
_compositions_ or _mixins_ - concepts that are widely accepted as
|
||||||
|
a best practice in various programming languages.
|
||||||
|
|
||||||
|
To this end, `kustomize` offers various _addition_ directives. One
|
||||||
|
can add labels, annotations, patches, resources and bases.
|
||||||
|
Corresponding _removal_ directives are not offered.
|
||||||
|
|
||||||
|
Removal semantics would introduce many possibilities for
|
||||||
|
inconsistency, and the need to add code to detect, report and
|
||||||
|
reject it. It would also allow, and possibly encourage,
|
||||||
|
unnecessarily complex configuration layouts.
|
||||||
|
|
||||||
|
When faced with a situation where removal is desirable, it's
|
||||||
|
always possible to remove things from a base like labels and
|
||||||
|
annotations, and/or split multi-resource manifests into individual
|
||||||
|
resource files - then add things back as desired via the
|
||||||
|
[kustomization].
|
||||||
|
|
||||||
|
If the underlying base is outside of one's control, an [OTS
|
||||||
|
workflow] is the recommended best practice. Fork the base, remove
|
||||||
|
what you don't want and commit it to your private fork, then use
|
||||||
|
kustomize on your fork. As often as desired, use _git rebase_ to
|
||||||
|
capture improvements from the upstream base.
|
||||||
|
|
||||||
|
## Build-time side effects from CLI args or env variables
|
||||||
|
|
||||||
|
`kustomize` supports the best practice of storing one's
|
||||||
|
entire configuration in a version control system.
|
||||||
|
|
||||||
|
Changing `kustomize build` configuration output as a result
|
||||||
|
of additional arguments or flags to `build`, or by
|
||||||
|
consulting shell environment variable values in `build`
|
||||||
|
code, would violate that goal.
|
||||||
|
|
||||||
|
`kustomize` insteads offers [kustomization] file `edit`
|
||||||
|
commands. Like any shell command, they can accept
|
||||||
|
environment variable arguments.
|
||||||
|
|
||||||
|
For example, to set the tag used on an image to match an
|
||||||
|
environment variable, run
|
||||||
|
|
||||||
|
```
|
||||||
|
kustomize edit set imagetag nginx:$MY_NGINX_VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
as part of some encapsulating work flow executed before
|
||||||
|
`kustomize build`.
|
||||||
|
|
||||||
|
|
||||||
|
## Globs in kustomization files
|
||||||
|
|
||||||
|
`kustomize` supports the best practice of storing one's
|
||||||
|
entire configuration in a version control system.
|
||||||
|
|
||||||
|
Globbing the local file system for files not explicitly
|
||||||
|
declared in the [kustomization] file at `kustomize build` time
|
||||||
|
would violate that goal.
|
||||||
|
|
||||||
|
Allowing globbing in a kustomization file would also introduce
|
||||||
|
the same problems as allowing globbing in [java import]
|
||||||
|
declarations or BUILD/Makefile dependency rules.
|
||||||
|
|
||||||
|
`kustomize` will instead provide kustomization file editting
|
||||||
|
commands that accept globbed arguments, expand them at _edit
|
||||||
|
time_ relative to the local file system, and store the resulting
|
||||||
|
explicit names into the kustomization file.
|
||||||
|
|
||||||
|
In this way the resources, patches and bases used at _build time_
|
||||||
|
remain explicitly declared in version control.
|
||||||
|
|
||||||
|
|
||||||
|
[base]: glossary.md#base
|
||||||
|
[kustomization]: glossary.md#kustomization
|
||||||
|
[OTS workflow]: workflows.md#off-the-shelf-configuration
|
||||||
|
[java import]: https://www.codebyamir.com/blog/pitfalls-java-import-wildcards
|
||||||
@@ -136,3 +136,67 @@ patches:
|
|||||||
crds:
|
crds:
|
||||||
- crds/typeA.yaml
|
- crds/typeA.yaml
|
||||||
- crds/typeB.yaml
|
- crds/typeB.yaml
|
||||||
|
|
||||||
|
# Vars are used to insert values from resources that cannot be referenced
|
||||||
|
# otherwise. For example if you need to pass a Service's name to the arguments
|
||||||
|
# or environment variables of a program but without hard coding the actual name
|
||||||
|
# of the Service you'd insert `$(MY_SERVICE_NAME)` into the value field of the
|
||||||
|
# env var or into the command or args of the container as shown here:
|
||||||
|
# ```
|
||||||
|
# containers:
|
||||||
|
# - image: myimage
|
||||||
|
# command: ["start", "--host", "$(MY_SERVICE_NAME)"]
|
||||||
|
# env:
|
||||||
|
# - name: SECRET_TOKEN
|
||||||
|
# value: $(SOME_SECRET_NAME)
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Then you'll add an entry to `vars:` like shown below with the same name
|
||||||
|
# and a reference to the resource from which to pull the field's value.
|
||||||
|
# The actual field's path is optional and by default it will use
|
||||||
|
# `metadata.name`. Currently only string type fields are supported, no integers
|
||||||
|
# or booleans, etc. Also array access is currently not possible. For example getting
|
||||||
|
# the image field of container number 2 inside of a pod can currently not be done.
|
||||||
|
#
|
||||||
|
# Not every location of a variable is supported. To see a complete list of locations
|
||||||
|
# see the file [refvars.go](https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/transformers/refvars.go#L20).
|
||||||
|
#
|
||||||
|
# An example of a situation where you'd not use vars is when you'd like to set a
|
||||||
|
# pod's `serviceAccountName`. In that case you would just reference the ServiceAccount
|
||||||
|
# by name and Kustomize will resolve it to the eventual name while building the manifests.
|
||||||
|
vars:
|
||||||
|
- name: SOME_SECRET_NAME
|
||||||
|
objref:
|
||||||
|
kind: Secret
|
||||||
|
name: my-secret
|
||||||
|
apiVersion: v1
|
||||||
|
- name: MY_SERVICE_NAME
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: my-service
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: ANOTHER_DEPLOYMENTS_POD_RESTART_POLICY
|
||||||
|
objref:
|
||||||
|
kind: Deployment
|
||||||
|
name: my-deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.template.spec.restartPolicy
|
||||||
|
|
||||||
|
# ImageTags modify the tags for images without creating patches.
|
||||||
|
# E.g. Given this fragment of a Deployment:
|
||||||
|
# ```
|
||||||
|
# containers:
|
||||||
|
# - name: myapp
|
||||||
|
# image: mycontainerregistry/myimage:v0
|
||||||
|
# - name: nginxapp
|
||||||
|
# image: nginx:1.7.9
|
||||||
|
#```
|
||||||
|
# one can change the tag of myimage to v1 and the tag of nginx to 1.8.0 with the following:
|
||||||
|
imageTags:
|
||||||
|
- name: mycontainerregistry/myimage
|
||||||
|
newTag: v1
|
||||||
|
- name: nginx
|
||||||
|
newTag: 1.8.0
|
||||||
|
|||||||
@@ -23,11 +23,18 @@ go get github.com/kubernetes-sigs/kustomize
|
|||||||
* [springboot](springboot/README.md) - Create a Spring Boot
|
* [springboot](springboot/README.md) - Create a Spring Boot
|
||||||
application production configuration from scratch.
|
application production configuration from scratch.
|
||||||
|
|
||||||
* [configGeneration](configGeneration.md) -
|
* [combineConfigs](combineConfigs.md) -
|
||||||
Mixing configuration data from different owners
|
Mixing configuration data from different owners
|
||||||
(e.g. devops/SRE and developers).
|
(e.g. devops/SRE and developers).
|
||||||
|
|
||||||
|
* [configGenerations](configGeneration.md) -
|
||||||
|
Rolling update when ConfigMapGenerator changes
|
||||||
|
|
||||||
* [breakfast](breakfast.md) - Customize breakfast for
|
* [breakfast](breakfast.md) - Customize breakfast for
|
||||||
Alice and Bob.
|
Alice and Bob.
|
||||||
|
|
||||||
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
|
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
|
||||||
|
|
||||||
|
* [image tags](imageTags.md) - Updating image tags without applying a patch.
|
||||||
|
|
||||||
|
* [multibases](multibases/README.md) - Composing three variants (dev, staging, production) with a common base.
|
||||||
298
examples/combineConfigs.md
Normal file
298
examples/combineConfigs.md
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
[overlay]: ../docs/glossary.md#overlay
|
||||||
|
[target]: ../docs/glossary.md#target
|
||||||
|
|
||||||
|
# Demo: combining config data from devops and developers
|
||||||
|
|
||||||
|
Scenario: you have a Java-based server storefront in
|
||||||
|
production that various internal development teams
|
||||||
|
(signups, checkout, search, etc.) contribute to.
|
||||||
|
|
||||||
|
The server runs in different environments:
|
||||||
|
_development_, _testing_, _staging_ and _production_,
|
||||||
|
accepting configuration parameters from java property
|
||||||
|
files.
|
||||||
|
|
||||||
|
Using one big properties file for each environment is
|
||||||
|
difficult to manage. The files change frequently, and
|
||||||
|
have to be changed by devops exclusively because
|
||||||
|
|
||||||
|
1. the files must at least partially agree on certain
|
||||||
|
values that devops cares about and that developers
|
||||||
|
ignore and
|
||||||
|
1. because the production
|
||||||
|
properties contain sensitive data like production
|
||||||
|
database credentials.
|
||||||
|
|
||||||
|
## Property sharding
|
||||||
|
|
||||||
|
With some study, we notice that the properties are
|
||||||
|
separable into categories.
|
||||||
|
|
||||||
|
### Common properties
|
||||||
|
|
||||||
|
E.g. internationalization data, static data like
|
||||||
|
physical constants, location of external services, etc.
|
||||||
|
|
||||||
|
_Things that are the same regardless of environment._
|
||||||
|
|
||||||
|
Only one set of values is needed.
|
||||||
|
|
||||||
|
Place them in a file called
|
||||||
|
|
||||||
|
* `common.properties`
|
||||||
|
|
||||||
|
(relative location defined below).
|
||||||
|
|
||||||
|
### Plumbing properties
|
||||||
|
|
||||||
|
E.g. serving location of static content (HTML, CSS,
|
||||||
|
javascript), location of product and customer database
|
||||||
|
tables, ports expected by load balancers, log sinks,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
_The different values for these properties are
|
||||||
|
precisely what sets the environments apart._
|
||||||
|
|
||||||
|
Devops or SRE will want full control over the values
|
||||||
|
used in production. Testing will have fixed
|
||||||
|
databases supporting testing. Developers will want
|
||||||
|
to do whatever they want to try scenarios under
|
||||||
|
development.
|
||||||
|
|
||||||
|
Places these values in
|
||||||
|
|
||||||
|
* `development/plumbing.properties`
|
||||||
|
* `staging/plumbing.properties`
|
||||||
|
* `production/plumbing.properties`
|
||||||
|
|
||||||
|
|
||||||
|
### Secret properties
|
||||||
|
|
||||||
|
E.g. location of actual user tables, database
|
||||||
|
credentials, decryption keys, etc.
|
||||||
|
|
||||||
|
_Things that are a subset of devops controls, that
|
||||||
|
nobody else has (or should want) access to._
|
||||||
|
|
||||||
|
Places these values in
|
||||||
|
|
||||||
|
* `development/secret.properties`
|
||||||
|
* `staging/secret.properties`
|
||||||
|
* `production/secret.properties`
|
||||||
|
|
||||||
|
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
|
||||||
|
|
||||||
|
and control access to them with (for example) unix file
|
||||||
|
owner and mode bits, or better yet, put them in
|
||||||
|
a server dedicated to storing password protected
|
||||||
|
secrets, and use a field called `secretGenerator`
|
||||||
|
in your _kustomization_ to create a kubernetes
|
||||||
|
secret holding them (not covering that here).
|
||||||
|
|
||||||
|
<!--
|
||||||
|
secretGenerator:
|
||||||
|
- name: app-tls
|
||||||
|
commands:
|
||||||
|
tls.crt: "cat tls.cert"
|
||||||
|
tls.key: "cat tls.key"
|
||||||
|
type: "kubernetes.io/tls"
|
||||||
|
EOF
|
||||||
|
-->
|
||||||
|
|
||||||
|
## A mixin approach to management
|
||||||
|
|
||||||
|
The way to create _n_ cluster environments that share
|
||||||
|
some common information is to create _n_ overlays of a
|
||||||
|
common base.
|
||||||
|
|
||||||
|
For the rest of this example, we'll do _n==2_, just
|
||||||
|
_development_ and _production_, since adding more
|
||||||
|
environments follows the same pattern.
|
||||||
|
|
||||||
|
A cluster environment is created by
|
||||||
|
running `kustomize build` on a [target] that happens to
|
||||||
|
be an [overlay].
|
||||||
|
|
||||||
|
[helloworld]: helloWorld/README.md
|
||||||
|
|
||||||
|
The following example will do that, but will focus on
|
||||||
|
configMap construction, and not worry about how to
|
||||||
|
connect the configMaps to deployments (that is covered
|
||||||
|
in the [helloworld] example).
|
||||||
|
|
||||||
|
|
||||||
|
All files - including the shared property files
|
||||||
|
discussed above - will be created in a directory tree
|
||||||
|
that is consistent with the base vs overlay file layout
|
||||||
|
defined in the [helloworld] demo.
|
||||||
|
|
||||||
|
It will all live in this work directory:
|
||||||
|
|
||||||
|
<!-- @makeWorkplace @test -->
|
||||||
|
```
|
||||||
|
DEMO_HOME=$(mktemp -d)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the base
|
||||||
|
|
||||||
|
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
|
||||||
|
|
||||||
|
Make a place to put the base configuration:
|
||||||
|
|
||||||
|
<!-- @baseDir @test -->
|
||||||
|
```
|
||||||
|
mkdir -p $DEMO_HOME/base
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the data for the base. This direction by
|
||||||
|
definition should hold resources common to all
|
||||||
|
environments. Here we're only defining a java
|
||||||
|
properties file, and a `kustomization` file that
|
||||||
|
references it.
|
||||||
|
|
||||||
|
<!-- @baseKustomization @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/base/common.properties
|
||||||
|
color=blue
|
||||||
|
height=10m
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
|
||||||
|
configMapGenerator:
|
||||||
|
- name: my-configmap
|
||||||
|
files:
|
||||||
|
- common.properties
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Create and use the overlay for _development_
|
||||||
|
|
||||||
|
Make an abbreviation for the parent of the overlay
|
||||||
|
directories:
|
||||||
|
|
||||||
|
<!-- @overlays @test -->
|
||||||
|
```
|
||||||
|
OVERLAYS=$DEMO_HOME/overlays
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the files that define the _development_ overlay:
|
||||||
|
|
||||||
|
<!-- @developmentFiles @test -->
|
||||||
|
```
|
||||||
|
mkdir -p $OVERLAYS/development
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/development/plumbing.properties
|
||||||
|
port=30000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/development/secret.properties
|
||||||
|
dbpassword=mothersMaidenName
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/development/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ../../base
|
||||||
|
namePrefix: dev-
|
||||||
|
configMapGenerator:
|
||||||
|
- name: my-configmap
|
||||||
|
behavior: merge
|
||||||
|
files:
|
||||||
|
- plumbing.properties
|
||||||
|
- secret.properties
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
One can now generate the configMaps for development:
|
||||||
|
|
||||||
|
<!-- @runDev @test -->
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/development
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Check the ConfigMap name
|
||||||
|
|
||||||
|
The name of the generated `ConfigMap` is visible in this
|
||||||
|
output.
|
||||||
|
|
||||||
|
The name should be something like `dev-my-configmap-b5m75ck895`:
|
||||||
|
|
||||||
|
* `"dev-"` comes from the `namePrefix` field,
|
||||||
|
* `"my-configmap"` comes from the `configMapGenerator/name` field,
|
||||||
|
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
|
||||||
|
computes from the contents of the configMap.
|
||||||
|
|
||||||
|
The hash suffix is critical. If the configMap content
|
||||||
|
changes, so does the configMap name, along with all
|
||||||
|
references to that name that appear in the YAML output
|
||||||
|
from `kustomize`.
|
||||||
|
|
||||||
|
The name change means deployments will do a rolling
|
||||||
|
restart to get new data if this YAML is applied to the
|
||||||
|
cluster using a command like
|
||||||
|
|
||||||
|
> ```
|
||||||
|
> kustomize build $OVERLAYS/development | kubectl apply -f -
|
||||||
|
> ```
|
||||||
|
|
||||||
|
A deployment has no means to automatically know when or
|
||||||
|
if a configMap in use by the deployment changes.
|
||||||
|
|
||||||
|
If one changes a configMap without changing its name
|
||||||
|
and all references to that name, one must imperatively
|
||||||
|
restart the cluster to pick up the change.
|
||||||
|
|
||||||
|
The best practice is to treat configMaps as immutable.
|
||||||
|
|
||||||
|
Instead of editing configMaps, modify your declarative
|
||||||
|
specification of the cluster's desired state to
|
||||||
|
point deployments to _new_ configMaps with _new_ names.
|
||||||
|
`kustomize` makes this easy with its
|
||||||
|
`configMapGenerator` directive and associated naming
|
||||||
|
controls. A GC process in the k8s master eventually
|
||||||
|
deletes unused configMaps.
|
||||||
|
|
||||||
|
|
||||||
|
### Create and use the overlay for _production_
|
||||||
|
|
||||||
|
Next, create the files for the _production_ overlay:
|
||||||
|
|
||||||
|
|
||||||
|
<!-- @productionFiles @test -->
|
||||||
|
```
|
||||||
|
mkdir -p $OVERLAYS/production
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/production/plumbing.properties
|
||||||
|
port=8080
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/production/secret.properties
|
||||||
|
dbpassword=thisShouldProbablyBeInASecretInstead
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$OVERLAYS/production/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ../../base
|
||||||
|
namePrefix: prod-
|
||||||
|
configMapGenerator:
|
||||||
|
- name: my-configmap
|
||||||
|
behavior: merge
|
||||||
|
files:
|
||||||
|
- plumbing.properties
|
||||||
|
- secret.properties
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
One can now generate the configMaps for production:
|
||||||
|
|
||||||
|
<!-- @runProd @test -->
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/production
|
||||||
|
```
|
||||||
|
|
||||||
|
A CICD process could apply this directly to
|
||||||
|
the cluser using:
|
||||||
|
|
||||||
|
> ```
|
||||||
|
> kustomize build $OVERLAYS/production | kubectl apply -f -
|
||||||
|
> ```
|
||||||
@@ -1,298 +1,208 @@
|
|||||||
[overlay]: ../docs/glossary.md#overlay
|
[patch]: ../../docs/glossary.md#patch
|
||||||
[target]: ../docs/glossary.md#target
|
[resource]: ../../docs/glossary.md#resource
|
||||||
|
[variant]: ../../docs/glossary.md#variant
|
||||||
|
|
||||||
# Demo: combining config data from devops and developers
|
## ConfigMap generation and rolling updates
|
||||||
|
|
||||||
Scenario: you have a Java-based server storefront in
|
Kustomize provides two ways of adding ConfigMap in one `kustomization`, either by declaring ConfigMap as a [resource] or declaring ConfigMap from a ConfigMapGenerator. The formats inside `kustomization.yaml` are
|
||||||
production that various internal development teams
|
|
||||||
(signups, checkout, search, etc.) contribute to.
|
|
||||||
|
|
||||||
The server runs in different environments:
|
> ```
|
||||||
_development_, _testing_, _staging_ and _production_,
|
> # declare ConfigMap as a resource
|
||||||
accepting configuration parameters from java property
|
> resources:
|
||||||
files.
|
> - configmap.yaml
|
||||||
|
>
|
||||||
|
> # declare ConfigMap from a ConfigMapGenerator
|
||||||
|
> configMapGenerator:
|
||||||
|
> - name: a-configmap
|
||||||
|
> files:
|
||||||
|
> - configs/configfile
|
||||||
|
> - configs/another_configfile
|
||||||
|
> ```
|
||||||
|
|
||||||
Using one big properties file for each environment is
|
The ConfigMaps declared as [resource] are treated the same way as other resources. Kustomize doesn't append any hash to the ConfigMap name. The ConfigMap declared from a ConfigMapGenerator is treated differently. A hash is appended to the name and any change in the ConfigMap will trigger a rolling update.
|
||||||
difficult to manage. The files change frequently, and
|
|
||||||
have to be changed by devops exclusively because
|
|
||||||
|
|
||||||
1. the files must at least partially agree on certain
|
In this demo, the same [hello_world](helloWorld/README.md) is used while the ConfigMap declared as [resources] is replaced by a ConfigMap declared from a ConfigmapGenerator. The change in this ConfigMap will result in a hash change and a rolling update.
|
||||||
values that devops cares about and that developers
|
|
||||||
ignore and
|
|
||||||
1. because the production
|
|
||||||
properties contain sensitive data like production
|
|
||||||
database credentials.
|
|
||||||
|
|
||||||
## Property sharding
|
### Establish base and staging
|
||||||
|
|
||||||
With some study, we notice that the properties are
|
Establish the base with a configMapGenerator
|
||||||
separable into categories.
|
<!-- @establishBase @test -->
|
||||||
|
|
||||||
### Common properties
|
|
||||||
|
|
||||||
E.g. internationalization data, static data like
|
|
||||||
physical constants, location of external services, etc.
|
|
||||||
|
|
||||||
_Things that are the same regardless of environment._
|
|
||||||
|
|
||||||
Only one set of values is needed.
|
|
||||||
|
|
||||||
Place them in a file called
|
|
||||||
|
|
||||||
* `common.properties`
|
|
||||||
|
|
||||||
(relative location defined below).
|
|
||||||
|
|
||||||
### Plumbing properties
|
|
||||||
|
|
||||||
E.g. serving location of static content (HTML, CSS,
|
|
||||||
javascript), location of product and customer database
|
|
||||||
tables, ports expected by load balancers, log sinks,
|
|
||||||
etc.
|
|
||||||
|
|
||||||
_The different values for these properties are
|
|
||||||
precisely what sets the environments apart._
|
|
||||||
|
|
||||||
Devops or SRE will want full control over the values
|
|
||||||
used in production. Testing will have fixed
|
|
||||||
databases supporting testing. Developers will want
|
|
||||||
to do whatever they want to try scenarios under
|
|
||||||
development.
|
|
||||||
|
|
||||||
Places these values in
|
|
||||||
|
|
||||||
* `development/plumbing.properties`
|
|
||||||
* `staging/plumbing.properties`
|
|
||||||
* `production/plumbing.properties`
|
|
||||||
|
|
||||||
|
|
||||||
### Secret properties
|
|
||||||
|
|
||||||
E.g. location of actual user tables, database
|
|
||||||
credentials, decryption keys, etc.
|
|
||||||
|
|
||||||
_Things that are a subset of devops controls, that
|
|
||||||
nobody else has (or should want) access to._
|
|
||||||
|
|
||||||
Places these values in
|
|
||||||
|
|
||||||
* `development/secret.properties`
|
|
||||||
* `staging/secret.properties`
|
|
||||||
* `production/secret.properties`
|
|
||||||
|
|
||||||
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
|
|
||||||
|
|
||||||
and control access to them with (for example) unix file
|
|
||||||
owner and mode bits, or better yet, put them in
|
|
||||||
a server dedicated to storing password protected
|
|
||||||
secrets, and use a field called `secretGenerator`
|
|
||||||
in your _kustomization_ to create a kubernetes
|
|
||||||
secret holding them (not covering that here).
|
|
||||||
|
|
||||||
<!--
|
|
||||||
secretGenerator:
|
|
||||||
- name: app-tls
|
|
||||||
commands:
|
|
||||||
tls.crt: "cat tls.cert"
|
|
||||||
tls.key: "cat tls.key"
|
|
||||||
type: "kubernetes.io/tls"
|
|
||||||
EOF
|
|
||||||
-->
|
|
||||||
|
|
||||||
## A mixin approach to management
|
|
||||||
|
|
||||||
The way to create _n_ cluster environments that share
|
|
||||||
some common information is to create _n_ overlays of a
|
|
||||||
common base.
|
|
||||||
|
|
||||||
For the rest of this example, we'll do _n==2_, just
|
|
||||||
_development_ and _production_, since adding more
|
|
||||||
environments follows the same pattern.
|
|
||||||
|
|
||||||
A cluster environment is created by
|
|
||||||
running `kustomize build` on a [target] that happens to
|
|
||||||
be an [overlay].
|
|
||||||
|
|
||||||
[helloworld]: helloworld.md
|
|
||||||
|
|
||||||
The following example will do that, but will focus on
|
|
||||||
configMap construction, and not worry about how to
|
|
||||||
connect the configMaps to deployments (that is covered
|
|
||||||
in the [helloworld] example).
|
|
||||||
|
|
||||||
|
|
||||||
All files - including the shared property files
|
|
||||||
discussed above - will be created in a directory tree
|
|
||||||
that is consistent with the base vs overlay file layout
|
|
||||||
defined in the [helloworld] demo.
|
|
||||||
|
|
||||||
It will all live in this work directory:
|
|
||||||
|
|
||||||
<!-- @makeWorkplace @test -->
|
|
||||||
```
|
```
|
||||||
DEMO_HOME=$(mktemp -d)
|
DEMO_HOME=$(mktemp -d)
|
||||||
```
|
|
||||||
|
|
||||||
### Create the base
|
BASE=$DEMO_HOME/base
|
||||||
|
mkdir -p $BASE
|
||||||
|
|
||||||
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
|
curl -s -o "$BASE/#1.yaml" "https://raw.githubusercontent.com\
|
||||||
|
/kubernetes-sigs/kustomize\
|
||||||
|
/master/examples/helloWorld\
|
||||||
|
/{deployment,service}.yaml"
|
||||||
|
|
||||||
Make a place to put the base configuration:
|
cat <<'EOF' >$BASE/kustomization.yaml
|
||||||
|
commonLabels:
|
||||||
<!-- @baseDir @test -->
|
app: hello
|
||||||
```
|
resources:
|
||||||
mkdir -p $DEMO_HOME/base
|
- deployment.yaml
|
||||||
```
|
- service.yaml
|
||||||
|
configMapGenerator:
|
||||||
Make the data for the base. This direction by
|
- name: the-map
|
||||||
definition should hold resources common to all
|
literals:
|
||||||
environments. Here we're only defining a java
|
- altGreeting=Good Morning!
|
||||||
properties file, and a `kustomization` file that
|
- enableRisky="false"
|
||||||
references it.
|
|
||||||
|
|
||||||
<!-- @baseKustomization @test -->
|
|
||||||
```
|
|
||||||
cat <<EOF >$DEMO_HOME/base/common.properties
|
|
||||||
color=blue
|
|
||||||
height=10m
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
|
|
||||||
configMapGenerator:
|
|
||||||
- name: my-configmap
|
|
||||||
files:
|
|
||||||
- common.properties
|
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Establish the staging with a patch applied to the ConfigMap
|
||||||
### Create and use the overlay for _development_
|
<!-- @establishStaging @test -->
|
||||||
|
|
||||||
Make an abbreviation for the parent of the overlay
|
|
||||||
directories:
|
|
||||||
|
|
||||||
<!-- @overlays @test -->
|
|
||||||
```
|
```
|
||||||
OVERLAYS=$DEMO_HOME/overlays
|
OVERLAYS=$DEMO_HOME/overlays
|
||||||
```
|
mkdir -p $OVERLAYS/staging
|
||||||
|
|
||||||
Create the files that define the _development_ overlay:
|
cat <<'EOF' >$OVERLAYS/staging/kustomization.yaml
|
||||||
|
namePrefix: staging-
|
||||||
<!-- @developmentFiles @test -->
|
commonLabels:
|
||||||
```
|
variant: staging
|
||||||
mkdir -p $OVERLAYS/development
|
org: acmeCorporation
|
||||||
|
commonAnnotations:
|
||||||
cat <<EOF >$OVERLAYS/development/plumbing.properties
|
note: Hello, I am staging!
|
||||||
port=30000
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >$OVERLAYS/development/secret.properties
|
|
||||||
dbpassword=mothersMaidenName
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >$OVERLAYS/development/kustomization.yaml
|
|
||||||
bases:
|
bases:
|
||||||
- ../../base
|
- ../../base
|
||||||
namePrefix: dev-
|
patches:
|
||||||
configMapGenerator:
|
- map.yaml
|
||||||
- name: my-configmap
|
EOF
|
||||||
behavior: merge
|
|
||||||
files:
|
cat <<EOF >$OVERLAYS/staging/map.yaml
|
||||||
- plumbing.properties
|
apiVersion: v1
|
||||||
- secret.properties
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: the-map
|
||||||
|
data:
|
||||||
|
altGreeting: "Have a pineapple!"
|
||||||
|
enableRisky: "true"
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
One can now generate the configMaps for development:
|
### Review
|
||||||
|
|
||||||
<!-- @runDev @test -->
|
The _hello-world_ deployment running in this cluster is
|
||||||
|
configured with data from a configMap.
|
||||||
|
|
||||||
|
The deployment refers to this map by name:
|
||||||
|
|
||||||
|
|
||||||
|
<!-- @showDeployment @test -->
|
||||||
```
|
```
|
||||||
kustomize build $OVERLAYS/development
|
grep -C 2 configMapKeyRef $BASE/deployment.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Check the ConfigMap name
|
Changing the data held by a live configMap in a cluster
|
||||||
|
is considered bad practice. Deployments have no means
|
||||||
|
to know that the configMaps they refer to have
|
||||||
|
changed, so such updates have no effect.
|
||||||
|
|
||||||
The name of the generated `ConfigMap` is visible in this
|
The recommended way to change a deployment's
|
||||||
output.
|
configuration is to
|
||||||
|
|
||||||
The name should be something like `dev-my-configmap-b5m75ck895`:
|
1. create a new configMap with a new name,
|
||||||
|
1. patch the _deployment_, modifying the name value of
|
||||||
|
the appropriate `configMapKeyRef` field.
|
||||||
|
|
||||||
* `"dev-"` comes from the `namePrefix` field,
|
This latter change initiates rolling update to the pods
|
||||||
* `"my-configmap"` comes from the `configMapGenerator/name` field,
|
in the deployment. The older configMap, when no longer
|
||||||
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
|
referenced by any other resource, is eventually garbage
|
||||||
computes from the contents of the configMap.
|
collected.
|
||||||
|
|
||||||
The hash suffix is critical. If the configMap content
|
### How this works with kustomize
|
||||||
changes, so does the configMap name, along with all
|
|
||||||
references to that name that appear in the YAML output
|
|
||||||
from `kustomize`.
|
|
||||||
|
|
||||||
The name change means deployments will do a rolling
|
The _staging_ [variant] here has a configMap [patch]:
|
||||||
restart to get new data if this YAML is applied to the
|
|
||||||
cluster using a command like
|
|
||||||
|
|
||||||
> ```
|
<!-- @showMapPatch @test -->
|
||||||
> kustomize build $OVERLAYS/development | kubectl apply -f -
|
|
||||||
> ```
|
|
||||||
|
|
||||||
A deployment has no means to automatically know when or
|
|
||||||
if a configMap in use by the deployment changes.
|
|
||||||
|
|
||||||
If one changes a configMap without changing its name
|
|
||||||
and all references to that name, one must imperatively
|
|
||||||
restart the cluster to pick up the change.
|
|
||||||
|
|
||||||
The best practice is to treat configMaps as immutable.
|
|
||||||
|
|
||||||
Instead of editing configMaps, modify your declarative
|
|
||||||
specification of the cluster's desired state to
|
|
||||||
point deployments to _new_ configMaps with _new_ names.
|
|
||||||
`kustomize` makes this easy with its
|
|
||||||
`configMapGenerator` directive and associated naming
|
|
||||||
controls. A GC process in the k8s master eventually
|
|
||||||
deletes unused configMaps.
|
|
||||||
|
|
||||||
|
|
||||||
### Create and use the overlay for _production_
|
|
||||||
|
|
||||||
Next, create the files for the _production_ overlay:
|
|
||||||
|
|
||||||
|
|
||||||
<!-- @productionFiles @test -->
|
|
||||||
```
|
```
|
||||||
mkdir -p $OVERLAYS/production
|
cat $OVERLAYS/staging/map.yaml
|
||||||
|
|
||||||
cat <<EOF >$OVERLAYS/production/plumbing.properties
|
|
||||||
port=8080
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >$OVERLAYS/production/secret.properties
|
|
||||||
dbpassword=thisShouldProbablyBeInASecretInstead
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >$OVERLAYS/production/kustomization.yaml
|
|
||||||
bases:
|
|
||||||
- ../../base
|
|
||||||
namePrefix: prod-
|
|
||||||
configMapGenerator:
|
|
||||||
- name: my-configmap
|
|
||||||
behavior: merge
|
|
||||||
files:
|
|
||||||
- plumbing.properties
|
|
||||||
- secret.properties
|
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
One can now generate the configMaps for production:
|
This patch is by definition a named but not necessarily
|
||||||
|
complete resource spec intended to modify a complete
|
||||||
|
resource spec.
|
||||||
|
|
||||||
<!-- @runProd @test -->
|
The ConfigMap it modifies is declared from a configMapGenerator.
|
||||||
|
|
||||||
|
<!-- @showMapBase @test -->
|
||||||
```
|
```
|
||||||
kustomize build $OVERLAYS/production
|
grep -C 4 configMapGenerator $BASE/kustomization.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
A CICD process could apply this directly to
|
For a patch to work, the names in the `metadata/name`
|
||||||
the cluser using:
|
fields must match.
|
||||||
|
|
||||||
> ```
|
However, the name values specified in the file are
|
||||||
> kustomize build $OVERLAYS/production | kubectl apply -f -
|
_not_ what gets used in the cluster. By design,
|
||||||
> ```
|
kustomize modifies names of ConfigMaps declared from ConfigMapGenerator. To see the names
|
||||||
|
ultimately used in the cluster, just run kustomize:
|
||||||
|
|
||||||
|
<!-- @grepStagingName @test -->
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/staging |\
|
||||||
|
grep -B 8 -A 1 staging-the-map
|
||||||
|
```
|
||||||
|
|
||||||
|
The configMap name is prefixed by _staging-_, per the
|
||||||
|
`namePrefix` field in
|
||||||
|
`$OVERLAYS/staging/kustomization.yaml`.
|
||||||
|
|
||||||
|
The suffix to the configMap name is generated from a
|
||||||
|
hash of the maps content - in this case the name suffix
|
||||||
|
is _hhhhkfmgmk_:
|
||||||
|
|
||||||
|
<!-- @grepStagingHash @test -->
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
|
||||||
|
```
|
||||||
|
|
||||||
|
Now modify the map patch, to change the greeting
|
||||||
|
the server will use:
|
||||||
|
|
||||||
|
<!-- @changeMap @test -->
|
||||||
|
```
|
||||||
|
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
See the new greeting:
|
||||||
|
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/staging |\
|
||||||
|
grep -B 2 -A 3 kiwi
|
||||||
|
```
|
||||||
|
|
||||||
|
Run kustomize again to see the new configMap names:
|
||||||
|
|
||||||
|
<!-- @grepStagingName @test -->
|
||||||
|
```
|
||||||
|
kustomize build $OVERLAYS/staging |\
|
||||||
|
grep -B 8 -A 1 staging-the-map
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm that the change in configMap content resulted
|
||||||
|
in three new names ending in _khk45ktkd9_ - one in the
|
||||||
|
configMap name itself, and two in the deployment that
|
||||||
|
uses the map:
|
||||||
|
|
||||||
|
<!-- @countHashes @test -->
|
||||||
|
```
|
||||||
|
test 3 == \
|
||||||
|
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
|
||||||
|
echo $?
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying these resources to the cluster will result in
|
||||||
|
a rolling update of the deployments pods, retargetting
|
||||||
|
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
|
||||||
|
maps. The system will later garbage collect the
|
||||||
|
unused maps.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
To rollback, one would undo whatever edits were made to
|
||||||
|
the configuation in source control, then rerun kustomize
|
||||||
|
on the reverted configuration and apply it to the
|
||||||
|
cluster.
|
||||||
|
|||||||
@@ -315,130 +315,3 @@ To deploy, pipe the above commands to kubectl apply:
|
|||||||
> kustomize build $OVERLAYS/production |\
|
> kustomize build $OVERLAYS/production |\
|
||||||
> kubectl apply -f -
|
> kubectl apply -f -
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
## Rolling updates
|
|
||||||
|
|
||||||
### Review
|
|
||||||
|
|
||||||
The _hello-world_ deployment running in this cluster is
|
|
||||||
configured with data from a configMap.
|
|
||||||
|
|
||||||
The deployment refers to this map by name:
|
|
||||||
|
|
||||||
|
|
||||||
<!-- @showDeployment @test -->
|
|
||||||
```
|
|
||||||
grep -C 2 configMapKeyRef $DEMO_HOME/base/deployment.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Changing the data held by a live configMap in a cluster
|
|
||||||
is considered bad practice. Deployments have no means
|
|
||||||
to know that the configMaps they refer to have
|
|
||||||
changed, so such updates have no effect.
|
|
||||||
|
|
||||||
The recommended way to change a deployment's
|
|
||||||
configuration is to
|
|
||||||
|
|
||||||
1. create a new configMap with a new name,
|
|
||||||
1. patch the _deployment_, modifying the name value of
|
|
||||||
the appropriate `configMapKeyRef` field.
|
|
||||||
|
|
||||||
This latter change initiates rolling update to the pods
|
|
||||||
in the deployment. The older configMap, when no longer
|
|
||||||
referenced by any other resource, is eventually garbage
|
|
||||||
collected.
|
|
||||||
|
|
||||||
### How this works with kustomize
|
|
||||||
|
|
||||||
The _staging_ [variant] here has a configMap [patch]:
|
|
||||||
|
|
||||||
<!-- @showMapPatch @test -->
|
|
||||||
```
|
|
||||||
cat $OVERLAYS/staging/map.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
This patch is by definition a named but not necessarily
|
|
||||||
complete resource spec intended to modify a complete
|
|
||||||
resource spec.
|
|
||||||
|
|
||||||
The resource it modifies is here:
|
|
||||||
|
|
||||||
<!-- @showMapBase @test -->
|
|
||||||
```
|
|
||||||
cat $DEMO_HOME/base/configMap.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
For a patch to work, the names in the `metadata/name`
|
|
||||||
fields must match.
|
|
||||||
|
|
||||||
However, the name values specified in the file are
|
|
||||||
_not_ what gets used in the cluster. By design,
|
|
||||||
kustomize modifies these names. To see the names
|
|
||||||
ultimately used in the cluster, just run kustomize:
|
|
||||||
|
|
||||||
<!-- @grepStagingName @test -->
|
|
||||||
```
|
|
||||||
kustomize build $OVERLAYS/staging |\
|
|
||||||
grep -B 8 -A 1 staging-the-map
|
|
||||||
```
|
|
||||||
|
|
||||||
The configMap name is prefixed by _staging-_, per the
|
|
||||||
`namePrefix` field in
|
|
||||||
`$OVERLAYS/staging/kustomization.yaml`.
|
|
||||||
|
|
||||||
The suffix to the configMap name is generated from a
|
|
||||||
hash of the maps content - in this case the name suffix
|
|
||||||
is _hhhhkfmgmk_:
|
|
||||||
|
|
||||||
<!-- @grepStagingHash @test -->
|
|
||||||
```
|
|
||||||
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
|
|
||||||
```
|
|
||||||
|
|
||||||
Now modify the map patch, to change the greeting
|
|
||||||
the server will use:
|
|
||||||
|
|
||||||
<!-- @changeMap @test -->
|
|
||||||
```
|
|
||||||
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
See the new greeting:
|
|
||||||
|
|
||||||
```
|
|
||||||
kustomize build $OVERLAYS/staging |\
|
|
||||||
grep -B 2 -A 3 kiwi
|
|
||||||
```
|
|
||||||
|
|
||||||
Run kustomize again to see the new configMap names:
|
|
||||||
|
|
||||||
<!-- @grepStagingName @test -->
|
|
||||||
```
|
|
||||||
kustomize build $OVERLAYS/staging |\
|
|
||||||
grep -B 8 -A 1 staging-the-map
|
|
||||||
```
|
|
||||||
|
|
||||||
Confirm that the change in configMap content resulted
|
|
||||||
in three new names ending in _khk45ktkd9_ - one in the
|
|
||||||
configMap name itself, and two in the deployment that
|
|
||||||
uses the map:
|
|
||||||
|
|
||||||
<!-- @countHashes @test -->
|
|
||||||
```
|
|
||||||
test 3 == \
|
|
||||||
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
|
|
||||||
echo $?
|
|
||||||
```
|
|
||||||
|
|
||||||
Applying these resources to the cluster will result in
|
|
||||||
a rolling update of the deployments pods, retargetting
|
|
||||||
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
|
|
||||||
maps. The system will later garbage collect the
|
|
||||||
unused maps.
|
|
||||||
|
|
||||||
## Rollback
|
|
||||||
|
|
||||||
To rollback, one would undo whatever edits were made to
|
|
||||||
the configuation in source control, then rerun kustomize
|
|
||||||
on the reverted configuration and apply it to the
|
|
||||||
cluster.
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ commonLabels:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
- configMap.yaml
|
|
||||||
- service.yaml
|
- service.yaml
|
||||||
|
- configMap.yaml
|
||||||
|
|||||||
75
examples/imageTags.md
Normal file
75
examples/imageTags.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Demo: change image tags
|
||||||
|
|
||||||
|
|
||||||
|
Define a place to work:
|
||||||
|
|
||||||
|
<!-- @makeWorkplace @test -->
|
||||||
|
```
|
||||||
|
DEMO_HOME=$(mktemp -d)
|
||||||
|
```
|
||||||
|
|
||||||
|
Make a `kustomization` containing a pod resource
|
||||||
|
|
||||||
|
<!-- @createKustomization @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Declare the pod resource
|
||||||
|
|
||||||
|
<!-- @createDeployment @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/pod.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: myapp-pod
|
||||||
|
labels:
|
||||||
|
app: myapp
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: myapp-container
|
||||||
|
image: busybox:1.29.0
|
||||||
|
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
|
||||||
|
initContainers:
|
||||||
|
- name: init-mydb
|
||||||
|
image: busybox:1.29.0
|
||||||
|
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
The `myapp-pod` resource declares an initContainer and a container, both use the image `busybox:1.29.0`.
|
||||||
|
The tag `1.29.0` can be changed by adding `imageTags` in `kustomization.yaml`.
|
||||||
|
|
||||||
|
|
||||||
|
Add `imageTags`:
|
||||||
|
<!-- @addImageTags @test -->
|
||||||
|
```
|
||||||
|
cd $DEMO_HOME
|
||||||
|
kustomize edit set imagetag busybox:1.29.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The `kustomization.yaml` will be added following `imageTags`.
|
||||||
|
> ```
|
||||||
|
> imageTags:
|
||||||
|
> - name: busybox
|
||||||
|
> newTag: 1.29.1
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Now build this `kustomization`
|
||||||
|
<!-- @kustomizeBuild @test -->
|
||||||
|
```
|
||||||
|
kustomize build $DEMO_HOME
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm that this replaces _both_ busybox tags:
|
||||||
|
|
||||||
|
<!-- @confirmTags @test -->
|
||||||
|
```
|
||||||
|
test 2 == \
|
||||||
|
$(kustomize build $DEMO_HOME | grep busybox:1.29.1 | wc -l); \
|
||||||
|
echo $?
|
||||||
|
```
|
||||||
127
examples/multibases/README.md
Normal file
127
examples/multibases/README.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Demo: multibases with a common base
|
||||||
|
|
||||||
|
`kustomize` encourages defining multiple variants - e.g. dev, staging and prod, as overlays on a common base.
|
||||||
|
|
||||||
|
It's possible to create an additional overlay to compose these variants together - just declare the overlays as the bases of a new kustomization.
|
||||||
|
|
||||||
|
This is also a means to apply a common label or annotation across the variants, if for some reason the base isn't under your control. It also allows one to define a left-most namePrefix across the variants - something that cannot be done by modifying the common base.
|
||||||
|
|
||||||
|
The following demonstrates this using a base that's just one pod.
|
||||||
|
|
||||||
|
Define a place to work:
|
||||||
|
|
||||||
|
<!-- @makeWorkplace @test -->
|
||||||
|
```
|
||||||
|
DEMO_HOME=$(mktemp -d)
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a common base:
|
||||||
|
<!-- @makeBase @test -->
|
||||||
|
```
|
||||||
|
BASE=$DEMO_HOME/base
|
||||||
|
mkdir $BASE
|
||||||
|
|
||||||
|
cat <<EOF >$BASE/kustomization.yaml
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$BASE/pod.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: myapp-pod
|
||||||
|
labels:
|
||||||
|
app: myapp
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.7.9
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a dev variant overlaying base:
|
||||||
|
<!-- @makeDev @test -->
|
||||||
|
```
|
||||||
|
DEV=$DEMO_HOME/dev
|
||||||
|
mkdir $DEV
|
||||||
|
|
||||||
|
cat <<EOF >$DEV/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
namePrefix: dev-
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a staging variant overlaying base:
|
||||||
|
<!-- @makeStaging @test -->
|
||||||
|
```
|
||||||
|
STAG=$DEMO_HOME/staging
|
||||||
|
mkdir $STAG
|
||||||
|
|
||||||
|
cat <<EOF >$STAG/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
namePrefix: stag-
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a production variant overlaying base:
|
||||||
|
<!-- @makeProd @test -->
|
||||||
|
```
|
||||||
|
PROD=$DEMO_HOME/production
|
||||||
|
mkdir $PROD
|
||||||
|
|
||||||
|
cat <<EOF >$PROD/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
namePrefix: prod-
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Then define a _Kustomization_ composing three variants together:
|
||||||
|
<!-- @makeTopLayer @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./dev
|
||||||
|
- ./staging
|
||||||
|
- ./production
|
||||||
|
|
||||||
|
namePrefix: cluster-a-
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the workspace has following directories
|
||||||
|
> ```
|
||||||
|
> .
|
||||||
|
> ├── base
|
||||||
|
> │ ├── kustomization.yaml
|
||||||
|
> │ └── pod.yaml
|
||||||
|
> ├── dev
|
||||||
|
> │ └── kustomization.yaml
|
||||||
|
> ├── kustomization.yaml
|
||||||
|
> ├── production
|
||||||
|
> │ └── kustomization.yaml
|
||||||
|
> └── staging
|
||||||
|
> └── kustomization.yaml
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Confirm that the `kustomize build` output contains three pod objects from dev, staing and production variants.
|
||||||
|
|
||||||
|
<!-- @confirmVariants @test -->
|
||||||
|
```
|
||||||
|
test 1 == \
|
||||||
|
$(kustomize build $DEMO_HOME | grep cluster-a-dev-myapp-pod | wc -l); \
|
||||||
|
echo $?
|
||||||
|
|
||||||
|
test 1 == \
|
||||||
|
$(kustomize build $DEMO_HOME | grep cluster-a-stag-myapp-pod | wc -l); \
|
||||||
|
echo $?
|
||||||
|
|
||||||
|
test 1 == \
|
||||||
|
$(kustomize build $DEMO_HOME | grep cluster-a-prod-myapp-pod | wc -l); \
|
||||||
|
echo $?
|
||||||
|
```
|
||||||
|
Similarly to adding different `namePrefix` in different variants, one can also add different `namespace` and compose those variants in
|
||||||
|
one _kustomization_. For more details, take a look at [multi-namespaces](multi-namespace.md).
|
||||||
2
examples/multibases/base/kustomization.yaml
Normal file
2
examples/multibases/base/kustomization.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
10
examples/multibases/base/pod.yaml
Normal file
10
examples/multibases/base/pod.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: myapp-pod
|
||||||
|
labels:
|
||||||
|
app: myapp
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.7.9
|
||||||
4
examples/multibases/dev/kustomization.yaml
Normal file
4
examples/multibases/dev/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
|
||||||
|
namePrefix: dev-
|
||||||
6
examples/multibases/kustomization.yaml
Normal file
6
examples/multibases/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
bases:
|
||||||
|
- ./dev
|
||||||
|
- ./staging
|
||||||
|
- ./production
|
||||||
|
|
||||||
|
namePrefix: cluster-a-
|
||||||
115
examples/multibases/multi-namespace.md
Normal file
115
examples/multibases/multi-namespace.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Demo: multi namespaces with a common base
|
||||||
|
|
||||||
|
`kustomize` supports defining multiple variants with different namespace, as overlays on a common base.
|
||||||
|
|
||||||
|
It's possible to create an additional overlay to compose these variants together - just declare the overlays as the bases of a new kustomization. The following demonstrates this using a base that's just one pod.
|
||||||
|
|
||||||
|
Define a place to work:
|
||||||
|
|
||||||
|
<!-- @makeWorkplace @test -->
|
||||||
|
```
|
||||||
|
DEMO_HOME=$(mktemp -d)
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a common base:
|
||||||
|
<!-- @makeBase @test -->
|
||||||
|
```
|
||||||
|
BASE=$DEMO_HOME/base
|
||||||
|
mkdir $BASE
|
||||||
|
|
||||||
|
cat <<EOF >$BASE/kustomization.yaml
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$BASE/pod.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: myapp-pod
|
||||||
|
labels:
|
||||||
|
app: myapp
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.7.9
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a variant in namespace-a overlaying base:
|
||||||
|
<!-- @makeNamespaceA @test -->
|
||||||
|
```
|
||||||
|
NSA=$DEMO_HOME/namespace-a
|
||||||
|
mkdir $NSA
|
||||||
|
|
||||||
|
cat <<EOF >$NSA/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
namespace: namespace-a
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$NSA/namespace.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: namespace-a
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a variant in namespace-b overlaying base:
|
||||||
|
<!-- @makeNamespaceB @test -->
|
||||||
|
```
|
||||||
|
NSB=$DEMO_HOME/namespace-b
|
||||||
|
mkdir $NSB
|
||||||
|
|
||||||
|
cat <<EOF >$NSB/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
namespace: namespace-b
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$NSB/namespace.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: namespace-b
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Then define a _Kustomization_ composing two variants together:
|
||||||
|
<!-- @makeTopLayer @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||||
|
bases:
|
||||||
|
- ./namespace-a
|
||||||
|
- ./namespace-b
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the workspace has following directories
|
||||||
|
> ```
|
||||||
|
> .
|
||||||
|
> ├── base
|
||||||
|
> │ ├── kustomization.yaml
|
||||||
|
> │ └── pod.yaml
|
||||||
|
> ├── kustomization.yaml
|
||||||
|
> ├── namespace-a
|
||||||
|
> │ ├── kustomization.yaml
|
||||||
|
> │ └── namespace.yaml
|
||||||
|
> └── namespace-b
|
||||||
|
> ├── kustomization.yaml
|
||||||
|
> └── namespace.yaml
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Confirm that the `kustomize build` output contains two pod objects from namespace-a and namespace-b.
|
||||||
|
|
||||||
|
<!-- @confirmVariants @test -->
|
||||||
|
```
|
||||||
|
test 2 == \
|
||||||
|
$(kustomize build $DEMO_HOME| grep -B 4 "namespace: namespace-[ab]" | grep "name: myapp-pod" | wc -l); \
|
||||||
|
echo $?
|
||||||
|
```
|
||||||
4
examples/multibases/production/kustomization.yaml
Normal file
4
examples/multibases/production/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
|
||||||
|
namePrefix: prod-
|
||||||
4
examples/multibases/staging/kustomization.yaml
Normal file
4
examples/multibases/staging/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
bases:
|
||||||
|
- ./../base
|
||||||
|
|
||||||
|
namePrefix: staging-
|
||||||
@@ -26,8 +26,10 @@ import (
|
|||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/crds"
|
"github.com/kubernetes-sigs/kustomize/pkg/crds"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
|
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||||
@@ -43,12 +45,13 @@ import (
|
|||||||
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
|
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
|
||||||
type Application struct {
|
type Application struct {
|
||||||
kustomization *types.Kustomization
|
kustomization *types.Kustomization
|
||||||
loader loader.Loader
|
ldr loader.Loader
|
||||||
|
fSys fs.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication returns a new instance of Application primed with a Loader.
|
// NewApplication returns a new instance of Application primed with a Loader.
|
||||||
func NewApplication(loader loader.Loader) (*Application, error) {
|
func NewApplication(ldr loader.Loader, fSys fs.FileSystem) (*Application, error) {
|
||||||
content, err := loader.Load(constants.KustomizationFileName)
|
content, err := ldr.Load(constants.KustomizationFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,8 @@ func NewApplication(loader loader.Loader) (*Application, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Application{kustomization: &m, loader: loader}, nil
|
|
||||||
|
return &Application{kustomization: &m, ldr: ldr, fSys: fSys}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshal(y []byte, o interface{}) error {
|
func unmarshal(y []byte, o interface{}) error {
|
||||||
@@ -98,8 +102,12 @@ func (a *Application) MakeUncustomizedResMap() (resmap.ResMap, error) {
|
|||||||
|
|
||||||
// resolveRefsToGeneratedResources fixes all name references.
|
// resolveRefsToGeneratedResources fixes all name references.
|
||||||
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
|
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
|
||||||
r := []transformers.Transformer{transformers.NewNameHashTransformer()}
|
err := transformers.NewNameHashTransformer().Transform(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r []transformers.Transformer
|
||||||
t, err := transformers.NewDefaultingNameReferenceTransformer()
|
t, err := transformers.NewDefaultingNameReferenceTransformer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -130,16 +138,19 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
|
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
|
||||||
}
|
}
|
||||||
err = crds.RegisterCRDs(a.loader, a.kustomization.CRDs)
|
err = crds.RegisterCRDs(a.ldr, a.kustomization.Crds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "RegisterCRDs"))
|
errs.Append(errors.Wrap(err, "RegisterCRDs"))
|
||||||
}
|
}
|
||||||
|
cms, err := resmap.NewResMapFromConfigMapArgs(
|
||||||
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
|
configmapandsecret.NewConfigMapFactory(a.fSys, a.ldr),
|
||||||
|
a.kustomization.ConfigMapGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
|
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
|
||||||
}
|
}
|
||||||
secrets, err := resmap.NewResMapFromSecretArgs(a.loader.Root(), a.kustomization.SecretGenerator)
|
secrets, err := resmap.NewResMapFromSecretArgs(
|
||||||
|
configmapandsecret.NewSecretFactory(a.fSys, a.ldr.Root()),
|
||||||
|
a.kustomization.SecretGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
|
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
|
||||||
}
|
}
|
||||||
@@ -153,7 +164,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
|
patches, err := resmap.NewResourceSliceFromPatches(a.ldr, a.kustomization.Patches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
|
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
|
||||||
}
|
}
|
||||||
@@ -161,11 +172,20 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
|||||||
if len(errs.Get()) > 0 {
|
if len(errs.Get()) > 0 {
|
||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var r []transformers.Transformer
|
||||||
t, err := a.newTransformer(patches)
|
t, err := a.newTransformer(patches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = t.Transform(result)
|
r = append(r, t)
|
||||||
|
t, err = transformers.NewImageTagTransformer(a.kustomization.ImageTags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r = append(r, t)
|
||||||
|
|
||||||
|
err = transformers.NewMultiTransformer(r).Transform(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,7 +195,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
|||||||
// Gets Bases and Resources as advertised.
|
// Gets Bases and Resources as advertised.
|
||||||
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
|
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
|
||||||
bases, errs := a.loadCustomizedBases()
|
bases, errs := a.loadCustomizedBases()
|
||||||
resources, err := resmap.NewResMapFromFiles(a.loader, a.kustomization.Resources)
|
resources, err := resmap.NewResMapFromFiles(a.ldr, a.kustomization.Resources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
|
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
|
||||||
}
|
}
|
||||||
@@ -188,15 +208,15 @@ func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
|
|||||||
// Loop through the Bases of this kustomization recursively loading resources.
|
// Loop through the Bases of this kustomization recursively loading resources.
|
||||||
// Combine into one ResMap, demanding unique Ids for each resource.
|
// Combine into one ResMap, demanding unique Ids for each resource.
|
||||||
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
|
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
|
||||||
list := []resmap.ResMap{}
|
var list []resmap.ResMap
|
||||||
errs := &interror.KustomizationErrors{}
|
errs := &interror.KustomizationErrors{}
|
||||||
for _, path := range a.kustomization.Bases {
|
for _, path := range a.kustomization.Bases {
|
||||||
loader, err := a.loader.New(path)
|
ldr, err := a.ldr.New(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "couldn't make loader for "+path))
|
errs.Append(errors.Wrap(err, "couldn't make ldr for "+path))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
app, err := NewApplication(loader)
|
app, err := NewApplication(ldr, a.fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
|
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
|
||||||
continue
|
continue
|
||||||
@@ -219,12 +239,12 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
|
|||||||
var result []*Application
|
var result []*Application
|
||||||
errs := &interror.KustomizationErrors{}
|
errs := &interror.KustomizationErrors{}
|
||||||
for _, path := range a.kustomization.Bases {
|
for _, path := range a.kustomization.Bases {
|
||||||
loader, err := a.loader.New(path)
|
ldr, err := a.ldr.New(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(err)
|
errs.Append(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
a, err := NewApplication(loader)
|
a, err := NewApplication(ldr, a.fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Append(err)
|
errs.Append(err)
|
||||||
continue
|
continue
|
||||||
@@ -239,7 +259,7 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
|
|||||||
|
|
||||||
// newTransformer makes a Transformer that does everything except resolve generated names.
|
// newTransformer makes a Transformer that does everything except resolve generated names.
|
||||||
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
|
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
|
||||||
r := []transformers.Transformer{}
|
var r []transformers.Transformer
|
||||||
t, err := transformers.NewPatchTransformer(patches)
|
t, err := transformers.NewPatchTransformer(patches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -272,7 +292,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
|
|||||||
}
|
}
|
||||||
for _, v := range vars {
|
for _, v := range vars {
|
||||||
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
|
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
|
||||||
if r, found := m[id]; found {
|
if r, found := m.DemandOneMatchForId(id); found {
|
||||||
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
|
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
|
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
|
||||||
@@ -287,7 +307,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
|
|||||||
|
|
||||||
// getAllVars returns all the "environment" style Var instances defined in the app.
|
// getAllVars returns all the "environment" style Var instances defined in the app.
|
||||||
func (a *Application) getAllVars() ([]types.Var, error) {
|
func (a *Application) getAllVars() ([]types.Var, error) {
|
||||||
result := []types.Var{}
|
var result []types.Var
|
||||||
errs := &interror.KustomizationErrors{}
|
errs := &interror.KustomizationErrors{}
|
||||||
|
|
||||||
bases, err := a.loadBasesAsFlatList()
|
bases, err := a.loadBasesAsFlatList()
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
|
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||||
@@ -67,20 +67,20 @@ metadata:
|
|||||||
)
|
)
|
||||||
|
|
||||||
func makeLoader1(t *testing.T) loader.Loader {
|
func makeLoader1(t *testing.T) loader.Loader {
|
||||||
loader := loadertest.NewFakeLoader("/testpath")
|
ldr := loadertest.NewFakeLoader("/testpath")
|
||||||
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
|
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
err = loader.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
|
err = ldr.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
err = loader.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
|
err = ldr.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
return loader
|
return ldr
|
||||||
}
|
}
|
||||||
|
|
||||||
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||||
@@ -91,7 +91,7 @@ var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
|
|||||||
|
|
||||||
func TestResources1(t *testing.T) {
|
func TestResources1(t *testing.T) {
|
||||||
expected := resmap.ResMap{
|
expected := resmap.ResMap{
|
||||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
resource.NewResIdWithPrefixNamespace(deploy, "dply1", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
@@ -123,7 +123,7 @@ func TestResources1(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
resource.NewResId(cmap, "literalConfigMap"): resource.NewResourceFromMap(
|
resource.NewResIdWithPrefixNamespace(cmap, "literalConfigMap", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
@@ -142,8 +142,8 @@ func TestResources1(t *testing.T) {
|
|||||||
"DB_USERNAME": "admin",
|
"DB_USERNAME": "admin",
|
||||||
"DB_PASSWORD": "somepw",
|
"DB_PASSWORD": "somepw",
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
resource.NewResId(secret, "secret"): resource.NewResourceFromMap(
|
resource.NewResIdWithPrefixNamespace(secret, "secret", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Secret",
|
"kind": "Secret",
|
||||||
@@ -163,8 +163,8 @@ func TestResources1(t *testing.T) {
|
|||||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
|
resource.NewResIdWithPrefixNamespace(ns, "ns1", "foo-", ""): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Namespace",
|
"kind": "Namespace",
|
||||||
@@ -180,7 +180,9 @@ func TestResources1(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
l := makeLoader1(t)
|
l := makeLoader1(t)
|
||||||
app, err := NewApplication(l)
|
fakeFs := fs.MakeFakeFS()
|
||||||
|
fakeFs.Mkdir("/")
|
||||||
|
app, err := NewApplication(l, fakeFs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected construction error %v", err)
|
t.Fatalf("Unexpected construction error %v", err)
|
||||||
}
|
}
|
||||||
@@ -215,7 +217,7 @@ func TestRawResources1(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
l := makeLoader1(t)
|
l := makeLoader1(t)
|
||||||
app, err := NewApplication(l)
|
app, err := NewApplication(l, fs.MakeFakeFS())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected construction error %v", err)
|
t.Fatalf("Unexpected construction error %v", err)
|
||||||
}
|
}
|
||||||
@@ -255,28 +257,28 @@ spec:
|
|||||||
)
|
)
|
||||||
|
|
||||||
func makeLoader2(t *testing.T) loader.Loader {
|
func makeLoader2(t *testing.T) loader.Loader {
|
||||||
loader := loadertest.NewFakeLoader("/testpath")
|
ldr := loadertest.NewFakeLoader("/testpath")
|
||||||
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
|
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = loader.AddFile("/testpath/service.yaml", []byte(serviceContent))
|
err = ldr.AddFile("/testpath/service.yaml", []byte(serviceContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
err = loader.AddDirectory("/testpath/base", os.ModeDir)
|
err = ldr.AddDirectory("/testpath/base")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
err = loader.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
|
err = ldr.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
err = loader.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
|
err = ldr.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
return loader
|
return ldr
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This test covers incorrect behavior; it should not pass.
|
// TODO: This test covers incorrect behavior; it should not pass.
|
||||||
@@ -287,7 +289,7 @@ func makeLoader2(t *testing.T) loader.Loader {
|
|||||||
// perhaps it's not worth supporting the command.
|
// perhaps it's not worth supporting the command.
|
||||||
func TestRawResources2(t *testing.T) {
|
func TestRawResources2(t *testing.T) {
|
||||||
expected := resmap.ResMap{
|
expected := resmap.ResMap{
|
||||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
@@ -325,7 +327,7 @@ func TestRawResources2(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
l := makeLoader2(t)
|
l := makeLoader2(t)
|
||||||
app, err := NewApplication(l)
|
app, err := NewApplication(l, fs.MakeFakeFS())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected construction error %v", err)
|
t.Fatalf("Unexpected construction error %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,9 +84,8 @@ func (o *addBaseOptions) RunAddBase(fsys fs.FileSystem) error {
|
|||||||
// split directory paths
|
// split directory paths
|
||||||
paths := strings.Split(o.baseDirectoryPaths, ",")
|
paths := strings.Split(o.baseDirectoryPaths, ",")
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
_, err := fsys.Stat(path)
|
if !fsys.Exists(path) {
|
||||||
if err != nil {
|
return errors.New(path + " does not exist")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if stringInSlice(path, m.Bases) {
|
if stringInSlice(path, m.Bases) {
|
||||||
return fmt.Errorf("base %s already in kustomization file", path)
|
return fmt.Errorf("base %s already in kustomization file", path)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func TestAddBaseHappyPath(t *testing.T) {
|
|||||||
fakeFS := fs.MakeFakeFS()
|
fakeFS := fs.MakeFakeFS()
|
||||||
bases := strings.Split(baseDirectoryPaths, ",")
|
bases := strings.Split(baseDirectoryPaths, ",")
|
||||||
for _, base := range bases {
|
for _, base := range bases {
|
||||||
fakeFS.Mkdir(base, 0777)
|
fakeFS.Mkdir(base)
|
||||||
}
|
}
|
||||||
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func TestAddBaseAlreadyThere(t *testing.T) {
|
|||||||
// Create fake directories
|
// Create fake directories
|
||||||
bases := strings.Split(baseDirectoryPaths, ",")
|
bases := strings.Split(baseDirectoryPaths, ",")
|
||||||
for _, base := range bases {
|
for _, base := range bases {
|
||||||
fakeFS.Mkdir(base, 0777)
|
fakeFS.Mkdir(base)
|
||||||
}
|
}
|
||||||
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
@@ -77,9 +77,9 @@ func TestAddBaseAlreadyThere(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var expectedErrors []string
|
var expectedErrors []string
|
||||||
for _, base := range bases {
|
for _, base := range bases {
|
||||||
error := "base " + base + " already in kustomization file"
|
msg := "base " + base + " already in kustomization file"
|
||||||
expectedErrors = append(expectedErrors, error)
|
expectedErrors = append(expectedErrors, msg)
|
||||||
if !stringInSlice(error, expectedErrors) {
|
if !stringInSlice(msg, expectedErrors) {
|
||||||
t.Errorf("unexpected error %v", err)
|
t.Errorf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"log"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type addPatchOptions struct {
|
type addPatchOptions struct {
|
||||||
patchFilePath string
|
patchFilePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
|
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
|
||||||
@@ -56,10 +56,10 @@ func newCmdAddPatch(fsys fs.FileSystem) *cobra.Command {
|
|||||||
|
|
||||||
// Validate validates addPatch command.
|
// Validate validates addPatch command.
|
||||||
func (o *addPatchOptions) Validate(args []string) error {
|
func (o *addPatchOptions) Validate(args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) == 0 {
|
||||||
return errors.New("must specify a patch file")
|
return errors.New("must specify a patch file")
|
||||||
}
|
}
|
||||||
o.patchFilePath = args[0]
|
o.patchFilePaths = args
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +70,13 @@ func (o *addPatchOptions) Complete(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// RunAddPatch runs addPatch command (do real work).
|
// RunAddPatch runs addPatch command (do real work).
|
||||||
func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
|
func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
|
||||||
_, err := fsys.Stat(o.patchFilePath)
|
patches, err := globPatterns(fsys, o.patchFilePaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(patches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,11 +88,13 @@ func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if stringInSlice(o.patchFilePath, m.Patches) {
|
for _, patch := range patches {
|
||||||
return fmt.Errorf("patch %s already in kustomization file", o.patchFilePath)
|
if stringInSlice(patch, m.Patches) {
|
||||||
|
log.Printf("patch %s already in kustomization file", patch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.Patches = append(m.Patches, patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Patches = append(m.Patches, o.patchFilePath)
|
|
||||||
|
|
||||||
return mf.write(m)
|
return mf.write(m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|||||||
func TestAddPatchHappyPath(t *testing.T) {
|
func TestAddPatchHappyPath(t *testing.T) {
|
||||||
fakeFS := fs.MakeFakeFS()
|
fakeFS := fs.MakeFakeFS()
|
||||||
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
|
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
|
||||||
|
fakeFS.WriteFile(patchFileName+"another", []byte(patchFileContent))
|
||||||
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
cmd := newCmdAddPatch(fakeFS)
|
cmd := newCmdAddPatch(fakeFS)
|
||||||
args := []string{patchFileName}
|
args := []string{patchFileName + "*"}
|
||||||
err := cmd.RunE(cmd, args)
|
err := cmd.RunE(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected cmd error: %v", err)
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
@@ -51,6 +52,9 @@ func TestAddPatchHappyPath(t *testing.T) {
|
|||||||
if !strings.Contains(string(content), patchFileName) {
|
if !strings.Contains(string(content), patchFileName) {
|
||||||
t.Errorf("expected patch name in kustomization")
|
t.Errorf("expected patch name in kustomization")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(string(content), patchFileName+"another") {
|
||||||
|
t.Errorf("expected patch name in kustomization")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddPatchAlreadyThere(t *testing.T) {
|
func TestAddPatchAlreadyThere(t *testing.T) {
|
||||||
@@ -65,13 +69,10 @@ func TestAddPatchAlreadyThere(t *testing.T) {
|
|||||||
t.Fatalf("unexpected cmd error: %v", err)
|
t.Fatalf("unexpected cmd error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// adding an existing patch should return an error
|
// adding an existing patch shouldn't return an error
|
||||||
err = cmd.RunE(cmd, args)
|
err = cmd.RunE(cmd, args)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("expected already there problem")
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
}
|
|
||||||
if err.Error() != "patch "+patchFileName+" already in kustomization file" {
|
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"log"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type addResourceOptions struct {
|
type addResourceOptions struct {
|
||||||
resourceFilePath string
|
resourceFilePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
|
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
|
||||||
@@ -56,10 +56,10 @@ func newCmdAddResource(fsys fs.FileSystem) *cobra.Command {
|
|||||||
|
|
||||||
// Validate validates addResource command.
|
// Validate validates addResource command.
|
||||||
func (o *addResourceOptions) Validate(args []string) error {
|
func (o *addResourceOptions) Validate(args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) == 0 {
|
||||||
return errors.New("must specify a resource file")
|
return errors.New("must specify a resource file")
|
||||||
}
|
}
|
||||||
o.resourceFilePath = args[0]
|
o.resourceFilePaths = args
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +70,13 @@ func (o *addResourceOptions) Complete(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// RunAddResource runs addResource command (do real work).
|
// RunAddResource runs addResource command (do real work).
|
||||||
func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
|
func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
|
||||||
_, err := fsys.Stat(o.resourceFilePath)
|
resources, err := globPatterns(fsys, o.resourceFilePaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(resources) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,11 +88,13 @@ func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if stringInSlice(o.resourceFilePath, m.Resources) {
|
for _, resource := range resources {
|
||||||
return fmt.Errorf("resource %s already in kustomization file", o.resourceFilePath)
|
if stringInSlice(resource, m.Resources) {
|
||||||
|
log.Printf("resource %s already in kustomization file", resource)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.Resources = append(m.Resources, resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Resources = append(m.Resources, o.resourceFilePath)
|
|
||||||
|
|
||||||
return mf.write(m)
|
return mf.write(m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ limitations under the License.
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
@@ -52,10 +51,11 @@ secretGenerator: []
|
|||||||
func TestAddResourceHappyPath(t *testing.T) {
|
func TestAddResourceHappyPath(t *testing.T) {
|
||||||
fakeFS := fs.MakeFakeFS()
|
fakeFS := fs.MakeFakeFS()
|
||||||
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
|
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
|
||||||
|
fakeFS.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
|
||||||
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
cmd := newCmdAddResource(fakeFS)
|
cmd := newCmdAddResource(fakeFS)
|
||||||
args := []string{resourceFileName}
|
args := []string{resourceFileName + "*"}
|
||||||
err := cmd.RunE(cmd, args)
|
err := cmd.RunE(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected cmd error: %v", err)
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
@@ -67,6 +67,9 @@ func TestAddResourceHappyPath(t *testing.T) {
|
|||||||
if !strings.Contains(string(content), resourceFileName) {
|
if !strings.Contains(string(content), resourceFileName) {
|
||||||
t.Errorf("expected resource name in kustomization")
|
t.Errorf("expected resource name in kustomization")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(string(content), resourceFileName+"another") {
|
||||||
|
t.Errorf("expected resource name in kustomization")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddResourceAlreadyThere(t *testing.T) {
|
func TestAddResourceAlreadyThere(t *testing.T) {
|
||||||
@@ -81,13 +84,10 @@ func TestAddResourceAlreadyThere(t *testing.T) {
|
|||||||
t.Fatalf("unexpected cmd error: %v", err)
|
t.Fatalf("unexpected cmd error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// adding an existing resource should return an error
|
// adding an existing resource doesn't return an error
|
||||||
err = cmd.RunE(cmd, args)
|
err = cmd.RunE(cmd, args)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("expected already there problem")
|
t.Errorf("unexpected cmd error :%v", err)
|
||||||
}
|
|
||||||
if err.Error() != "resource "+resourceFileName+" already in kustomization file" {
|
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
|
|
||||||
type buildOptions struct {
|
type buildOptions struct {
|
||||||
kustomizationPath string
|
kustomizationPath string
|
||||||
|
outputPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCmdBuild creates a new build command.
|
// newCmdBuild creates a new build command.
|
||||||
@@ -52,6 +53,10 @@ func newCmdBuild(out io.Writer, fs fs.FileSystem) *cobra.Command {
|
|||||||
return o.RunBuild(out, fs)
|
return o.RunBuild(out, fs)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmd.Flags().StringVarP(
|
||||||
|
&o.outputPath,
|
||||||
|
"output", "o", "",
|
||||||
|
"If specified, write the build output to this path.")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +74,8 @@ func (o *buildOptions) Validate(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunBuild runs build command.
|
// RunBuild runs build command.
|
||||||
func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
|
func (o *buildOptions) RunBuild(out io.Writer, fSys fs.FileSystem) error {
|
||||||
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
|
l := loader.NewFileLoader(fSys)
|
||||||
|
|
||||||
absPath, err := filepath.Abs(o.kustomizationPath)
|
absPath, err := filepath.Abs(o.kustomizationPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,7 +87,7 @@ func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := app.NewApplication(rootLoader)
|
application, err := app.NewApplication(rootLoader, fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,6 +103,10 @@ func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.outputPath != "" {
|
||||||
|
return fSys.WriteFile(o.outputPath, res)
|
||||||
|
}
|
||||||
_, err = out.Write(res)
|
_, err = out.Write(res)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func TestBuildValidate(t *testing.T) {
|
|||||||
func TestBuild(t *testing.T) {
|
func TestBuild(t *testing.T) {
|
||||||
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
|
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
|
||||||
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
|
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
|
||||||
fs := fs.MakeRealFS()
|
fSys := fs.MakeRealFS()
|
||||||
|
|
||||||
testcases := sets.NewString()
|
testcases := sets.NewString()
|
||||||
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
|
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
|
||||||
@@ -104,12 +104,12 @@ func TestBuild(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testcaseName := range testcases.List() {
|
for _, testcaseName := range testcases.List() {
|
||||||
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fs) })
|
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fSys) })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem) {
|
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fSys fs.FileSystem) {
|
||||||
name := testcaseName
|
name := testcaseName
|
||||||
testcase := buildTestCase{}
|
testcase := buildTestCase{}
|
||||||
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
||||||
@@ -125,7 +125,7 @@ func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected
|
|||||||
kustomizationPath: testcase.Filename,
|
kustomizationPath: testcase.Filename,
|
||||||
}
|
}
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
err = ops.RunBuild(buf, fs)
|
err = ops.RunBuild(buf, fSys)
|
||||||
switch {
|
switch {
|
||||||
case err != nil && len(testcase.ExpectedError) == 0:
|
case err != nil && len(testcase.ExpectedError) == 0:
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dataConfig encapsulates the options for add configmap/Secret commands.
|
// cMapFlagsAndArgs encapsulates the options for add configmap commands.
|
||||||
type dataConfig struct {
|
type cMapFlagsAndArgs struct {
|
||||||
// Name of configMap/Secret (required)
|
// Name of configMap/Secret (required)
|
||||||
Name string
|
Name string
|
||||||
// FileSources to derive the configMap/Secret from (optional)
|
// FileSources to derive the configMap/Secret from (optional)
|
||||||
@@ -34,7 +36,7 @@ type dataConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates required fields are set to support structured generation.
|
// Validate validates required fields are set to support structured generation.
|
||||||
func (a *dataConfig) Validate(args []string) error {
|
func (a *cMapFlagsAndArgs) Validate(args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("name must be specified once")
|
return fmt.Errorf("name must be specified once")
|
||||||
}
|
}
|
||||||
@@ -48,3 +50,12 @@ func (a *dataConfig) Validate(args []string) error {
|
|||||||
// TODO: Should we check if the path exists? if it's valid, if it's within the same (sub-)directory?
|
// TODO: Should we check if the path exists? if it's valid, if it's within the same (sub-)directory?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *cMapFlagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error {
|
||||||
|
result, err := globPatterns(fSys, a.FileSources)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.FileSources = result
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -17,11 +17,14 @@ limitations under the License.
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDataConfigValidation_NoName(t *testing.T) {
|
func TestDataConfigValidation_NoName(t *testing.T) {
|
||||||
config := dataConfig{}
|
config := cMapFlagsAndArgs{}
|
||||||
|
|
||||||
if config.Validate([]string{}) == nil {
|
if config.Validate([]string{}) == nil {
|
||||||
t.Fatal("Validation should fail if no name is specified")
|
t.Fatal("Validation should fail if no name is specified")
|
||||||
@@ -29,7 +32,7 @@ func TestDataConfigValidation_NoName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
|
func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
|
||||||
config := dataConfig{}
|
config := cMapFlagsAndArgs{}
|
||||||
|
|
||||||
if config.Validate([]string{"name", "othername"}) == nil {
|
if config.Validate([]string{"name", "othername"}) == nil {
|
||||||
t.Fatal("Validation should fail if more than one name is specified")
|
t.Fatal("Validation should fail if more than one name is specified")
|
||||||
@@ -39,12 +42,12 @@ func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
|
|||||||
func TestDataConfigValidation_Flags(t *testing.T) {
|
func TestDataConfigValidation_Flags(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
config dataConfig
|
config cMapFlagsAndArgs
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "env-file-source and literal are both set",
|
name: "env-file-source and literal are both set",
|
||||||
config: dataConfig{
|
config: cMapFlagsAndArgs{
|
||||||
LiteralSources: []string{"one", "two"},
|
LiteralSources: []string{"one", "two"},
|
||||||
EnvFileSource: "three",
|
EnvFileSource: "three",
|
||||||
},
|
},
|
||||||
@@ -52,7 +55,7 @@ func TestDataConfigValidation_Flags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "env-file-source and from-file are both set",
|
name: "env-file-source and from-file are both set",
|
||||||
config: dataConfig{
|
config: cMapFlagsAndArgs{
|
||||||
FileSources: []string{"one", "two"},
|
FileSources: []string{"one", "two"},
|
||||||
EnvFileSource: "three",
|
EnvFileSource: "three",
|
||||||
},
|
},
|
||||||
@@ -60,12 +63,12 @@ func TestDataConfigValidation_Flags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "we don't have any option set",
|
name: "we don't have any option set",
|
||||||
config: dataConfig{},
|
config: cMapFlagsAndArgs{},
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "we have from-file and literal ",
|
name: "we have from-file and literal ",
|
||||||
config: dataConfig{
|
config: cMapFlagsAndArgs{
|
||||||
LiteralSources: []string{"one", "two"},
|
LiteralSources: []string{"one", "two"},
|
||||||
FileSources: []string{"three", "four"},
|
FileSources: []string{"three", "four"},
|
||||||
},
|
},
|
||||||
@@ -81,3 +84,21 @@ func TestDataConfigValidation_Flags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExpandFileSource(t *testing.T) {
|
||||||
|
fakeFS := fs.MakeFakeFS()
|
||||||
|
fakeFS.Create("dir/config1")
|
||||||
|
fakeFS.Create("dir/config2")
|
||||||
|
fakeFS.Create("dir/reademe")
|
||||||
|
config := cMapFlagsAndArgs{
|
||||||
|
FileSources: []string{"dir/config*"},
|
||||||
|
}
|
||||||
|
config.ExpandFileSource(fakeFS)
|
||||||
|
expected := []string{
|
||||||
|
"dir/config1",
|
||||||
|
"dir/config2",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(config.FileSources, expected) {
|
||||||
|
t.Fatalf("FileSources is not correctly expanded: %v", config.FileSources)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,6 +123,7 @@ func newCmdSet(fsys fs.FileSystem) *cobra.Command {
|
|||||||
|
|
||||||
c.AddCommand(
|
c.AddCommand(
|
||||||
newCmdSetNamePrefix(fsys),
|
newCmdSetNamePrefix(fsys),
|
||||||
|
newCmdSetImageTag(fsys),
|
||||||
)
|
)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ import (
|
|||||||
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
|
func newCmdAddConfigMap(fSys fs.FileSystem) *cobra.Command {
|
||||||
var config dataConfig
|
var flagsAndArgs cMapFlagsAndArgs
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]",
|
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]",
|
||||||
Short: "Adds a configmap to the kustomization file.",
|
Short: "Adds a configmap to the kustomization file.",
|
||||||
@@ -44,47 +45,55 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
|
|||||||
kustomize edit add configmap my-configmap --from-env-file=env/path.env
|
kustomize edit add configmap my-configmap --from-env-file=env/path.env
|
||||||
`,
|
`,
|
||||||
RunE: func(_ *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
err := config.Validate(args)
|
err := flagsAndArgs.ExpandFileSource(fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load in the kustomization file.
|
err = flagsAndArgs.Validate(args)
|
||||||
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := mf.read()
|
// Load the kustomization file.
|
||||||
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the config map to the kustomization file.
|
kustomization, err := mf.read()
|
||||||
err = addConfigMap(m, config)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the flagsAndArgs map to the kustomization file.
|
||||||
|
err = addConfigMap(
|
||||||
|
kustomization, flagsAndArgs,
|
||||||
|
configmapandsecret.NewConfigMapFactory(
|
||||||
|
fSys, loader.NewFileLoader(fSys)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write out the kustomization file with added configmap.
|
// Write out the kustomization file with added configmap.
|
||||||
return mf.write(m)
|
return mf.write(kustomization)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&config.FileSources,
|
&flagsAndArgs.FileSources,
|
||||||
"from-file",
|
"from-file",
|
||||||
[]string{},
|
[]string{},
|
||||||
"Key file can be specified using its file path, in which case file basename will be used as configmap "+
|
"Key file can be specified using its file path, in which case file basename will be used as configmap "+
|
||||||
"key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+
|
"key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+
|
||||||
"directory will iterate each named file in the directory whose basename is a valid configmap key.")
|
"directory will iterate each named file in the directory whose basename is a valid configmap key.")
|
||||||
cmd.Flags().StringArrayVar(
|
cmd.Flags().StringArrayVar(
|
||||||
&config.LiteralSources,
|
&flagsAndArgs.LiteralSources,
|
||||||
"from-literal",
|
"from-literal",
|
||||||
[]string{},
|
[]string{},
|
||||||
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&config.EnvFileSource,
|
&flagsAndArgs.EnvFileSource,
|
||||||
"from-env-file",
|
"from-env-file",
|
||||||
"",
|
"",
|
||||||
"Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
|
"Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
|
||||||
@@ -92,27 +101,27 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// addConfigMap updates a configmap within a kustomization file, using the data in config.
|
// addConfigMap adds a configmap to a kustomization file.
|
||||||
// Note: error may leave kustomization file in an undefined state. Suggest passing a copy
|
// Note: error may leave kustomization file in an undefined state.
|
||||||
// of kustomization file.
|
// Suggest passing a copy of kustomization file.
|
||||||
func addConfigMap(m *types.Kustomization, config dataConfig) error {
|
func addConfigMap(
|
||||||
cm := getOrCreateConfigMap(m, config.Name)
|
k *types.Kustomization,
|
||||||
|
flagsAndArgs cMapFlagsAndArgs,
|
||||||
err := mergeData(&cm.DataSources, config)
|
factory *configmapandsecret.ConfigMapFactory) error {
|
||||||
|
cmArgs := makeConfigMapArgs(k, flagsAndArgs.Name)
|
||||||
|
err := mergeFlagsIntoCmArgs(&cmArgs.DataSources, flagsAndArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate by trying to create corev1.configmap.
|
// Validate by trying to create corev1.configmap.
|
||||||
_, _, err = configmapandsecret.MakeConfigmapAndGenerateName(*cm)
|
_, _, err = factory.MakeUnstructAndGenerateName(cmArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapArgs {
|
func makeConfigMapArgs(m *types.Kustomization, name string) *types.ConfigMapArgs {
|
||||||
for i, v := range m.ConfigMapGenerator {
|
for i, v := range m.ConfigMapGenerator {
|
||||||
if name == v.Name {
|
if name == v.Name {
|
||||||
return &m.ConfigMapGenerator[i]
|
return &m.ConfigMapGenerator[i]
|
||||||
@@ -124,13 +133,12 @@ func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapA
|
|||||||
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
|
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeData(src *types.DataSources, config dataConfig) error {
|
func mergeFlagsIntoCmArgs(src *types.DataSources, flags cMapFlagsAndArgs) error {
|
||||||
src.LiteralSources = append(src.LiteralSources, config.LiteralSources...)
|
src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...)
|
||||||
src.FileSources = append(src.FileSources, config.FileSources...)
|
src.FileSources = append(src.FileSources, flags.FileSources...)
|
||||||
if src.EnvSource != "" && src.EnvSource != config.EnvFileSource {
|
if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource {
|
||||||
return fmt.Errorf("updating existing env source '%s' not allowed.", src.EnvSource)
|
return fmt.Errorf("updating existing env source '%s' not allowed", src.EnvSource)
|
||||||
}
|
}
|
||||||
src.EnvSource = config.EnvFileSource
|
src.EnvSource = flags.EnvFileSource
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestNewAddConfigMapIsNotNil(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOrCreateConfigMap(t *testing.T) {
|
func TestMakeConfigMapArgs(t *testing.T) {
|
||||||
cmName := "test-config-name"
|
cmName := "test-config-name"
|
||||||
|
|
||||||
kustomization := &types.Kustomization{
|
kustomization := &types.Kustomization{
|
||||||
@@ -39,24 +39,24 @@ func TestGetOrCreateConfigMap(t *testing.T) {
|
|||||||
if len(kustomization.ConfigMapGenerator) != 0 {
|
if len(kustomization.ConfigMapGenerator) != 0 {
|
||||||
t.Fatal("Initial kustomization should not have any configmaps")
|
t.Fatal("Initial kustomization should not have any configmaps")
|
||||||
}
|
}
|
||||||
cm := getOrCreateConfigMap(kustomization, cmName)
|
args := makeConfigMapArgs(kustomization, cmName)
|
||||||
|
|
||||||
if cm == nil {
|
if args == nil {
|
||||||
t.Fatalf("ConfigMap should always be non-nil")
|
t.Fatalf("args should always be non-nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(kustomization.ConfigMapGenerator) != 1 {
|
if len(kustomization.ConfigMapGenerator) != 1 {
|
||||||
t.Fatalf("Kustomization should have newly created configmap")
|
t.Fatalf("Kustomization should have newly created configmap")
|
||||||
}
|
}
|
||||||
|
|
||||||
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != cm {
|
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != args {
|
||||||
t.Fatalf("Pointer address for newly inserted configmap should be same")
|
t.Fatalf("Pointer address for newly inserted configmap generator should be same")
|
||||||
}
|
}
|
||||||
|
|
||||||
existingCM := getOrCreateConfigMap(kustomization, cmName)
|
args2 := makeConfigMapArgs(kustomization, cmName)
|
||||||
|
|
||||||
if existingCM != cm {
|
if args2 != args {
|
||||||
t.Fatalf("should have returned an existing cm with name: %v", cmName)
|
t.Fatalf("should have returned an existing args with name: %v", cmName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(kustomization.ConfigMapGenerator) != 1 {
|
if len(kustomization.ConfigMapGenerator) != 1 {
|
||||||
@@ -64,10 +64,10 @@ func TestGetOrCreateConfigMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeData_LiteralSources(t *testing.T) {
|
func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) {
|
||||||
ds := &types.DataSources{}
|
ds := &types.DataSources{}
|
||||||
|
|
||||||
err := mergeData(ds, dataConfig{LiteralSources: []string{"k1=v1"}})
|
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k1=v1"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Merge initial literal source should not return error")
|
t.Fatalf("Merge initial literal source should not return error")
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func TestMergeData_LiteralSources(t *testing.T) {
|
|||||||
t.Fatalf("Initial literal source should have been added")
|
t.Fatalf("Initial literal source should have been added")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mergeData(ds, dataConfig{LiteralSources: []string{"k2=v2"}})
|
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k2=v2"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Merge second literal source should not return error")
|
t.Fatalf("Merge second literal source should not return error")
|
||||||
}
|
}
|
||||||
@@ -86,10 +86,10 @@ func TestMergeData_LiteralSources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeData_FileSources(t *testing.T) {
|
func TestMergeFlagsIntoCmArgs_FileSources(t *testing.T) {
|
||||||
ds := &types.DataSources{}
|
ds := &types.DataSources{}
|
||||||
|
|
||||||
err := mergeData(ds, dataConfig{FileSources: []string{"file1"}})
|
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file1"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Merge initial file source should not return error")
|
t.Fatalf("Merge initial file source should not return error")
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func TestMergeData_FileSources(t *testing.T) {
|
|||||||
t.Fatalf("Initial file source should have been added")
|
t.Fatalf("Initial file source should have been added")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mergeData(ds, dataConfig{FileSources: []string{"file2"}})
|
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file2"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Merge second file source should not return error")
|
t.Fatalf("Merge second file source should not return error")
|
||||||
}
|
}
|
||||||
@@ -108,12 +108,12 @@ func TestMergeData_FileSources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeData_EnvSource(t *testing.T) {
|
func TestMergeFlagsIntoCmArgs_EnvSource(t *testing.T) {
|
||||||
envFileName := "env1"
|
envFileName := "env1"
|
||||||
envFileName2 := "env2"
|
envFileName2 := "env2"
|
||||||
ds := &types.DataSources{}
|
ds := &types.DataSources{}
|
||||||
|
|
||||||
err := mergeData(ds, dataConfig{EnvFileSource: envFileName})
|
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Merge initial env source should not return error")
|
t.Fatalf("Merge initial env source should not return error")
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ func TestMergeData_EnvSource(t *testing.T) {
|
|||||||
t.Fatalf("Initial env source filename should have been added")
|
t.Fatalf("Initial env source filename should have been added")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mergeData(ds, dataConfig{EnvFileSource: envFileName2})
|
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName2})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("Updating env source should return an error")
|
t.Fatalf("Updating env source should return an error")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ func (o *diffOptions) Validate(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
|
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
|
||||||
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
|
func (o *diffOptions) RunDiff(out, errOut io.Writer, fSys fs.FileSystem) error {
|
||||||
|
|
||||||
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
|
l := loader.NewFileLoader(fSys)
|
||||||
|
|
||||||
absPath, err := filepath.Abs(o.kustomizationPath)
|
absPath, err := filepath.Abs(o.kustomizationPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,7 +80,7 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := app.NewApplication(rootLoader)
|
application, err := app.NewApplication(rootLoader, fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ func TestDiff(t *testing.T) {
|
|||||||
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
|
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
|
||||||
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
|
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
|
||||||
|
|
||||||
noopDir, _ := regexp.Compile(`/tmp/noop-[0-9]*/`)
|
tempDir := regexp.QuoteMeta(filepath.Clean(os.TempDir()))
|
||||||
transformedDir, _ := regexp.Compile(`/tmp/transformed-[0-9]*/`)
|
noopDir, _ := regexp.Compile(tempDir + `/noop-[0-9]*/`)
|
||||||
|
transformedDir, _ := regexp.Compile(tempDir + `/transformed-[0-9]*/`)
|
||||||
timestamp, _ := regexp.Compile(`[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9].[0-9]* [+-]{1}[0-9]{4}`)
|
timestamp, _ := regexp.Compile(`[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9].[0-9]* [+-]{1}[0-9]{4}`)
|
||||||
|
|
||||||
fs := fs.MakeRealFS()
|
fSys := fs.MakeRealFS()
|
||||||
|
|
||||||
testcases := sets.NewString()
|
testcases := sets.NewString()
|
||||||
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
|
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
|
||||||
@@ -74,7 +75,7 @@ func TestDiff(t *testing.T) {
|
|||||||
|
|
||||||
for _, testcaseName := range testcases.List() {
|
for _, testcaseName := range testcases.List() {
|
||||||
t.Run(testcaseName, func(t *testing.T) {
|
t.Run(testcaseName, func(t *testing.T) {
|
||||||
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fs,
|
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fSys,
|
||||||
noopDir, transformedDir, timestamp)
|
noopDir, transformedDir, timestamp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ limitations under the License.
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
@@ -30,9 +34,27 @@ import (
|
|||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kustomizationFields = []string{"resources", "bases", "namePrefix", "namespace", "crds", "commonLabels", "commonAnnotations", "patches", "configMapGenerator", "secretGenerator", "vars", "imageTags"}
|
||||||
|
recognizedFields = regexp.MustCompile("^(" + strings.Join(kustomizationFields, "|") + "):")
|
||||||
|
)
|
||||||
|
|
||||||
|
// commentedField records the comment associated with a kustomization field
|
||||||
|
// field has to be a recognized kustomization field
|
||||||
|
// comment can be empty
|
||||||
|
type commentedField struct {
|
||||||
|
field string
|
||||||
|
comment []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf *commentedField) appendComment(comment []byte) {
|
||||||
|
cf.comment = append(cf.comment, comment...)
|
||||||
|
}
|
||||||
|
|
||||||
type kustomizationFile struct {
|
type kustomizationFile struct {
|
||||||
path string
|
path string
|
||||||
fsys fs.FileSystem
|
fsys fs.FileSystem
|
||||||
|
originalFields []*commentedField
|
||||||
}
|
}
|
||||||
|
|
||||||
func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) { // nolint
|
func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) { // nolint
|
||||||
@@ -45,25 +67,23 @@ func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mf *kustomizationFile) validate() error {
|
func (mf *kustomizationFile) validate() error {
|
||||||
f, err := mf.fsys.Stat(mf.path)
|
if !mf.fsys.Exists(mf.path) {
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
|
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
|
||||||
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
||||||
return merr
|
return merr
|
||||||
}
|
}
|
||||||
if f.IsDir() {
|
if mf.fsys.IsDir(mf.path) {
|
||||||
mf.path = path.Join(mf.path, constants.KustomizationFileName)
|
mf.path = path.Join(mf.path, constants.KustomizationFileName)
|
||||||
_, err = mf.fsys.Stat(mf.path)
|
if !mf.fsys.Exists(mf.path) {
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
|
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
|
||||||
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
||||||
return merr
|
return merr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
|
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
|
||||||
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationFileSuffix)
|
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n",
|
||||||
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
mf.path, constants.KustomizationFileSuffix)
|
||||||
return merr
|
return interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -79,18 +99,21 @@ func (mf *kustomizationFile) read() (*types.Kustomization, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = mf.parseCommentedFields(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &kustomization, err
|
return &kustomization, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mf *kustomizationFile) write(kustomization *types.Kustomization) error {
|
func (mf *kustomizationFile) write(kustomization *types.Kustomization) error {
|
||||||
if kustomization == nil {
|
if kustomization == nil {
|
||||||
return errors.New("util: kustomization file arg is nil.")
|
return errors.New("util: kustomization file arg is nil")
|
||||||
}
|
}
|
||||||
bytes, err := yaml.Marshal(kustomization)
|
bytes, err := mf.marshal(kustomization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mf.fsys.WriteFile(mf.path, bytes)
|
return mf.fsys.WriteFile(mf.path, bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,3 +125,95 @@ func stringInSlice(str string, list []string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
|
||||||
|
buffer := bytes.NewBuffer(content)
|
||||||
|
var comments [][]byte
|
||||||
|
var currentfield string
|
||||||
|
|
||||||
|
line, err := buffer.ReadBytes('\n')
|
||||||
|
for err == nil {
|
||||||
|
if isCommentOrBlankLine(line) {
|
||||||
|
comments = append(comments, line)
|
||||||
|
} else if recognizedFields.Match(line) {
|
||||||
|
fields := recognizedFields.FindSubmatch(line)
|
||||||
|
currentfield = string(fields[1])
|
||||||
|
mf.originalFields = append(mf.originalFields, &commentedField{field: currentfield, comment: bytes.Join(comments, []byte(``))})
|
||||||
|
comments = [][]byte{}
|
||||||
|
} else if len(comments) > 0 {
|
||||||
|
mf.originalFields[len(mf.originalFields)-1].appendComment(bytes.Join(comments, []byte(``)))
|
||||||
|
comments = [][]byte{}
|
||||||
|
}
|
||||||
|
line, err = buffer.ReadBytes('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
|
||||||
|
output := []byte{}
|
||||||
|
for _, comment := range mf.originalFields {
|
||||||
|
output = append(output, comment.comment...)
|
||||||
|
content, err := marshalField(comment.field, kustomization)
|
||||||
|
if err != nil {
|
||||||
|
return content, err
|
||||||
|
}
|
||||||
|
output = append(output, content...)
|
||||||
|
}
|
||||||
|
for _, field := range kustomizationFields {
|
||||||
|
if mf.hasField(field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content, err := marshalField(field, kustomization)
|
||||||
|
if err != nil {
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
output = append(output, content...)
|
||||||
|
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mf *kustomizationFile) hasField(name string) bool {
|
||||||
|
for _, n := range mf.originalFields {
|
||||||
|
if n.field == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
isCommentOrBlankLine determines if a line is a comment or blank line
|
||||||
|
Return true for following lines
|
||||||
|
# This line is a comment
|
||||||
|
# This line is also a comment with several leading white spaces
|
||||||
|
|
||||||
|
(The line above is a blank line)
|
||||||
|
*/
|
||||||
|
func isCommentOrBlankLine(line []byte) bool {
|
||||||
|
s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
|
||||||
|
return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalField marshal a given field of a kustomization object into yaml format.
|
||||||
|
// If the field wasn't in the original kustomization.yaml file or wasn't added,
|
||||||
|
// an empty []byte is returned.
|
||||||
|
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
|
||||||
|
r := reflect.ValueOf(*kustomization)
|
||||||
|
v := r.FieldByName(strings.Title(field))
|
||||||
|
|
||||||
|
if !v.IsValid() || v.Len() == 0 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k := &types.Kustomization{}
|
||||||
|
kr := reflect.ValueOf(k)
|
||||||
|
kv := kr.Elem().FieldByName(strings.Title(field))
|
||||||
|
kv.Set(v)
|
||||||
|
|
||||||
|
return yaml.Marshal(k)
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestEmptyFile(t *testing.T) {
|
|||||||
func TestNewNotExist(t *testing.T) {
|
func TestNewNotExist(t *testing.T) {
|
||||||
badSuffix := "foo.bar"
|
badSuffix := "foo.bar"
|
||||||
fakeFS := fs.MakeFakeFS()
|
fakeFS := fs.MakeFakeFS()
|
||||||
fakeFS.Mkdir(".", 0644)
|
fakeFS.Mkdir(".")
|
||||||
fakeFS.Create(badSuffix)
|
fakeFS.Create(badSuffix)
|
||||||
_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
|
_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -88,3 +88,136 @@ func TestNewNotExist(t *testing.T) {
|
|||||||
t.Fatalf("expect an error contains %q, but got %v", contained, err)
|
t.Fatalf("expect an error contains %q, but got %v", contained, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreserveComments(t *testing.T) {
|
||||||
|
kustomizationContentWithComments := []byte(
|
||||||
|
`# shem qing some comments
|
||||||
|
# This is some comment we should preserve
|
||||||
|
# don't delete it
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
- service.yaml
|
||||||
|
# something you may want to keep
|
||||||
|
vars:
|
||||||
|
- fieldref:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
name: MY_SERVICE_NAME
|
||||||
|
objref:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
name: my-service
|
||||||
|
bases:
|
||||||
|
- ../namespaces
|
||||||
|
# some descriptions for the patches
|
||||||
|
patches:
|
||||||
|
- service.yaml
|
||||||
|
- pod.yaml
|
||||||
|
`)
|
||||||
|
fsys := fs.MakeFakeFS()
|
||||||
|
fsys.Create(constants.KustomizationFileName)
|
||||||
|
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
|
||||||
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
kustomization, err := mf.read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
if err = mf.write(kustomization); err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
bytes, _ := fsys.ReadFile(mf.path)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(kustomizationContentWithComments, bytes) {
|
||||||
|
t.Fatal("written kustomization with comments is not the same as original one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreserveCommentsWithAdjust(t *testing.T) {
|
||||||
|
kustomizationContentWithComments := []byte(`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# shem qing some comments
|
||||||
|
# This is some comment we should preserve
|
||||||
|
# don't delete it
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
# See which field this comment goes into
|
||||||
|
- service.yaml
|
||||||
|
|
||||||
|
|
||||||
|
# something you may want to keep
|
||||||
|
vars:
|
||||||
|
- fieldref:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
name: MY_SERVICE_NAME
|
||||||
|
objref:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
name: my-service
|
||||||
|
|
||||||
|
bases:
|
||||||
|
- ../namespaces
|
||||||
|
|
||||||
|
# some descriptions for the patches
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- service.yaml
|
||||||
|
- pod.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
expected := []byte(`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# shem qing some comments
|
||||||
|
# This is some comment we should preserve
|
||||||
|
# don't delete it
|
||||||
|
# See which field this comment goes into
|
||||||
|
resources:
|
||||||
|
- pod.yaml
|
||||||
|
- service.yaml
|
||||||
|
|
||||||
|
|
||||||
|
# something you may want to keep
|
||||||
|
vars:
|
||||||
|
- fieldref:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
name: MY_SERVICE_NAME
|
||||||
|
objref:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
name: my-service
|
||||||
|
|
||||||
|
bases:
|
||||||
|
- ../namespaces
|
||||||
|
|
||||||
|
# some descriptions for the patches
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- service.yaml
|
||||||
|
- pod.yaml
|
||||||
|
`)
|
||||||
|
fsys := fs.MakeFakeFS()
|
||||||
|
fsys.Create(constants.KustomizationFileName)
|
||||||
|
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
|
||||||
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kustomization, err := mf.read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
if err = mf.write(kustomization); err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
bytes, _ := fsys.ReadFile(mf.path)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, bytes) {
|
||||||
|
t.Fatal("written kustomization with comments is not the same as original one\n", string(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
113
pkg/commands/setimagetag.go
Normal file
113
pkg/commands/setimagetag.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type setImageTagOptions struct {
|
||||||
|
imageTagMap map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$")
|
||||||
|
|
||||||
|
// newCmdSetImageTag sets the new tags for images in the kustomization.
|
||||||
|
func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command {
|
||||||
|
var o setImageTagOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "imagetag",
|
||||||
|
Short: "Sets images and their new tags in the kustomization file",
|
||||||
|
Example: `
|
||||||
|
The command
|
||||||
|
set imagetag nginx:1.8.0 my-app:latest
|
||||||
|
will add
|
||||||
|
|
||||||
|
imageTags:
|
||||||
|
- name: nginx
|
||||||
|
newTag: 1.8.0
|
||||||
|
- name: my-app
|
||||||
|
newTag: latest
|
||||||
|
|
||||||
|
to the kustomization file if it doesn't exist,
|
||||||
|
and overwrite the previous newTag if the image name exists.
|
||||||
|
`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := o.Validate(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return o.RunSetImageTags(fsys)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates setImageTag command.
|
||||||
|
func (o *setImageTagOptions) Validate(args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("No image and newTag specified.")
|
||||||
|
}
|
||||||
|
o.imageTagMap = make(map[string]string)
|
||||||
|
for _, arg := range args {
|
||||||
|
imagetag := pattern.FindStringSubmatch(arg)
|
||||||
|
if len(imagetag) != 3 {
|
||||||
|
return errors.New("Invalid format of imagetag, must specify it as <image>:<newtag>")
|
||||||
|
}
|
||||||
|
o.imageTagMap[imagetag[1]] = imagetag[2]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunSetImageTags runs setImageTags command (does real work).
|
||||||
|
func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error {
|
||||||
|
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := mf.read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
imageTagMap := map[string]string{}
|
||||||
|
for _, it := range m.ImageTags {
|
||||||
|
imageTagMap[it.Name] = it.NewTag
|
||||||
|
}
|
||||||
|
for key, value := range o.imageTagMap {
|
||||||
|
imageTagMap[key] = value
|
||||||
|
}
|
||||||
|
var imageTags []types.ImageTag
|
||||||
|
for key, value := range imageTagMap {
|
||||||
|
imageTags = append(imageTags, types.ImageTag{Name: key, NewTag: value})
|
||||||
|
}
|
||||||
|
sort.Slice(imageTags, func(i, j int) bool {
|
||||||
|
return imageTags[i].Name < imageTags[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
m.ImageTags = imageTags
|
||||||
|
|
||||||
|
return mf.write(m)
|
||||||
|
}
|
||||||
99
pkg/commands/setimagetag_test.go
Normal file
99
pkg/commands/setimagetag_test.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetImageTagsHappyPath(t *testing.T) {
|
||||||
|
fakeFS := fs.MakeFakeFS()
|
||||||
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
|
cmd := newCmdSetImageTag(fakeFS)
|
||||||
|
args := []string{"image1:tag1", "image2:tag2", "localhost:5000/operator:1.0.0"}
|
||||||
|
err := cmd.RunE(cmd, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
|
}
|
||||||
|
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected read error: %v", err)
|
||||||
|
}
|
||||||
|
expected := []byte(`
|
||||||
|
imageTags:
|
||||||
|
- name: image1
|
||||||
|
newTag: tag1
|
||||||
|
- name: image2
|
||||||
|
newTag: tag2
|
||||||
|
- name: localhost:5000/operator
|
||||||
|
newTag: 1.0.0
|
||||||
|
`)
|
||||||
|
if !strings.Contains(string(content), string(expected)) {
|
||||||
|
t.Errorf("expected imageTags in kustomization file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetImageTagsOverride(t *testing.T) {
|
||||||
|
fakeFS := fs.MakeFakeFS()
|
||||||
|
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
|
||||||
|
|
||||||
|
cmd := newCmdSetImageTag(fakeFS)
|
||||||
|
args := []string{"image1:tag1", "image2:tag1"}
|
||||||
|
err := cmd.RunE(cmd, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
|
}
|
||||||
|
args = []string{"image2:tag2", "image3:tag3"}
|
||||||
|
err = cmd.RunE(cmd, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected cmd error: %v", err)
|
||||||
|
}
|
||||||
|
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected read error: %v", err)
|
||||||
|
}
|
||||||
|
expected := []byte(`
|
||||||
|
imageTags:
|
||||||
|
- name: image1
|
||||||
|
newTag: tag1
|
||||||
|
- name: image2
|
||||||
|
newTag: tag2
|
||||||
|
- name: image3
|
||||||
|
newTag: tag3
|
||||||
|
`)
|
||||||
|
if !strings.Contains(string(content), string(expected)) {
|
||||||
|
t.Errorf("expected imageTags in kustomization file %s", string(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetImageTagsNoArgs(t *testing.T) {
|
||||||
|
fakeFS := fs.MakeFakeFS()
|
||||||
|
|
||||||
|
cmd := newCmdSetImageTag(fakeFS)
|
||||||
|
err := cmd.Execute()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error: %v", err)
|
||||||
|
}
|
||||||
|
if err.Error() != "No image and newTag specified." {
|
||||||
|
t.Errorf("incorrect error: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,33 @@ diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: nginx
|
- image: nginx
|
||||||
|
diff -u -N /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml
|
||||||
|
--- /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
+++ /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
@@ -1,13 +1,21 @@
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
- name: nginx
|
||||||
|
+ annotations:
|
||||||
|
+ note: This is a test annotation
|
||||||
|
+ labels:
|
||||||
|
+ app: mynginx
|
||||||
|
+ org: example.com
|
||||||
|
+ team: foo
|
||||||
|
+ name: team-foo-nginx
|
||||||
|
spec:
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
- app: nginx
|
||||||
|
+ app: mynginx
|
||||||
|
+ org: example.com
|
||||||
|
+ team: foo
|
||||||
|
podSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
|
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
|
||||||
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
|
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
|
||||||
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
|
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
|||||||
@@ -44,3 +44,28 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- image: nginx
|
- image: nginx
|
||||||
name: nginx
|
name: nginx
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: mynginx
|
||||||
|
org: example.com
|
||||||
|
team: foo
|
||||||
|
name: team-foo-nginx
|
||||||
|
spec:
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: mynginx
|
||||||
|
org: example.com
|
||||||
|
team: foo
|
||||||
|
podSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- test
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ commonLabels:
|
|||||||
commonAnnotations:
|
commonAnnotations:
|
||||||
note: This is a test annotation
|
note: This is a test annotation
|
||||||
resources:
|
resources:
|
||||||
- resources/*.yaml
|
- resources/deployment.yaml
|
||||||
|
- resources/networkpolicy.yaml
|
||||||
|
- resources/service.yaml
|
||||||
|
|||||||
13
pkg/commands/testdata/testcase-base-only/in/resources/networkpolicy.yaml
vendored
Normal file
13
pkg/commands/testdata/testcase-base-only/in/resources/networkpolicy.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- {key: app, operator: In, values: [test]}
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
@@ -12,8 +12,8 @@ diff -u -N /tmp/noop/jingfang.example.com_v1beta1_MyKind_mykind.yaml /tmp/transf
|
|||||||
- name: bee
|
- name: bee
|
||||||
+ name: test-bee
|
+ name: test-bee
|
||||||
secretRef:
|
secretRef:
|
||||||
- name: crdsecret-m5ht5thcb4
|
- name: crdsecret
|
||||||
+ name: test-crdsecret-m48btmkck5
|
+ name: test-crdsecret
|
||||||
diff -u -N /tmp/noop/v1beta1_Bee_bee.yaml /tmp/transformed/v1beta1_Bee_bee.yaml
|
diff -u -N /tmp/noop/v1beta1_Bee_bee.yaml /tmp/transformed/v1beta1_Bee_bee.yaml
|
||||||
--- /tmp/noop/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
|
--- /tmp/noop/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
|
||||||
+++ /tmp/transformed/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
|
+++ /tmp/transformed/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
|
||||||
@@ -32,5 +32,5 @@ diff -u -N /tmp/noop/v1_Secret_crdsecret.yaml /tmp/transformed/v1_Secret_crdsecr
|
|||||||
PATH: YmJiYmJiYmIK
|
PATH: YmJiYmJiYmIK
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
- name: crdsecret-m5ht5thcb4
|
- name: crdsecret
|
||||||
+ name: test-crdsecret-m48btmkck5
|
+ name: test-crdsecret
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ data:
|
|||||||
PATH: YmJiYmJiYmIK
|
PATH: YmJiYmJiYmIK
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: test-crdsecret-m48btmkck5
|
name: test-crdsecret
|
||||||
---
|
---
|
||||||
apiVersion: v1beta1
|
apiVersion: v1beta1
|
||||||
kind: Bee
|
kind: Bee
|
||||||
@@ -20,4 +20,4 @@ spec:
|
|||||||
beeRef:
|
beeRef:
|
||||||
name: test-bee
|
name: test-bee
|
||||||
secretRef:
|
secretRef:
|
||||||
name: test-crdsecret-m48btmkck5
|
name: test-crdsecret
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ namePrefix: staging-
|
|||||||
commonLabels:
|
commonLabels:
|
||||||
env: staging
|
env: staging
|
||||||
patches:
|
patches:
|
||||||
- patches/deployment-patch*.yaml
|
- patches/deployment-patch1.yaml
|
||||||
|
- patches/deployment-patch2.yaml
|
||||||
bases:
|
bases:
|
||||||
- ../package/
|
- ../package/
|
||||||
configMapGenerator:
|
configMapGenerator:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ diff -u -N /tmp/noop/extensions_v1beta1_Deployment_mungebot.yaml /tmp/transforme
|
|||||||
- name: foo
|
- name: foo
|
||||||
value: bar
|
value: bar
|
||||||
- image: nginx
|
- image: nginx
|
||||||
+ image: nginx:1.7.9
|
+ image: nginx:1.8.0
|
||||||
name: nginx
|
name: nginx
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ spec:
|
|||||||
name: test-infra-app-tls-6hkmhf2224
|
name: test-infra-app-tls-6hkmhf2224
|
||||||
- name: foo
|
- name: foo
|
||||||
value: bar
|
value: bar
|
||||||
image: nginx:1.7.9
|
image: nginx:1.8.0
|
||||||
name: nginx
|
name: nginx
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
|
|||||||
@@ -53,12 +53,14 @@ diff -u -N /tmp/noop/batch_v1beta1_CronJob_cronjob-example.yaml /tmp/transformed
|
|||||||
spec:
|
spec:
|
||||||
concurrencyPolicy: Forbid
|
concurrencyPolicy: Forbid
|
||||||
jobTemplate:
|
jobTemplate:
|
||||||
@@ -11,10 +11,10 @@
|
@@ -11,11 +11,11 @@
|
||||||
containers:
|
containers:
|
||||||
- command:
|
- command:
|
||||||
- echo
|
- echo
|
||||||
- - base-cockroachdb
|
- - base-cockroachdb
|
||||||
|
- - base-test-config-map-259876d7fg
|
||||||
+ - dev-base-cockroachdb
|
+ - dev-base-cockroachdb
|
||||||
|
+ - dev-base-test-config-map-b2g2dmd64b
|
||||||
env:
|
env:
|
||||||
- name: CDB_PUBLIC_SVC
|
- name: CDB_PUBLIC_SVC
|
||||||
- value: base-cockroachdb-public
|
- value: base-cockroachdb-public
|
||||||
@@ -140,6 +142,15 @@ diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml /tm
|
|||||||
rules:
|
rules:
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
|
diff -u -N /tmp/noop/v1_ConfigMap_test-config-map.yaml /tmp/transformed/v1_ConfigMap_test-config-map.yaml
|
||||||
|
--- /tmp/noop/v1_ConfigMap_test-config-map.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
+++ /tmp/transformed/v1_ConfigMap_test-config-map.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
@@ -5,4 +5,4 @@
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
- name: base-test-config-map-259876d7fg
|
||||||
|
+ name: dev-base-test-config-map-b2g2dmd64b
|
||||||
diff -u -N /tmp/noop/v1_ServiceAccount_cockroachdb.yaml /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml
|
diff -u -N /tmp/noop/v1_ServiceAccount_cockroachdb.yaml /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml
|
||||||
--- /tmp/noop/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
--- /tmp/noop/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||||
+++ /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
+++ /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ subjects:
|
|||||||
namespace: default
|
namespace: default
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
baz: qux
|
||||||
|
foo: bar
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: dev-base-test-config-map-b2g2dmd64b
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
@@ -206,6 +215,7 @@ spec:
|
|||||||
- command:
|
- command:
|
||||||
- echo
|
- echo
|
||||||
- dev-base-cockroachdb
|
- dev-base-cockroachdb
|
||||||
|
- dev-base-test-config-map-b2g2dmd64b
|
||||||
env:
|
env:
|
||||||
- name: CDB_PUBLIC_SVC
|
- name: CDB_PUBLIC_SVC
|
||||||
value: dev-base-cockroachdb-public
|
value: dev-base-cockroachdb-public
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- echo
|
- echo
|
||||||
- "$(CDB_STATEFULSET_NAME)"
|
- "$(CDB_STATEFULSET_NAME)"
|
||||||
|
- "$(TEST_CONFIG_MAP)"
|
||||||
env:
|
env:
|
||||||
- name: CDB_PUBLIC_SVC
|
- name: CDB_PUBLIC_SVC
|
||||||
value: "$(CDB_PUBLIC_SVC)"
|
value: "$(CDB_PUBLIC_SVC)"
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ namePrefix: base-
|
|||||||
resources:
|
resources:
|
||||||
- cockroachdb-statefulset-secure.yaml
|
- cockroachdb-statefulset-secure.yaml
|
||||||
- cronjob.yaml
|
- cronjob.yaml
|
||||||
|
configMapGenerator:
|
||||||
|
- name: test-config-map
|
||||||
|
literals:
|
||||||
|
- foo=bar
|
||||||
|
- baz=qux
|
||||||
vars:
|
vars:
|
||||||
- name: CDB_PUBLIC_SVC
|
- name: CDB_PUBLIC_SVC
|
||||||
objref:
|
objref:
|
||||||
@@ -25,3 +30,10 @@ vars:
|
|||||||
fieldref:
|
fieldref:
|
||||||
fieldpath: metadata.name
|
fieldpath: metadata.name
|
||||||
|
|
||||||
|
- name: TEST_CONFIG_MAP
|
||||||
|
objref:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: test-config-map
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
|||||||
39
pkg/commands/util.go
Normal file
39
pkg/commands/util.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func globPatterns(fsys fs.FileSystem, patterns []string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
files, err := fsys.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
log.Printf("%s has no match", pattern)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, files...)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package configmapandsecret generates configmaps and secrets per generator rules.
|
|
||||||
package configmapandsecret
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/hash"
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MakeConfigmapAndGenerateName makes a configmap and returns the configmap and the name appended with a hash.
|
|
||||||
func MakeConfigmapAndGenerateName(cm types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
|
|
||||||
corev1CM, err := makeConfigMap(cm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
h, err := hash.ConfigMapHash(corev1CM)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
nameWithHash := fmt.Sprintf("%s-%s", corev1CM.GetName(), h)
|
|
||||||
unstructuredCM, err := objectToUnstructured(corev1CM)
|
|
||||||
return unstructuredCM, nameWithHash, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeSecretAndGenerateName returns a secret with the name appended with a hash.
|
|
||||||
func MakeSecretAndGenerateName(secret types.SecretArgs, path string) (*unstructured.Unstructured, string, error) {
|
|
||||||
corev1Secret, err := makeSecret(secret, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
h, err := hash.SecretHash(corev1Secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
nameWithHash := fmt.Sprintf("%s-%s", secret.Name, h)
|
|
||||||
unstructuredCM, err := objectToUnstructured(corev1Secret)
|
|
||||||
return unstructuredCM, nameWithHash, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
|
|
||||||
marshaled, err := json.Marshal(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var out unstructured.Unstructured
|
|
||||||
err = out.UnmarshalJSON(marshaled)
|
|
||||||
return &out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeConfigMap(cm types.ConfigMapArgs) (*corev1.ConfigMap, error) {
|
|
||||||
corev1cm := &corev1.ConfigMap{}
|
|
||||||
corev1cm.APIVersion = "v1"
|
|
||||||
corev1cm.Kind = "ConfigMap"
|
|
||||||
corev1cm.Name = cm.Name
|
|
||||||
corev1cm.Data = map[string]string{}
|
|
||||||
|
|
||||||
if cm.EnvSource != "" {
|
|
||||||
if err := cutil.HandleConfigMapFromEnvFileSource(corev1cm, cm.EnvSource); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cm.FileSources != nil {
|
|
||||||
if err := cutil.HandleConfigMapFromFileSources(corev1cm, cm.FileSources); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cm.LiteralSources != nil {
|
|
||||||
if err := cutil.HandleConfigMapFromLiteralSources(corev1cm, cm.LiteralSources); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return corev1cm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSecret(secret types.SecretArgs, path string) (*corev1.Secret, error) {
|
|
||||||
corev1secret := &corev1.Secret{}
|
|
||||||
corev1secret.APIVersion = "v1"
|
|
||||||
corev1secret.Kind = "Secret"
|
|
||||||
corev1secret.Name = secret.Name
|
|
||||||
corev1secret.Type = corev1.SecretType(secret.Type)
|
|
||||||
if corev1secret.Type == "" {
|
|
||||||
corev1secret.Type = corev1.SecretTypeOpaque
|
|
||||||
}
|
|
||||||
corev1secret.Data = map[string][]byte{}
|
|
||||||
|
|
||||||
for k, v := range secret.Commands {
|
|
||||||
out, err := createSecretKey(path, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
corev1secret.Data[k] = out
|
|
||||||
}
|
|
||||||
|
|
||||||
return corev1secret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSecretKey(wd string, command string) ([]byte, error) {
|
|
||||||
fi, err := os.Stat(wd)
|
|
||||||
if err != nil || !fi.IsDir() {
|
|
||||||
wd = filepath.Dir(wd)
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
|
||||||
cmd.Dir = wd
|
|
||||||
|
|
||||||
return cmd.Output()
|
|
||||||
}
|
|
||||||
217
pkg/configmapandsecret/configmapfactory.go
Normal file
217
pkg/configmapandsecret/configmapfactory.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package configmapandsecret generates configmaps and secrets per generator rules.
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/hash"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMapFactory makes ConfigMaps.
|
||||||
|
type ConfigMapFactory struct {
|
||||||
|
fSys fs.FileSystem
|
||||||
|
ldr loader.Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigMapFactory returns a new ConfigMapFactory.
|
||||||
|
func NewConfigMapFactory(
|
||||||
|
fSys fs.FileSystem, l loader.Loader) *ConfigMapFactory {
|
||||||
|
return &ConfigMapFactory{fSys: fSys, ldr: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeUnstructAndGenerateName returns an configmap and the name appended with a hash.
|
||||||
|
func (f *ConfigMapFactory) MakeUnstructAndGenerateName(
|
||||||
|
args *types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
|
||||||
|
cm, err := f.MakeConfigMap(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
h, err := hash.ConfigMapHash(cm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
nameWithHash := fmt.Sprintf("%s-%s", cm.GetName(), h)
|
||||||
|
unstructuredCM, err := objectToUnstructured(cm)
|
||||||
|
return unstructuredCM, nameWithHash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
|
||||||
|
marshaled, err := json.Marshal(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out unstructured.Unstructured
|
||||||
|
err = out.UnmarshalJSON(marshaled)
|
||||||
|
return &out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ConfigMapFactory) makeFreshConfigMap(
|
||||||
|
args *types.ConfigMapArgs) *corev1.ConfigMap {
|
||||||
|
cm := &corev1.ConfigMap{}
|
||||||
|
cm.APIVersion = "v1"
|
||||||
|
cm.Kind = "ConfigMap"
|
||||||
|
cm.Name = args.Name
|
||||||
|
cm.Data = map[string]string{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeConfigMap returns a new ConfigMap, or nil and an error.
|
||||||
|
func (f *ConfigMapFactory) MakeConfigMap(
|
||||||
|
args *types.ConfigMapArgs) (*corev1.ConfigMap, error) {
|
||||||
|
var all []kvPair
|
||||||
|
var err error
|
||||||
|
cm := f.makeFreshConfigMap(args)
|
||||||
|
|
||||||
|
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"env source file: %s",
|
||||||
|
args.EnvSource))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"literal sources %v", args.LiteralSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"file sources: %v", args.FileSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
for _, kv := range all {
|
||||||
|
err = addKvToConfigMap(cm, kv.key, kv.value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValuesFromLiteralSources(sources []string) ([]kvPair, error) {
|
||||||
|
var kvs []kvPair
|
||||||
|
for _, s := range sources {
|
||||||
|
k, v, err := parseLiteralSource(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs = append(kvs, kvPair{key: k, value: v})
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValuesFromFileSources(ldr loader.Loader, sources []string) ([]kvPair, error) {
|
||||||
|
var kvs []kvPair
|
||||||
|
for _, s := range sources {
|
||||||
|
k, fPath, err := parseFileSource(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
content, err := ldr.Load(fPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs = append(kvs, kvPair{key: k, value: string(content)})
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValuesFromEnvFile(l loader.Loader, path string) ([]kvPair, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
content, err := l.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return keyValuesFromLines(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKvToConfigMap adds the given key and data to the given config map.
|
||||||
|
// Error if key invalid, or already exists.
|
||||||
|
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
|
||||||
|
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
|
||||||
|
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||||
|
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
if _, entryExists := configMap.Data[keyName]; entryExists {
|
||||||
|
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v", keyName, configMap.Data)
|
||||||
|
}
|
||||||
|
configMap.Data[keyName] = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFileSource parses the source given.
|
||||||
|
//
|
||||||
|
// Acceptable formats include:
|
||||||
|
// 1. source-path: the basename will become the key name
|
||||||
|
// 2. source-name=source-path: the source-name will become the key name and
|
||||||
|
// source-path is the path to the key file.
|
||||||
|
//
|
||||||
|
// Key names cannot include '='.
|
||||||
|
func parseFileSource(source string) (keyName, filePath string, err error) {
|
||||||
|
numSeparators := strings.Count(source, "=")
|
||||||
|
switch {
|
||||||
|
case numSeparators == 0:
|
||||||
|
return path.Base(source), source, nil
|
||||||
|
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
|
||||||
|
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
|
||||||
|
case numSeparators > 1:
|
||||||
|
return "", "", errors.New("key names or file paths cannot contain '='")
|
||||||
|
default:
|
||||||
|
components := strings.Split(source, "=")
|
||||||
|
return components[0], components[1], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLiteralSource parses the source key=val pair into its component pieces.
|
||||||
|
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
|
||||||
|
// it returns an error in the case of empty keys, values, or a missing equals sign.
|
||||||
|
func parseLiteralSource(source string) (keyName, value string, err error) {
|
||||||
|
// leading equal is invalid
|
||||||
|
if strings.Index(source, "=") == 0 {
|
||||||
|
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||||
|
}
|
||||||
|
// split after the first equal (so values can have the = character)
|
||||||
|
items := strings.SplitN(source, "=", 2)
|
||||||
|
if len(items) != 2 {
|
||||||
|
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items[0], items[1], nil
|
||||||
|
}
|
||||||
@@ -17,10 +17,11 @@ limitations under the License.
|
|||||||
package configmapandsecret
|
package configmapandsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -93,41 +94,6 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestSecret(name string) *corev1.Secret {
|
|
||||||
return &corev1.Secret{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "Secret",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"DB_USERNAME": []byte("admin"),
|
|
||||||
"DB_PASSWORD": []byte("somepw"),
|
|
||||||
},
|
|
||||||
Type: corev1.SecretTypeOpaque,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUnstructuredSecret(name string) *unstructured.Unstructured {
|
|
||||||
return &unstructured.Unstructured{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Secret",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": name,
|
|
||||||
"creationTimestamp": nil,
|
|
||||||
},
|
|
||||||
"type": string(corev1.SecretTypeOpaque),
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
|
||||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConstructConfigMap(t *testing.T) {
|
func TestConstructConfigMap(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
description string
|
description string
|
||||||
@@ -168,8 +134,11 @@ func TestConstructConfigMap(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: all tests should use a FakeFs
|
||||||
|
fSys := fs.MakeRealFS()
|
||||||
|
f := NewConfigMapFactory(fSys, loader.NewFileLoader(fSys))
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
cm, err := makeConfigMap(tc.input)
|
cm, err := f.MakeConfigMap(&tc.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -179,39 +148,6 @@ func TestConstructConfigMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConstructSecret(t *testing.T) {
|
|
||||||
secret := types.SecretArgs{
|
|
||||||
Name: "secret",
|
|
||||||
Commands: map[string]string{
|
|
||||||
"DB_USERNAME": "printf admin",
|
|
||||||
"DB_PASSWORD": "printf somepw",
|
|
||||||
},
|
|
||||||
Type: "Opaque",
|
|
||||||
}
|
|
||||||
cm, err := makeSecret(secret, ".")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
expected := makeTestSecret("secret")
|
|
||||||
if !reflect.DeepEqual(*cm, *expected) {
|
|
||||||
t.Fatalf("%#v\ndoesn't match expected:\n%#v", *cm, *expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFailConstructSecret(t *testing.T) {
|
|
||||||
secret := types.SecretArgs{
|
|
||||||
Name: "secret",
|
|
||||||
Commands: map[string]string{
|
|
||||||
"FAILURE": "false", // This will fail.
|
|
||||||
},
|
|
||||||
Type: "Opaque",
|
|
||||||
}
|
|
||||||
_, err := makeSecret(secret, ".")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected failure.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObjectConvertToUnstructured(t *testing.T) {
|
func TestObjectConvertToUnstructured(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
description string
|
description string
|
||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package resmap
|
package configmapandsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -28,14 +28,14 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
|
|
||||||
|
|
||||||
// kvPair represents a key value pair.
|
// kvPair represents a key value pair.
|
||||||
type kvPair struct {
|
type kvPair struct {
|
||||||
key string
|
key string
|
||||||
value string
|
value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
|
||||||
|
|
||||||
// keyValuesFromLines parses given content in to a list of key-value pairs.
|
// keyValuesFromLines parses given content in to a list of key-value pairs.
|
||||||
func keyValuesFromLines(content []byte) ([]kvPair, error) {
|
func keyValuesFromLines(content []byte) ([]kvPair, error) {
|
||||||
var kvs []kvPair
|
var kvs []kvPair
|
||||||
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
package resmap
|
package configmapandsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
134
pkg/configmapandsecret/secretfactory.go
Normal file
134
pkg/configmapandsecret/secretfactory.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecretFactory makes Secrets.
|
||||||
|
type SecretFactory struct {
|
||||||
|
fSys fs.FileSystem
|
||||||
|
wd string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecretFactory returns a new SecretFactory.
|
||||||
|
func NewSecretFactory(fSys fs.FileSystem, wd string) *SecretFactory {
|
||||||
|
return &SecretFactory{fSys: fSys, wd: wd}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret {
|
||||||
|
s := &corev1.Secret{}
|
||||||
|
s.APIVersion = "v1"
|
||||||
|
s.Kind = "Secret"
|
||||||
|
s.Name = args.Name
|
||||||
|
s.Type = corev1.SecretType(args.Type)
|
||||||
|
if s.Type == "" {
|
||||||
|
s.Type = corev1.SecretTypeOpaque
|
||||||
|
}
|
||||||
|
s.Data = map[string][]byte{}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSecret returns a new secret.
|
||||||
|
func (f *SecretFactory) MakeSecret(args *types.SecretArgs) (*corev1.Secret, error) {
|
||||||
|
var all []kvPair
|
||||||
|
var err error
|
||||||
|
s := f.makeFreshSecret(args)
|
||||||
|
|
||||||
|
pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"env source file: %s",
|
||||||
|
args.EnvCommand))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = f.keyValuesFromCommands(args.Commands)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"commands %v", args.Commands))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
for _, kv := range all {
|
||||||
|
err = addKvToSecret(s, kv.key, kv.value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
|
||||||
|
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
|
||||||
|
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||||
|
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
if _, entryExists := secret.Data[keyName]; entryExists {
|
||||||
|
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
|
||||||
|
}
|
||||||
|
secret.Data[keyName] = []byte(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SecretFactory) keyValuesFromEnvFileCommand(cmd string) ([]kvPair, error) {
|
||||||
|
content, err := f.createSecretKey(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return keyValuesFromLines(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SecretFactory) keyValuesFromCommands(sources map[string]string) ([]kvPair, error) {
|
||||||
|
var kvs []kvPair
|
||||||
|
for k, cmd := range sources {
|
||||||
|
content, err := f.createSecretKey(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs = append(kvs, kvPair{key: k, value: string(content)})
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a command, return its output as the secret.
|
||||||
|
func (f *SecretFactory) createSecretKey(command string) ([]byte, error) {
|
||||||
|
if !f.fSys.IsDir(f.wd) {
|
||||||
|
f.wd = filepath.Dir(f.wd)
|
||||||
|
if !f.fSys.IsDir(f.wd) {
|
||||||
|
return nil, errors.New("not a directory: " + f.wd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
||||||
|
cmd.Dir = f.wd
|
||||||
|
return cmd.Output()
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleConfigMapFromLiteralSources adds the specified literal source
|
|
||||||
// information into the provided configMap.
|
|
||||||
func HandleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error {
|
|
||||||
for _, literalSource := range literalSources {
|
|
||||||
keyName, value, err := ParseLiteralSource(literalSource)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleConfigMapFromFileSources adds the specified file source information
|
|
||||||
// into the provided configMap
|
|
||||||
func HandleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error {
|
|
||||||
for _, fileSource := range fileSources {
|
|
||||||
keyName, filePath, err := ParseFileSource(fileSource)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := os.Stat(filePath)
|
|
||||||
if err != nil {
|
|
||||||
switch err := err.(type) {
|
|
||||||
case *os.PathError:
|
|
||||||
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("error reading %s: %v", filePath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
if strings.Contains(fileSource, "=") {
|
|
||||||
return fmt.Errorf("cannot give a key name for a directory path.")
|
|
||||||
}
|
|
||||||
fileList, err := ioutil.ReadDir(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
|
||||||
}
|
|
||||||
for _, item := range fileList {
|
|
||||||
itemPath := path.Join(filePath, item.Name())
|
|
||||||
if item.Mode().IsRegular() {
|
|
||||||
keyName = item.Name()
|
|
||||||
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleConfigMapFromEnvFileSource adds the specified env file source information
|
|
||||||
// into the provided configMap
|
|
||||||
func HandleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error {
|
|
||||||
info, err := os.Stat(envFileSource)
|
|
||||||
if err != nil {
|
|
||||||
switch err := err.(type) {
|
|
||||||
case *os.PathError:
|
|
||||||
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("error reading %s: %v", envFileSource, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return fmt.Errorf("env config file cannot be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
return addFromEnvFile(envFileSource, func(key, value string) error {
|
|
||||||
return addKeyFromLiteralToConfigMap(configMap, key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
|
|
||||||
// the value with the content of the given file path, or returns an error.
|
|
||||||
func addKeyFromFileToConfigMap(configMap *v1.ConfigMap, keyName, filePath string) error {
|
|
||||||
data, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
|
|
||||||
// returning an error if the key is not valid or if the key already exists.
|
|
||||||
func addKeyFromLiteralToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
|
|
||||||
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
|
|
||||||
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
|
||||||
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
|
|
||||||
}
|
|
||||||
if _, entryExists := configMap.Data[keyName]; entryExists {
|
|
||||||
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data)
|
|
||||||
}
|
|
||||||
configMap.Data[keyName] = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
|
|
||||||
|
|
||||||
// processEnvFileLine returns a blank key if the line is empty or a comment.
|
|
||||||
// The value will be retrieved from the environment if necessary.
|
|
||||||
func processEnvFileLine(line []byte, filePath string,
|
|
||||||
currentLine int) (key, value string, err error) {
|
|
||||||
|
|
||||||
if !utf8.Valid(line) {
|
|
||||||
return ``, ``, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v",
|
|
||||||
filePath, currentLine+1, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We trim UTF8 BOM from the first line of the file but no others
|
|
||||||
if currentLine == 0 {
|
|
||||||
line = bytes.TrimPrefix(line, utf8bom)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim the line from all leading whitespace first
|
|
||||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
|
||||||
|
|
||||||
// If the line is empty or a comment, we return a blank key/value pair.
|
|
||||||
if len(line) == 0 || line[0] == '#' {
|
|
||||||
return ``, ``, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data := strings.SplitN(string(line), "=", 2)
|
|
||||||
key = data[0]
|
|
||||||
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
|
|
||||||
return ``, ``, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) == 2 {
|
|
||||||
value = data[1]
|
|
||||||
} else {
|
|
||||||
// No value (no `=` in the line) is a signal to obtain the value
|
|
||||||
// from the environment.
|
|
||||||
value = os.Getenv(key)
|
|
||||||
}
|
|
||||||
return key, value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// addFromEnvFile processes an env file allows a generic addTo to handle the
|
|
||||||
// collection of key value pairs or returns an error.
|
|
||||||
func addFromEnvFile(filePath string, addTo func(key, value string) error) error {
|
|
||||||
f, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
currentLine := 0
|
|
||||||
for scanner.Scan() {
|
|
||||||
// Process the current line, retrieving a key/value pair if
|
|
||||||
// possible.
|
|
||||||
scannedBytes := scanner.Bytes()
|
|
||||||
key, value, err := processEnvFileLine(scannedBytes, filePath, currentLine)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
currentLine++
|
|
||||||
|
|
||||||
if len(key) == 0 {
|
|
||||||
// no key means line was empty or a comment
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = addTo(key, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleFromLiteralSources adds the specified literal source information into the provided secret
|
|
||||||
func HandleFromLiteralSources(secret *v1.Secret, literalSources []string) error {
|
|
||||||
for _, literalSource := range literalSources {
|
|
||||||
keyName, value, err := ParseLiteralSource(literalSource)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFromFileSources adds the specified file source information into the provided secret
|
|
||||||
func HandleFromFileSources(secret *v1.Secret, fileSources []string) error {
|
|
||||||
for _, fileSource := range fileSources {
|
|
||||||
keyName, filePath, err := ParseFileSource(fileSource)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := os.Stat(filePath)
|
|
||||||
if err != nil {
|
|
||||||
switch err := err.(type) {
|
|
||||||
case *os.PathError:
|
|
||||||
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("error reading %s: %v", filePath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
if strings.Contains(fileSource, "=") {
|
|
||||||
return fmt.Errorf("cannot give a key name for a directory path.")
|
|
||||||
}
|
|
||||||
fileList, err := ioutil.ReadDir(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
|
||||||
}
|
|
||||||
for _, item := range fileList {
|
|
||||||
itemPath := path.Join(filePath, item.Name())
|
|
||||||
if item.Mode().IsRegular() {
|
|
||||||
keyName = item.Name()
|
|
||||||
if err = addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFromEnvFileSource adds the specified env file source information
|
|
||||||
// into the provided secret
|
|
||||||
func HandleFromEnvFileSource(secret *v1.Secret, envFileSource string) error {
|
|
||||||
info, err := os.Stat(envFileSource)
|
|
||||||
if err != nil {
|
|
||||||
switch err := err.(type) {
|
|
||||||
case *os.PathError:
|
|
||||||
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("error reading %s: %v", envFileSource, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return fmt.Errorf("env secret file cannot be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
return addFromEnvFile(envFileSource, func(key, value string) error {
|
|
||||||
return addKeyFromLiteralToSecret(secret, key, []byte(value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeyFromFileToSecret(secret *v1.Secret, keyName, filePath string) error {
|
|
||||||
data, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return addKeyFromLiteralToSecret(secret, keyName, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeyFromLiteralToSecret(secret *v1.Secret, keyName string, data []byte) error {
|
|
||||||
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
|
||||||
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, entryExists := secret.Data[keyName]; entryExists {
|
|
||||||
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, secret.Data)
|
|
||||||
}
|
|
||||||
secret.Data[keyName] = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package util offers Configmap and Secret generation utilities.
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
|
|
||||||
func ParseRFC3339(s string) (metav1.Time, error) {
|
|
||||||
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
|
|
||||||
return metav1.Time{Time: t}, nil
|
|
||||||
}
|
|
||||||
t, err := time.Parse(time.RFC3339, s)
|
|
||||||
if err != nil {
|
|
||||||
return metav1.Time{}, err
|
|
||||||
}
|
|
||||||
return metav1.Time{Time: t}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashObject encodes object using given codec and returns MD5 sum of the result.
|
|
||||||
func HashObject(obj runtime.Object, codec runtime.Encoder) (string, error) {
|
|
||||||
data, err := runtime.Encode(codec, obj)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256(data)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFileSource parses the source given.
|
|
||||||
//
|
|
||||||
// Acceptable formats include:
|
|
||||||
// 1. source-path: the basename will become the key name
|
|
||||||
// 2. source-name=source-path: the source-name will become the key name and
|
|
||||||
// source-path is the path to the key file.
|
|
||||||
//
|
|
||||||
// Key names cannot include '='.
|
|
||||||
func ParseFileSource(source string) (keyName, filePath string, err error) {
|
|
||||||
numSeparators := strings.Count(source, "=")
|
|
||||||
switch {
|
|
||||||
case numSeparators == 0:
|
|
||||||
return path.Base(source), source, nil
|
|
||||||
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
|
||||||
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
|
|
||||||
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
|
||||||
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
|
|
||||||
case numSeparators > 1:
|
|
||||||
return "", "", errors.New("Key names or file paths cannot contain '='.")
|
|
||||||
default:
|
|
||||||
components := strings.Split(source, "=")
|
|
||||||
return components[0], components[1], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLiteralSource parses the source key=val pair into its component pieces.
|
|
||||||
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
|
|
||||||
// it returns an error in the case of empty keys, values, or a missing equals sign.
|
|
||||||
func ParseLiteralSource(source string) (keyName, value string, err error) {
|
|
||||||
// leading equal is invalid
|
|
||||||
if strings.Index(source, "=") == 0 {
|
|
||||||
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
|
||||||
}
|
|
||||||
// split after the first equal (so values can have the = character)
|
|
||||||
items := strings.SplitN(source, "=", 2)
|
|
||||||
if len(items) != 2 {
|
|
||||||
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
|
||||||
}
|
|
||||||
|
|
||||||
return items[0], items[1], nil
|
|
||||||
}
|
|
||||||
@@ -66,7 +66,7 @@ func (p *pathConfigs) addPrefixPathConfig(config transformers.PathConfig) {
|
|||||||
|
|
||||||
// RegisterCRDs parse CRD schemas from paths and update various pathConfigs
|
// RegisterCRDs parse CRD schemas from paths and update various pathConfigs
|
||||||
func RegisterCRDs(loader loader.Loader, paths []string) error {
|
func RegisterCRDs(loader loader.Loader, paths []string) error {
|
||||||
pathConfigs := []pathConfigs{}
|
var pathConfigs []pathConfigs
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
pathConfig, err := registerCRD(loader, path)
|
pathConfig, err := registerCRD(loader, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,8 +80,7 @@ func RegisterCRDs(loader loader.Loader, paths []string) error {
|
|||||||
|
|
||||||
// register CRD from one path
|
// register CRD from one path
|
||||||
func registerCRD(loader loader.Loader, path string) ([]pathConfigs, error) {
|
func registerCRD(loader loader.Loader, path string) ([]pathConfigs, error) {
|
||||||
result := []pathConfigs{}
|
var result []pathConfigs
|
||||||
|
|
||||||
content, err := loader.Load(path)
|
content, err := loader.Load(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
|
|||||||
@@ -142,12 +142,12 @@ If it is not set we generate a secret dynamically",
|
|||||||
)
|
)
|
||||||
|
|
||||||
func makeLoader(t *testing.T) loader.Loader {
|
func makeLoader(t *testing.T) loader.Loader {
|
||||||
loader := loadertest.NewFakeLoader("/testpath")
|
ldr := loadertest.NewFakeLoader("/testpath")
|
||||||
err := loader.AddFile("/testpath/crd.json", []byte(crdContent))
|
err := ldr.AddFile("/testpath/crd.json", []byte(crdContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup fake loader.")
|
t.Fatalf("Failed to setup fake ldr.")
|
||||||
}
|
}
|
||||||
return loader
|
return ldr
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterCRD(t *testing.T) {
|
func TestRegisterCRD(t *testing.T) {
|
||||||
@@ -184,9 +184,9 @@ func TestRegisterCRD(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
loader := makeLoader(t)
|
ldr := makeLoader(t)
|
||||||
|
|
||||||
pathconfig, _ := registerCRD(loader, "/testpath/crd.json")
|
pathconfig, _ := registerCRD(ldr, "/testpath/crd.json")
|
||||||
|
|
||||||
sort.Slice(pathconfig[0].namereferencePathConfigs, func(i, j int) bool {
|
sort.Slice(pathconfig[0].namereferencePathConfigs, func(i, j int) bool {
|
||||||
return pathconfig[0].namereferencePathConfigs[i].GVK() < pathconfig[0].namereferencePathConfigs[j].GVK()
|
return pathconfig[0].namereferencePathConfigs[i].GVK() < pathconfig[0].namereferencePathConfigs[j].GVK()
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for id, obj := range in {
|
for id, obj := range in {
|
||||||
f, err := dir.newFile(id.String())
|
f, err := dir.newFile(id.GvknString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = print(obj, f)
|
err = write(obj, f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -69,8 +69,8 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
|
|||||||
return dir, nil
|
return dir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the object as YAML.
|
// Write the object as YAML.
|
||||||
func print(obj interface{}, w io.Writer) error {
|
func write(obj interface{}, w io.Writer) error {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,3 +21,6 @@ secretGenerator:
|
|||||||
tls.crt: "cat secret/tls.cert"
|
tls.crt: "cat secret/tls.cert"
|
||||||
tls.key: "cat secret/tls.key"
|
tls.key: "cat secret/tls.key"
|
||||||
type: "kubernetes.io/tls"
|
type: "kubernetes.io/tls"
|
||||||
|
imageTags:
|
||||||
|
- name: nginx
|
||||||
|
newTag: 1.8.0
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNew() {
|
func ExampleNew() {
|
||||||
exec := exec.New()
|
cmd := exec.New().Command("echo", "Bonjour!")
|
||||||
|
|
||||||
cmd := exec.Command("echo", "Bonjour!")
|
|
||||||
buff := bytes.Buffer{}
|
buff := bytes.Buffer{}
|
||||||
cmd.SetStdout(&buff)
|
cmd.SetStdout(&buff)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
|||||||
@@ -114,6 +114,6 @@ func tryReadVariableName(input string) (string, bool, int) {
|
|||||||
// Not the beginning of an expression, ie, an operator
|
// Not the beginning of an expression, ie, an operator
|
||||||
// that doesn't begin an expression. Return the operator
|
// that doesn't begin an expression. Return the operator
|
||||||
// and the first rune in the string.
|
// and the first rune in the string.
|
||||||
return (string(operator) + string(input[0])), false, 1
|
return string(operator) + string(input[0]), false, 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ FileSystem = &FakeFS{}
|
var _ FileSystem = &FakeFS{}
|
||||||
@@ -42,7 +43,7 @@ func (fs *FakeFS) Create(name string) (File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mkdir assures a fake directory appears in the in-memory file system.
|
// Mkdir assures a fake directory appears in the in-memory file system.
|
||||||
func (fs *FakeFS) Mkdir(name string, perm os.FileMode) error {
|
func (fs *FakeFS) Mkdir(name string) error {
|
||||||
fs.m[name] = makeDir(name)
|
fs.m[name] = makeDir(name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -55,12 +56,31 @@ func (fs *FakeFS) Open(name string) (File, error) {
|
|||||||
return fs.m[name], nil
|
return fs.m[name], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat always returns nil FileInfo, and returns an error if file does not exist.
|
// Exists returns true if file is known.
|
||||||
func (fs *FakeFS) Stat(name string) (os.FileInfo, error) {
|
func (fs *FakeFS) Exists(name string) bool {
|
||||||
if f, found := fs.m[name]; found {
|
_, found := fs.m[name]
|
||||||
return &Fakefileinfo{f}, nil
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob returns the list of matching files
|
||||||
|
func (fs *FakeFS) Glob(pattern string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
for p := range fs.m {
|
||||||
|
if fs.pathMatch(p, pattern) {
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("file %q does not exist", name)
|
sort.Strings(result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns true if the file exists and is a directory.
|
||||||
|
func (fs *FakeFS) IsDir(name string) bool {
|
||||||
|
f, found := fs.m[name]
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile always returns an empty bytes and error depending on content of m.
|
// ReadFile always returns an empty bytes and error depending on content of m.
|
||||||
@@ -71,18 +91,6 @@ func (fs *FakeFS) ReadFile(name string) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("cannot read file %q", name)
|
return nil, fmt.Errorf("cannot read file %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFiles looks through all files in the fake filesystem
|
|
||||||
// and find the matching files and then read content from all of them
|
|
||||||
func (fs *FakeFS) ReadFiles(name string) (map[string][]byte, error) {
|
|
||||||
result := map[string][]byte{}
|
|
||||||
for p, f := range fs.m {
|
|
||||||
if fs.pathMatch(p, name) {
|
|
||||||
result[p] = f.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile always succeeds and does nothing.
|
// WriteFile always succeeds and does nothing.
|
||||||
func (fs *FakeFS) WriteFile(name string, c []byte) error {
|
func (fs *FakeFS) WriteFile(name string, c []byte) error {
|
||||||
ff := &FakeFile{}
|
ff := &FakeFile{}
|
||||||
@@ -92,8 +100,6 @@ func (fs *FakeFS) WriteFile(name string, c []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FakeFS) pathMatch(path, pattern string) bool {
|
func (fs *FakeFS) pathMatch(path, pattern string) bool {
|
||||||
if path == pattern {
|
match, _ := filepath.Match(pattern, path)
|
||||||
return true
|
return match
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,37 +18,29 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatNotExist(t *testing.T) {
|
func TestExists(t *testing.T) {
|
||||||
x := MakeFakeFS()
|
x := MakeFakeFS()
|
||||||
info, err := x.Stat("foo")
|
if x.Exists("foo") {
|
||||||
if info != nil {
|
t.Fatalf("expected no foo")
|
||||||
t.Fatalf("expected nil info")
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestIsDir(t *testing.T) {
|
||||||
x := MakeFakeFS()
|
x := MakeFakeFS()
|
||||||
expectedName := "my-dir"
|
expectedName := "my-dir"
|
||||||
err := x.Mkdir(expectedName, 0666)
|
err := x.Mkdir(expectedName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
info, err := x.Stat(expectedName)
|
if !x.Exists(expectedName) {
|
||||||
if err != nil {
|
t.Fatalf(expectedName + " should exist")
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
}
|
||||||
name := info.Name()
|
if !x.IsDir(expectedName) {
|
||||||
if name != expectedName {
|
t.Fatalf(expectedName + " should be a dir")
|
||||||
t.Fatalf("expected %v but got %v", expectedName, name)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
t.Fatalf("expected IsDir() return true")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +53,8 @@ func TestCreate(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error")
|
t.Fatalf("unexpected error")
|
||||||
}
|
}
|
||||||
info, err := x.Stat("foo")
|
if !x.Exists("foo") {
|
||||||
if info == nil {
|
t.Fatalf("expected foo to exist")
|
||||||
t.Fatalf("expected non-nil info")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,3 +91,20 @@ func TestWriteFile(t *testing.T) {
|
|||||||
t.Fatalf("incorrect content: %v", content)
|
t.Fatalf("incorrect content: %v", content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlob(t *testing.T) {
|
||||||
|
x := MakeFakeFS()
|
||||||
|
x.Create("dir/foo")
|
||||||
|
x.Create("dir/bar")
|
||||||
|
files, err := x.Glob("dir/*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error")
|
||||||
|
}
|
||||||
|
expected := []string{
|
||||||
|
"dir/bar",
|
||||||
|
"dir/foo",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(files, expected) {
|
||||||
|
t.Fatalf("incorrect files found by glob: %v", files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ import (
|
|||||||
// FileSystem groups basic os filesystem methods.
|
// FileSystem groups basic os filesystem methods.
|
||||||
type FileSystem interface {
|
type FileSystem interface {
|
||||||
Create(name string) (File, error)
|
Create(name string) (File, error)
|
||||||
Mkdir(name string, perm os.FileMode) error
|
Mkdir(name string) error
|
||||||
Open(name string) (File, error)
|
Open(name string) (File, error)
|
||||||
Stat(name string) (os.FileInfo, error)
|
IsDir(name string) bool
|
||||||
|
Exists(name string) bool
|
||||||
|
Glob(pattern string) ([]string, error)
|
||||||
ReadFile(name string) ([]byte, error)
|
ReadFile(name string) ([]byte, error)
|
||||||
ReadFiles(name string) (map[string][]byte, error)
|
|
||||||
WriteFile(name string, data []byte) error
|
WriteFile(name string, data []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,14 +27,6 @@ type realFile struct {
|
|||||||
file *os.File
|
file *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeRealFile makes an instance of realFile.
|
|
||||||
func MakeRealFile(f *os.File) (File, error) {
|
|
||||||
if f == nil {
|
|
||||||
return nil, errors.New("file argument may not be nil")
|
|
||||||
}
|
|
||||||
return &realFile{file: f}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a file.
|
// Close closes a file.
|
||||||
func (f *realFile) Close() error { return f.file.Close() }
|
func (f *realFile) Close() error { return f.file.Close() }
|
||||||
|
|
||||||
|
|||||||
@@ -36,37 +36,36 @@ func MakeRealFS() FileSystem {
|
|||||||
func (realFS) Create(name string) (File, error) { return os.Create(name) }
|
func (realFS) Create(name string) (File, error) { return os.Create(name) }
|
||||||
|
|
||||||
// Mkdir delegates to os.Mkdir.
|
// Mkdir delegates to os.Mkdir.
|
||||||
func (realFS) Mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
|
func (realFS) Mkdir(name string) error {
|
||||||
|
return os.Mkdir(name, 0777|os.ModeDir)
|
||||||
|
}
|
||||||
|
|
||||||
// Open delegates to os.Open.
|
// Open delegates to os.Open.
|
||||||
func (realFS) Open(name string) (File, error) { return os.Open(name) }
|
func (realFS) Open(name string) (File, error) { return os.Open(name) }
|
||||||
|
|
||||||
// Stat delegates to os.Stat.
|
// Exists returns true if os.Stat succeeds.
|
||||||
func (realFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
func (realFS) Exists(name string) bool {
|
||||||
|
_, err := os.Stat(name)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob returns the list of matching files
|
||||||
|
func (realFS) Glob(pattern string) ([]string, error) {
|
||||||
|
return filepath.Glob(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir delegates to os.Stat and FileInfo.IsDir
|
||||||
|
func (realFS) IsDir(name string) bool {
|
||||||
|
info, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
// ReadFile delegates to ioutil.ReadFile.
|
// ReadFile delegates to ioutil.ReadFile.
|
||||||
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
|
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
|
||||||
|
|
||||||
// ReadFiles use glob to find the matching files and then read content from all of them
|
|
||||||
func (realFS) ReadFiles(name string) (map[string][]byte, error) {
|
|
||||||
files, err := filepath.Glob(name)
|
|
||||||
if err != nil || len(files) == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
output := map[string][]byte{}
|
|
||||||
for _, file := range files {
|
|
||||||
bytes, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if bytes != nil {
|
|
||||||
output[file] = bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
|
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
|
||||||
func (realFS) WriteFile(name string, c []byte) error {
|
func (realFS) WriteFile(name string, c []byte) error {
|
||||||
return ioutil.WriteFile(name, c, 0666)
|
return ioutil.WriteFile(name, c, 0666)
|
||||||
|
|||||||
@@ -26,33 +26,44 @@ import (
|
|||||||
func TestReadFilesRealFS(t *testing.T) {
|
func TestReadFilesRealFS(t *testing.T) {
|
||||||
x := MakeRealFS()
|
x := MakeRealFS()
|
||||||
testDir := "kustomize_testing_dir"
|
testDir := "kustomize_testing_dir"
|
||||||
err := x.Mkdir(testDir, 0777)
|
err := x.Mkdir(testDir)
|
||||||
defer os.RemoveAll(testDir)
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %s", err)
|
t.Fatalf("unexpected error %s", err)
|
||||||
}
|
}
|
||||||
|
if !x.Exists(testDir) {
|
||||||
|
t.Fatalf("expected existence")
|
||||||
|
}
|
||||||
|
if !x.IsDir(testDir) {
|
||||||
|
t.Fatalf("expected directory")
|
||||||
|
}
|
||||||
|
|
||||||
err = x.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
err = x.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %s", err)
|
t.Fatalf("unexpected error %s", err)
|
||||||
}
|
}
|
||||||
|
if !x.Exists(path.Join(testDir, "foo")) {
|
||||||
|
t.Fatalf("expected foo")
|
||||||
|
}
|
||||||
|
if x.IsDir(path.Join(testDir, "foo")) {
|
||||||
|
t.Fatalf("expected foo not to be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
err = x.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
err = x.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %s", err)
|
t.Fatalf("unexpected error %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := map[string][]byte{
|
files, err := x.Glob(path.Join("testDir", "*"))
|
||||||
path.Join(testDir, "foo"): []byte(`foo`),
|
expected := []string{
|
||||||
path.Join(testDir, "bar"): []byte(`bar`),
|
path.Join(testDir, "bar"),
|
||||||
}
|
path.Join(testDir, "foo"),
|
||||||
|
|
||||||
content, err := x.ReadFiles("kustomize_testing_dir/*")
|
|
||||||
if !reflect.DeepEqual(content, expected) {
|
|
||||||
t.Fatalf("actual: %+v doesn't match expected: %+v", content, expected)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %s", err)
|
t.Fatalf("expected no error")
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(files, expected) {
|
||||||
|
t.Fatalf("incorrect files found by glob: %v", files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package error
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
yaml "k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
|
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
|
||||||
@@ -33,8 +33,8 @@ func (e YamlFormatError) Error() string {
|
|||||||
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
|
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorHandler handles YamlFormatError
|
// Handler handles YamlFormatError
|
||||||
func ErrorHandler(e error, path string) error {
|
func Handler(e error, path string) error {
|
||||||
if err, ok := e.(yaml.YAMLSyntaxError); ok {
|
if err, ok := e.(yaml.YAMLSyntaxError); ok {
|
||||||
return YamlFormatError{
|
return YamlFormatError{
|
||||||
Path: path,
|
Path: path,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||||
yaml "k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -49,9 +49,9 @@ func TestYamlFormatError_Error(t *testing.T) {
|
|||||||
func TestErrorHandler(t *testing.T) {
|
func TestErrorHandler(t *testing.T) {
|
||||||
f := foo{}
|
f := foo{}
|
||||||
err := yaml.NewYAMLToJSONDecoder(bytes.NewReader([]byte(doc))).Decode(&f)
|
err := yaml.NewYAMLToJSONDecoder(bytes.NewReader([]byte(doc))).Decode(&f)
|
||||||
testErr := ErrorHandler(err, filepath)
|
testErr := Handler(err, filepath)
|
||||||
expectedErr := fmt.Errorf("Format error message")
|
expectedErr := fmt.Errorf("format error message")
|
||||||
fmtErr := ErrorHandler(expectedErr, filepath)
|
fmtErr := Handler(expectedErr, filepath)
|
||||||
if fmtErr.Error() != expectedErr.Error() {
|
if fmtErr.Error() != expectedErr.Error() {
|
||||||
t.Errorf("Expected returning fmt.Error, but found %T", fmtErr)
|
t.Errorf("Expected returning fmt.Error, but found %T", fmtErr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ limitations under the License.
|
|||||||
package loadertest
|
package loadertest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||||
)
|
)
|
||||||
@@ -36,11 +34,9 @@ type FakeLoader struct {
|
|||||||
func NewFakeLoader(initialDir string) FakeLoader {
|
func NewFakeLoader(initialDir string) FakeLoader {
|
||||||
// Create fake filesystem and inject it into initial Loader.
|
// Create fake filesystem and inject it into initial Loader.
|
||||||
fakefs := fs.MakeFakeFS()
|
fakefs := fs.MakeFakeFS()
|
||||||
var schemes []loader.SchemeLoader
|
rootLoader := loader.NewFileLoader(fakefs)
|
||||||
schemes = append(schemes, loader.NewFileLoader(fakefs))
|
ldr, _ := rootLoader.New(initialDir)
|
||||||
rootLoader := loader.Init(schemes)
|
return FakeLoader{fs: fakefs, delegate: ldr}
|
||||||
loader, _ := rootLoader.New(initialDir)
|
|
||||||
return FakeLoader{fs: fakefs, delegate: loader}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFile adds a fake file to the file system.
|
// AddFile adds a fake file to the file system.
|
||||||
@@ -49,8 +45,8 @@ func (f FakeLoader) AddFile(fullFilePath string, content []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDirectory adds a fake directory to the file system.
|
// AddDirectory adds a fake directory to the file system.
|
||||||
func (f FakeLoader) AddDirectory(fullDirPath string, mode os.FileMode) error {
|
func (f FakeLoader) AddDirectory(fullDirPath string) error {
|
||||||
return f.fs.Mkdir(fullDirPath, mode)
|
return f.fs.Mkdir(fullDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root returns root.
|
// Root returns root.
|
||||||
@@ -67,8 +63,3 @@ func (f FakeLoader) New(newRoot string) (loader.Loader, error) {
|
|||||||
func (f FakeLoader) Load(location string) ([]byte, error) {
|
func (f FakeLoader) Load(location string) ([]byte, error) {
|
||||||
return f.delegate.Load(location)
|
return f.delegate.Load(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GlobLoad performs load from a given location.
|
|
||||||
func (f FakeLoader) GlobLoad(location string) (map[string][]byte, error) {
|
|
||||||
return f.delegate.GlobLoad(location)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,33 +26,62 @@ import (
|
|||||||
|
|
||||||
const currentDir = "."
|
const currentDir = "."
|
||||||
|
|
||||||
// Internal implementation of SchemeLoader interface.
|
// fileLoader loads files from a file system.
|
||||||
type fileLoader struct {
|
type fileLoader struct {
|
||||||
fs fs.FileSystem
|
root string
|
||||||
|
fSys fs.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileLoader returns a SchemeLoader to handle a file system.
|
// NewFileLoader returns a new fileLoader.
|
||||||
func NewFileLoader(fs fs.FileSystem) SchemeLoader {
|
func NewFileLoader(fSys fs.FileSystem) *fileLoader {
|
||||||
return &fileLoader{fs: fs}
|
return newFileLoaderAtRoot("", fSys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the location calculated with the root and location params a full file path.
|
// newFileLoaderAtRoot returns a new fileLoader with given root.
|
||||||
func (l *fileLoader) IsScheme(root string, location string) bool {
|
func newFileLoaderAtRoot(root string, fs fs.FileSystem) *fileLoader {
|
||||||
fullFilePath, err := l.FullLocation(root, location)
|
return &fileLoader{root: root, fSys: fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root location for this Loader.
|
||||||
|
func (l *fileLoader) Root() string {
|
||||||
|
return l.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Loader rooted at newRoot. "newRoot" MUST be
|
||||||
|
// a directory (not a file). The directory can have a trailing
|
||||||
|
// slash or not.
|
||||||
|
// Example: "/home/seans/project" or "/home/seans/project/"
|
||||||
|
// NOT "/home/seans/project/file.yaml".
|
||||||
|
func (l *fileLoader) New(newRoot string) (Loader, error) {
|
||||||
|
if !l.IsAbsPath(l.root, newRoot) {
|
||||||
|
return nil, fmt.Errorf("Not abs path: l.root='%s', loc='%s'\n", l.root, newRoot)
|
||||||
|
}
|
||||||
|
root, err := l.fullLocation(l.root, newRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newFileLoaderAtRoot(root, l.fSys), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAbsPath return true if the location calculated with the root
|
||||||
|
// and location params a full file path.
|
||||||
|
func (l *fileLoader) IsAbsPath(root string, location string) bool {
|
||||||
|
fullFilePath, err := l.fullLocation(root, location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return filepath.IsAbs(fullFilePath)
|
return filepath.IsAbs(fullFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullLocation returns some notion of a full path.
|
||||||
// If location is a full file path, then ignore root. If location is relative, then
|
// If location is a full file path, then ignore root. If location is relative, then
|
||||||
// join the root path with the location path. Either root or location can be empty,
|
// join the root path with the location path. Either root or location can be empty,
|
||||||
// but not both. Special case for ".": Expands to current working directory.
|
// but not both. Special case for ".": Expands to current working directory.
|
||||||
// Example: "/home/seans/project", "subdir/bar" -> "/home/seans/project/subdir/bar".
|
// Example: "/home/seans/project", "subdir/bar" -> "/home/seans/project/subdir/bar".
|
||||||
func (l *fileLoader) FullLocation(root string, location string) (string, error) {
|
func (l *fileLoader) fullLocation(root string, location string) (string, error) {
|
||||||
// First, validate the parameters
|
// First, validate the parameters
|
||||||
if len(root) == 0 && len(location) == 0 {
|
if len(root) == 0 && len(location) == 0 {
|
||||||
return "", fmt.Errorf("Unable to calculate full location: root and location empty")
|
return "", fmt.Errorf("unable to calculate full location: root and location empty")
|
||||||
}
|
}
|
||||||
// Special case current directory, expanding to full file path.
|
// Special case current directory, expanding to full file path.
|
||||||
if location == currentDir {
|
if location == currentDir {
|
||||||
@@ -72,20 +101,11 @@ func (l *fileLoader) FullLocation(root string, location string) (string, error)
|
|||||||
|
|
||||||
// Load returns the bytes from reading a file at fullFilePath.
|
// Load returns the bytes from reading a file at fullFilePath.
|
||||||
// Implements the Loader interface.
|
// Implements the Loader interface.
|
||||||
func (l *fileLoader) Load(fullFilePath string) ([]byte, error) {
|
func (l *fileLoader) Load(location string) ([]byte, error) {
|
||||||
// Validate path to load from is a full file path.
|
fullLocation, err := l.fullLocation(l.root, location)
|
||||||
if !filepath.IsAbs(fullFilePath) {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Attempting to load file without full file path: %s\n", fullFilePath)
|
fmt.Printf("Trouble in fulllocation: %v\n", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return l.fs.ReadFile(fullFilePath)
|
return l.fSys.ReadFile(fullLocation)
|
||||||
}
|
|
||||||
|
|
||||||
// GlobLoad returns the map from path to bytes from reading a glob path.
|
|
||||||
// Implements the Loader interface.
|
|
||||||
func (l *fileLoader) GlobLoad(fullFilePath string) (map[string][]byte, error) {
|
|
||||||
// Validate path to load from is a full file path.
|
|
||||||
if !filepath.IsAbs(fullFilePath) {
|
|
||||||
return nil, fmt.Errorf("Attempting to load file without full file path: %s\n", fullFilePath)
|
|
||||||
}
|
|
||||||
return l.fs.ReadFiles(fullFilePath)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,6 @@ import (
|
|||||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initializeRootLoader(fakefs fs.FileSystem) Loader {
|
|
||||||
var schemes []SchemeLoader
|
|
||||||
schemes = append(schemes, NewFileLoader(fakefs))
|
|
||||||
rootLoader := Init(schemes)
|
|
||||||
return rootLoader
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoader_Root(t *testing.T) {
|
func TestLoader_Root(t *testing.T) {
|
||||||
|
|
||||||
// Initialize the fake file system and the root loader.
|
// Initialize the fake file system and the root loader.
|
||||||
@@ -38,7 +31,7 @@ func TestLoader_Root(t *testing.T) {
|
|||||||
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("Unused"))
|
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("Unused"))
|
||||||
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Unused"))
|
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Unused"))
|
||||||
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("Unused"))
|
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("Unused"))
|
||||||
rootLoader := initializeRootLoader(fakefs)
|
rootLoader := NewFileLoader(fakefs)
|
||||||
|
|
||||||
_, err := rootLoader.New("")
|
_, err := rootLoader.New("")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -46,7 +39,7 @@ func TestLoader_Root(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, err = rootLoader.New("https://google.com/project")
|
_, err = rootLoader.New("https://google.com/project")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("Expected error for unknown scheme not returned")
|
t.Fatalf("Expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with trailing slash in directory.
|
// Test with trailing slash in directory.
|
||||||
@@ -92,7 +85,7 @@ func TestLoader_Load(t *testing.T) {
|
|||||||
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("This is a yaml file"))
|
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("This is a yaml file"))
|
||||||
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Subdirectory file content"))
|
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Subdirectory file content"))
|
||||||
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("This is another yaml file"))
|
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("This is another yaml file"))
|
||||||
rootLoader := initializeRootLoader(fakefs)
|
rootLoader := NewFileLoader(fakefs)
|
||||||
|
|
||||||
loader, err := rootLoader.New("/home/seans/project")
|
loader, err := rootLoader.New("/home/seans/project")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -17,105 +17,12 @@ limitations under the License.
|
|||||||
// Package loader has a data loading interface and various implementations.
|
// Package loader has a data loading interface and various implementations.
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
import "fmt"
|
// Loader interface exposes methods to read bytes.
|
||||||
|
|
||||||
// Loader interface exposes methods to read bytes in a scheme-agnostic manner.
|
|
||||||
// The Loader encapsulating a root location to calculate where to read from.
|
|
||||||
type Loader interface {
|
type Loader interface {
|
||||||
// Root returns the scheme-specific string representing the root location for this Loader.
|
// Root returns the root location for this Loader.
|
||||||
Root() string
|
Root() string
|
||||||
// New returns Loader located at newRoot.
|
// New returns Loader located at newRoot.
|
||||||
New(newRoot string) (Loader, error)
|
New(newRoot string) (Loader, error)
|
||||||
// Load returns the bytes read from the location or an error.
|
// Load returns the bytes read from the location or an error.
|
||||||
Load(location string) ([]byte, error)
|
Load(location string) ([]byte, error)
|
||||||
// GlobLoad returns the bytes read from a glob path or an error.
|
|
||||||
GlobLoad(location string) (map[string][]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private implmentation of Loader interface.
|
|
||||||
type loaderImpl struct {
|
|
||||||
root string
|
|
||||||
schemes []SchemeLoader
|
|
||||||
}
|
|
||||||
|
|
||||||
// SchemeLoader is the interface for different types of loaders (e.g. fileLoader, httpLoader, etc.)
|
|
||||||
type SchemeLoader interface {
|
|
||||||
// Does this location correspond to this scheme.
|
|
||||||
IsScheme(root string, location string) bool
|
|
||||||
// Combines the root and path into a full location string.
|
|
||||||
FullLocation(root string, path string) (string, error)
|
|
||||||
// Load bytes at scheme-specific location or an error.
|
|
||||||
Load(location string) ([]byte, error)
|
|
||||||
// GlobLoad returns the bytes read from a glob path or an error.
|
|
||||||
GlobLoad(location string) (map[string][]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyRoot = ""
|
|
||||||
|
|
||||||
// Init initializes the first loader with the supported schemes.
|
|
||||||
// Example schemes: fileLoader, httpLoader, gitLoader.
|
|
||||||
func Init(schemes []SchemeLoader) Loader {
|
|
||||||
return &loaderImpl{root: emptyRoot, schemes: schemes}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root returns the scheme-specific root location for this Loader.
|
|
||||||
func (l *loaderImpl) Root() string {
|
|
||||||
return l.root
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new Loader rooted at newRoot. "newRoot" MUST be
|
|
||||||
// a directory (not a file). The directory can have a trailing
|
|
||||||
// slash or not.
|
|
||||||
// Example: "/home/seans/project" or "/home/seans/project/"
|
|
||||||
// NOT "/home/seans/project/file.yaml".
|
|
||||||
func (l *loaderImpl) New(newRoot string) (Loader, error) {
|
|
||||||
scheme, err := l.getSchemeLoader(newRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
root, err := scheme.FullLocation(l.root, newRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &loaderImpl{root: root, schemes: l.schemes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load returns all the bytes read from scheme-specific location or an error.
|
|
||||||
// "location" can be an absolute path, or if relative, full location is
|
|
||||||
// calculated from the Root().
|
|
||||||
func (l *loaderImpl) Load(location string) ([]byte, error) {
|
|
||||||
scheme, err := l.getSchemeLoader(location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fullLocation, err := scheme.FullLocation(l.root, location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return scheme.Load(fullLocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobLoad returns a map from path to bytes read from scheme-specific location or an error.
|
|
||||||
// "location" can be an absolute path, or if relative, full location is
|
|
||||||
// calculated from the Root().
|
|
||||||
func (l *loaderImpl) GlobLoad(location string) (map[string][]byte, error) {
|
|
||||||
scheme, err := l.getSchemeLoader(location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fullLocation, err := scheme.FullLocation(l.root, location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return scheme.GlobLoad(fullLocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to parse scheme from location parameter.
|
|
||||||
func (l *loaderImpl) getSchemeLoader(location string) (SchemeLoader, error) {
|
|
||||||
for _, scheme := range l.schemes {
|
|
||||||
if scheme.IsScheme(l.root, location) {
|
|
||||||
return scheme, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Unknown Scheme: %s, %s\n", l.root, location)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,120 +17,27 @@ limitations under the License.
|
|||||||
package resmap
|
package resmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
"strings"
|
|
||||||
|
|
||||||
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newResourceFromConfigMap(l loader.Loader, cmArgs types.ConfigMapArgs) (*resource.Resource, error) {
|
// NewResMapFromConfigMapArgs returns a Resource slice given
|
||||||
cm, err := makeConfigMap(l, cmArgs)
|
// a configmap metadata slice from kustomization file.
|
||||||
if err != nil {
|
func NewResMapFromConfigMapArgs(
|
||||||
return nil, err
|
f *configmapandsecret.ConfigMapFactory,
|
||||||
}
|
cmArgsList []types.ConfigMapArgs) (ResMap, error) {
|
||||||
return resource.NewResourceWithBehavior(cm, resource.NewGenerationBehavior(cmArgs.Behavior))
|
var allResources []*resource.Resource
|
||||||
}
|
for _, cmArgs := range cmArgsList {
|
||||||
|
if cmArgs.Behavior == "" {
|
||||||
func makeConfigMap(l loader.Loader, cmArgs types.ConfigMapArgs) (*corev1.ConfigMap, error) {
|
cmArgs.Behavior = "create"
|
||||||
var envPairs, literalPairs, filePairs []kvPair
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cm := &corev1.ConfigMap{}
|
|
||||||
cm.APIVersion = "v1"
|
|
||||||
cm.Kind = "ConfigMap"
|
|
||||||
cm.Name = cmArgs.Name
|
|
||||||
cm.Data = map[string]string{}
|
|
||||||
|
|
||||||
if cmArgs.EnvSource != "" {
|
|
||||||
envPairs, err = keyValuesFromEnvFile(l, cmArgs.EnvSource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading keys from env source file: %s %v", cmArgs.EnvSource, err)
|
|
||||||
}
|
}
|
||||||
}
|
cm, err := f.MakeConfigMap(&cmArgs)
|
||||||
|
|
||||||
literalPairs, err = keyValuesFromLiteralSources(cmArgs.LiteralSources)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading key values from literal sources: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePairs, err = keyValuesFromFileSources(l, cmArgs.FileSources)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading key values from file sources: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
allPairs := append(append(envPairs, literalPairs...), filePairs...)
|
|
||||||
|
|
||||||
// merge key value pairs from all the sources
|
|
||||||
for _, kv := range allPairs {
|
|
||||||
err = addKV(cm.Data, kv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error adding key in configmap: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyValuesFromEnvFile(l loader.Loader, path string) ([]kvPair, error) {
|
|
||||||
content, err := l.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return keyValuesFromLines(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyValuesFromLiteralSources(sources []string) ([]kvPair, error) {
|
|
||||||
var kvs []kvPair
|
|
||||||
for _, s := range sources {
|
|
||||||
// TODO: move ParseLiteralSource in this file
|
|
||||||
k, v, err := cutil.ParseLiteralSource(s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
kvs = append(kvs, kvPair{key: k, value: v})
|
res, err := resource.NewResourceWithBehavior(
|
||||||
}
|
cm, resource.NewGenerationBehavior(cmArgs.Behavior))
|
||||||
return kvs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyValuesFromFileSources(l loader.Loader, sources []string) ([]kvPair, error) {
|
|
||||||
var kvs []kvPair
|
|
||||||
|
|
||||||
for _, s := range sources {
|
|
||||||
key, path, err := cutil.ParseFileSource(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fileContent, err := l.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kvs = append(kvs, kvPair{key: key, value: string(fileContent)})
|
|
||||||
}
|
|
||||||
return kvs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addKV adds key-value pair to the provided map.
|
|
||||||
func addKV(m map[string]string, kv kvPair) error {
|
|
||||||
if errs := validation.IsConfigMapKey(kv.key); len(errs) != 0 {
|
|
||||||
return fmt.Errorf("%q is not a valid key name: %s", kv.key, strings.Join(errs, ";"))
|
|
||||||
}
|
|
||||||
if _, exists := m[kv.key]; exists {
|
|
||||||
return fmt.Errorf("key %s already exists: %v.", kv.key, m)
|
|
||||||
}
|
|
||||||
m[kv.key] = kv.value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResMapFromConfigMapArgs returns a Resource slice given a configmap metadata slice from kustomization file.
|
|
||||||
func NewResMapFromConfigMapArgs(loader loader.Loader, cmList []types.ConfigMapArgs) (ResMap, error) {
|
|
||||||
allResources := []*resource.Resource{}
|
|
||||||
for _, cm := range cmList {
|
|
||||||
res, err := newResourceFromConfigMap(loader, cm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
|
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
@@ -38,6 +40,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l := loadertest.NewFakeLoader("/home/seans/project/")
|
l := loadertest.NewFakeLoader("/home/seans/project/")
|
||||||
|
f := configmapandsecret.NewConfigMapFactory(fs.MakeFakeFS(), l)
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
{
|
{
|
||||||
description: "construct config map from env",
|
description: "construct config map from env",
|
||||||
@@ -64,7 +67,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
|||||||
"DB_USERNAME": "admin",
|
"DB_USERNAME": "admin",
|
||||||
"DB_PASSWORD": "somepw",
|
"DB_PASSWORD": "somepw",
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -92,7 +95,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
|||||||
BAR=baz
|
BAR=baz
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -118,7 +121,7 @@ BAR=baz
|
|||||||
"a": "x",
|
"a": "x",
|
||||||
"b": "y",
|
"b": "y",
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: add testcase for data coming from multiple sources like
|
// TODO: add testcase for data coming from multiple sources like
|
||||||
@@ -126,11 +129,10 @@ BAR=baz
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
||||||
if ferr := l.AddFile(tc.filepath, []byte(tc.content)); ferr != nil {
|
if ferr := l.AddFile(tc.filepath, []byte(tc.content)); ferr != nil {
|
||||||
t.Fatalf("Error adding fake file: %v\n", ferr)
|
t.Fatalf("Error adding fake file: %v\n", ferr)
|
||||||
}
|
}
|
||||||
r, err := NewResMapFromConfigMapArgs(l, tc.input)
|
r, err := NewResMapFromConfigMapArgs(f, tc.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@@ -37,9 +38,29 @@ import (
|
|||||||
// ResMap is a map from ResId to Resource.
|
// ResMap is a map from ResId to Resource.
|
||||||
type ResMap map[resource.ResId]*resource.Resource
|
type ResMap map[resource.ResId]*resource.Resource
|
||||||
|
|
||||||
|
// FindByGVKN find the matched ResIds by Group/Version/Kind and Name
|
||||||
|
func (m ResMap) FindByGVKN(inputId resource.ResId) []resource.ResId {
|
||||||
|
var result []resource.ResId
|
||||||
|
for id := range m {
|
||||||
|
if id.GvknEquals(inputId) {
|
||||||
|
result = append(result, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// DemandOneMatchForId find the matched resource by Group/Version/Kind and Name
|
||||||
|
func (m ResMap) DemandOneMatchForId(inputId resource.ResId) (*resource.Resource, bool) {
|
||||||
|
result := m.FindByGVKN(inputId)
|
||||||
|
if len(result) == 1 {
|
||||||
|
return m[result[0]], true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
|
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
|
||||||
func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
||||||
ids := []resource.ResId{}
|
var ids []resource.ResId
|
||||||
for id := range m {
|
for id := range m {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
@@ -73,8 +94,8 @@ func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
|||||||
// ErrorIfNotEqual returns error if maps are not equal.
|
// ErrorIfNotEqual returns error if maps are not equal.
|
||||||
func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
|
func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
|
||||||
if len(m) != len(m2) {
|
if len(m) != len(m2) {
|
||||||
keySet1 := []resource.ResId{}
|
var keySet1 []resource.ResId
|
||||||
keySet2 := []resource.ResId{}
|
var keySet2 []resource.ResId
|
||||||
for id := range m {
|
for id := range m {
|
||||||
keySet1 = append(keySet1, id)
|
keySet1 = append(keySet1, id)
|
||||||
}
|
}
|
||||||
@@ -101,7 +122,7 @@ func (m ResMap) insert(newName string, obj *unstructured.Unstructured) error {
|
|||||||
id := resource.NewResId(gvk, oldName)
|
id := resource.NewResId(gvk, oldName)
|
||||||
|
|
||||||
if _, found := m[id]; found {
|
if _, found := m[id]; found {
|
||||||
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
|
return fmt.Errorf("the <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
|
||||||
}
|
}
|
||||||
obj.SetName(newName)
|
obj.SetName(newName)
|
||||||
m[id] = resource.NewResourceFromUnstruct(*obj)
|
m[id] = resource.NewResourceFromUnstruct(*obj)
|
||||||
@@ -111,39 +132,34 @@ func (m ResMap) insert(newName string, obj *unstructured.Unstructured) error {
|
|||||||
// NewResourceSliceFromPatches returns a slice of resources given a patch path slice from a kustomization file.
|
// NewResourceSliceFromPatches returns a slice of resources given a patch path slice from a kustomization file.
|
||||||
func NewResourceSliceFromPatches(
|
func NewResourceSliceFromPatches(
|
||||||
loader loader.Loader, paths []string) ([]*resource.Resource, error) {
|
loader loader.Loader, paths []string) ([]*resource.Resource, error) {
|
||||||
result := []*resource.Resource{}
|
var result []*resource.Resource
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
contents, err := loader.GlobLoad(path)
|
content, err := loader.Load(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for p, content := range contents {
|
res, err := newResourceSliceFromBytes(content)
|
||||||
res, err := newResourceSliceFromBytes(content)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, internal.Handler(err, path)
|
||||||
return nil, internal.ErrorHandler(err, p)
|
|
||||||
}
|
|
||||||
result = append(result, res...)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
result = append(result, res...)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResMapFromFiles returns a ResMap given a resource path slice.
|
// NewResMapFromFiles returns a ResMap given a resource path slice.
|
||||||
func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) {
|
func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) {
|
||||||
result := []ResMap{}
|
var result []ResMap
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
contents, err := loader.GlobLoad(path)
|
content, err := loader.Load(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "Load from path "+path+" failed")
|
return nil, errors.Wrap(err, "Load from path "+path+" failed")
|
||||||
}
|
}
|
||||||
for p, content := range contents {
|
res, err := newResMapFromBytes(content)
|
||||||
res, err := newResMapFromBytes(content)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, internal.Handler(err, path)
|
||||||
return nil, internal.ErrorHandler(err, p)
|
|
||||||
}
|
|
||||||
result = append(result, res)
|
|
||||||
}
|
}
|
||||||
|
result = append(result, res)
|
||||||
}
|
}
|
||||||
return MergeWithoutOverride(result...)
|
return MergeWithoutOverride(result...)
|
||||||
}
|
}
|
||||||
@@ -180,16 +196,14 @@ func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error)
|
|||||||
|
|
||||||
func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
|
func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
|
||||||
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
|
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
|
||||||
result := []*resource.Resource{}
|
var result []*resource.Resource
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for {
|
for err == nil || isEmptyYamlError(err) {
|
||||||
var out unstructured.Unstructured
|
var out unstructured.Unstructured
|
||||||
err = decoder.Decode(&out)
|
err = decoder.Decode(&out)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
break
|
result = append(result, resource.NewResourceFromUnstruct(out))
|
||||||
}
|
}
|
||||||
result = append(result, resource.NewResourceFromUnstruct(out))
|
|
||||||
}
|
}
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -201,11 +215,11 @@ func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
|
|||||||
func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
|
func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
|
||||||
result := ResMap{}
|
result := ResMap{}
|
||||||
for _, m := range maps {
|
for _, m := range maps {
|
||||||
for id, resource := range m {
|
for id, res := range m {
|
||||||
if _, found := result[id]; found {
|
if _, found := result[id]; found {
|
||||||
return nil, fmt.Errorf("id '%q' already used", id)
|
return nil, fmt.Errorf("id '%q' already used", id)
|
||||||
}
|
}
|
||||||
result[id] = resource
|
result[id] = res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -218,10 +232,12 @@ func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
|
|||||||
// must be BehaviorMerge or BehaviorReplace. If X is not in the map, then it's
|
// must be BehaviorMerge or BehaviorReplace. If X is not in the map, then it's
|
||||||
// behavior cannot be merge or replace.
|
// behavior cannot be merge or replace.
|
||||||
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
||||||
result := ResMap{}
|
result := maps[0]
|
||||||
for _, m := range maps {
|
for _, m := range maps[1:] {
|
||||||
for id, r := range m {
|
for id, r := range m {
|
||||||
if _, found := result[id]; found {
|
matchedId := result.FindByGVKN(id)
|
||||||
|
if len(matchedId) == 1 {
|
||||||
|
id = matchedId[0]
|
||||||
switch r.Behavior() {
|
switch r.Behavior() {
|
||||||
case resource.BehaviorReplace:
|
case resource.BehaviorReplace:
|
||||||
glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object)
|
glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object)
|
||||||
@@ -235,17 +251,23 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
|||||||
glog.V(4).Infof("Merged object is %v", result[id].Object)
|
glog.V(4).Infof("Merged object is %v", result[id].Object)
|
||||||
result[id].SetBehavior(resource.BehaviorCreate)
|
result[id].SetBehavior(resource.BehaviorCreate)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Id %#v exists; must merge or replace.", id)
|
return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
|
||||||
}
|
}
|
||||||
} else {
|
} else if len(matchedId) == 0 {
|
||||||
switch r.Behavior() {
|
switch r.Behavior() {
|
||||||
case resource.BehaviorMerge, resource.BehaviorReplace:
|
case resource.BehaviorMerge, resource.BehaviorReplace:
|
||||||
return nil, fmt.Errorf("Id %#v does not exist; cannot merge or replace.", id)
|
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
|
||||||
default:
|
default:
|
||||||
result[id] = r
|
result[id] = r
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isEmptyYamlError(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "is missing in 'null'")
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: dply2
|
name: dply2
|
||||||
|
---
|
||||||
|
# some comment
|
||||||
|
---
|
||||||
|
---
|
||||||
`
|
`
|
||||||
|
|
||||||
l := loadertest.NewFakeLoader("/home/seans/project")
|
l := loadertest.NewFakeLoader("/home/seans/project")
|
||||||
|
|||||||
@@ -17,68 +17,30 @@ limitations under the License.
|
|||||||
package resmap
|
package resmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newResourceFromSecretGenerator(p string, sArgs types.SecretArgs) (*resource.Resource, error) {
|
// NewResMapFromSecretArgs takes a SecretArgs slice, generates
|
||||||
s, err := makeSecret(p, sArgs)
|
// secrets from each entry, and accumulates them in a ResMap.
|
||||||
if err != nil {
|
func NewResMapFromSecretArgs(
|
||||||
return nil, errors.Wrap(err, "makeSecret")
|
f *configmapandsecret.SecretFactory,
|
||||||
}
|
secretList []types.SecretArgs) (ResMap, error) {
|
||||||
return resource.NewResourceWithBehavior(
|
var allResources []*resource.Resource
|
||||||
s, resource.NewGenerationBehavior(sArgs.Behavior))
|
for _, args := range secretList {
|
||||||
}
|
s, err := f.MakeSecret(&args)
|
||||||
|
|
||||||
func makeSecret(p string, sArgs types.SecretArgs) (*corev1.Secret, error) {
|
|
||||||
s := &corev1.Secret{}
|
|
||||||
s.APIVersion = "v1"
|
|
||||||
s.Kind = "Secret"
|
|
||||||
s.Name = sArgs.Name
|
|
||||||
s.Type = corev1.SecretType(sArgs.Type)
|
|
||||||
if s.Type == "" {
|
|
||||||
s.Type = corev1.SecretTypeOpaque
|
|
||||||
}
|
|
||||||
s.Data = map[string][]byte{}
|
|
||||||
|
|
||||||
for k, v := range sArgs.Commands {
|
|
||||||
out, err := createSecretKey(p, v)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "createSecretKey")
|
return nil, errors.Wrap(err, "makeSecret")
|
||||||
}
|
}
|
||||||
s.Data[k] = out
|
if args.Behavior == "" {
|
||||||
}
|
args.Behavior = "create"
|
||||||
return s, nil
|
}
|
||||||
}
|
res, err := resource.NewResourceWithBehavior(
|
||||||
|
s, resource.NewGenerationBehavior(args.Behavior))
|
||||||
func createSecretKey(wd string, command string) ([]byte, error) {
|
|
||||||
fi, err := os.Stat(wd)
|
|
||||||
if err != nil || !fi.IsDir() {
|
|
||||||
wd = filepath.Dir(wd)
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
|
||||||
cmd.Dir = wd
|
|
||||||
return cmd.Output()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResMapFromSecretArgs takes a SecretArgs slice and executes its command in directory p
|
|
||||||
// then writes the output to a Resource slice and return it.
|
|
||||||
func NewResMapFromSecretArgs(p string, secretList []types.SecretArgs) (ResMap, error) {
|
|
||||||
allResources := []*resource.Resource{}
|
|
||||||
for _, secret := range secretList {
|
|
||||||
res, err := newResourceFromSecretGenerator(p, secret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "newResourceFromSecretGenerator")
|
return nil, errors.Wrap(err, "NewResourceWithBehavior")
|
||||||
}
|
}
|
||||||
allResources = append(allResources, res)
|
allResources = append(allResources, res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -33,14 +35,26 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
|
|||||||
secrets := []types.SecretArgs{
|
secrets := []types.SecretArgs{
|
||||||
{
|
{
|
||||||
Name: "apple",
|
Name: "apple",
|
||||||
Commands: map[string]string{
|
CommandSources: types.CommandSources{
|
||||||
"DB_USERNAME": "printf admin",
|
Commands: map[string]string{
|
||||||
"DB_PASSWORD": "printf somepw",
|
"DB_USERNAME": "printf admin",
|
||||||
|
"DB_PASSWORD": "printf somepw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "Opaque",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "peanuts",
|
||||||
|
CommandSources: types.CommandSources{
|
||||||
|
EnvCommand: "printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"",
|
||||||
},
|
},
|
||||||
Type: "Opaque",
|
Type: "Opaque",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := NewResMapFromSecretArgs(".", secrets)
|
fakeFs := fs.MakeFakeFS()
|
||||||
|
fakeFs.Mkdir(".")
|
||||||
|
actual, err := NewResMapFromSecretArgs(
|
||||||
|
configmapandsecret.NewSecretFactory(fakeFs, "."), secrets)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
@@ -60,7 +74,21 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
|
|||||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
|
resource.NewResId(secret, "peanuts"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "peanuts",
|
||||||
|
"creationTimestamp": nil,
|
||||||
|
},
|
||||||
|
"type": string(corev1.SecretTypeOpaque),
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||||
|
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||||
|
},
|
||||||
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected)
|
t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected)
|
||||||
|
|||||||
@@ -28,18 +28,51 @@ type ResId struct {
|
|||||||
gvk schema.GroupVersionKind
|
gvk schema.GroupVersionKind
|
||||||
// original name of the resource before transformation.
|
// original name of the resource before transformation.
|
||||||
name string
|
name string
|
||||||
|
// namePrefix of the resource
|
||||||
|
// an untransformed resource has no prefix, fully transformed resource has an arbitrary number of prefixes
|
||||||
|
// concatenated together.
|
||||||
|
prefix string
|
||||||
|
// namespace the resource belongs to
|
||||||
|
// an untransformed resource has no namespace, fully transformed resource has the namespace from
|
||||||
|
// the top most overlay
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithPrefixNamespace creates new resource identifier with a prefix and a namespace
|
||||||
|
func NewResIdWithPrefixNamespace(g schema.GroupVersionKind, n, p, ns string) ResId {
|
||||||
|
return ResId{gvk: g, name: n, prefix: p, namespace: ns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithPrefix creates new resource identifier with a prefix
|
||||||
|
func NewResIdWithPrefix(g schema.GroupVersionKind, n, p string) ResId {
|
||||||
|
return ResId{gvk: g, name: n, prefix: p}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResId creates new resource identifier
|
// NewResId creates new resource identifier
|
||||||
func NewResId(g schema.GroupVersionKind, n string) ResId {
|
func NewResId(g schema.GroupVersionKind, n string) ResId {
|
||||||
return ResId{gvk: g, name: n}
|
return NewResIdWithPrefix(g, n, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String of ResId based on GVK, name and prefix
|
||||||
func (n ResId) String() string {
|
func (n ResId) String() string {
|
||||||
|
fields := []string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.namespace, n.prefix, n.name}
|
||||||
|
return strings.Join(fields, "_") + ".yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GvknString of ResId based on GVK and name
|
||||||
|
func (n ResId) GvknString() string {
|
||||||
if n.gvk.Group == "" {
|
if n.gvk.Group == "" {
|
||||||
return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
|
return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
|
||||||
}
|
}
|
||||||
return strings.Join([]string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
|
return strings.Join([]string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GvknEquals return if two ResId have the same Group/Version/Kind and name
|
||||||
|
// The comparison excludes prefix
|
||||||
|
func (n ResId) GvknEquals(id ResId) bool {
|
||||||
|
return n.gvk.Group == id.gvk.Group && n.gvk.Version == id.gvk.Version &&
|
||||||
|
n.gvk.Kind == id.gvk.Kind && n.name == id.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gvk returns Group/Version/Kind of the resource.
|
// Gvk returns Group/Version/Kind of the resource.
|
||||||
@@ -51,3 +84,23 @@ func (n ResId) Gvk() schema.GroupVersionKind {
|
|||||||
func (n ResId) Name() string {
|
func (n ResId) Name() string {
|
||||||
return n.name
|
return n.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefix returns name prefix.
|
||||||
|
func (n ResId) Prefix() string {
|
||||||
|
return n.prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace returns resource namespace.
|
||||||
|
func (n ResId) Namespace() string {
|
||||||
|
return n.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyWithNewPrefix make a new copy from current ResId and append a new prefix
|
||||||
|
func (n ResId) CopyWithNewPrefix(p string) ResId {
|
||||||
|
return ResId{gvk: n.gvk, name: n.name, prefix: p + n.prefix, namespace: n.namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyWithNewNamespace make a new copy from current ResId and set a new namespace
|
||||||
|
func (n ResId) CopyWithNewNamespace(ns string) ResId {
|
||||||
|
return ResId{gvk: n.gvk, name: n.name, prefix: n.prefix, namespace: ns}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,8 +62,14 @@ func (r *Resource) Behavior() GenerationBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetBehavior changes the resource to the new behavior
|
// SetBehavior changes the resource to the new behavior
|
||||||
func (r *Resource) SetBehavior(b GenerationBehavior) {
|
func (r *Resource) SetBehavior(b GenerationBehavior) *Resource {
|
||||||
r.b = b
|
r.b = b
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGenerated checks if the resource is generated from a generator
|
||||||
|
func (r *Resource) IsGenerated() bool {
|
||||||
|
return r.b != BehaviorUnspecified
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the ResId for the resource.
|
// Id returns the ResId for the resource.
|
||||||
@@ -115,7 +121,7 @@ func (r *Resource) GetFieldValue(fieldPath string) (string, error) {
|
|||||||
|
|
||||||
func getFieldValue(m map[string]interface{}, pathToField []string) (string, error) {
|
func getFieldValue(m map[string]interface{}, pathToField []string) (string, error) {
|
||||||
if len(pathToField) == 0 {
|
if len(pathToField) == 0 {
|
||||||
return "", fmt.Errorf("Field not found")
|
return "", fmt.Errorf("field not found")
|
||||||
}
|
}
|
||||||
if len(pathToField) == 1 {
|
if len(pathToField) == 1 {
|
||||||
if v, found := m[pathToField[0]]; found {
|
if v, found := m[pathToField[0]]; found {
|
||||||
|
|||||||
122
pkg/transformers/imagetag.go
Normal file
122
pkg/transformers/imagetag.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transformers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// imageTagTransformer replace image tags
|
||||||
|
type imageTagTransformer struct {
|
||||||
|
imageTags []types.ImageTag
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Transformer = &imageTagTransformer{}
|
||||||
|
|
||||||
|
// NewImageTagTransformer constructs a imageTagTransformer.
|
||||||
|
func NewImageTagTransformer(slice []types.ImageTag) (Transformer, error) {
|
||||||
|
return &imageTagTransformer{slice}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform finds the matching images and replace the tag
|
||||||
|
func (pt *imageTagTransformer) Transform(resources resmap.ResMap) error {
|
||||||
|
if len(pt.imageTags) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, res := range resources {
|
||||||
|
err := pt.findAndReplaceTag(res.UnstructuredContent())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
findAndReplaceTag replaces the image tags inside one object
|
||||||
|
It searches the object for container session
|
||||||
|
then loops though all images inside containers session, finds matched ones and update the tag name
|
||||||
|
*/
|
||||||
|
func (pt *imageTagTransformer) findAndReplaceTag(obj map[string]interface{}) error {
|
||||||
|
paths := []string{"containers", "initContainers"}
|
||||||
|
found := false
|
||||||
|
for _, path := range paths {
|
||||||
|
_, found = obj[path]
|
||||||
|
if found {
|
||||||
|
err := pt.updateContainers(obj, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return pt.findContainers(obj)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *imageTagTransformer) updateContainers(obj map[string]interface{}, path string) error {
|
||||||
|
containers := obj[path].([]interface{})
|
||||||
|
for i := range containers {
|
||||||
|
container := containers[i].(map[string]interface{})
|
||||||
|
image, found := container["image"]
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, imagetag := range pt.imageTags {
|
||||||
|
if isImageMatched(image.(string), imagetag.Name) {
|
||||||
|
container["image"] = strings.Join([]string{imagetag.Name, imagetag.NewTag}, ":")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *imageTagTransformer) findContainers(obj map[string]interface{}) error {
|
||||||
|
for key := range obj {
|
||||||
|
switch typedV := obj[key].(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
err := pt.findAndReplaceTag(typedV)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for i := range typedV {
|
||||||
|
item := typedV[i]
|
||||||
|
typedItem, ok := item.(map[string]interface{})
|
||||||
|
if ok {
|
||||||
|
err := pt.findAndReplaceTag(typedItem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isImageMatched(s, t string) bool {
|
||||||
|
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||||
|
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
|
||||||
|
return pattern.MatchString(s)
|
||||||
|
}
|
||||||
175
pkg/transformers/imagetag_test.go
Normal file
175
pkg/transformers/imagetag_test.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transformers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||||
|
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageTagTransformer(t *testing.T) {
|
||||||
|
m := resmap.ResMap{
|
||||||
|
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"initContainers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx2",
|
||||||
|
"image": "my-nginx:1.8.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resource.NewResId(schema.GroupVersionKind{Kind: "randomeKind"}, "random"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx1",
|
||||||
|
"image": "nginx",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "myimage",
|
||||||
|
"image": "myprivaterepohostname:1234/my/image:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec2": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx3",
|
||||||
|
"image": "nginx:v1",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx4",
|
||||||
|
"image": "my-nginx:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"initContainers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx2",
|
||||||
|
"image": "my-nginx:previous",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:v2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resource.NewResId(schema.GroupVersionKind{Kind: "randomeKind"}, "random"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx1",
|
||||||
|
"image": "nginx:v2",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "myimage",
|
||||||
|
"image": "myprivaterepohostname:1234/my/image:v1.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec2": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx3",
|
||||||
|
"image": "nginx:v2",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx4",
|
||||||
|
"image": "my-nginx:previous",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
it, err := NewImageTagTransformer([]types.ImageTag{
|
||||||
|
{Name: "nginx", NewTag: "v2"},
|
||||||
|
{Name: "my-nginx", NewTag: "previous"},
|
||||||
|
{Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = it.Transform(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(m, expected) {
|
||||||
|
err = expected.ErrorIfNotEqual(m)
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,10 +30,15 @@ var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
|
|||||||
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
|
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
|
||||||
var ns = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}
|
var ns = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}
|
||||||
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||||
|
var statefulset = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}
|
||||||
var foo = schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}
|
var foo = schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}
|
||||||
var crd = schema.GroupVersionKind{Group: "apiwctensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition"}
|
var crd = schema.GroupVersionKind{Group: "apiwctensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition"}
|
||||||
var job = schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"}
|
var job = schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"}
|
||||||
var cronjob = schema.GroupVersionKind{Group: "batch", Version: "v1beta1", Kind: "CronJob"}
|
var cronjob = schema.GroupVersionKind{Group: "batch", Version: "v1beta1", Kind: "CronJob"}
|
||||||
|
var pvc = schema.GroupVersionKind{Version: "v1", Kind: "PersistentVolumeClaim"}
|
||||||
|
var crb = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}
|
||||||
|
var sa = schema.GroupVersionKind{Version: "v1", Kind: "ServiceAccount"}
|
||||||
|
var ingress = schema.GroupVersionKind{Kind: "Ingress"}
|
||||||
|
|
||||||
func TestLabelsRun(t *testing.T) {
|
func TestLabelsRun(t *testing.T) {
|
||||||
m := resmap.ResMap{
|
m := resmap.ResMap{
|
||||||
|
|||||||
@@ -52,6 +52,32 @@ var defaultLabelsPathConfigs = []PathConfig{
|
|||||||
Path: []string{"spec", "template", "metadata", "labels"},
|
Path: []string{"spec", "template", "metadata", "labels"},
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "Deployment"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAffinity",
|
||||||
|
"preferredDuringSchedulingIgnoredDuringExecution",
|
||||||
|
"podAffinityTerm", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "Deployment"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAffinity",
|
||||||
|
"requiredDuringSchedulingIgnoredDuringExecution", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "Deployment"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAntiAffinity",
|
||||||
|
"preferredDuringSchedulingIgnoredDuringExecution",
|
||||||
|
"podAffinityTerm", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "Deployment"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAntiAffinity",
|
||||||
|
"requiredDuringSchedulingIgnoredDuringExecution", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
GroupVersionKind: &schema.GroupVersionKind{Kind: "ReplicaSet"},
|
GroupVersionKind: &schema.GroupVersionKind{Kind: "ReplicaSet"},
|
||||||
Path: []string{"spec", "selector", "matchLabels"},
|
Path: []string{"spec", "selector", "matchLabels"},
|
||||||
@@ -82,6 +108,32 @@ var defaultLabelsPathConfigs = []PathConfig{
|
|||||||
Path: []string{"spec", "template", "metadata", "labels"},
|
Path: []string{"spec", "template", "metadata", "labels"},
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "StatefulSet"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAffinity",
|
||||||
|
"preferredDuringSchedulingIgnoredDuringExecution",
|
||||||
|
"podAffinityTerm", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "StatefulSet"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAffinity",
|
||||||
|
"requiredDuringSchedulingIgnoredDuringExecution", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "StatefulSet"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAntiAffinity",
|
||||||
|
"preferredDuringSchedulingIgnoredDuringExecution",
|
||||||
|
"podAffinityTerm", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "apps", Kind: "StatefulSet"},
|
||||||
|
Path: []string{"spec", "template", "spec", "affinity", "podAntiAffinity",
|
||||||
|
"requiredDuringSchedulingIgnoredDuringExecution", "labelSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
GroupVersionKind: &schema.GroupVersionKind{Group: "batch", Kind: "Job"},
|
GroupVersionKind: &schema.GroupVersionKind{Group: "batch", Kind: "Job"},
|
||||||
Path: []string{"spec", "selector", "matchLabels"},
|
Path: []string{"spec", "selector", "matchLabels"},
|
||||||
@@ -107,6 +159,21 @@ var defaultLabelsPathConfigs = []PathConfig{
|
|||||||
Path: []string{"spec", "selector", "matchLabels"},
|
Path: []string{"spec", "selector", "matchLabels"},
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},
|
||||||
|
Path: []string{"spec", "podSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},
|
||||||
|
Path: []string{"spec", "ingress", "from", "podSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},
|
||||||
|
Path: []string{"spec", "egress", "to", "podSelector", "matchLabels"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultLabelsPathConfigs is the default configuration for mutating annotations
|
// defaultLabelsPathConfigs is the default configuration for mutating annotations
|
||||||
|
|||||||
@@ -41,17 +41,19 @@ func NewNameHashTransformer() Transformer {
|
|||||||
// Transform appends hash to configmaps and secrets.
|
// Transform appends hash to configmaps and secrets.
|
||||||
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
|
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
|
||||||
for id, res := range m {
|
for id, res := range m {
|
||||||
switch {
|
if res.IsGenerated() {
|
||||||
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}):
|
switch {
|
||||||
err := appendHashForConfigMap(res)
|
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}):
|
||||||
if err != nil {
|
err := appendHashForConfigMap(res)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}):
|
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}):
|
||||||
err := appendHashForSecret(res)
|
err := appendHashForSecret(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func TestNameHashTransformer(t *testing.T) {
|
|||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "secret1",
|
"name": "secret1",
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := resmap.ResMap{
|
expected := resmap.ResMap{
|
||||||
@@ -92,7 +92,7 @@ func TestNameHashTransformer(t *testing.T) {
|
|||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "cm1-m462kdfb68",
|
"name": "cm1",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||||
@@ -144,7 +144,7 @@ func TestNameHashTransformer(t *testing.T) {
|
|||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "secret1-7kc45hd5f7",
|
"name": "secret1-7kc45hd5f7",
|
||||||
},
|
},
|
||||||
}),
|
}).SetBehavior(resource.BehaviorCreate),
|
||||||
}
|
}
|
||||||
|
|
||||||
tran := NewNameHashTransformer()
|
tran := NewNameHashTransformer()
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ func TestNameReferenceRun(t *testing.T) {
|
|||||||
"name": "someprefix-cm1-somehash",
|
"name": "someprefix-cm1-somehash",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
resource.NewResId(cmap, "cm2"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm2-somehash",
|
||||||
|
},
|
||||||
|
}),
|
||||||
resource.NewResId(secret, "secret1"): resource.NewResourceFromMap(
|
resource.NewResId(secret, "secret1"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -43,6 +51,34 @@ func TestNameReferenceRun(t *testing.T) {
|
|||||||
"name": "someprefix-secret1-somehash",
|
"name": "someprefix-secret1-somehash",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
resource.NewResId(pvc, "claim1"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "PersistentVolumeClaim",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "someprefix-claim1",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resource.NewResId(ingress, "ingress1"): resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "extensions",
|
||||||
|
"apiVersion": "v1beta1",
|
||||||
|
"kind": "Ingress",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "ingress1",
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"ingress.kubernetes.io/auth-secret": "secret1",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-secret": "secret1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"backend": map[string]interface{}{
|
||||||
|
"serviceName": "testsvc",
|
||||||
|
"servicePort": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"group": "apps",
|
"group": "apps",
|
||||||
@@ -103,40 +139,31 @@ func TestNameReferenceRun(t *testing.T) {
|
|||||||
"configMap": map[string]interface{}{
|
"configMap": map[string]interface{}{
|
||||||
"name": "cm1",
|
"name": "cm1",
|
||||||
},
|
},
|
||||||
|
"projected": map[string]interface{}{
|
||||||
|
"sources": map[string]interface{}{
|
||||||
|
"configMap": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"secret": map[string]interface{}{
|
"secret": map[string]interface{}{
|
||||||
"secretName": "secret1",
|
"secretName": "secret1",
|
||||||
},
|
},
|
||||||
|
"persistentVolumeClaim": map[string]interface{}{
|
||||||
|
"claimName": "claim1",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}
|
resource.NewResId(statefulset, "statefulset1"): resource.NewResourceFromMap(
|
||||||
|
|
||||||
expected := resmap.ResMap{
|
|
||||||
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "someprefix-cm1-somehash",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
resource.NewResId(secret, "secret1"): resource.NewResourceFromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Secret",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "someprefix-secret1-somehash",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"group": "apps",
|
"group": "apps",
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Deployment",
|
"kind": "StatefulSet",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "deploy1",
|
"name": "statefulset1",
|
||||||
},
|
},
|
||||||
"spec": map[string]interface{}{
|
"spec": map[string]interface{}{
|
||||||
"template": map[string]interface{}{
|
"template": map[string]interface{}{
|
||||||
@@ -145,53 +172,15 @@ func TestNameReferenceRun(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"image": "nginx:1.7.9",
|
"image": "nginx:1.7.9",
|
||||||
"env": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "CM_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"configMapKeyRef": map[string]interface{}{
|
|
||||||
"name": "someprefix-cm1-somehash",
|
|
||||||
"key": "somekey",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "SECRET_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"secretKeyRef": map[string]interface{}{
|
|
||||||
"name": "someprefix-secret1-somehash",
|
|
||||||
"key": "somekey",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"envFrom": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"configMapRef": map[string]interface{}{
|
|
||||||
"name": "someprefix-cm1-somehash",
|
|
||||||
"key": "somekey",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"secretRef": map[string]interface{}{
|
|
||||||
"name": "someprefix-secret1-somehash",
|
|
||||||
"key": "somekey",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"imagePullSecrets": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "someprefix-secret1-somehash",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"volumes": map[string]interface{}{
|
"volumes": map[string]interface{}{
|
||||||
"configMap": map[string]interface{}{
|
"projected": map[string]interface{}{
|
||||||
"name": "someprefix-cm1-somehash",
|
"sources": map[string]interface{}{
|
||||||
},
|
"configMap": map[string]interface{}{
|
||||||
"secret": map[string]interface{}{
|
"name": "cm2",
|
||||||
"secretName": "someprefix-secret1-somehash",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -200,6 +189,140 @@ func TestNameReferenceRun(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected := resmap.ResMap{}
|
||||||
|
for k, v := range m {
|
||||||
|
expected[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
expected[resource.NewResId(deploy, "deploy1")] = resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "CM_FOO",
|
||||||
|
"valueFrom": map[string]interface{}{
|
||||||
|
"configMapKeyRef": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm1-somehash",
|
||||||
|
"key": "somekey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SECRET_FOO",
|
||||||
|
"valueFrom": map[string]interface{}{
|
||||||
|
"secretKeyRef": map[string]interface{}{
|
||||||
|
"name": "someprefix-secret1-somehash",
|
||||||
|
"key": "somekey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"envFrom": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"configMapRef": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm1-somehash",
|
||||||
|
"key": "somekey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"secretRef": map[string]interface{}{
|
||||||
|
"name": "someprefix-secret1-somehash",
|
||||||
|
"key": "somekey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"imagePullSecrets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "someprefix-secret1-somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"volumes": map[string]interface{}{
|
||||||
|
"configMap": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm1-somehash",
|
||||||
|
},
|
||||||
|
"projected": map[string]interface{}{
|
||||||
|
"sources": map[string]interface{}{
|
||||||
|
"configMap": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm2-somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"secret": map[string]interface{}{
|
||||||
|
"secretName": "someprefix-secret1-somehash",
|
||||||
|
},
|
||||||
|
"persistentVolumeClaim": map[string]interface{}{
|
||||||
|
"claimName": "someprefix-claim1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expected[resource.NewResId(statefulset, "statefulset1")] = resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "StatefulSet",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "statefulset1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"volumes": map[string]interface{}{
|
||||||
|
"projected": map[string]interface{}{
|
||||||
|
"sources": map[string]interface{}{
|
||||||
|
"configMap": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm2-somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expected[resource.NewResId(ingress, "ingress1")] = resource.NewResourceFromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "extensions",
|
||||||
|
"apiVersion": "v1beta1",
|
||||||
|
"kind": "Ingress",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "ingress1",
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"ingress.kubernetes.io/auth-secret": "someprefix-secret1-somehash",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-secret": "someprefix-secret1-somehash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"backend": map[string]interface{}{
|
||||||
|
"serviceName": "testsvc",
|
||||||
|
"servicePort": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
nrt, err := NewDefaultingNameReferenceTransformer()
|
nrt, err := NewDefaultingNameReferenceTransformer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user