Pull controller-go back into this repository as internal/controllerlib
.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
parent
371b172616
commit
a503fa8673
1
go.mod
1
go.mod
@ -12,7 +12,6 @@ require (
|
|||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f
|
|
||||||
github.com/suzerain-io/pinniped/generated/1.19/apis v0.0.0-00010101000000-000000000000
|
github.com/suzerain-io/pinniped/generated/1.19/apis v0.0.0-00010101000000-000000000000
|
||||||
github.com/suzerain-io/pinniped/generated/1.19/client v0.0.0-00010101000000-000000000000
|
github.com/suzerain-io/pinniped/generated/1.19/client v0.0.0-00010101000000-000000000000
|
||||||
k8s.io/api v0.19.0
|
k8s.io/api v0.19.0
|
||||||
|
37
go.sum
37
go.sum
@ -120,8 +120,6 @@ github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao
|
|||||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8 h1:DM7gHzQfHwIj+St8zaPOI6iQEPAxOwIkskvw6s9rDaM=
|
|
||||||
github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8/go.mod h1:pmLOTb3x90VhIKxsA9yeQG5yfOkkKnkk1h+Ql8NDYDw=
|
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
@ -237,8 +235,6 @@ github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy
|
|||||||
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
|
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
|
||||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||||
github.com/golangci/golangci-lint v1.29.0 h1:0ufaO3l2R1R712cFC+KT3TtwO/IOcsloKZBavRtzrBk=
|
|
||||||
github.com/golangci/golangci-lint v1.29.0/go.mod h1:Iq2GFBB9OoolSDWD81m0iJ2MR4MwDVbi4eC93fO7wh0=
|
|
||||||
github.com/golangci/golangci-lint v1.30.0 h1:UhdK5WbO0GBd7W+k2lOD7BEJH4Wsa7zKfw8m3/aEJGQ=
|
github.com/golangci/golangci-lint v1.30.0 h1:UhdK5WbO0GBd7W+k2lOD7BEJH4Wsa7zKfw8m3/aEJGQ=
|
||||||
github.com/golangci/golangci-lint v1.30.0/go.mod h1:5t0i3wHlqQc9deBBvZsP+a/4xz7cfjV+zhp5U0Mzp14=
|
github.com/golangci/golangci-lint v1.30.0/go.mod h1:5t0i3wHlqQc9deBBvZsP+a/4xz7cfjV+zhp5U0Mzp14=
|
||||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
|
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
|
||||||
@ -280,7 +276,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
||||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||||
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
|
||||||
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@ -339,7 +334,6 @@ github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0
|
|||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@ -436,16 +430,12 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
|
|||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
|
||||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
|
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
|
||||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
|
|
||||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
|
||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
@ -502,8 +492,6 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ
|
|||||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/securego/gosec/v2 v2.3.0 h1:y/9mCF2WPDbSDpL3QDWZD3HHGrSYw0QSHnCqTfs4JPE=
|
|
||||||
github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
|
|
||||||
github.com/securego/gosec/v2 v2.4.0 h1:ivAoWcY5DMs9n04Abc1VkqZBO0FL0h4ShTcVsC53lCE=
|
github.com/securego/gosec/v2 v2.4.0 h1:ivAoWcY5DMs9n04Abc1VkqZBO0FL0h4ShTcVsC53lCE=
|
||||||
github.com/securego/gosec/v2 v2.4.0/go.mod h1:0/Q4cjmlFDfDUj1+Fib61sc+U5IQb2w+Iv9/C3wPVko=
|
github.com/securego/gosec/v2 v2.4.0/go.mod h1:0/Q4cjmlFDfDUj1+Fib61sc+U5IQb2w+Iv9/C3wPVko=
|
||||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
|
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
|
||||||
@ -562,12 +550,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f h1:gZ6rAdl+VE9DT0yE52xY/kJZ/hOJYxwtsgGoPr5vItI=
|
|
||||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f/go.mod h1:+v9upryFWBJac6KXKlheGHr7e3kqpk1ldH1iIMFopMs=
|
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||||
github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8=
|
|
||||||
github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
|
|
||||||
github.com/tetafro/godot v0.4.8 h1:h61+hQraWhdI6WYqMwAwZYCE5yxL6a9/Orw4REbabSU=
|
github.com/tetafro/godot v0.4.8 h1:h61+hQraWhdI6WYqMwAwZYCE5yxL6a9/Orw4REbabSU=
|
||||||
github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
|
github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
|
||||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
|
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
|
||||||
@ -619,7 +603,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -770,20 +753,14 @@ golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1 h1:rD1FcWVsRaMY+l8biE9jbWP5MS/CJJ/90a9TMkMgNrM=
|
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1 h1:rD1FcWVsRaMY+l8biE9jbWP5MS/CJJ/90a9TMkMgNrM=
|
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305 h1:yaM5S0KcY0lIoZo7Fl+oi91b/DdlU2zuWpfHrpWbCS0=
|
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305 h1:yaM5S0KcY0lIoZo7Fl+oi91b/DdlU2zuWpfHrpWbCS0=
|
||||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -872,21 +849,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
k8s.io/api v0.19.0-rc.0 h1:K+xi+F3RNAxpFyS1f7uHekMNprjFX7WVZDx2lJE+A3A=
|
|
||||||
k8s.io/api v0.19.0-rc.0/go.mod h1:WBGMHEmngOdQBAvJiYUgP5mGDdCWXM52yDm1gtos8C0=
|
|
||||||
k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc=
|
k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc=
|
||||||
k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw=
|
k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw=
|
||||||
k8s.io/apimachinery v0.19.0-rc.0 h1:IBmRy0elJCGgxtCT0bHT93N+rhx+vF2DD1XXJ3ntLa8=
|
|
||||||
k8s.io/apimachinery v0.19.0-rc.0/go.mod h1:EjWiYOPi+BZennZ5pGa3JLkQ+znhEOodGy/+umjiLDU=
|
|
||||||
k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ=
|
k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ=
|
||||||
k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||||
k8s.io/apiserver v0.19.0 h1:jLhrL06wGAADbLUUQm8glSLnAGP6c7y5R3p19grkBoY=
|
k8s.io/apiserver v0.19.0 h1:jLhrL06wGAADbLUUQm8glSLnAGP6c7y5R3p19grkBoY=
|
||||||
k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk=
|
k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk=
|
||||||
k8s.io/client-go v0.19.0-rc.0 h1:6WW8MElhoLeYcLiN4ky1159XG5E39KYdmLCrV/6lNiE=
|
|
||||||
k8s.io/client-go v0.19.0-rc.0/go.mod h1:3kWGD05F7c58atlk7ep9ob1hg2Yu9NSz8gJxCNNTHhc=
|
|
||||||
k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k=
|
k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k=
|
||||||
k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU=
|
k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU=
|
||||||
k8s.io/code-generator v0.19.0-rc.0/go.mod h1:2jgaU9hVSqti1GiO69UFSoTZcL5XAvZSrXaNnK5RVA0=
|
|
||||||
k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
|
k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
|
||||||
k8s.io/component-base v0.19.0 h1:OueXf1q3RW7NlLlUCj2Dimwt7E1ys6ZqRnq53l2YuoE=
|
k8s.io/component-base v0.19.0 h1:OueXf1q3RW7NlLlUCj2Dimwt7E1ys6ZqRnq53l2YuoE=
|
||||||
k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y=
|
k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y=
|
||||||
@ -897,12 +867,8 @@ k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
|
|||||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||||
k8s.io/kube-aggregator v0.19.0 h1:rL4fsftMaqkKjaibArYDaBeqN41CHaJzgRJjUB9IrIg=
|
k8s.io/kube-aggregator v0.19.0 h1:rL4fsftMaqkKjaibArYDaBeqN41CHaJzgRJjUB9IrIg=
|
||||||
k8s.io/kube-aggregator v0.19.0/go.mod h1:1Ln45PQggFAG8xOqWPIYMxUq8WNtpPnYsbUJ39DpF/A=
|
k8s.io/kube-aggregator v0.19.0/go.mod h1:1Ln45PQggFAG8xOqWPIYMxUq8WNtpPnYsbUJ39DpF/A=
|
||||||
k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 h1:5NC2ITmvg8RoxoH0wgmL4zn4VZqXGsKbxrikjaQx6s4=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
|
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
|
||||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
|
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
|
||||||
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo=
|
|
||||||
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
|
||||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=
|
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=
|
||||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f h1:gi7cb8HTDZ6q8VqsUpkdoFi3vxwHMneQ6+Q5Ap5hjPE=
|
mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f h1:gi7cb8HTDZ6q8VqsUpkdoFi3vxwHMneQ6+Q5Ap5hjPE=
|
||||||
@ -920,9 +886,6 @@ rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
|
|||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9 h1:rusRLrDhjBp6aYtl9sGEvQJr6faoHoDLd0YcUBTZguI=
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9 h1:rusRLrDhjBp6aYtl9sGEvQJr6faoHoDLd0YcUBTZguI=
|
||||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0=
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0=
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
|
sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
"github.com/suzerain-io/pinniped/internal/constable"
|
"github.com/suzerain-io/pinniped/internal/constable"
|
||||||
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
type certsExpirerController struct {
|
type certsExpirerController struct {
|
||||||
@ -33,7 +33,7 @@ type certsExpirerController struct {
|
|||||||
renewBefore time.Duration
|
renewBefore time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertsExpirerController returns a controller.Controller that will delete a
|
// NewCertsExpirerController returns a controllerlib.Controller that will delete a
|
||||||
// certificate secret once it gets within some threshold of its expiration time. The
|
// certificate secret once it gets within some threshold of its expiration time. The
|
||||||
// deletion forces rotation of the secret with the help of other controllers.
|
// deletion forces rotation of the secret with the help of other controllers.
|
||||||
func NewCertsExpirerController(
|
func NewCertsExpirerController(
|
||||||
@ -42,9 +42,9 @@ func NewCertsExpirerController(
|
|||||||
secretInformer corev1informers.SecretInformer,
|
secretInformer corev1informers.SecretInformer,
|
||||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
renewBefore time.Duration,
|
renewBefore time.Duration,
|
||||||
) controller.Controller {
|
) controllerlib.Controller {
|
||||||
return controller.New(
|
return controllerlib.New(
|
||||||
controller.Config{
|
controllerlib.Config{
|
||||||
Name: "certs-expirer-controller",
|
Name: "certs-expirer-controller",
|
||||||
Syncer: &certsExpirerController{
|
Syncer: &certsExpirerController{
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@ -56,13 +56,13 @@ func NewCertsExpirerController(
|
|||||||
withInformer(
|
withInformer(
|
||||||
secretInformer,
|
secretInformer,
|
||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
||||||
controller.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync implements controller.Syncer.Sync.
|
// Sync implements controller.Syncer.Sync.
|
||||||
func (c *certsExpirerController) Sync(ctx controller.Context) error {
|
func (c *certsExpirerController) Sync(ctx controllerlib.Context) error {
|
||||||
secret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
secret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
||||||
notFound := k8serrors.IsNotFound(err)
|
notFound := k8serrors.IsNotFound(err)
|
||||||
if err != nil && !notFound {
|
if err != nil && !notFound {
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||||
kubetesting "k8s.io/client-go/testing"
|
kubetesting "k8s.io/client-go/testing"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/testutil"
|
"github.com/suzerain-io/pinniped/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -247,15 +247,15 @@ func TestExpirerControllerSync(t *testing.T) {
|
|||||||
namespace,
|
namespace,
|
||||||
kubeAPIClient,
|
kubeAPIClient,
|
||||||
kubeInformers.Core().V1().Secrets(),
|
kubeInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
test.renewBefore,
|
test.renewBefore,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Must start informers before calling TestRunSynchronously().
|
// Must start informers before calling TestRunSynchronously().
|
||||||
kubeInformers.Start(ctx.Done())
|
kubeInformers.Start(ctx.Done())
|
||||||
controller.TestRunSynchronously(t, c)
|
controllerlib.TestRunSynchronously(t, c)
|
||||||
|
|
||||||
err := controller.TestSync(t, c, controller.Context{
|
err := controllerlib.TestSync(t, c, controllerlib.Context{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
})
|
})
|
||||||
if test.wantError != "" {
|
if test.wantError != "" {
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
"github.com/suzerain-io/pinniped/internal/certauthority"
|
"github.com/suzerain-io/pinniped/internal/certauthority"
|
||||||
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -50,9 +50,9 @@ func NewCertsManagerController(
|
|||||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
|
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
|
||||||
certDuration time.Duration,
|
certDuration time.Duration,
|
||||||
) controller.Controller {
|
) controllerlib.Controller {
|
||||||
return controller.New(
|
return controllerlib.New(
|
||||||
controller.Config{
|
controllerlib.Config{
|
||||||
Name: "certs-manager-controller",
|
Name: "certs-manager-controller",
|
||||||
Syncer: &certsManagerController{
|
Syncer: &certsManagerController{
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@ -65,17 +65,17 @@ func NewCertsManagerController(
|
|||||||
withInformer(
|
withInformer(
|
||||||
secretInformer,
|
secretInformer,
|
||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
||||||
controller.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
// Be sure to run once even if the Secret that the informer is watching doesn't exist.
|
// Be sure to run once even if the Secret that the informer is watching doesn't exist.
|
||||||
withInitialEvent(controller.Key{
|
withInitialEvent(controllerlib.Key{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Name: certsSecretName,
|
Name: certsSecretName,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *certsManagerController) Sync(ctx controller.Context) error {
|
func (c *certsManagerController) Sync(ctx controllerlib.Context) error {
|
||||||
// Try to get the secret from the informer cache.
|
// Try to get the secret from the informer cache.
|
||||||
_, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
_, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
||||||
notFound := k8serrors.IsNotFound(err)
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
@ -24,8 +24,8 @@ import (
|
|||||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
aggregatorfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
|
aggregatorfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
pinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/pinniped/v1alpha1"
|
pinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/pinniped/v1alpha1"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/testutil"
|
"github.com/suzerain-io/pinniped/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ func TestManagerControllerOptions(t *testing.T) {
|
|||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
||||||
var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption
|
var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption
|
||||||
var secretsInformerFilter controller.Filter
|
var secretsInformerFilter controllerlib.Filter
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
r = require.New(t)
|
r = require.New(t)
|
||||||
@ -56,7 +56,7 @@ func TestManagerControllerOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
when("watching Secret objects", func() {
|
when("watching Secret objects", func() {
|
||||||
var subject controller.Filter
|
var subject controllerlib.Filter
|
||||||
var target, wrongNamespace, wrongName, unrelated *corev1.Secret
|
var target, wrongNamespace, wrongName, unrelated *corev1.Secret
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -105,7 +105,7 @@ func TestManagerControllerOptions(t *testing.T) {
|
|||||||
|
|
||||||
when("starting up", func() {
|
when("starting up", func() {
|
||||||
it("asks for an initial event because the Secret may not exist yet and it needs to run anyway", func() {
|
it("asks for an initial event because the Secret may not exist yet and it needs to run anyway", func() {
|
||||||
r.Equal(controller.Key{
|
r.Equal(controllerlib.Key{
|
||||||
Namespace: installedInNamespace,
|
Namespace: installedInNamespace,
|
||||||
Name: "api-serving-cert",
|
Name: "api-serving-cert",
|
||||||
}, observableWithInitialEventOption.GetInitialEventKey())
|
}, observableWithInitialEventOption.GetInitialEventKey())
|
||||||
@ -121,14 +121,14 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
|
|
||||||
var subject controller.Controller
|
var subject controllerlib.Controller
|
||||||
var kubeAPIClient *kubernetesfake.Clientset
|
var kubeAPIClient *kubernetesfake.Clientset
|
||||||
var aggregatorAPIClient *aggregatorfake.Clientset
|
var aggregatorAPIClient *aggregatorfake.Clientset
|
||||||
var kubeInformerClient *kubernetesfake.Clientset
|
var kubeInformerClient *kubernetesfake.Clientset
|
||||||
var kubeInformers kubeinformers.SharedInformerFactory
|
var kubeInformers kubeinformers.SharedInformerFactory
|
||||||
var timeoutContext context.Context
|
var timeoutContext context.Context
|
||||||
var timeoutContextCancel context.CancelFunc
|
var timeoutContextCancel context.CancelFunc
|
||||||
var syncContext *controller.Context
|
var syncContext *controllerlib.Context
|
||||||
|
|
||||||
// Defer starting the informers until the last possible moment so that the
|
// Defer starting the informers until the last possible moment so that the
|
||||||
// nested Before's can keep adding things to the informer caches.
|
// nested Before's can keep adding things to the informer caches.
|
||||||
@ -139,16 +139,16 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
kubeAPIClient,
|
kubeAPIClient,
|
||||||
aggregatorAPIClient,
|
aggregatorAPIClient,
|
||||||
kubeInformers.Core().V1().Secrets(),
|
kubeInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
controller.WithInitialEvent,
|
controllerlib.WithInitialEvent,
|
||||||
certDuration,
|
certDuration,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set this at the last second to support calling subject.Name().
|
// Set this at the last second to support calling subject.Name().
|
||||||
syncContext = &controller.Context{
|
syncContext = &controllerlib.Context{
|
||||||
Context: timeoutContext,
|
Context: timeoutContext,
|
||||||
Name: subject.Name(),
|
Name: subject.Name(),
|
||||||
Key: controller.Key{
|
Key: controllerlib.Key{
|
||||||
Namespace: installedInNamespace,
|
Namespace: installedInNamespace,
|
||||||
Name: "api-serving-cert",
|
Name: "api-serving-cert",
|
||||||
},
|
},
|
||||||
@ -156,7 +156,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
// Must start informers before calling TestRunSynchronously()
|
// Must start informers before calling TestRunSynchronously()
|
||||||
kubeInformers.Start(timeoutContext.Done())
|
kubeInformers.Start(timeoutContext.Done())
|
||||||
controller.TestRunSynchronously(t, subject)
|
controllerlib.TestRunSynchronously(t, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -203,7 +203,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("creates the api-serving-cert Secret and updates the APIService's ca bundle", func() {
|
it("creates the api-serving-cert Secret and updates the APIService's ca bundle", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
// Check all the relevant fields from the create Secret action
|
// Check all the relevant fields from the create Secret action
|
||||||
@ -269,7 +269,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns the update error", func() {
|
it("returns the update error", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.EqualError(err, "could not update the API service: could not update API service: update failed")
|
r.EqualError(err, "could not update the API service: could not update API service: update failed")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -287,7 +287,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns an error", func() {
|
it("returns an error", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.Error(err)
|
r.Error(err)
|
||||||
r.Regexp("could not get existing version of API service: .* not found", err.Error())
|
r.Regexp("could not get existing version of API service: .* not found", err.Error())
|
||||||
})
|
})
|
||||||
@ -306,7 +306,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns the create error and does not update the APIService", func() {
|
it("returns the create error and does not update the APIService", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.EqualError(err, "could not create secret: create failed")
|
r.EqualError(err, "could not create secret: create failed")
|
||||||
r.Empty(aggregatorAPIClient.Actions())
|
r.Empty(aggregatorAPIClient.Actions())
|
||||||
})
|
})
|
||||||
@ -327,7 +327,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("does not need to make any API calls with its API clients", func() {
|
it("does not need to make any API calls with its API clients", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
r.Empty(kubeAPIClient.Actions())
|
r.Empty(kubeAPIClient.Actions())
|
||||||
r.Empty(aggregatorAPIClient.Actions())
|
r.Empty(aggregatorAPIClient.Actions())
|
||||||
|
@ -12,8 +12,8 @@ import (
|
|||||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/provider"
|
"github.com/suzerain-io/pinniped/internal/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,9 +28,9 @@ func NewCertsObserverController(
|
|||||||
dynamicCertProvider provider.DynamicTLSServingCertProvider,
|
dynamicCertProvider provider.DynamicTLSServingCertProvider,
|
||||||
secretInformer corev1informers.SecretInformer,
|
secretInformer corev1informers.SecretInformer,
|
||||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
) controller.Controller {
|
) controllerlib.Controller {
|
||||||
return controller.New(
|
return controllerlib.New(
|
||||||
controller.Config{
|
controllerlib.Config{
|
||||||
Name: "certs-observer-controller",
|
Name: "certs-observer-controller",
|
||||||
Syncer: &certsObserverController{
|
Syncer: &certsObserverController{
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@ -41,12 +41,12 @@ func NewCertsObserverController(
|
|||||||
withInformer(
|
withInformer(
|
||||||
secretInformer,
|
secretInformer,
|
||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace),
|
||||||
controller.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *certsObserverController) Sync(_ controller.Context) error {
|
func (c *certsObserverController) Sync(_ controllerlib.Context) error {
|
||||||
// Try to get the secret from the informer cache.
|
// Try to get the secret from the informer cache.
|
||||||
certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName)
|
||||||
notFound := k8serrors.IsNotFound(err)
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/provider"
|
"github.com/suzerain-io/pinniped/internal/provider"
|
||||||
"github.com/suzerain-io/pinniped/internal/testutil"
|
"github.com/suzerain-io/pinniped/internal/testutil"
|
||||||
)
|
)
|
||||||
@ -29,7 +29,7 @@ func TestObserverControllerInformerFilters(t *testing.T) {
|
|||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
||||||
var secretsInformerFilter controller.Filter
|
var secretsInformerFilter controllerlib.Filter
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
r = require.New(t)
|
r = require.New(t)
|
||||||
@ -45,7 +45,7 @@ func TestObserverControllerInformerFilters(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
when("watching Secret objects", func() {
|
when("watching Secret objects", func() {
|
||||||
var subject controller.Filter
|
var subject controllerlib.Filter
|
||||||
var target, wrongNamespace, wrongName, unrelated *corev1.Secret
|
var target, wrongNamespace, wrongName, unrelated *corev1.Secret
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -100,12 +100,12 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
|
|
||||||
var subject controller.Controller
|
var subject controllerlib.Controller
|
||||||
var kubeInformerClient *kubernetesfake.Clientset
|
var kubeInformerClient *kubernetesfake.Clientset
|
||||||
var kubeInformers kubeinformers.SharedInformerFactory
|
var kubeInformers kubeinformers.SharedInformerFactory
|
||||||
var timeoutContext context.Context
|
var timeoutContext context.Context
|
||||||
var timeoutContextCancel context.CancelFunc
|
var timeoutContextCancel context.CancelFunc
|
||||||
var syncContext *controller.Context
|
var syncContext *controllerlib.Context
|
||||||
var dynamicCertProvider provider.DynamicTLSServingCertProvider
|
var dynamicCertProvider provider.DynamicTLSServingCertProvider
|
||||||
|
|
||||||
// Defer starting the informers until the last possible moment so that the
|
// Defer starting the informers until the last possible moment so that the
|
||||||
@ -116,14 +116,14 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
installedInNamespace,
|
installedInNamespace,
|
||||||
dynamicCertProvider,
|
dynamicCertProvider,
|
||||||
kubeInformers.Core().V1().Secrets(),
|
kubeInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set this at the last second to support calling subject.Name().
|
// Set this at the last second to support calling subject.Name().
|
||||||
syncContext = &controller.Context{
|
syncContext = &controllerlib.Context{
|
||||||
Context: timeoutContext,
|
Context: timeoutContext,
|
||||||
Name: subject.Name(),
|
Name: subject.Name(),
|
||||||
Key: controller.Key{
|
Key: controllerlib.Key{
|
||||||
Namespace: installedInNamespace,
|
Namespace: installedInNamespace,
|
||||||
Name: "api-serving-cert",
|
Name: "api-serving-cert",
|
||||||
},
|
},
|
||||||
@ -131,7 +131,7 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
// Must start informers before calling TestRunSynchronously()
|
// Must start informers before calling TestRunSynchronously()
|
||||||
kubeInformers.Start(timeoutContext.Done())
|
kubeInformers.Start(timeoutContext.Done())
|
||||||
controller.TestRunSynchronously(t, subject)
|
controllerlib.TestRunSynchronously(t, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -164,7 +164,7 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("sets the dynamicCertProvider's cert and key to nil", func() {
|
it("sets the dynamicCertProvider's cert and key to nil", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
||||||
@ -194,7 +194,7 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("updates the dynamicCertProvider's cert and key", func() {
|
it("updates the dynamicCertProvider's cert and key", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
||||||
@ -220,7 +220,7 @@ func TestObserverControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
it("set the missing values in the dynamicCertProvider as nil", func() {
|
it("set the missing values in the dynamicCertProvider as nil", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
actualCertChain, actualKey := dynamicCertProvider.CurrentCertKeyContent()
|
||||||
|
@ -14,11 +14,11 @@ import (
|
|||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
crdpinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
crdpinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
||||||
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
||||||
crdpinnipedv1alpha1informers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions/crdpinniped/v1alpha1"
|
crdpinnipedv1alpha1informers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions/crdpinniped/v1alpha1"
|
||||||
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -45,9 +45,9 @@ func NewPublisherController(
|
|||||||
configMapInformer corev1informers.ConfigMapInformer,
|
configMapInformer corev1informers.ConfigMapInformer,
|
||||||
credentialIssuerConfigInformer crdpinnipedv1alpha1informers.CredentialIssuerConfigInformer,
|
credentialIssuerConfigInformer crdpinnipedv1alpha1informers.CredentialIssuerConfigInformer,
|
||||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
) controller.Controller {
|
) controllerlib.Controller {
|
||||||
return controller.New(
|
return controllerlib.New(
|
||||||
controller.Config{
|
controllerlib.Config{
|
||||||
Name: "publisher-controller",
|
Name: "publisher-controller",
|
||||||
Syncer: &publisherController{
|
Syncer: &publisherController{
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@ -60,17 +60,17 @@ func NewPublisherController(
|
|||||||
withInformer(
|
withInformer(
|
||||||
configMapInformer,
|
configMapInformer,
|
||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(clusterInfoName, ClusterInfoNamespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(clusterInfoName, ClusterInfoNamespace),
|
||||||
controller.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
withInformer(
|
withInformer(
|
||||||
credentialIssuerConfigInformer,
|
credentialIssuerConfigInformer,
|
||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(configName, namespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(configName, namespace),
|
||||||
controller.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *publisherController) Sync(ctx controller.Context) error {
|
func (c *publisherController) Sync(ctx controllerlib.Context) error {
|
||||||
configMap, err := c.configMapInformer.
|
configMap, err := c.configMapInformer.
|
||||||
Lister().
|
Lister().
|
||||||
ConfigMaps(ClusterInfoNamespace).
|
ConfigMaps(ClusterInfoNamespace).
|
||||||
|
@ -23,10 +23,10 @@ import (
|
|||||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||||
coretesting "k8s.io/client-go/testing"
|
coretesting "k8s.io/client-go/testing"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
crdpinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
crdpinnipedv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
||||||
pinnipedfake "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned/fake"
|
pinnipedfake "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned/fake"
|
||||||
pinnipedinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions"
|
pinnipedinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/testutil"
|
"github.com/suzerain-io/pinniped/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +36,8 @@ func TestInformerFilters(t *testing.T) {
|
|||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
||||||
var configMapInformerFilter controller.Filter
|
var configMapInformerFilter controllerlib.Filter
|
||||||
var credentialIssuerConfigInformerFilter controller.Filter
|
var credentialIssuerConfigInformerFilter controllerlib.Filter
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
r = require.New(t)
|
r = require.New(t)
|
||||||
@ -57,7 +57,7 @@ func TestInformerFilters(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
when("watching ConfigMap objects", func() {
|
when("watching ConfigMap objects", func() {
|
||||||
var subject controller.Filter
|
var subject controllerlib.Filter
|
||||||
var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap
|
var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -105,7 +105,7 @@ func TestInformerFilters(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
when("watching CredentialIssuerConfig objects", func() {
|
when("watching CredentialIssuerConfig objects", func() {
|
||||||
var subject controller.Filter
|
var subject controllerlib.Filter
|
||||||
var target, wrongNamespace, wrongName, unrelated *crdpinnipedv1alpha1.CredentialIssuerConfig
|
var target, wrongNamespace, wrongName, unrelated *crdpinnipedv1alpha1.CredentialIssuerConfig
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -168,7 +168,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
|
|
||||||
var subject controller.Controller
|
var subject controllerlib.Controller
|
||||||
var serverOverride *string
|
var serverOverride *string
|
||||||
var kubeInformerClient *kubernetesfake.Clientset
|
var kubeInformerClient *kubernetesfake.Clientset
|
||||||
var pinnipedInformerClient *pinnipedfake.Clientset
|
var pinnipedInformerClient *pinnipedfake.Clientset
|
||||||
@ -177,7 +177,7 @@ func TestSync(t *testing.T) {
|
|||||||
var pinnipedAPIClient *pinnipedfake.Clientset
|
var pinnipedAPIClient *pinnipedfake.Clientset
|
||||||
var timeoutContext context.Context
|
var timeoutContext context.Context
|
||||||
var timeoutContextCancel context.CancelFunc
|
var timeoutContextCancel context.CancelFunc
|
||||||
var syncContext *controller.Context
|
var syncContext *controllerlib.Context
|
||||||
|
|
||||||
var expectedCredentialIssuerConfig = func(expectedNamespace, expectedServerURL, expectedCAData string) (schema.GroupVersionResource, *crdpinnipedv1alpha1.CredentialIssuerConfig) {
|
var expectedCredentialIssuerConfig = func(expectedNamespace, expectedServerURL, expectedCAData string) (schema.GroupVersionResource, *crdpinnipedv1alpha1.CredentialIssuerConfig) {
|
||||||
expectedCredentialIssuerConfigGVR := schema.GroupVersionResource{
|
expectedCredentialIssuerConfigGVR := schema.GroupVersionResource{
|
||||||
@ -211,14 +211,14 @@ func TestSync(t *testing.T) {
|
|||||||
pinnipedAPIClient,
|
pinnipedAPIClient,
|
||||||
kubeInformers.Core().V1().ConfigMaps(),
|
kubeInformers.Core().V1().ConfigMaps(),
|
||||||
pinnipedInformers.Crd().V1alpha1().CredentialIssuerConfigs(),
|
pinnipedInformers.Crd().V1alpha1().CredentialIssuerConfigs(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set this at the last second to support calling subject.Name().
|
// Set this at the last second to support calling subject.Name().
|
||||||
syncContext = &controller.Context{
|
syncContext = &controllerlib.Context{
|
||||||
Context: timeoutContext,
|
Context: timeoutContext,
|
||||||
Name: subject.Name(),
|
Name: subject.Name(),
|
||||||
Key: controller.Key{
|
Key: controllerlib.Key{
|
||||||
Namespace: "kube-public",
|
Namespace: "kube-public",
|
||||||
Name: "cluster-info",
|
Name: "cluster-info",
|
||||||
},
|
},
|
||||||
@ -227,7 +227,7 @@ func TestSync(t *testing.T) {
|
|||||||
// Must start informers before calling TestRunSynchronously()
|
// Must start informers before calling TestRunSynchronously()
|
||||||
kubeInformers.Start(timeoutContext.Done())
|
kubeInformers.Start(timeoutContext.Done())
|
||||||
pinnipedInformers.Start(timeoutContext.Done())
|
pinnipedInformers.Start(timeoutContext.Done())
|
||||||
controller.TestRunSynchronously(t, subject)
|
controllerlib.TestRunSynchronously(t, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -274,7 +274,7 @@ func TestSync(t *testing.T) {
|
|||||||
when("the CredentialIssuerConfig does not already exist", func() {
|
when("the CredentialIssuerConfig does not already exist", func() {
|
||||||
it("creates a CredentialIssuerConfig", func() {
|
it("creates a CredentialIssuerConfig", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
||||||
@ -308,7 +308,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns the create error", func() {
|
it("returns the create error", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.EqualError(err, "could not create or update credentialissuerconfig: create failed: create failed")
|
r.EqualError(err, "could not create or update credentialissuerconfig: create failed: create failed")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -319,7 +319,7 @@ func TestSync(t *testing.T) {
|
|||||||
*serverOverride = "https://some-server-override"
|
*serverOverride = "https://some-server-override"
|
||||||
|
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
||||||
@ -357,7 +357,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("does not update the CredentialIssuerConfig to avoid unnecessary etcd writes/api calls", func() {
|
it("does not update the CredentialIssuerConfig to avoid unnecessary etcd writes/api calls", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
r.Empty(pinnipedAPIClient.Actions())
|
r.Empty(pinnipedAPIClient.Actions())
|
||||||
@ -378,7 +378,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("updates the existing CredentialIssuerConfig", func() {
|
it("updates the existing CredentialIssuerConfig", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
||||||
@ -409,7 +409,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns the update error", func() {
|
it("returns the update error", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.EqualError(err, "could not create or update credentialissuerconfig: update failed")
|
r.EqualError(err, "could not create or update credentialissuerconfig: update failed")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -431,7 +431,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("keeps waiting for it to exist", func() {
|
it("keeps waiting for it to exist", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
r.Empty(pinnipedAPIClient.Actions())
|
r.Empty(pinnipedAPIClient.Actions())
|
||||||
})
|
})
|
||||||
@ -451,7 +451,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("keeps waiting for it to be properly formatted", func() {
|
it("keeps waiting for it to be properly formatted", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
r.Empty(pinnipedAPIClient.Actions())
|
r.Empty(pinnipedAPIClient.Actions())
|
||||||
})
|
})
|
||||||
@ -472,7 +472,7 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("keeps waiting for one", func() {
|
it("keeps waiting for one", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controller.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
r.Empty(pinnipedAPIClient.Actions())
|
r.Empty(pinnipedAPIClient.Actions())
|
||||||
})
|
})
|
||||||
|
@ -8,14 +8,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NameAndNamespaceExactMatchFilterFactory(name, namespace string) controller.FilterFuncs {
|
func NameAndNamespaceExactMatchFilterFactory(name, namespace string) controllerlib.FilterFuncs {
|
||||||
objMatchesFunc := func(obj metav1.Object) bool {
|
objMatchesFunc := func(obj metav1.Object) bool {
|
||||||
return obj.GetName() == name && obj.GetNamespace() == namespace
|
return obj.GetName() == name && obj.GetNamespace() == namespace
|
||||||
}
|
}
|
||||||
return controller.FilterFuncs{
|
return controllerlib.FilterFuncs{
|
||||||
AddFunc: objMatchesFunc,
|
AddFunc: objMatchesFunc,
|
||||||
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||||
return objMatchesFunc(oldObj) || objMatchesFunc(newObj)
|
return objMatchesFunc(oldObj) || objMatchesFunc(newObj)
|
||||||
@ -24,11 +24,11 @@ func NameAndNamespaceExactMatchFilterFactory(name, namespace string) controller.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same signature as controller.WithInformer().
|
// Same signature as controllerlib.WithInformer().
|
||||||
type WithInformerOptionFunc func(
|
type WithInformerOptionFunc func(
|
||||||
getter controller.InformerGetter,
|
getter controllerlib.InformerGetter,
|
||||||
filter controller.Filter,
|
filter controllerlib.Filter,
|
||||||
opt controller.InformerOption) controller.Option
|
opt controllerlib.InformerOption) controllerlib.Option
|
||||||
|
|
||||||
// Same signature as controller.WithInitialEvent().
|
// Same signature as controllerlib.WithInitialEvent().
|
||||||
type WithInitialEventOptionFunc func(key controller.Key) controller.Option
|
type WithInitialEventOptionFunc func(key controllerlib.Key) controllerlib.Option
|
||||||
|
224
internal/controllerlib/controller.go
Normal file
224
internal/controllerlib/controller.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller interface represents a runnable Kubernetes controller.
|
||||||
|
// Cancelling the context passed will cause the controller to shutdown.
|
||||||
|
// Number of workers determine how much parallel the job processing should be.
|
||||||
|
type Controller interface {
|
||||||
|
// Run runs the controller and blocks until the controller is finished.
|
||||||
|
// Number of workers can be specified via workers parameter.
|
||||||
|
// This function will return when all internal loops are finished.
|
||||||
|
// Note that having more than one worker usually means handing parallelization of Sync().
|
||||||
|
Run(ctx context.Context, workers int)
|
||||||
|
|
||||||
|
// Name returns the controller name string.
|
||||||
|
Name() string
|
||||||
|
|
||||||
|
// The methods below should only be called during tests via the Test* functions.
|
||||||
|
|
||||||
|
// sync contains the main controller logic.
|
||||||
|
// This can be used in unit tests to exercise the Syncer by directly calling it.
|
||||||
|
sync(ctx Context) error
|
||||||
|
|
||||||
|
// wrap wraps the main controller logic provided via the Syncer.
|
||||||
|
// This can be used in tests to synchronize asynchronous events as seen by a running controller.
|
||||||
|
// The wrapping must be done after New is called and before Run is called.
|
||||||
|
wrap(wrapper SyncWrapperFunc)
|
||||||
|
|
||||||
|
// These are called by the Run() method but also need to be called by Test* functions sometimes.
|
||||||
|
waitForCacheSyncWithTimeout() bool
|
||||||
|
invokeAllRunOpts()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Controller = &controller{}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string
|
||||||
|
Syncer Syncer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config, opts ...Option) Controller {
|
||||||
|
c := &controller{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up defaults
|
||||||
|
WithRateLimiter(workqueue.DefaultControllerRateLimiter())(c)
|
||||||
|
WithRecorder(klogRecorder{})(c)
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
config Config
|
||||||
|
|
||||||
|
queue workqueue.RateLimitingInterface
|
||||||
|
queueWrapper Queue
|
||||||
|
maxRetries int
|
||||||
|
recorder events.EventRecorder
|
||||||
|
|
||||||
|
run bool
|
||||||
|
runOpts []Option
|
||||||
|
|
||||||
|
cacheSyncs []cache.InformerSynced
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Run(ctx context.Context, workers int) {
|
||||||
|
defer utilruntime.HandleCrash(crash) // prevent panics from killing the process
|
||||||
|
|
||||||
|
klog.InfoS("starting controller", "controller", c.Name(), "workers", workers)
|
||||||
|
|
||||||
|
c.invokeAllRunOpts()
|
||||||
|
|
||||||
|
if !c.waitForCacheSyncWithTimeout() {
|
||||||
|
panic(die(fmt.Sprintf("%s: timed out waiting for caches to sync", c.Name())))
|
||||||
|
}
|
||||||
|
|
||||||
|
var workerWg sync.WaitGroup
|
||||||
|
|
||||||
|
// workerContext is used to track and initiate worker shutdown
|
||||||
|
workerContext, workerContextCancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.queue.ShutDown() // shutdown the controller queue first
|
||||||
|
workerContextCancel() // cancel the worker context, which tell workers to initiate shutdown
|
||||||
|
|
||||||
|
// Wait for all workers to finish their job.
|
||||||
|
// at this point the Run() can hang and callers have to implement the logic that will kill
|
||||||
|
// this controller (SIGKILL).
|
||||||
|
workerWg.Wait()
|
||||||
|
klog.InfoS("all workers have been terminated, shutting down", "controller", c.Name(), "workers", workers)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 1; i <= workers; i++ {
|
||||||
|
idx := i
|
||||||
|
klog.InfoS("starting worker", "controller", c.Name(), "worker", idx)
|
||||||
|
workerWg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer utilruntime.HandleCrash(crash) // prevent panics from killing the process
|
||||||
|
defer func() {
|
||||||
|
klog.InfoS("shutting down worker", "controller", c.Name(), "worker", idx)
|
||||||
|
workerWg.Done()
|
||||||
|
}()
|
||||||
|
c.runWorker(workerContext)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ctx.Done() // wait for controller context to be cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) invokeAllRunOpts() {
|
||||||
|
c.run = true
|
||||||
|
for _, opt := range c.runOpts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Name() string {
|
||||||
|
return c.config.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) sync(ctx Context) error {
|
||||||
|
return c.config.Syncer.Sync(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) wrap(wrapper SyncWrapperFunc) {
|
||||||
|
c.runOpts = append(c.runOpts, toRunOpt(func(c *controller) {
|
||||||
|
c.config.Syncer = wrapper(c.config.Syncer)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) waitForCacheSyncWithTimeout() bool {
|
||||||
|
// prevent us from blocking forever due to a broken informer
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return cache.WaitForCacheSync(ctx.Done(), c.cacheSyncs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) add(filter Filter, object metav1.Object) {
|
||||||
|
key := filter.Parent(object)
|
||||||
|
c.queueWrapper.Add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runWorker runs a single worker
|
||||||
|
// The worker is asked to terminate when the passed context is cancelled.
|
||||||
|
func (c *controller) runWorker(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
c.processNextWorkItem(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) processNextWorkItem(ctx context.Context) {
|
||||||
|
queueKey, quit := c.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := queueKey.(Key)
|
||||||
|
defer c.queue.Done(key)
|
||||||
|
|
||||||
|
syncCtx := Context{
|
||||||
|
Context: ctx,
|
||||||
|
Name: c.Name(),
|
||||||
|
Key: key,
|
||||||
|
Queue: c.queueWrapper,
|
||||||
|
Recorder: c.recorder,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.sync(syncCtx)
|
||||||
|
c.handleKey(key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) handleKey(key Key, err error) {
|
||||||
|
if err == nil {
|
||||||
|
c.queue.Forget(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retryForever := c.maxRetries <= 0
|
||||||
|
shouldRetry := retryForever || c.queue.NumRequeues(key) < c.maxRetries
|
||||||
|
|
||||||
|
if !shouldRetry {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("%s: dropping key %v out of the queue: %w", c.Name(), key, err))
|
||||||
|
c.queue.Forget(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrSyntheticRequeue) {
|
||||||
|
// logging this helps detecting wedged controllers with missing pre-requirements
|
||||||
|
klog.V(4).InfoS("requested synthetic requeue", "controller", c.Name(), "key", key)
|
||||||
|
} else {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("%s: %v failed with: %w", c.Name(), key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.queue.AddRateLimited(key)
|
||||||
|
}
|
15
internal/controllerlib/die.go
Normal file
15
internal/controllerlib/die.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
type die string
|
||||||
|
|
||||||
|
func crash(i interface{}) {
|
||||||
|
mustDie, ok := i.(die)
|
||||||
|
if ok {
|
||||||
|
panic(string(mustDie))
|
||||||
|
}
|
||||||
|
}
|
18
internal/controllerlib/error.go
Normal file
18
internal/controllerlib/error.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
// ErrSyntheticRequeue can be returned from a Syncer to force a retry artificially for the current key.
|
||||||
|
// This can also be done by re-adding the key to queue, but this is more convenient and has better logging.
|
||||||
|
const ErrSyntheticRequeue = constErr("synthetic requeue request")
|
||||||
|
|
||||||
|
var _ error = constErr("")
|
||||||
|
|
||||||
|
type constErr string
|
||||||
|
|
||||||
|
func (e constErr) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
76
internal/controllerlib/filter.go
Normal file
76
internal/controllerlib/filter.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Filter interface {
|
||||||
|
Add(obj metav1.Object) bool
|
||||||
|
Update(oldObj, newObj metav1.Object) bool
|
||||||
|
Delete(obj metav1.Object) bool
|
||||||
|
|
||||||
|
Parent(obj metav1.Object) Key
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Filter = FilterFuncs{}
|
||||||
|
|
||||||
|
type ParentFunc func(obj metav1.Object) Key
|
||||||
|
|
||||||
|
type FilterFuncs struct {
|
||||||
|
ParentFunc ParentFunc
|
||||||
|
AddFunc func(obj metav1.Object) bool
|
||||||
|
UpdateFunc func(oldObj, newObj metav1.Object) bool
|
||||||
|
DeleteFunc func(obj metav1.Object) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FilterFuncs) Parent(obj metav1.Object) Key {
|
||||||
|
if f.ParentFunc == nil {
|
||||||
|
return Key{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: obj.GetName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.ParentFunc(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FilterFuncs) Add(obj metav1.Object) bool {
|
||||||
|
if f.AddFunc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.AddFunc(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FilterFuncs) Update(oldObj, newObj metav1.Object) bool {
|
||||||
|
if f.UpdateFunc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.UpdateFunc(oldObj, newObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FilterFuncs) Delete(obj metav1.Object) bool {
|
||||||
|
if f.DeleteFunc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.DeleteFunc(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterByNames(parentFunc ParentFunc, names ...string) Filter {
|
||||||
|
set := sets.NewString(names...)
|
||||||
|
has := func(obj metav1.Object) bool {
|
||||||
|
return set.Has(obj.GetName())
|
||||||
|
}
|
||||||
|
return FilterFuncs{
|
||||||
|
ParentFunc: parentFunc,
|
||||||
|
AddFunc: has,
|
||||||
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||||
|
return has(newObj)
|
||||||
|
},
|
||||||
|
DeleteFunc: has,
|
||||||
|
}
|
||||||
|
}
|
25
internal/controllerlib/informer.go
Normal file
25
internal/controllerlib/informer.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import "k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
|
type InformerGetter interface {
|
||||||
|
Informer() cache.SharedIndexInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
type InformerOption struct {
|
||||||
|
SkipSync bool
|
||||||
|
SkipEvents bool
|
||||||
|
|
||||||
|
// TODO maybe add a field like:
|
||||||
|
// ResyncPeriod time.Duration
|
||||||
|
// to support using AddEventHandlerWithResyncPeriod
|
||||||
|
// this field would be mutually exclusive with SkipEvents
|
||||||
|
// I suspect we do not need this level of flexibility and resyncs can mask bugs in controller logic
|
||||||
|
// A related change could be an Option such as WithResyncSchedule to allow for cron style control loops
|
||||||
|
// It is unclear to me if we would ever need that since we assume that all events come from a Kube watch
|
||||||
|
}
|
59
internal/controllerlib/manager.go
Normal file
59
internal/controllerlib/manager.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
Start(ctx context.Context)
|
||||||
|
WithController(controller Controller, workers int) Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() Manager {
|
||||||
|
return &controllerManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runnableController represents single controller runnable configuration.
|
||||||
|
type runnableController struct {
|
||||||
|
controller Controller
|
||||||
|
workers int
|
||||||
|
}
|
||||||
|
|
||||||
|
type controllerManager struct {
|
||||||
|
controllers []runnableController
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &controllerManager{}
|
||||||
|
|
||||||
|
func (c *controllerManager) WithController(controller Controller, workers int) Manager {
|
||||||
|
c.controllers = append(c.controllers, runnableController{
|
||||||
|
controller: controller,
|
||||||
|
workers: workers,
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start will run all managed controllers and block until all controllers shutdown.
|
||||||
|
// When the context passed is cancelled, all controllers are signalled to shutdown.
|
||||||
|
func (c *controllerManager) Start(ctx context.Context) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(c.controllers))
|
||||||
|
for i := range c.controllers {
|
||||||
|
idx := i
|
||||||
|
go func() {
|
||||||
|
r := c.controllers[idx]
|
||||||
|
defer klog.InfoS("controller terminated", "controller", r.controller.Name())
|
||||||
|
defer wg.Done()
|
||||||
|
r.controller.Run(ctx, r.workers)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
154
internal/controllerlib/option.go
Normal file
154
internal/controllerlib/option.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(*controller)
|
||||||
|
|
||||||
|
func WithMaxRetries(maxRetries int) Option {
|
||||||
|
return func(c *controller) {
|
||||||
|
c.maxRetries = maxRetries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInitialEvent(key Key) Option {
|
||||||
|
return toNaiveRunOpt(func(c *controller) {
|
||||||
|
c.queueWrapper.Add(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRateLimiter(limiter workqueue.RateLimiter) Option {
|
||||||
|
return func(c *controller) {
|
||||||
|
c.queue = workqueue.NewNamedRateLimitingQueue(limiter, c.Name())
|
||||||
|
c.queueWrapper = &queueWrapper{queue: c.queue}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRecorder(recorder events.EventRecorder) Option {
|
||||||
|
return func(c *controller) {
|
||||||
|
c.recorder = recorder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInformer(getter InformerGetter, filter Filter, opt InformerOption) Option {
|
||||||
|
informer := getter.Informer() // immediately signal that we intend to use this informer in case it is lazily initialized
|
||||||
|
return toRunOpt(func(c *controller) {
|
||||||
|
if opt.SkipSync && opt.SkipEvents {
|
||||||
|
panic(die("cannot skip syncing and event handlers at the same time"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opt.SkipSync {
|
||||||
|
c.cacheSyncs = append(c.cacheSyncs, informer.HasSynced)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.SkipEvents {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
object := metaOrDie(obj)
|
||||||
|
if filter.Add(object) {
|
||||||
|
klog.V(4).InfoS("handling add",
|
||||||
|
"controller", c.Name(),
|
||||||
|
"namespace", object.GetNamespace(),
|
||||||
|
"name", object.GetName(),
|
||||||
|
"selfLink", object.GetSelfLink(), // TODO: self link is deprecated so we need to extract the GVR in some other way (using a series of schemes?)
|
||||||
|
"kind", fmt.Sprintf("%T", object),
|
||||||
|
)
|
||||||
|
c.add(filter, object)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||||
|
oldObject := metaOrDie(oldObj)
|
||||||
|
newObject := metaOrDie(newObj)
|
||||||
|
if filter.Update(oldObject, newObject) {
|
||||||
|
klog.V(4).InfoS("handling update",
|
||||||
|
"controller", c.Name(),
|
||||||
|
"namespace", newObject.GetNamespace(),
|
||||||
|
"name", newObject.GetName(),
|
||||||
|
"selfLink", newObject.GetSelfLink(), // TODO: self link is deprecated so we need to extract the GVR in some other way (using a series of schemes?)
|
||||||
|
"kind", fmt.Sprintf("%T", newObject),
|
||||||
|
)
|
||||||
|
c.add(filter, newObject)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||||
|
if !ok {
|
||||||
|
//nolint: goerr113
|
||||||
|
utilruntime.HandleError(fmt.Errorf("%s: could not get object from tombstone: %+v", c.Name(), obj))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accessor, err = meta.Accessor(tombstone.Obj)
|
||||||
|
if err != nil {
|
||||||
|
//nolint: goerr113
|
||||||
|
utilruntime.HandleError(fmt.Errorf("%s: tombstone contained object that is not an accessor: %+v", c.Name(), obj))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filter.Delete(accessor) {
|
||||||
|
klog.V(4).InfoS("handling delete",
|
||||||
|
"controller", c.Name(),
|
||||||
|
"namespace", accessor.GetNamespace(),
|
||||||
|
"name", accessor.GetName(),
|
||||||
|
"selfLink", accessor.GetSelfLink(), // TODO: self link is deprecated so we need to extract the GVR in some other way (using a series of schemes?)
|
||||||
|
"kind", fmt.Sprintf("%T", accessor),
|
||||||
|
)
|
||||||
|
c.add(filter, accessor)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// toRunOpt guarantees that an Option only runs once on the first call to Run (and not New), even if a controller is stopped and restarted.
|
||||||
|
func toRunOpt(opt Option) Option {
|
||||||
|
return toOnceOpt(toNaiveRunOpt(opt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// toNaiveRunOpt guarantees that an Option only runs on calls to Run (and not New), even if a controller is stopped and restarted.
|
||||||
|
func toNaiveRunOpt(opt Option) Option {
|
||||||
|
return func(c *controller) {
|
||||||
|
if c.run {
|
||||||
|
opt(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.runOpts = append(c.runOpts, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toOnceOpt guarantees that an Option only runs once.
|
||||||
|
func toOnceOpt(opt Option) Option {
|
||||||
|
var once sync.Once
|
||||||
|
return func(c *controller) {
|
||||||
|
once.Do(func() {
|
||||||
|
opt(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaOrDie(obj interface{}) metav1.Object {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // this should never happen
|
||||||
|
}
|
||||||
|
return accessor
|
||||||
|
}
|
27
internal/controllerlib/option_test.go
Normal file
27
internal/controllerlib/option_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getter bool
|
||||||
|
|
||||||
|
func (g *getter) Informer() cache.SharedIndexInformer {
|
||||||
|
*g = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInformerCalled(t *testing.T) {
|
||||||
|
g := getter(false)
|
||||||
|
_ = New(Config{}, WithInformer(&g, FilterByNames(nil), InformerOption{}))
|
||||||
|
if !g {
|
||||||
|
t.Error("expected InformerGetter.Informer() to be called")
|
||||||
|
}
|
||||||
|
}
|
41
internal/controllerlib/queue.go
Normal file
41
internal/controllerlib/queue.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Queue interface {
|
||||||
|
// Add immediately adds a key to the queue and marks it as needing processing.
|
||||||
|
Add(key Key)
|
||||||
|
|
||||||
|
// AddRateLimited adds a key to the queue after the rate limiter says it is ok.
|
||||||
|
AddRateLimited(key Key)
|
||||||
|
|
||||||
|
// AddAfter adds a key to the queue after the indicated duration has passed.
|
||||||
|
AddAfter(key Key, duration time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Queue = &queueWrapper{}
|
||||||
|
|
||||||
|
type queueWrapper struct {
|
||||||
|
queue workqueue.RateLimitingInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueWrapper) Add(key Key) {
|
||||||
|
q.queue.Add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueWrapper) AddRateLimited(key Key) {
|
||||||
|
q.queue.AddRateLimited(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueWrapper) AddAfter(key Key, duration time.Duration) {
|
||||||
|
q.queue.AddAfter(key, duration)
|
||||||
|
}
|
29
internal/controllerlib/recorder.go
Normal file
29
internal/controllerlib/recorder.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ events.EventRecorder = klogRecorder{}
|
||||||
|
|
||||||
|
type klogRecorder struct{}
|
||||||
|
|
||||||
|
func (n klogRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||||
|
klog.V(4).InfoS("recording event",
|
||||||
|
"regarding", regarding,
|
||||||
|
"related", related,
|
||||||
|
"eventtype", eventtype,
|
||||||
|
"reason", reason,
|
||||||
|
"action", action,
|
||||||
|
"message", fmt.Sprintf(note, args...),
|
||||||
|
)
|
||||||
|
}
|
44
internal/controllerlib/sync.go
Normal file
44
internal/controllerlib/sync.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Syncer = SyncFunc(nil)
|
||||||
|
|
||||||
|
type Syncer interface {
|
||||||
|
Sync(ctx Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncFunc func(ctx Context) error
|
||||||
|
|
||||||
|
func (s SyncFunc) Sync(ctx Context) error {
|
||||||
|
return s(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Context context.Context
|
||||||
|
Name string
|
||||||
|
Key Key
|
||||||
|
Queue Queue
|
||||||
|
Recorder events.EventRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Namespace string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// TODO determine if it makes sense to add a field like:
|
||||||
|
// Extra interface{}
|
||||||
|
// This would allow a custom ParentFunc to pass extra data through to the Syncer
|
||||||
|
// The boxed type would have to be comparable (i.e. usable as a map key)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncWrapperFunc func(syncer Syncer) Syncer
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
// Annotation on service.
|
||||||
|
const SecretNameAnnotation = "service.placeholder.io/secret-name"
|
||||||
|
|
||||||
|
// Annotations on secret.
|
||||||
|
const (
|
||||||
|
// ServiceUIDAnnotation is an annotation on a secret that indicates which service created it, by UID
|
||||||
|
ServiceUIDAnnotation = "service.placeholder.io/service-uid"
|
||||||
|
// ServiceNameAnnotation is an annotation on a secret that indicates which service created it, by Name
|
||||||
|
// to allow reverse lookups on services for comparison against UIDs
|
||||||
|
ServiceNameAnnotation = "service.placeholder.io/service-name"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SecretDataKey = "secret-data"
|
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
|
func NewExampleCreatingController(
|
||||||
|
services corev1informers.ServiceInformer,
|
||||||
|
secrets corev1informers.SecretInformer,
|
||||||
|
secretClient corev1client.SecretsGetter,
|
||||||
|
recorder events.EventRecorder,
|
||||||
|
secretData string,
|
||||||
|
) controllerlib.Controller {
|
||||||
|
serviceLister := services.Lister()
|
||||||
|
secretLister := secrets.Lister()
|
||||||
|
|
||||||
|
// note that these functions do not need to be inlined
|
||||||
|
// this just demonstrates that for simple Syncer implementations, everything can be in one place
|
||||||
|
|
||||||
|
requiresSecretGeneration := func(service *corev1.Service) (bool, error) {
|
||||||
|
// check the secret since it could not have been created yet
|
||||||
|
secretName := service.Annotations[api.SecretNameAnnotation]
|
||||||
|
if len(secretName) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := secretLister.Secrets(service.Namespace).Get(secretName)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
// we have not created the secret yet
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to get the secret %s/%s: %w", service.Namespace, secretName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(secret.Data[api.SecretDataKey]) == secretData {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// the secret exists but the data does not match what we expect (i.e. we have new secretData now)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSecret := func(service *corev1.Service) error {
|
||||||
|
klog.V(4).InfoS("generating new secret for service", "namespace", service.Namespace, "name", service.Name)
|
||||||
|
|
||||||
|
secret := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: service.Annotations[api.SecretNameAnnotation],
|
||||||
|
Namespace: service.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.ServiceUIDAnnotation: string(service.UID),
|
||||||
|
api.ServiceNameAnnotation: service.Name,
|
||||||
|
},
|
||||||
|
OwnerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Service",
|
||||||
|
Name: service.Name,
|
||||||
|
UID: service.UID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Finalizers: nil, // TODO maybe add finalizer to guarantee we never miss a delete event?
|
||||||
|
},
|
||||||
|
Type: corev1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
api.SecretDataKey: []byte(secretData),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := secretClient.Secrets(service.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
|
||||||
|
if apierrors.IsAlreadyExists(err) {
|
||||||
|
actualSecret, getErr := secretClient.Secrets(service.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
|
||||||
|
if getErr != nil {
|
||||||
|
return getErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualSecret.Annotations[api.ServiceUIDAnnotation] != string(service.UID) {
|
||||||
|
//nolint: goerr113
|
||||||
|
utilruntime.HandleError(fmt.Errorf("secret %s/%s does not have corresponding service UID %v", actualSecret.Namespace, actualSecret.Name, service.UID))
|
||||||
|
return nil // drop from queue because we cannot safely update this secret
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(4).InfoS("updating data in existing secret", "namespace", secret.Namespace, "name", secret.Name)
|
||||||
|
// Actually update the secret in the regeneration case (the secret already exists but we want to update to new secretData).
|
||||||
|
_, updateErr := secretClient.Secrets(secret.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create secret %s/%s: %w", secret.Namespace, secret.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syncer := controllerlib.SyncFunc(func(ctx controllerlib.Context) error {
|
||||||
|
service, err := serviceLister.Services(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get the service %s/%s: %w", service.Namespace, service.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := requiresSecretGeneration(service)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateSecret(service)
|
||||||
|
})
|
||||||
|
|
||||||
|
config := controllerlib.Config{
|
||||||
|
Name: "example-controller-creating",
|
||||||
|
Syncer: syncer,
|
||||||
|
}
|
||||||
|
|
||||||
|
toServiceName := func(secret *corev1.Secret) (string, bool) {
|
||||||
|
serviceName := secret.Annotations[api.ServiceNameAnnotation]
|
||||||
|
return serviceName, len(serviceName) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSecretNameAnnotation := func(obj metav1.Object) bool {
|
||||||
|
return len(obj.GetAnnotations()[api.SecretNameAnnotation]) != 0
|
||||||
|
}
|
||||||
|
hasSecretNameAnnotationUpdate := func(oldObj, newObj metav1.Object) bool {
|
||||||
|
return hasSecretNameAnnotation(newObj) || hasSecretNameAnnotation(oldObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return controllerlib.New(config,
|
||||||
|
controllerlib.WithInformer(services, controllerlib.FilterFuncs{
|
||||||
|
AddFunc: hasSecretNameAnnotation,
|
||||||
|
UpdateFunc: hasSecretNameAnnotationUpdate,
|
||||||
|
}, controllerlib.InformerOption{}),
|
||||||
|
|
||||||
|
controllerlib.WithInformer(secrets, controllerlib.FilterFuncs{
|
||||||
|
ParentFunc: func(obj metav1.Object) controllerlib.Key {
|
||||||
|
secret := obj.(*corev1.Secret)
|
||||||
|
serviceName, _ := toServiceName(secret)
|
||||||
|
return controllerlib.Key{Namespace: secret.Namespace, Name: serviceName}
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj metav1.Object) bool {
|
||||||
|
secret := obj.(*corev1.Secret)
|
||||||
|
serviceName, ok := toServiceName(secret)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
service, err := serviceLister.Services(secret.Namespace).Get(serviceName)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("unable to get service %s/%s: %w", secret.Namespace, serviceName, err))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
klog.V(4).InfoS("recreating secret", "namespace", service.Namespace, "name", service.Name)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}, controllerlib.InformerOption{}),
|
||||||
|
|
||||||
|
controllerlib.WithRecorder(recorder), // TODO actually use the recorder
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
coretesting "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewExampleCreatingController(t *testing.T) {
|
||||||
|
secretsGVR := schema.GroupVersionResource{Version: "v1", Resource: "secrets"}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
services []*corev1.Service
|
||||||
|
secrets []*corev1.Secret
|
||||||
|
secretData string
|
||||||
|
}
|
||||||
|
type keyErr struct {
|
||||||
|
key controllerlib.Key
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantActions []coretesting.Action
|
||||||
|
wantKeyErrs []keyErr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "service has annotation but secret does not exist",
|
||||||
|
args: args{
|
||||||
|
services: []*corev1.Service{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "ns-1",
|
||||||
|
Name: "service-1",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.SecretNameAnnotation: "secret-1",
|
||||||
|
},
|
||||||
|
UID: "0001",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secretData: "foo-secret-1",
|
||||||
|
},
|
||||||
|
wantKeyErrs: []keyErr{
|
||||||
|
{
|
||||||
|
key: controllerlib.Key{
|
||||||
|
Namespace: "ns-1",
|
||||||
|
Name: "service-1",
|
||||||
|
},
|
||||||
|
err: nil, // we expect no error with this key
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantActions: []coretesting.Action{
|
||||||
|
coretesting.NewCreateAction(secretsGVR, "ns-1", &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "secret-1",
|
||||||
|
Namespace: "ns-1",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.ServiceUIDAnnotation: "0001",
|
||||||
|
api.ServiceNameAnnotation: "service-1",
|
||||||
|
},
|
||||||
|
OwnerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Service",
|
||||||
|
Name: "service-1",
|
||||||
|
UID: "0001",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: corev1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
api.SecretDataKey: []byte("foo-secret-1"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
kubeClient := fake.NewSimpleClientset()
|
||||||
|
for i := range tt.args.services {
|
||||||
|
service := tt.args.services[i]
|
||||||
|
err := kubeClient.Tracker().Add(service)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
for i := range tt.args.secrets {
|
||||||
|
secret := tt.args.secrets[i]
|
||||||
|
err := kubeClient.Tracker().Add(secret)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder := events.NewEventBroadcasterAdapter(kubeClient).NewRecorder("example-controller")
|
||||||
|
kubeInformers := informers.NewSharedInformerFactory(kubeClient, 0)
|
||||||
|
|
||||||
|
creatingController := NewExampleCreatingController(
|
||||||
|
kubeInformers.Core().V1().Services(),
|
||||||
|
kubeInformers.Core().V1().Secrets(),
|
||||||
|
kubeClient.CoreV1(),
|
||||||
|
recorder,
|
||||||
|
tt.args.secretData,
|
||||||
|
)
|
||||||
|
|
||||||
|
keyErrs := make(chan keyErr)
|
||||||
|
controllerlib.TestWrap(t, creatingController, func(syncer controllerlib.Syncer) controllerlib.Syncer {
|
||||||
|
return controllerlib.SyncFunc(func(ctx controllerlib.Context) error {
|
||||||
|
err := syncer.Sync(ctx)
|
||||||
|
|
||||||
|
keyErrs <- keyErr{
|
||||||
|
key: ctx.Key,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// a different approach would be to use TestSync and run each iteration manually:
|
||||||
|
//
|
||||||
|
// err := controller.TestSync(t, c, ...)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeInformers.Start(ctx.Done())
|
||||||
|
go creatingController.Run(ctx, 5) // TODO maybe only use one worker?
|
||||||
|
|
||||||
|
var actualKeyErrs []keyErr
|
||||||
|
done:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case key := <-keyErrs:
|
||||||
|
actualKeyErrs = append(actualKeyErrs, key)
|
||||||
|
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
// this assumes that calls to Sync are never more than three seconds apart
|
||||||
|
// we have five workers so there is little chance they all hang around doing nothing for that long
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Figure out how to capture actions from informers
|
||||||
|
// TODO: I think we need some more fancy order independent equal comparison here
|
||||||
|
|
||||||
|
require.Equal(t, tt.wantKeyErrs, actualKeyErrs)
|
||||||
|
|
||||||
|
// ignore the discovery call from the event recorder and the list/watch from both informers (first five events)
|
||||||
|
require.Equal(t, tt.wantActions, kubeClient.Actions()[5:])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewExampleUpdatingController(
|
||||||
|
services corev1informers.ServiceInformer,
|
||||||
|
secrets corev1informers.SecretInformer,
|
||||||
|
secretClient corev1client.SecretsGetter,
|
||||||
|
recorder events.EventRecorder,
|
||||||
|
secretData string,
|
||||||
|
) controllerlib.Controller {
|
||||||
|
serviceLister := services.Lister()
|
||||||
|
secretLister := secrets.Lister()
|
||||||
|
|
||||||
|
// note that these functions do not need to be inlined
|
||||||
|
// this just demonstrates that for simple Syncer implementations, everything can be in one place
|
||||||
|
|
||||||
|
toServiceName := func(secret *corev1.Secret) (string, bool) {
|
||||||
|
serviceName := secret.Annotations[api.ServiceNameAnnotation]
|
||||||
|
return serviceName, len(serviceName) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureSecretData := func(service *corev1.Service, secretCopy *corev1.Secret) bool {
|
||||||
|
var needsUpdate bool
|
||||||
|
|
||||||
|
expectedData := map[string][]byte{
|
||||||
|
api.SecretDataKey: []byte(secretData),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(secretCopy.Data, expectedData) {
|
||||||
|
secretCopy.Data = expectedData
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedOwnerReferences := []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Service",
|
||||||
|
Name: service.Name,
|
||||||
|
UID: service.UID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(secretCopy.OwnerReferences, expectedOwnerReferences) {
|
||||||
|
secretCopy.OwnerReferences = expectedOwnerReferences
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
isSecretValidForService := func(service *corev1.Service, secret *corev1.Secret) bool {
|
||||||
|
if service.Annotations[api.SecretNameAnnotation] != secret.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if secret.Annotations[api.ServiceUIDAnnotation] != string(service.UID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
getServiceForSecret := func(secret *corev1.Secret) (*corev1.Service, error) {
|
||||||
|
serviceName, ok := toServiceName(secret)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
service, err := serviceLister.Services(secret.Namespace).Get(serviceName)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get service %s/%s: %w", secret.Namespace, serviceName, err)
|
||||||
|
}
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syncer := controllerlib.SyncFunc(func(ctx controllerlib.Context) error {
|
||||||
|
secret, err := secretLister.Secrets(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get the secret %s/%s: %w", secret.Namespace, secret.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := getServiceForSecret(secret)
|
||||||
|
if err != nil || service == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isSecretValidForService(service, secret) {
|
||||||
|
//nolint: goerr113
|
||||||
|
utilruntime.HandleError(fmt.Errorf("secret %s/%s does not have corresponding service UID %v", secret.Namespace, secret.Name, service.UID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a copy to avoid mutating cache state
|
||||||
|
secretCopy := secret.DeepCopy()
|
||||||
|
|
||||||
|
if needsUpdate := ensureSecretData(service, secretCopy); needsUpdate {
|
||||||
|
_, updateErr := secretClient.Secrets(secretCopy.Namespace).Update(context.TODO(), secretCopy, metav1.UpdateOptions{})
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
config := controllerlib.Config{
|
||||||
|
Name: "example-controller-updating",
|
||||||
|
Syncer: syncer,
|
||||||
|
}
|
||||||
|
|
||||||
|
addSecret := func(obj metav1.Object) bool {
|
||||||
|
secret := obj.(*corev1.Secret)
|
||||||
|
_, ok := toServiceName(secret)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
return controllerlib.New(config,
|
||||||
|
controllerlib.WithInformer(services, controllerlib.FilterFuncs{}, controllerlib.InformerOption{SkipEvents: true}),
|
||||||
|
|
||||||
|
controllerlib.WithInformer(secrets, controllerlib.FilterFuncs{
|
||||||
|
AddFunc: addSecret,
|
||||||
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||||
|
return addSecret(newObj) || addSecret(oldObj)
|
||||||
|
},
|
||||||
|
}, controllerlib.InformerOption{}),
|
||||||
|
|
||||||
|
controllerlib.WithRecorder(recorder), // TODO actually use the recorder
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package starter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
examplecontroller "github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartExampleController(ctx context.Context, config *rest.Config, secretData string) error {
|
||||||
|
kubeClient, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeInformers := informers.NewSharedInformerFactory(kubeClient, 20*time.Minute)
|
||||||
|
|
||||||
|
recorder := events.NewEventBroadcasterAdapter(kubeClient).NewRecorder("example-controller")
|
||||||
|
|
||||||
|
manager := controllerlib.NewManager().
|
||||||
|
WithController(
|
||||||
|
examplecontroller.NewExampleCreatingController(
|
||||||
|
kubeInformers.Core().V1().Services(),
|
||||||
|
kubeInformers.Core().V1().Secrets(),
|
||||||
|
kubeClient.CoreV1(),
|
||||||
|
recorder,
|
||||||
|
secretData,
|
||||||
|
), 5,
|
||||||
|
).
|
||||||
|
WithController(
|
||||||
|
examplecontroller.NewExampleUpdatingController(
|
||||||
|
kubeInformers.Core().V1().Services(),
|
||||||
|
kubeInformers.Core().V1().Secrets(),
|
||||||
|
kubeClient.CoreV1(),
|
||||||
|
recorder,
|
||||||
|
secretData,
|
||||||
|
), 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
kubeInformers.Start(ctx.Done())
|
||||||
|
go manager.Start(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/api"
|
||||||
|
examplestart "github.com/suzerain-io/pinniped/internal/controllerlib/test/integration/examplecontroller/starter"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib/test/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExampleController(t *testing.T) {
|
||||||
|
config := library.NewClientConfig(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
secretData := "super-secret-data-1"
|
||||||
|
|
||||||
|
err := examplestart.StartExampleController(ctx, config, secretData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client := library.NewClientset(t)
|
||||||
|
|
||||||
|
namespaces := client.CoreV1().Namespaces()
|
||||||
|
|
||||||
|
namespace, err := namespaces.Create(ctx, &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "example-controller-test-",
|
||||||
|
},
|
||||||
|
}, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
deleteErr := namespaces.Delete(context.Background(), namespace.Name, metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, deleteErr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
services := client.CoreV1().Services(namespace.Name)
|
||||||
|
secrets := client.CoreV1().Secrets(namespace.Name)
|
||||||
|
|
||||||
|
secretsWatch, err := secrets.Watch(context.Background(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer secretsWatch.Stop()
|
||||||
|
|
||||||
|
service := &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "example-service-test",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.SecretNameAnnotation: "example-secret-name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = services.Create(ctx, service, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
timeout := time.After(10 * time.Second)
|
||||||
|
done:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-secretsWatch.ResultChan():
|
||||||
|
if event.Type != watch.Added {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secret, ok := event.Object.(*corev1.Secret)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if secret.Name != service.Annotations[api.SecretNameAnnotation] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedData := map[string][]byte{
|
||||||
|
api.SecretDataKey: []byte(secretData),
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedData, secret.Data, "expected to see new secret data: %s", library.Sdump(secret))
|
||||||
|
break done // immediately stop consuming events because we want to check for updated events below
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatal("timed out waiting to see new secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown the controllers so we can change the secret data
|
||||||
|
cancel()
|
||||||
|
time.Sleep(5 * time.Second) // wait a bit for the controllers to shut down
|
||||||
|
|
||||||
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
secretData2 := "super-secret-data-2"
|
||||||
|
|
||||||
|
err = examplestart.StartExampleController(ctx, config, secretData2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
timeout = time.After(10 * time.Second)
|
||||||
|
done2:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-secretsWatch.ResultChan():
|
||||||
|
if event.Type != watch.Modified {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secret, ok := event.Object.(*corev1.Secret)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if secret.Name != service.Annotations[api.SecretNameAnnotation] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedData := map[string][]byte{
|
||||||
|
api.SecretDataKey: []byte(secretData2),
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedData, secret.Data, "expected to see updated secret data: %s", library.Sdump(secret))
|
||||||
|
break done2 // immediately stop consuming events because we want to check for hot loops below
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatal("timed out waiting to see updated secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = time.After(5 * time.Second)
|
||||||
|
done3:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-secretsWatch.ResultChan():
|
||||||
|
secret, ok := event.Object.(*corev1.Secret)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if secret.Name != service.Annotations[api.SecretNameAnnotation] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// this assumes that no other actor in the system is trying to mutate this secret
|
||||||
|
t.Errorf("unexpected event seen for secret: %s", library.Sdump(event))
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
break done3 // we saw no events matching our secret meaning that we are not hot looping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
internal/controllerlib/test/integration/main_test.go
Normal file
32
internal/controllerlib/test/integration/main_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// force users to opt-in to running the integration tests.
|
||||||
|
// this prevents them from running if someone does `go test ./...`
|
||||||
|
// these tests could be destructive to the cluster under test.
|
||||||
|
const magicIntegrationTestsEnvVar = "CONTROLLER_GO_TEST_INTEGRATION"
|
||||||
|
|
||||||
|
var shouldRunIntegrationTests = func() bool {
|
||||||
|
b, _ := strconv.ParseBool(os.Getenv(magicIntegrationTestsEnvVar))
|
||||||
|
return b
|
||||||
|
}()
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
if !shouldRunIntegrationTests {
|
||||||
|
fmt.Printf("SKIP: %s=true env var must be explicitly set for integration tests to run\n", magicIntegrationTestsEnvVar)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
57
internal/controllerlib/test/library/client.go
Normal file
57
internal/controllerlib/test/library/client.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package library
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewClientConfig(t *testing.T) *rest.Config {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return newClientConfigWithOverrides(t, &clientcmd.ConfigOverrides{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientConfigWithCertAndKey(t *testing.T, cert, key string) *rest.Config {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return newClientConfigWithOverrides(t, &clientcmd.ConfigOverrides{
|
||||||
|
AuthInfo: clientcmdapi.AuthInfo{
|
||||||
|
ClientCertificateData: []byte(cert),
|
||||||
|
ClientKeyData: []byte(key),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientConfigWithOverrides(t *testing.T, overrides *clientcmd.ConfigOverrides) *rest.Config {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
loader := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
|
||||||
|
config, err := clientConfig.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientset(t *testing.T) kubernetes.Interface {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return NewClientsetWithConfig(t, NewClientConfig(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientsetWithConfig(t *testing.T, config *rest.Config) kubernetes.Interface {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, err := kubernetes.NewForConfig(config)
|
||||||
|
require.NoError(t, err, "unexpected failure from kubernetes.NewForConfig()")
|
||||||
|
return result
|
||||||
|
}
|
23
internal/controllerlib/test/library/spew.go
Normal file
23
internal/controllerlib/test/library/spew.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package library
|
||||||
|
|
||||||
|
import "github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
config := spew.ConfigState{
|
||||||
|
Indent: "\t",
|
||||||
|
MaxDepth: 10, // prevent log explosion
|
||||||
|
DisableMethods: true,
|
||||||
|
DisablePointerMethods: true,
|
||||||
|
DisablePointerAddresses: true,
|
||||||
|
DisableCapacities: true,
|
||||||
|
ContinueOnMethod: true,
|
||||||
|
SortKeys: true,
|
||||||
|
SpewKeys: true,
|
||||||
|
}
|
||||||
|
return config.Sdump(a...)
|
||||||
|
}
|
29
internal/controllerlib/testing.go
Normal file
29
internal/controllerlib/testing.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllerlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSync(t *testing.T, controller Controller, ctx Context) error {
|
||||||
|
t.Helper() // force testing import to discourage external use
|
||||||
|
return controller.sync(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrap(t *testing.T, controller Controller, wrapper SyncWrapperFunc) {
|
||||||
|
t.Helper() // force testing import to discourage external use
|
||||||
|
controller.wrap(wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just enough of the internal implementation of controller.Run() to allow
|
||||||
|
// "running" the controller without any goroutines being involved. For use
|
||||||
|
// in synchronous unit tests that wish to invoke TestSync() directly.
|
||||||
|
func TestRunSynchronously(t *testing.T, controller Controller) {
|
||||||
|
t.Helper() // force testing import to discourage external use
|
||||||
|
controller.invokeAllRunOpts()
|
||||||
|
controller.waitForCacheSyncWithTimeout()
|
||||||
|
}
|
@ -16,11 +16,11 @@ import (
|
|||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
|
||||||
"github.com/suzerain-io/controller-go"
|
|
||||||
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
||||||
pinnipedinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions"
|
pinnipedinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions"
|
||||||
"github.com/suzerain-io/pinniped/internal/controller/apicerts"
|
"github.com/suzerain-io/pinniped/internal/controller/apicerts"
|
||||||
"github.com/suzerain-io/pinniped/internal/controller/issuerconfig"
|
"github.com/suzerain-io/pinniped/internal/controller/issuerconfig"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
"github.com/suzerain-io/pinniped/internal/provider"
|
"github.com/suzerain-io/pinniped/internal/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ func PrepareControllers(
|
|||||||
createInformers(serverInstallationNamespace, k8sClient, pinnipedClient)
|
createInformers(serverInstallationNamespace, k8sClient, pinnipedClient)
|
||||||
|
|
||||||
// Create controller manager.
|
// Create controller manager.
|
||||||
controllerManager := controller.
|
controllerManager := controllerlib.
|
||||||
NewManager().
|
NewManager().
|
||||||
WithController(
|
WithController(
|
||||||
issuerconfig.NewPublisherController(
|
issuerconfig.NewPublisherController(
|
||||||
@ -57,7 +57,7 @@ func PrepareControllers(
|
|||||||
pinnipedClient,
|
pinnipedClient,
|
||||||
kubePublicNamespaceK8sInformers.Core().V1().ConfigMaps(),
|
kubePublicNamespaceK8sInformers.Core().V1().ConfigMaps(),
|
||||||
installationNamespacePinnipedInformers.Crd().V1alpha1().CredentialIssuerConfigs(),
|
installationNamespacePinnipedInformers.Crd().V1alpha1().CredentialIssuerConfigs(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
singletonWorker,
|
singletonWorker,
|
||||||
).
|
).
|
||||||
@ -67,8 +67,8 @@ func PrepareControllers(
|
|||||||
k8sClient,
|
k8sClient,
|
||||||
aggregatorClient,
|
aggregatorClient,
|
||||||
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
controller.WithInitialEvent,
|
controllerlib.WithInitialEvent,
|
||||||
servingCertDuration,
|
servingCertDuration,
|
||||||
),
|
),
|
||||||
singletonWorker,
|
singletonWorker,
|
||||||
@ -78,7 +78,7 @@ func PrepareControllers(
|
|||||||
serverInstallationNamespace,
|
serverInstallationNamespace,
|
||||||
dynamicCertProvider,
|
dynamicCertProvider,
|
||||||
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
singletonWorker,
|
singletonWorker,
|
||||||
).
|
).
|
||||||
@ -87,7 +87,7 @@ func PrepareControllers(
|
|||||||
serverInstallationNamespace,
|
serverInstallationNamespace,
|
||||||
k8sClient,
|
k8sClient,
|
||||||
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
installationNamespaceK8sInformers.Core().V1().Secrets(),
|
||||||
controller.WithInformer,
|
controllerlib.WithInformer,
|
||||||
servingCertRenewBefore,
|
servingCertRenewBefore,
|
||||||
),
|
),
|
||||||
singletonWorker,
|
singletonWorker,
|
||||||
|
@ -5,27 +5,27 @@ SPDX-License-Identifier: Apache-2.0
|
|||||||
|
|
||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import "github.com/suzerain-io/controller-go"
|
import "github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
|
||||||
type ObservableWithInformerOption struct {
|
type ObservableWithInformerOption struct {
|
||||||
informerToFilterMap map[controller.InformerGetter]controller.Filter
|
informerToFilterMap map[controllerlib.InformerGetter]controllerlib.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObservableWithInformerOption() *ObservableWithInformerOption {
|
func NewObservableWithInformerOption() *ObservableWithInformerOption {
|
||||||
return &ObservableWithInformerOption{
|
return &ObservableWithInformerOption{
|
||||||
informerToFilterMap: make(map[controller.InformerGetter]controller.Filter),
|
informerToFilterMap: make(map[controllerlib.InformerGetter]controllerlib.Filter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ObservableWithInformerOption) WithInformer(
|
func (i *ObservableWithInformerOption) WithInformer(
|
||||||
getter controller.InformerGetter,
|
getter controllerlib.InformerGetter,
|
||||||
filter controller.Filter,
|
filter controllerlib.Filter,
|
||||||
opt controller.InformerOption,
|
opt controllerlib.InformerOption,
|
||||||
) controller.Option {
|
) controllerlib.Option {
|
||||||
i.informerToFilterMap[getter] = filter
|
i.informerToFilterMap[getter] = filter
|
||||||
return controller.WithInformer(getter, filter, opt)
|
return controllerlib.WithInformer(getter, filter, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ObservableWithInformerOption) GetFilterForInformer(getter controller.InformerGetter) controller.Filter {
|
func (i *ObservableWithInformerOption) GetFilterForInformer(getter controllerlib.InformerGetter) controllerlib.Filter {
|
||||||
return i.informerToFilterMap[getter]
|
return i.informerToFilterMap[getter]
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,21 @@ SPDX-License-Identifier: Apache-2.0
|
|||||||
|
|
||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import "github.com/suzerain-io/controller-go"
|
import "github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
|
||||||
type ObservableWithInitialEventOption struct {
|
type ObservableWithInitialEventOption struct {
|
||||||
key controller.Key
|
key controllerlib.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObservableWithInitialEventOption() *ObservableWithInitialEventOption {
|
func NewObservableWithInitialEventOption() *ObservableWithInitialEventOption {
|
||||||
return &ObservableWithInitialEventOption{}
|
return &ObservableWithInitialEventOption{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ObservableWithInitialEventOption) WithInitialEvent(key controller.Key) controller.Option {
|
func (i *ObservableWithInitialEventOption) WithInitialEvent(key controllerlib.Key) controllerlib.Option {
|
||||||
i.key = key
|
i.key = key
|
||||||
return controller.WithInitialEvent(key)
|
return controllerlib.WithInitialEvent(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ObservableWithInitialEventOption) GetInitialEventKey() controller.Key {
|
func (i *ObservableWithInitialEventOption) GetInitialEventKey() controllerlib.Key {
|
||||||
return i.key
|
return i.key
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user