Use ssa package from fluxcd/pkg

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2021-09-30 18:32:33 +03:00
parent d0222867e6
commit 6346591f02
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
25 changed files with 75 additions and 1769 deletions

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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=

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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:]...)
}

View File

@ -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)}
}

View File

@ -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
}

View File

@ -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)
}
})
}

View File

@ -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
}

View File

@ -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)
}
}
})
}

View File

@ -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)
}

View File

@ -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)
}
})
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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"