Use ssa package from fluxcd/pkg
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
d0222867e6
commit
6346591f02
|
@ -34,8 +34,6 @@ jobs:
|
|||
uses: fluxcd/pkg/actions/kubectl@main
|
||||
with:
|
||||
version: 1.21.2
|
||||
- name: Run internal tests
|
||||
run: make test-internal
|
||||
- name: Run controller tests
|
||||
run: make test
|
||||
- name: Check if working tree is dirty
|
||||
|
|
4
Makefile
4
Makefile
|
@ -24,10 +24,6 @@ KUBEBUILDER_ASSETS?="$(shell $(SETUP_ENVTEST) use -i $(ENVTEST_AKUBERNETES_VERSI
|
|||
test: generate fmt vet manifests api-docs download-crd-deps install-envtest
|
||||
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./controllers/... -v -coverprofile cover.out
|
||||
|
||||
# Run internal tests
|
||||
test-internal: install-envtest
|
||||
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./internal/... -v -coverprofile cover.out
|
||||
|
||||
# Build manager binary
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager main.go
|
||||
|
|
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/fluxcd/pkg/apis/kustomize v0.2.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.0
|
||||
github.com/fluxcd/pkg/runtime v0.12.0
|
||||
k8s.io/apiextensions-apiserver v0.22.1
|
||||
k8s.io/apimachinery v0.22.1
|
||||
sigs.k8s.io/controller-runtime v0.10.0
|
||||
k8s.io/apiextensions-apiserver v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
)
|
||||
|
|
29
api/go.sum
29
api/go.sum
|
@ -775,22 +775,22 @@ 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-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
|
||||
k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY=
|
||||
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
|
||||
k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw=
|
||||
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
|
||||
k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA=
|
||||
k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE=
|
||||
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
|
||||
k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4=
|
||||
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
|
||||
k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM=
|
||||
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk=
|
||||
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY=
|
||||
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
|
||||
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
|
||||
k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
|
||||
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
|
||||
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
|
||||
k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
|
||||
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
|
||||
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
|
||||
k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA=
|
||||
k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
|
||||
k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
|
@ -802,17 +802,16 @@ k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iL
|
|||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/controller-runtime v0.9.0/go.mod h1:TgkfvrhhEw3PlI0BRL/5xM+89y3/yc0ZDfdbTl84si8=
|
||||
sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U=
|
||||
sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg=
|
||||
sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg=
|
||||
sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
|
||||
|
|
|
@ -29,12 +29,6 @@ import (
|
|||
"time"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
"github.com/fluxcd/pkg/untar"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -57,9 +51,15 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"github.com/fluxcd/pkg/untar"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
"github.com/fluxcd/kustomize-controller/internal/ssa"
|
||||
)
|
||||
|
||||
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
|
||||
|
@ -360,7 +360,7 @@ func (r *KustomizationReconciler) reconcile(
|
|||
), err
|
||||
}
|
||||
|
||||
objects, err := objectutil.ReadObjects(bytes.NewReader(resources))
|
||||
objects, err := ssa.ReadObjects(bytes.NewReader(resources))
|
||||
if err != nil {
|
||||
return kustomizev1.KustomizationNotReady(
|
||||
kustomization,
|
||||
|
@ -618,7 +618,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context, manager *ssa.Resour
|
|||
var stageTwo []*unstructured.Unstructured
|
||||
|
||||
for _, u := range objects {
|
||||
if manager.IsClusterDefinition(u.GetKind()) {
|
||||
if ssa.IsClusterDefinition(u) {
|
||||
stageOne = append(stageOne, u)
|
||||
} else {
|
||||
stageTwo = append(stageTwo, u)
|
||||
|
@ -649,7 +649,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context, manager *ssa.Resour
|
|||
}
|
||||
|
||||
// sort by kind, validate and apply all the others objects
|
||||
sort.Sort(objectutil.SortableUnstructureds(stageTwo))
|
||||
sort.Sort(ssa.SortableUnstructureds(stageTwo))
|
||||
if len(stageTwo) > 0 {
|
||||
changeSet, err := manager.ApplyAll(ctx, stageTwo, kustomization.Spec.Force)
|
||||
if err != nil {
|
||||
|
@ -753,7 +753,7 @@ func (r *KustomizationReconciler) finalize(ctx context.Context, kustomization ku
|
|||
kubeClient, _, err := impersonation.GetClient(ctx)
|
||||
if err != nil {
|
||||
// when impersonation fails, log the stale objects and continue with the finalization
|
||||
msg := fmt.Sprintf("unable to prune objects: \n%s", objectutil.FmtUnstructuredList(objects))
|
||||
msg := fmt.Sprintf("unable to prune objects: \n%s", ssa.FmtUnstructuredList(objects))
|
||||
log.Error(fmt.Errorf("failed to build kube client: %w", err), msg)
|
||||
r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityError, msg, nil)
|
||||
} else {
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"sort"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
)
|
||||
|
||||
func NewInventory() *kustomizev1.ResourceInventory {
|
||||
|
@ -36,7 +36,7 @@ func NewInventory() *kustomizev1.ResourceInventory {
|
|||
|
||||
// AddObjectsToInventory extracts the metadata from the given objects and adds it to the inventory.
|
||||
func AddObjectsToInventory(inv *kustomizev1.ResourceInventory, objects []*unstructured.Unstructured) error {
|
||||
sort.Sort(objectutil.SortableUnstructureds(objects))
|
||||
sort.Sort(ssa.SortableUnstructureds(objects))
|
||||
for _, om := range objects {
|
||||
objMetadata := object.UnstructuredToObjMeta(om)
|
||||
gv, err := schema.ParseGroupVersion(om.GetAPIVersion())
|
||||
|
@ -74,7 +74,7 @@ func ListObjectsInInventory(inv *kustomizev1.ResourceInventory) ([]*unstructured
|
|||
objects = append(objects, u)
|
||||
}
|
||||
|
||||
sort.Sort(objectutil.SortableUnstructureds(objects))
|
||||
sort.Sort(ssa.SortableUnstructureds(objects))
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ func DiffInventory(inv *kustomizev1.ResourceInventory, target *kustomizev1.Resou
|
|||
objects = append(objects, u)
|
||||
}
|
||||
|
||||
sort.Sort(objectutil.SortableUnstructureds(objects))
|
||||
sort.Sort(ssa.SortableUnstructureds(objects))
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
|
|
18
go.mod
18
go.mod
|
@ -5,35 +5,35 @@ go 1.16
|
|||
replace github.com/fluxcd/kustomize-controller/api => ./api
|
||||
|
||||
require (
|
||||
filippo.io/age v1.0.0-beta7
|
||||
filippo.io/age v1.0.0
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/drone/envsubst v1.0.3-0.20200804185402-58bc65f69603
|
||||
github.com/fluxcd/kustomize-controller/api v0.14.1
|
||||
github.com/fluxcd/pkg/apis/kustomize v0.2.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.1
|
||||
github.com/fluxcd/pkg/runtime v0.12.1
|
||||
github.com/fluxcd/pkg/ssa v0.0.0-20211001071904-2da41b6c8ff8
|
||||
github.com/fluxcd/pkg/testserver v0.1.0
|
||||
github.com/fluxcd/pkg/untar v0.1.0
|
||||
github.com/fluxcd/source-controller/api v0.15.4
|
||||
github.com/go-logr/logr v0.4.0
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/hashicorp/go-retryablehttp v0.6.8
|
||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
|
||||
github.com/onsi/gomega v1.15.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a
|
||||
go.mozilla.org/sops/v3 v3.7.1
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
|
||||
google.golang.org/grpc v1.38.0
|
||||
k8s.io/api v0.22.1
|
||||
k8s.io/apiextensions-apiserver v0.22.1
|
||||
k8s.io/apimachinery v0.22.1
|
||||
k8s.io/client-go v0.22.1
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apiextensions-apiserver v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a
|
||||
sigs.k8s.io/controller-runtime v0.10.0
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
sigs.k8s.io/kustomize/api v0.9.0
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
// pin kustomize to v4.3.0
|
||||
|
|
56
go.sum
56
go.sum
|
@ -25,9 +25,11 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/age v1.0.0-beta7 h1:RZiSK+N3KL2UwT82xiCavjYw8jJHzWMEUYePAukTpk0=
|
||||
filippo.io/age v1.0.0-beta7/go.mod h1:chAuTrTb0FTTmKtvs6fQTGhYTvH9AigjN1uEUsvLdZ0=
|
||||
filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc=
|
||||
filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8=
|
||||
filippo.io/edwards25519 v1.0.0-alpha.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
github.com/Azure/azure-sdk-for-go v31.2.0+incompatible h1:kZFnTLmdQYNGfakatSivKHUfUnDZhqNdchHD4oIhp5k=
|
||||
github.com/Azure/azure-sdk-for-go v31.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
|
@ -212,6 +214,8 @@ github.com/fluxcd/pkg/apis/meta v0.10.1/go.mod h1:yUblM2vg+X8TE3A2VvJfdhkGmg+uqB
|
|||
github.com/fluxcd/pkg/runtime v0.12.0/go.mod h1:EyaTR2TOYcjL5U//C4yH3bt2tvTgIOSXpVRbWxUn/C4=
|
||||
github.com/fluxcd/pkg/runtime v0.12.1 h1:r0KQG80gKY1NMp62FggSEdFBV60ZfbnA2RHL9y06DOY=
|
||||
github.com/fluxcd/pkg/runtime v0.12.1/go.mod h1:9czAjokV0w22eYGR9/SQKUHXhvh7ISNVgc/6a6YMBE8=
|
||||
github.com/fluxcd/pkg/ssa v0.0.0-20211001071904-2da41b6c8ff8 h1:XDF1tPNh+oKNIOmtA9UNGhr1vLMC5ldvTrGeIOgnkwA=
|
||||
github.com/fluxcd/pkg/ssa v0.0.0-20211001071904-2da41b6c8ff8/go.mod h1:QisgqnXXnHKNfdnrpJ3wQrwuto111mvdNcKkfe9Cwvk=
|
||||
github.com/fluxcd/pkg/testserver v0.1.0 h1:nOYgM1HYFZNNSUFykuWDmrsxj4jQxUCvmLHWOQeqmyA=
|
||||
github.com/fluxcd/pkg/testserver v0.1.0/go.mod h1:fvt8BHhXw6c1+CLw1QFZxcQprlcXzsrL4rzXaiGM+Iw=
|
||||
github.com/fluxcd/pkg/untar v0.1.0 h1:k97V/xV5hFrAkIkVPuv5AVhyxh1ZzzAKba/lbDfGo6o=
|
||||
|
@ -319,8 +323,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -772,8 +777,8 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -920,14 +925,17 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
|
||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1139,38 +1147,38 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
|
||||
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
|
||||
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
|
||||
k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY=
|
||||
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
|
||||
k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw=
|
||||
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
|
||||
k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA=
|
||||
k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA=
|
||||
k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
|
||||
k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE=
|
||||
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
|
||||
k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4=
|
||||
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
|
||||
k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
||||
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
|
||||
k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM=
|
||||
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk=
|
||||
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY=
|
||||
k8s.io/apiserver v0.21.2/go.mod h1:lN4yBoGyiNT7SC1dmNk0ue6a5Wi6O3SWOIw91TsucQw=
|
||||
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
|
||||
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
|
||||
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
|
||||
k8s.io/cli-runtime v0.21.1 h1:Oj/iZxa7LLXrhzShaLNF4rFJEIEBTDHj0dJw4ra2vX4=
|
||||
k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw=
|
||||
k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
|
||||
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
|
||||
k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
|
||||
k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw=
|
||||
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
|
||||
k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc=
|
||||
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
|
||||
k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
|
||||
k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U=
|
||||
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
|
||||
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
|
||||
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
|
||||
k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA=
|
||||
k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc=
|
||||
k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
|
||||
k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo=
|
||||
k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
|
||||
k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M=
|
||||
k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
|
||||
k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
|
@ -1187,10 +1195,9 @@ k8s.io/metrics v0.21.1/go.mod h1:pyDVLsLe++FIGDBFU80NcW4xMFsuiVTWL8Zfi7+PpNo=
|
|||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210517184530-5a248b5acedc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -1203,8 +1210,8 @@ sigs.k8s.io/controller-runtime v0.9.0-beta.5.0.20210524185538-7181f1162e79/go.mo
|
|||
sigs.k8s.io/controller-runtime v0.9.0/go.mod h1:TgkfvrhhEw3PlI0BRL/5xM+89y3/yc0ZDfdbTl84si8=
|
||||
sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk=
|
||||
sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
|
||||
sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U=
|
||||
sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg=
|
||||
sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg=
|
||||
sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
|
||||
sigs.k8s.io/kustomize/api v0.9.0 h1:yGQnm/2GBbHBLSVbM0CsqgPmqK/NSDbhSGbXuhuVN7s=
|
||||
sigs.k8s.io/kustomize/api v0.9.0/go.mod h1:bOF7z4DcRIXcOCeSbVq5o9JhMRnNzWqrRSSBFtz05A4=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0=
|
||||
|
@ -1216,6 +1223,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
|
|||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package objectutil contains utilities for manipulating Kubernetes objects.
|
||||
package objectutil
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package objectutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
const fmtSeparator = "/"
|
||||
|
||||
// FmtObjMetadata returns the object ID in the format <kind>/<namespace>/<name>.
|
||||
func FmtObjMetadata(obj object.ObjMetadata) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(obj.GroupKind.Kind + fmtSeparator)
|
||||
if obj.Namespace != "" {
|
||||
builder.WriteString(obj.Namespace + fmtSeparator)
|
||||
}
|
||||
builder.WriteString(obj.Name)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// FmtUnstructured returns the object ID in the format <kind>/<namespace>/<name>.
|
||||
func FmtUnstructured(obj *unstructured.Unstructured) string {
|
||||
return FmtObjMetadata(object.UnstructuredToObjMeta(obj))
|
||||
}
|
||||
|
||||
// FmtUnstructuredList returns a line per object in the format <kind>/<namespace>/<name>.
|
||||
func FmtUnstructuredList(objects []*unstructured.Unstructured) string {
|
||||
var b strings.Builder
|
||||
for _, obj := range objects {
|
||||
b.WriteString(FmtObjMetadata(object.UnstructuredToObjMeta(obj)) + "\n")
|
||||
}
|
||||
return strings.TrimSuffix(b.String(), "\n")
|
||||
}
|
||||
|
||||
// MaskSecret replaces the data key values with the given mask.
|
||||
func MaskSecret(object *unstructured.Unstructured, mask string) (*unstructured.Unstructured, error) {
|
||||
data, found, err := unstructured.NestedMap(object.Object, "data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
for k, _ := range data {
|
||||
data[k] = mask
|
||||
}
|
||||
|
||||
err = unstructured.SetNestedMap(object.Object, data, "data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return object, err
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package objectutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
apiruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ReadObject decodes a YAML or JSON document from the given reader into an unstructured Kubernetes API object.
|
||||
func ReadObject(r io.Reader) (*unstructured.Unstructured, error) {
|
||||
reader := yamlutil.NewYAMLOrJSONDecoder(r, 2048)
|
||||
obj := &unstructured.Unstructured{}
|
||||
err := reader.Decode(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// ReadObjects decodes the YAML or JSON documents from the given reader into unstructured Kubernetes API objects.
|
||||
func ReadObjects(r io.Reader) ([]*unstructured.Unstructured, error) {
|
||||
reader := yamlutil.NewYAMLOrJSONDecoder(r, 2048)
|
||||
objects := make([]*unstructured.Unstructured, 0)
|
||||
|
||||
for {
|
||||
obj := &unstructured.Unstructured{}
|
||||
err := reader.Decode(obj)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
return objects, err
|
||||
}
|
||||
|
||||
if obj.IsList() {
|
||||
err = obj.EachListItem(func(item apiruntime.Object) error {
|
||||
obj := item.(*unstructured.Unstructured)
|
||||
objects = append(objects, obj)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return objects, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if IsKubernetesObject(obj) && !IsKustomization(obj) {
|
||||
objects = append(objects, obj)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func IsKubernetesObject(object *unstructured.Unstructured) bool {
|
||||
if object.GetName() == "" || object.GetKind() == "" || object.GetAPIVersion() == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsKustomization(object *unstructured.Unstructured) bool {
|
||||
if object.GetKind() == "Kustomization" && object.GroupVersionKind().GroupKind().Group == "kustomize.config.k8s.io" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ObjectToYAML encodes the given Kubernetes API object to YAML.
|
||||
func ObjectToYAML(object *unstructured.Unstructured) string {
|
||||
var builder strings.Builder
|
||||
data, err := yaml.Marshal(object)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
builder.Write(data)
|
||||
builder.WriteString("---\n")
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// ObjectsToYAML encodes the given Kubernetes API objects to a YAML multi-doc.
|
||||
func ObjectsToYAML(objects []*unstructured.Unstructured) (string, error) {
|
||||
var builder strings.Builder
|
||||
for _, obj := range objects {
|
||||
data, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
builder.Write(data)
|
||||
builder.WriteString("---\n")
|
||||
}
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// ObjectsToJSON encodes the given Kubernetes API objects to a YAML multi-doc.
|
||||
func ObjectsToJSON(objects []*unstructured.Unstructured) (string, error) {
|
||||
list := struct {
|
||||
ApiVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Items []*unstructured.Unstructured `json:"items,omitempty"`
|
||||
}{
|
||||
ApiVersion: "v1",
|
||||
Kind: "ListMeta",
|
||||
Items: objects,
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(list, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan.
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package objectutil
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
type SortableUnstructureds []*unstructured.Unstructured
|
||||
|
||||
var _ sort.Interface = SortableUnstructureds{}
|
||||
|
||||
func (a SortableUnstructureds) Len() int { return len(a) }
|
||||
func (a SortableUnstructureds) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortableUnstructureds) Less(i, j int) bool {
|
||||
first := object.UnstructuredToObjMeta(a[i])
|
||||
second := object.UnstructuredToObjMeta(a[j])
|
||||
return less(first, second)
|
||||
}
|
||||
|
||||
type SortableMetas []object.ObjMetadata
|
||||
|
||||
var _ sort.Interface = SortableMetas{}
|
||||
|
||||
func (a SortableMetas) Len() int { return len(a) }
|
||||
func (a SortableMetas) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SortableMetas) Less(i, j int) bool {
|
||||
return less(a[i], a[j])
|
||||
}
|
||||
|
||||
func less(i, j object.ObjMetadata) bool {
|
||||
if !Equals(i.GroupKind, j.GroupKind) {
|
||||
return IsLessThan(i.GroupKind, j.GroupKind)
|
||||
}
|
||||
// In case of tie, compare the namespace and name combination so that the output
|
||||
// order is consistent irrespective of input order
|
||||
if i.Namespace != j.Namespace {
|
||||
return i.Namespace < j.Namespace
|
||||
}
|
||||
return i.Name < j.Name
|
||||
}
|
||||
|
||||
var kind2index = computeKind2index()
|
||||
|
||||
func computeKind2index() map[string]int {
|
||||
// An attempt to order things to help k8s, e.g.
|
||||
// a Service should come before things that refer to it.
|
||||
// Namespace should be first.
|
||||
// In some cases order just specified to provide determinism.
|
||||
orderFirst := []string{
|
||||
"CustomResourceDefinition",
|
||||
"Namespace",
|
||||
"ResourceQuota",
|
||||
"StorageClass",
|
||||
"ServiceAccount",
|
||||
"PodSecurityPolicy",
|
||||
"Role",
|
||||
"ClusterRole",
|
||||
"RoleBinding",
|
||||
"ClusterRoleBinding",
|
||||
"ConfigMap",
|
||||
"Secret",
|
||||
"Service",
|
||||
"LimitRange",
|
||||
"PriorityClass",
|
||||
"Deployment",
|
||||
"StatefulSet",
|
||||
"CronJob",
|
||||
"PodDisruptionBudget",
|
||||
}
|
||||
orderLast := []string{
|
||||
"MutatingWebhookConfiguration",
|
||||
"ValidatingWebhookConfiguration",
|
||||
}
|
||||
kind2indexResult := make(map[string]int, len(orderFirst)+len(orderLast))
|
||||
for i, n := range orderFirst {
|
||||
kind2indexResult[n] = -len(orderFirst) + i
|
||||
}
|
||||
for i, n := range orderLast {
|
||||
kind2indexResult[n] = 1 + i
|
||||
}
|
||||
return kind2indexResult
|
||||
}
|
||||
|
||||
// getIndexByKind returns the index of the kind respecting the order
|
||||
func getIndexByKind(kind string) int {
|
||||
return kind2index[kind]
|
||||
}
|
||||
|
||||
func Equals(i, j schema.GroupKind) bool {
|
||||
return i.Group == j.Group && i.Kind == j.Kind
|
||||
}
|
||||
|
||||
func IsLessThan(i, j schema.GroupKind) bool {
|
||||
indexI := getIndexByKind(i.Kind)
|
||||
indexJ := getIndexByKind(j.Kind)
|
||||
if indexI != indexJ {
|
||||
return indexI < indexJ
|
||||
}
|
||||
if i.Group != j.Group {
|
||||
return i.Group < j.Group
|
||||
}
|
||||
return i.Kind < j.Kind
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Action resents the action type performed by the reconciliation process.
|
||||
type Action string
|
||||
|
||||
const (
|
||||
CreatedAction Action = "created"
|
||||
ConfiguredAction Action = "configured"
|
||||
UnchangedAction Action = "unchanged"
|
||||
DeletedAction Action = "deleted"
|
||||
UnknownAction Action = "unknown"
|
||||
)
|
||||
|
||||
// ChangeSet holds the result of the reconciliation of an object collection.
|
||||
type ChangeSet struct {
|
||||
Entries []ChangeSetEntry
|
||||
}
|
||||
|
||||
// NewChangeSet returns a ChangeSet will an empty slice of entries.
|
||||
func NewChangeSet() *ChangeSet {
|
||||
return &ChangeSet{Entries: []ChangeSetEntry{}}
|
||||
}
|
||||
|
||||
// Add appends the given entry to the end of the slice.
|
||||
func (c *ChangeSet) Add(e ChangeSetEntry) {
|
||||
c.Entries = append(c.Entries, e)
|
||||
}
|
||||
|
||||
// Append adds the given ChangeSet entries to end of the slice.
|
||||
func (c *ChangeSet) Append(e []ChangeSetEntry) {
|
||||
c.Entries = append(c.Entries, e...)
|
||||
}
|
||||
|
||||
func (c *ChangeSet) String() string {
|
||||
var b strings.Builder
|
||||
for _, entry := range c.Entries {
|
||||
b.WriteString(entry.String() + "\n")
|
||||
}
|
||||
return strings.TrimSuffix(b.String(), "\n")
|
||||
}
|
||||
|
||||
func (c *ChangeSet) ToMap() map[string]string {
|
||||
res := make(map[string]string)
|
||||
for _, entry := range c.Entries {
|
||||
res[entry.Subject] = entry.Action
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// ChangeSetEntry defines the result of an action performed on an object.
|
||||
type ChangeSetEntry struct {
|
||||
// Subject represents the Object ID in the format 'kind/namespace/name'.
|
||||
Subject string
|
||||
// Action represents the action type taken by the reconciler for this object.
|
||||
Action string
|
||||
// Diff contains the YAML diff resulting from server-side apply dry-run.
|
||||
Diff string
|
||||
}
|
||||
|
||||
func (e ChangeSetEntry) String() string {
|
||||
return fmt.Sprintf("%s %s", e.Subject, e.Action)
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package ssa contains utilities for managing Kubernetes resources using sever-side apply.
|
||||
// Adapted from https://github.com/stefanprodan/kustomizer/tree/v1.1.0/pkg/manager
|
||||
package ssa
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
var manager *ResourceManager
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testEnv := &envtest.Environment{}
|
||||
|
||||
cfg, err := testEnv.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
restMapper, err := apiutil.NewDynamicRESTMapper(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
kubeClient, err := client.New(cfg, client.Options{
|
||||
Mapper: restMapper,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
poller := polling.NewStatusPoller(kubeClient, restMapper)
|
||||
|
||||
manager = &ResourceManager{
|
||||
client: kubeClient,
|
||||
poller: poller,
|
||||
owner: Owner{
|
||||
Field: "resource-manager",
|
||||
Group: "resource-manager.io",
|
||||
},
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
|
||||
testEnv.Stop()
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func readManifest(manifest, namespace string) ([]*unstructured.Unstructured, error) {
|
||||
data, err := os.ReadFile(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yml := fmt.Sprintf(string(data), namespace)
|
||||
|
||||
objects, err := objectutil.ReadObjects(strings.NewReader(yml))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func setNamespace(objects []*unstructured.Unstructured, namespace string) {
|
||||
for _, object := range objects {
|
||||
object.SetNamespace(namespace)
|
||||
}
|
||||
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Kind: "Namespace",
|
||||
Version: "v1",
|
||||
})
|
||||
u.SetName(namespace)
|
||||
objects = append(objects, u)
|
||||
}
|
||||
|
||||
var nextNameId int64
|
||||
|
||||
func generateName(prefix string) string {
|
||||
id := atomic.AddInt64(&nextNameId, 1)
|
||||
return fmt.Sprintf("%s-%d", prefix, id)
|
||||
}
|
||||
|
||||
func getFirstObject(objects []*unstructured.Unstructured, kind, name string) (string, *unstructured.Unstructured) {
|
||||
for _, object := range objects {
|
||||
if object.GetKind() == kind && object.GetName() == name {
|
||||
return objectutil.FmtUnstructured(object), object
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func removeObject(s []*unstructured.Unstructured, index int) []*unstructured.Unstructured {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
)
|
||||
|
||||
// ResourceManager reconciles Kubernetes resources onto the target cluster using server-side apply.
|
||||
type ResourceManager struct {
|
||||
client client.Client
|
||||
poller *polling.StatusPoller
|
||||
owner Owner
|
||||
}
|
||||
|
||||
// NewResourceManager creates a ResourceManager for the given Kubernetes client.
|
||||
func NewResourceManager(client client.Client, poller *polling.StatusPoller, owner Owner) *ResourceManager {
|
||||
return &ResourceManager{
|
||||
client: client,
|
||||
poller: poller,
|
||||
owner: owner,
|
||||
}
|
||||
}
|
||||
|
||||
// Client returns the underlying controller-runtime client.
|
||||
func (m *ResourceManager) Client() client.Client {
|
||||
return m.client
|
||||
}
|
||||
|
||||
// SetOwnerLabels adds the ownership labels to the given objects.
|
||||
// The ownership labels are in the format:
|
||||
// <owner.group>/name: <name>
|
||||
// <owner.group>/namespace: <namespace>
|
||||
func (m *ResourceManager) SetOwnerLabels(objects []*unstructured.Unstructured, name, namespace string) {
|
||||
for _, object := range objects {
|
||||
labels := object.GetLabels()
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
|
||||
labels[m.owner.Group+"/name"] = name
|
||||
labels[m.owner.Group+"/namespace"] = namespace
|
||||
|
||||
object.SetLabels(labels)
|
||||
}
|
||||
}
|
||||
|
||||
// GetOwnerLabels returns a map of labels for the specified name and namespace.
|
||||
func (m *ResourceManager) GetOwnerLabels(name, namespace string) map[string]string {
|
||||
return map[string]string{
|
||||
m.owner.Group + "/name": name,
|
||||
m.owner.Group + "/namespace": namespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ResourceManager) changeSetEntry(object *unstructured.Unstructured, action Action) *ChangeSetEntry {
|
||||
return &ChangeSetEntry{Subject: objectutil.FmtUnstructured(object), Action: string(action)}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
)
|
||||
|
||||
// Apply performs a server-side apply of the given object if the matching in-cluster object is different or if it doesn't exist.
|
||||
// Drift detection is performed by comparing the server-side dry-run result with the existing object.
|
||||
// When immutable field changes are detected, the object is recreated if 'force' is set to 'true'.
|
||||
func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstructured, force bool) (*ChangeSetEntry, error) {
|
||||
existingObject := object.DeepCopy()
|
||||
_ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
|
||||
|
||||
dryRunObject := object.DeepCopy()
|
||||
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
|
||||
if force && strings.Contains(err.Error(), "immutable") {
|
||||
if err := m.client.Delete(ctx, existingObject); err != nil {
|
||||
return nil, fmt.Errorf("%s immutable field detected, failed to delete object, error: %w",
|
||||
objectutil.FmtUnstructured(dryRunObject), err)
|
||||
}
|
||||
return m.Apply(ctx, object, force)
|
||||
}
|
||||
|
||||
return nil, m.validationError(dryRunObject, err)
|
||||
}
|
||||
|
||||
// do not apply objects that have not drifted to avoid bumping the resource version
|
||||
if !m.hasDrifted(existingObject, dryRunObject) {
|
||||
return m.changeSetEntry(object, UnchangedAction), nil
|
||||
}
|
||||
|
||||
appliedObject := object.DeepCopy()
|
||||
if err := m.apply(ctx, appliedObject); err != nil {
|
||||
return nil, fmt.Errorf("%s apply failed, error: %w", objectutil.FmtUnstructured(appliedObject), err)
|
||||
}
|
||||
|
||||
if dryRunObject.GetResourceVersion() == "" {
|
||||
return m.changeSetEntry(appliedObject, CreatedAction), nil
|
||||
}
|
||||
|
||||
return m.changeSetEntry(appliedObject, ConfiguredAction), nil
|
||||
}
|
||||
|
||||
// ApplyAll performs a server-side dry-run of the given objects, and based on the diff result,
|
||||
// it applies the objects that are new or modified.
|
||||
func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.Unstructured, force bool) (*ChangeSet, error) {
|
||||
sort.Sort(objectutil.SortableUnstructureds(objects))
|
||||
changeSet := NewChangeSet()
|
||||
var toApply []*unstructured.Unstructured
|
||||
for _, object := range objects {
|
||||
existingObject := object.DeepCopy()
|
||||
_ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
|
||||
|
||||
dryRunObject := object.DeepCopy()
|
||||
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
|
||||
if force && strings.Contains(err.Error(), "immutable") {
|
||||
if err := m.client.Delete(ctx, existingObject); err != nil {
|
||||
return nil, fmt.Errorf("%s immutable field detected, failed to delete object, error: %w",
|
||||
objectutil.FmtUnstructured(dryRunObject), err)
|
||||
}
|
||||
return m.ApplyAll(ctx, objects, force)
|
||||
}
|
||||
|
||||
return nil, m.validationError(dryRunObject, err)
|
||||
}
|
||||
|
||||
if m.hasDrifted(existingObject, dryRunObject) {
|
||||
toApply = append(toApply, object)
|
||||
if dryRunObject.GetResourceVersion() == "" {
|
||||
changeSet.Add(*m.changeSetEntry(dryRunObject, CreatedAction))
|
||||
} else {
|
||||
changeSet.Add(*m.changeSetEntry(dryRunObject, ConfiguredAction))
|
||||
}
|
||||
} else {
|
||||
changeSet.Add(*m.changeSetEntry(dryRunObject, UnchangedAction))
|
||||
}
|
||||
}
|
||||
|
||||
for _, object := range toApply {
|
||||
appliedObject := object.DeepCopy()
|
||||
if err := m.apply(ctx, appliedObject); err != nil {
|
||||
return nil, fmt.Errorf("%s apply failed, error: %w", objectutil.FmtUnstructured(appliedObject), err)
|
||||
}
|
||||
}
|
||||
|
||||
return changeSet, nil
|
||||
}
|
||||
|
||||
// ApplyAllStaged extracts the CRDs and Namespaces, applies them with ApplyAll,
|
||||
// waits for CRDs and Namespaces to become ready, then is applies all the other objects.
|
||||
// This function should be used when the given objects have a mix of custom resource definition and custom resources,
|
||||
// or a mix of namespace definitions with namespaced objects.
|
||||
func (m *ResourceManager) ApplyAllStaged(ctx context.Context, objects []*unstructured.Unstructured, force bool, wait time.Duration) (*ChangeSet, error) {
|
||||
changeSet := NewChangeSet()
|
||||
|
||||
// contains only CRDs and Namespaces
|
||||
var stageOne []*unstructured.Unstructured
|
||||
|
||||
// contains all objects except for CRDs and Namespaces
|
||||
var stageTwo []*unstructured.Unstructured
|
||||
|
||||
for _, u := range objects {
|
||||
if m.IsClusterDefinition(u.GetKind()) {
|
||||
stageOne = append(stageOne, u)
|
||||
} else {
|
||||
stageTwo = append(stageTwo, u)
|
||||
}
|
||||
}
|
||||
|
||||
if len(stageOne) > 0 {
|
||||
cs, err := m.ApplyAll(ctx, stageOne, force)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeSet.Append(cs.Entries)
|
||||
|
||||
if err := m.Wait(stageOne, 2*time.Second, wait); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cs, err := m.ApplyAll(ctx, stageTwo, force)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeSet.Append(cs.Entries)
|
||||
|
||||
return changeSet, nil
|
||||
}
|
||||
|
||||
func (m *ResourceManager) dryRunApply(ctx context.Context, object *unstructured.Unstructured) error {
|
||||
opts := []client.PatchOption{
|
||||
client.DryRunAll,
|
||||
client.ForceOwnership,
|
||||
client.FieldOwner(m.owner.Field),
|
||||
}
|
||||
return m.client.Patch(ctx, object, client.Apply, opts...)
|
||||
}
|
||||
|
||||
func (m *ResourceManager) apply(ctx context.Context, object *unstructured.Unstructured) error {
|
||||
opts := []client.PatchOption{
|
||||
client.ForceOwnership,
|
||||
client.FieldOwner(m.owner.Field),
|
||||
}
|
||||
return m.client.Patch(ctx, object, client.Apply, opts...)
|
||||
}
|
||||
|
||||
func (m *ResourceManager) IsClusterDefinition(kind string) bool {
|
||||
switch strings.ToLower(kind) {
|
||||
case "customresourcedefinition":
|
||||
return true
|
||||
case "namespace":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
timeout := 10 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
id := generateName("apply")
|
||||
objects, err := readManifest("testdata/test1.yaml", id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
manager.SetOwnerLabels(objects, "app1", "default")
|
||||
|
||||
configMapName, configMap := getFirstObject(objects, "ConfigMap", id)
|
||||
secretName, secret := getFirstObject(objects, "Secret", id)
|
||||
|
||||
t.Run("creates objects in order", func(t *testing.T) {
|
||||
// create objects
|
||||
changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expected created order
|
||||
sort.Sort(objectutil.SortableUnstructureds(objects))
|
||||
var expected []string
|
||||
for _, object := range objects {
|
||||
expected = append(expected, objectutil.FmtUnstructured(object))
|
||||
}
|
||||
|
||||
// verify the change set contains only created actions
|
||||
var output []string
|
||||
for _, entry := range changeSet.Entries {
|
||||
if diff := cmp.Diff(entry.Action, string(CreatedAction)); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
output = append(output, entry.Subject)
|
||||
}
|
||||
|
||||
// verify the change set contains all objects in the right order
|
||||
if diff := cmp.Diff(expected, output); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not apply unchanged objects", func(t *testing.T) {
|
||||
// no-op apply
|
||||
changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify the change set contains only unchanged actions
|
||||
var output []string
|
||||
for _, entry := range changeSet.Entries {
|
||||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s\n%v", diff, changeSet)
|
||||
}
|
||||
output = append(output, entry.Subject)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("applies only changed objects", func(t *testing.T) {
|
||||
// update a value in the configmap
|
||||
err = unstructured.SetNestedField(configMap.Object, "val", "data", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// apply changes
|
||||
changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify the change set contains the configured action only for the configmap
|
||||
for _, entry := range changeSet.Entries {
|
||||
if entry.Subject == configMapName {
|
||||
if diff := cmp.Diff(string(ConfiguredAction), entry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the configmap from cluster
|
||||
configMapClone := configMap.DeepCopy()
|
||||
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get data value from the in-cluster configmap
|
||||
val, _, err := unstructured.NestedFieldCopy(configMapClone.Object, "data", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify the configmap was updated in cluster with the right data value
|
||||
if diff := cmp.Diff(val, "val"); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails to apply immutable secret", func(t *testing.T) {
|
||||
// update a value in the secret
|
||||
err = unstructured.SetNestedField(secret.Object, "val-secret", "stringData", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// apply and expect to fail
|
||||
_, err := manager.ApplyAllStaged(ctx, objects, false, timeout)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error got none")
|
||||
}
|
||||
|
||||
// verify that the error message does not contain sensitive information
|
||||
expectedErr := fmt.Sprintf("%s invalid, error: secret is immutable", objectutil.FmtUnstructured(secret))
|
||||
if diff := cmp.Diff(expectedErr, err.Error()); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("force applies immutable secret", func(t *testing.T) {
|
||||
// force apply
|
||||
changeSet, err := manager.ApplyAllStaged(ctx, objects, true, timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify the secret was recreated
|
||||
for _, entry := range changeSet.Entries {
|
||||
if entry.Subject == secretName {
|
||||
if diff := cmp.Diff(string(CreatedAction), entry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the secret from cluster
|
||||
secretClone := secret.DeepCopy()
|
||||
err = manager.client.Get(ctx, client.ObjectKeyFromObject(secretClone), secretClone)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get data value from the in-cluster secret
|
||||
val, _, err := unstructured.NestedFieldCopy(secretClone.Object, "data", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify the secret was updated in cluster with the right data value
|
||||
if diff := cmp.Diff(val, base64.StdEncoding.EncodeToString([]byte("val-secret"))); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Delete deletes the given object (not found errors are ignored).
|
||||
func (m *ResourceManager) Delete(ctx context.Context, object *unstructured.Unstructured, labelSelector map[string]string, skipFor map[string]string) (*ChangeSetEntry, error) {
|
||||
existingObject := object.DeepCopy()
|
||||
err := m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return m.changeSetEntry(object, UnknownAction),
|
||||
fmt.Errorf("%s query failed, error: %w", objectutil.FmtUnstructured(object), err)
|
||||
}
|
||||
return m.changeSetEntry(object, DeletedAction), nil
|
||||
}
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: labelSelector})
|
||||
if err != nil {
|
||||
return m.changeSetEntry(object, UnknownAction),
|
||||
fmt.Errorf("%s label selector failed, error: %w", objectutil.FmtUnstructured(object), err)
|
||||
}
|
||||
|
||||
if !sel.Matches(labels.Set(existingObject.GetLabels())) {
|
||||
return m.changeSetEntry(object, UnchangedAction), nil
|
||||
}
|
||||
|
||||
for n, s := range skipFor {
|
||||
if existingObject.GetLabels()[n] == s || existingObject.GetAnnotations()[n] == s {
|
||||
return m.changeSetEntry(object, UnchangedAction), nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.client.Delete(ctx, existingObject); err != nil {
|
||||
return m.changeSetEntry(object, UnknownAction),
|
||||
fmt.Errorf("%s delete failed, error: %w", objectutil.FmtUnstructured(object), err)
|
||||
}
|
||||
|
||||
return m.changeSetEntry(object, DeletedAction), nil
|
||||
}
|
||||
|
||||
// DeleteAll deletes the given set of objects (not found errors are ignored).
|
||||
// The given objects are filtered based on the metadata present in-cluster.
|
||||
func (m *ResourceManager) DeleteAll(ctx context.Context, objects []*unstructured.Unstructured, labelSelector map[string]string, skipFor map[string]string) (*ChangeSet, error) {
|
||||
sort.Sort(sort.Reverse(objectutil.SortableUnstructureds(objects)))
|
||||
changeSet := NewChangeSet()
|
||||
|
||||
var errors string
|
||||
for _, object := range objects {
|
||||
cse, err := m.Delete(ctx, object, labelSelector, skipFor)
|
||||
if cse != nil {
|
||||
changeSet.Add(*cse)
|
||||
}
|
||||
if err != nil {
|
||||
errors += err.Error() + ";"
|
||||
}
|
||||
}
|
||||
|
||||
if errors != "" {
|
||||
return changeSet, fmt.Errorf("delete failed, errors: %s", errors)
|
||||
}
|
||||
|
||||
return changeSet, nil
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
timeout := 10 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
id := generateName("delete")
|
||||
objects, err := readManifest("testdata/test1.yaml", id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, configMap := getFirstObject(objects, "ConfigMap", id)
|
||||
_, role := getFirstObject(objects, "ClusterRole", id)
|
||||
|
||||
if _, err = manager.ApplyAllStaged(ctx, objects, false, timeout); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("deletes objects in order", func(t *testing.T) {
|
||||
changeSet, err := manager.DeleteAll(ctx, objects, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// expected deleted order
|
||||
var expected []string
|
||||
for _, object := range objects {
|
||||
expected = append(expected, objectutil.FmtUnstructured(object))
|
||||
}
|
||||
|
||||
// verify the change set contains only created actions
|
||||
var output []string
|
||||
for _, entry := range changeSet.Entries {
|
||||
if diff := cmp.Diff(entry.Action, string(DeletedAction)); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
output = append(output, entry.Subject)
|
||||
}
|
||||
|
||||
// verify the change set contains all objects in the right order
|
||||
if diff := cmp.Diff(expected, output); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
configMapClone := configMap.DeepCopy()
|
||||
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
|
||||
if !apierrors.IsNotFound(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
roleClone := role.DeepCopy()
|
||||
err = manager.client.Get(ctx, client.ObjectKeyFromObject(roleClone), roleClone)
|
||||
if !apierrors.IsNotFound(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("waits for objects termination", func(t *testing.T) {
|
||||
_, err := manager.DeleteAll(ctx, objects, nil, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := manager.WaitForTermination(objects, time.Second, 5*time.Second); err != nil {
|
||||
// workaround for https://github.com/kubernetes-sigs/controller-runtime/issues/880
|
||||
if !strings.Contains(err.Error(), "Namespace/") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Diff performs a server-side apply dry-un and returns the fields that changed in YAML format.
|
||||
// If the diff contains Kubernetes Secrets, the data values are masked.
|
||||
func (m *ResourceManager) Diff(ctx context.Context, object *unstructured.Unstructured) (*ChangeSetEntry, error) {
|
||||
existingObject := object.DeepCopy()
|
||||
_ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
|
||||
|
||||
dryRunObject := object.DeepCopy()
|
||||
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
|
||||
return nil, m.validationError(dryRunObject, err)
|
||||
}
|
||||
|
||||
if dryRunObject.GetResourceVersion() == "" {
|
||||
return m.changeSetEntry(dryRunObject, CreatedAction), nil
|
||||
}
|
||||
|
||||
if m.hasDrifted(existingObject, dryRunObject) {
|
||||
cse := m.changeSetEntry(object, ConfiguredAction)
|
||||
|
||||
unstructured.RemoveNestedField(dryRunObject.Object, "metadata", "managedFields")
|
||||
unstructured.RemoveNestedField(existingObject.Object, "metadata", "managedFields")
|
||||
|
||||
if dryRunObject.GetKind() == "Secret" {
|
||||
d, err := objectutil.MaskSecret(dryRunObject, "******")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("masking secret data failed, error: %w", err)
|
||||
}
|
||||
dryRunObject = d
|
||||
ex, err := objectutil.MaskSecret(existingObject, "*****")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("masking secret data failed, error: %w", err)
|
||||
}
|
||||
existingObject = ex
|
||||
}
|
||||
|
||||
d, _ := yaml.Marshal(dryRunObject)
|
||||
e, _ := yaml.Marshal(existingObject)
|
||||
cse.Diff = cmp.Diff(string(e), string(d))
|
||||
|
||||
return cse, nil
|
||||
}
|
||||
|
||||
return m.changeSetEntry(dryRunObject, UnchangedAction), nil
|
||||
}
|
||||
|
||||
// hasDrifted detects changes to metadata labels, metadata annotations, spec and webhooks.
|
||||
func (m *ResourceManager) hasDrifted(existingObject, dryRunObject *unstructured.Unstructured) bool {
|
||||
if dryRunObject.GetResourceVersion() == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepDerivative(dryRunObject.GetLabels(), existingObject.GetLabels()) {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepDerivative(dryRunObject.GetAnnotations(), existingObject.GetAnnotations()) {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := existingObject.Object["spec"]; ok {
|
||||
if !apiequality.Semantic.DeepDerivative(dryRunObject.Object["spec"], existingObject.Object["spec"]) {
|
||||
return true
|
||||
}
|
||||
} else if _, ok := existingObject.Object["webhooks"]; ok {
|
||||
if !apiequality.Semantic.DeepDerivative(dryRunObject.Object["webhooks"], existingObject.Object["webhooks"]) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if !apiequality.Semantic.DeepDerivative(dryRunObject.Object, existingObject.Object) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validationError formats the given error and hides sensitive data
|
||||
// if the error was caused by an invalid Kubernetes secrets.
|
||||
func (m *ResourceManager) validationError(object *unstructured.Unstructured, err error) error {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("%s namespace not specified, error: %w", objectutil.FmtUnstructured(object), err)
|
||||
}
|
||||
|
||||
reason := fmt.Sprintf("%v", apierrors.ReasonForError(err))
|
||||
|
||||
if object.GetKind() == "Secret" {
|
||||
msg := "data values must be of type string"
|
||||
if strings.Contains(err.Error(), "immutable") {
|
||||
msg = "secret is immutable"
|
||||
}
|
||||
return fmt.Errorf("%s %s, error: %s", objectutil.FmtUnstructured(object), strings.ToLower(reason), msg)
|
||||
}
|
||||
|
||||
// detect managed field conflict
|
||||
if status, ok := apierrors.StatusCause(err, metav1.CauseTypeFieldManagerConflict); ok {
|
||||
reason = fmt.Sprintf("%v", status.Type)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s dry-run falied, reason: %s, error: %w",
|
||||
objectutil.FmtUnstructured(object), reason, err)
|
||||
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
timeout := 10 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
id := generateName("diff")
|
||||
objects, err := readManifest("testdata/test1.yaml", id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configMapName, configMap := getFirstObject(objects, "ConfigMap", id)
|
||||
secretName, secret := getFirstObject(objects, "Secret", id)
|
||||
|
||||
if err := unstructured.SetNestedField(secret.Object, false, "immutable"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = manager.ApplyAllStaged(ctx, objects, false, timeout); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("generates empty diff for unchanged object", func(t *testing.T) {
|
||||
changeSetEntry, err := manager.Diff(ctx, configMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(configMapName, changeSetEntry.Subject); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(string(UnchangedAction), changeSetEntry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("generates diff for changed object", func(t *testing.T) {
|
||||
newVal := "diff-test"
|
||||
err = unstructured.SetNestedField(configMap.Object, newVal, "data", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changeSetEntry, err := manager.Diff(ctx, configMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(string(ConfiguredAction), changeSetEntry.Action); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if !strings.Contains(changeSetEntry.Diff, newVal) {
|
||||
t.Errorf("Mismatch from expected value, want %s", newVal)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("masks secret values", func(t *testing.T) {
|
||||
newVal := "diff-test"
|
||||
err = unstructured.SetNestedField(secret.Object, newVal, "stringData", "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newKey := "key.new"
|
||||
err = unstructured.SetNestedField(secret.Object, newVal, "stringData", newKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changeSetEntry, err := manager.Diff(ctx, secret)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(secretName, changeSetEntry.Subject); diff != "" {
|
||||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if !strings.Contains(changeSetEntry.Diff, newKey) {
|
||||
t.Errorf("Mismatch from expected value, got %s", changeSetEntry.Diff)
|
||||
}
|
||||
|
||||
if strings.Contains(changeSetEntry.Diff, newVal) {
|
||||
t.Errorf("Mismatch from expected value, got %s", changeSetEntry.Diff)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/objectutil"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Wait checks if the given set of objects has been fully reconciled.
|
||||
func (m *ResourceManager) Wait(objects []*unstructured.Unstructured, interval, timeout time.Duration) error {
|
||||
objectsMeta := object.UnstructuredsToObjMetas(objects)
|
||||
statusCollector := collector.NewResourceStatusCollector(objectsMeta)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
opts := polling.Options{
|
||||
PollInterval: interval,
|
||||
UseCache: true,
|
||||
}
|
||||
eventsChan := m.poller.Poll(ctx, objectsMeta, opts)
|
||||
|
||||
lastStatus := make(map[object.ObjMetadata]*event.ResourceStatus)
|
||||
|
||||
done := statusCollector.ListenWithObserver(eventsChan, collector.ObserverFunc(
|
||||
func(statusCollector *collector.ResourceStatusCollector, e event.Event) {
|
||||
var rss []*event.ResourceStatus
|
||||
for _, rs := range statusCollector.ResourceStatuses {
|
||||
if rs == nil {
|
||||
continue
|
||||
}
|
||||
if rs.Error == nil {
|
||||
lastStatus[rs.Identifier] = rs
|
||||
}
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
desired := status.CurrentStatus
|
||||
aggStatus := aggregator.AggregateStatus(rss, desired)
|
||||
if aggStatus == desired {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
<-done
|
||||
|
||||
if statusCollector.Error != nil {
|
||||
return statusCollector.Error
|
||||
}
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
var errors = []string{}
|
||||
for id, rs := range statusCollector.ResourceStatuses {
|
||||
if rs == nil {
|
||||
errors = append(errors, fmt.Sprintf("can't determine status for %s", objectutil.FmtObjMetadata(id)))
|
||||
continue
|
||||
}
|
||||
if lastStatus[id].Status != status.CurrentStatus {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("%s status: '%s'",
|
||||
objectutil.FmtObjMetadata(rs.Identifier), lastStatus[id].Status))
|
||||
if rs.Error != nil {
|
||||
builder.WriteString(fmt.Sprintf(": %s", rs.Error))
|
||||
}
|
||||
errors = append(errors, builder.String())
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for: [%s]", strings.Join(errors, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForTermination waits for the given objects to be deleted from the cluster.
|
||||
func (m *ResourceManager) WaitForTermination(objects []*unstructured.Unstructured, interval, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
for _, object := range objects {
|
||||
if err := wait.PollImmediate(interval, timeout, m.isDeleted(ctx, object)); err != nil {
|
||||
return fmt.Errorf("%s termination timeout, error: %w", objectutil.FmtUnstructured(object), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ResourceManager) isDeleted(ctx context.Context, object *unstructured.Unstructured) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
obj := object.DeepCopy()
|
||||
err := m.client.Get(ctx, client.ObjectKeyFromObject(obj), obj)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Stefan Prodan
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ssa
|
||||
|
||||
// Owner contains options for setting the field manager and ownership labels group.
|
||||
type Owner struct {
|
||||
// Field sets the field manager name for the given server-side apply patch.
|
||||
Field string
|
||||
|
||||
// Group sets the owner label key prefix.
|
||||
Group string
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
namespace: "%[1]s"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
namespace: "%[1]s"
|
||||
immutable: true
|
||||
stringData:
|
||||
key: "private-key"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
namespace: "%[1]s"
|
||||
data:
|
||||
key: "public-key"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources: ["*"]
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: "%[1]s"
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: "%[1]s"
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "%[1]s"
|
||||
namespace: "%[1]s"
|
Loading…
Reference in New Issue