mirror of https://github.com/kubernetes/kops.git
Merge pull request #14626 from justinsb/set_apply_operation
Apply: Migration from CSA to SSA; set operation and merge
This commit is contained in:
commit
8695cb4559
3
go.mod
3
go.mod
|
|
@ -57,6 +57,7 @@ require (
|
||||||
helm.sh/helm/v3 v3.10.2
|
helm.sh/helm/v3 v3.10.2
|
||||||
k8s.io/api v0.25.4
|
k8s.io/api v0.25.4
|
||||||
k8s.io/apimachinery v0.25.4
|
k8s.io/apimachinery v0.25.4
|
||||||
|
k8s.io/apiserver v0.25.4
|
||||||
k8s.io/cli-runtime v0.25.4
|
k8s.io/cli-runtime v0.25.4
|
||||||
k8s.io/client-go v0.25.4
|
k8s.io/client-go v0.25.4
|
||||||
k8s.io/cloud-provider-aws v1.25.1
|
k8s.io/cloud-provider-aws v1.25.1
|
||||||
|
|
@ -69,6 +70,7 @@ require (
|
||||||
k8s.io/mount-utils v0.25.4
|
k8s.io/mount-utils v0.25.4
|
||||||
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2
|
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2
|
||||||
sigs.k8s.io/controller-runtime v0.13.1
|
sigs.k8s.io/controller-runtime v0.13.1
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.3
|
||||||
sigs.k8s.io/yaml v1.3.0
|
sigs.k8s.io/yaml v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -248,5 +250,4 @@ require (
|
||||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1345,6 +1345,8 @@ k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwz
|
||||||
k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8=
|
k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8=
|
||||||
k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc=
|
k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc=
|
||||||
k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
|
k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
|
||||||
|
k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo=
|
||||||
|
k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0=
|
||||||
k8s.io/cli-runtime v0.25.4 h1:GTSBN7aKBrc2LqpdO30CmHQqJtRmotxV7XsMSP+QZIk=
|
k8s.io/cli-runtime v0.25.4 h1:GTSBN7aKBrc2LqpdO30CmHQqJtRmotxV7XsMSP+QZIk=
|
||||||
k8s.io/cli-runtime v0.25.4/go.mod h1:JGOw1CR8v4Mcz6cEKA7bFQe0bPrNn1l5sGAX1/Ke4Eg=
|
k8s.io/cli-runtime v0.25.4/go.mod h1:JGOw1CR8v4Mcz6cEKA7bFQe0bPrNn1l5sGAX1/Ke4Eg=
|
||||||
k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8=
|
k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8=
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
)
|
)
|
||||||
|
|
@ -102,6 +100,11 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
||||||
trackers := a.trackers
|
trackers := a.trackers
|
||||||
a.mutex.Unlock()
|
a.mutex.Unlock()
|
||||||
|
|
||||||
|
client := &UnstructuredClient{
|
||||||
|
client: a.client,
|
||||||
|
restMapper: a.restMapper,
|
||||||
|
}
|
||||||
|
|
||||||
results := &ApplyResults{total: len(trackers.items)}
|
results := &ApplyResults{total: len(trackers.items)}
|
||||||
|
|
||||||
for i := range trackers.items {
|
for i := range trackers.items {
|
||||||
|
|
@ -113,54 +116,28 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
||||||
gvk := expectedObject.GroupVersionKind()
|
gvk := expectedObject.GroupVersionKind()
|
||||||
nn := types.NamespacedName{Namespace: ns, Name: name}
|
nn := types.NamespacedName{Namespace: ns, Name: name}
|
||||||
|
|
||||||
restMapping, err := a.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
currentObj, err := client.Get(ctx, gvk, nn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results.applyError(gvk, nn, fmt.Errorf("error getting rest mapping for %v: %w", gvk, err))
|
if !apierrors.IsNotFound(err) {
|
||||||
|
results.applyError(gvk, nn, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gvr := restMapping.Resource
|
}
|
||||||
|
|
||||||
var dynamicResource dynamic.ResourceInterface
|
// If the object exists, we need to update any client-side-apply field-managers
|
||||||
|
// Otherwise we often end up with old and new objects combined, which
|
||||||
switch restMapping.Scope.Name() {
|
// is unexpected and can be invalid.
|
||||||
case meta.RESTScopeNameNamespace:
|
if currentObj != nil {
|
||||||
if ns == "" {
|
managedFields := &ManagedFieldsMigrator{
|
||||||
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
NewManager: "kops",
|
||||||
results.applyError(gvk, nn, fmt.Errorf("namespace was not provided for namespace-scoped object %v", gvk))
|
Client: client,
|
||||||
|
}
|
||||||
|
if err := managedFields.Migrate(ctx, currentObj); err != nil {
|
||||||
|
results.applyError(gvk, nn, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dynamicResource = a.client.Resource(gvr).Namespace(ns)
|
|
||||||
|
|
||||||
case meta.RESTScopeNameRoot:
|
|
||||||
if ns != "" {
|
|
||||||
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
|
||||||
results.applyError(gvk, nn, fmt.Errorf("namespace %q was provided for cluster-scoped object %v", expectedObject.GetNamespace(), gvk))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dynamicResource = a.client.Resource(gvr)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Internal error ... this is panic-level
|
|
||||||
return nil, fmt.Errorf("unknown scope for gvk %s: %q", gvk, restMapping.Scope.Name())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentObj, err := dynamicResource.Get(ctx, name, v1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if !errors.IsNotFound(err) {
|
|
||||||
return nil, fmt.Errorf("could not get existing object: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsonData, err := createManagedFieldPatch(currentObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create json patch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonData != nil {
|
|
||||||
_, err := dynamicResource.Patch(ctx, name, types.MergePatchType, jsonData, v1.PatchOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to patch object %q: %w", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j, err := json.Marshal(expectedObject)
|
j, err := json.Marshal(expectedObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
||||||
|
|
@ -168,7 +145,7 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lastApplied, err := dynamicResource.Patch(ctx, name, types.ApplyPatchType, j, a.patchOptions)
|
lastApplied, err := client.Patch(ctx, gvk, nn, types.ApplyPatchType, j, a.patchOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results.applyError(gvk, nn, fmt.Errorf("error from apply: %w", err))
|
results.applyError(gvk, nn, fmt.Errorf("error from apply: %w", err))
|
||||||
continue
|
continue
|
||||||
|
|
@ -181,31 +158,3 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createManagedFieldPatch(currentObject *unstructured.Unstructured) ([]byte, error) {
|
|
||||||
if currentObject == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
fixedManagedFields := []v1.ManagedFieldsEntry{}
|
|
||||||
for _, managedField := range currentObject.GetManagedFields() {
|
|
||||||
fixedManagedField := managedField.DeepCopy()
|
|
||||||
if managedField.Manager == "kubectl-edit" || managedField.Manager == "kubectl-client-side-apply" {
|
|
||||||
fixedManagedField.Manager = "kops"
|
|
||||||
}
|
|
||||||
fixedManagedFields = append(fixedManagedFields, *fixedManagedField)
|
|
||||||
}
|
|
||||||
if len(fixedManagedFields) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
meta := &v1.ObjectMeta{}
|
|
||||||
meta.SetManagedFields(fixedManagedFields)
|
|
||||||
patchObject := map[string]interface{}{
|
|
||||||
"metadata": meta,
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonData, err := json.Marshal(patchObject)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marsal %q into json: %w", currentObject.GetName(), err)
|
|
||||||
}
|
|
||||||
return jsonData, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 applyset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManagedFieldsMigrator manages the migration of field managers from client-side managers to the server-side manager.
|
||||||
|
type ManagedFieldsMigrator struct {
|
||||||
|
Client *UnstructuredClient
|
||||||
|
NewManager string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate migrates from client-side field managers to the NewManager (with an Apply operation).
|
||||||
|
// This is needed to move from client-side apply to server-side apply.
|
||||||
|
func (m *ManagedFieldsMigrator) Migrate(ctx context.Context, obj *unstructured.Unstructured) error {
|
||||||
|
managedFieldPatch, err := m.createManagedFieldPatch(obj)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create managed-fields patch: %w", err)
|
||||||
|
}
|
||||||
|
if managedFieldPatch != nil {
|
||||||
|
gvk := obj.GroupVersionKind()
|
||||||
|
nn := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
|
||||||
|
_, err := m.Client.Patch(ctx, gvk, nn, types.MergePatchType, managedFieldPatch, metav1.PatchOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to patch object managed-fields for %q: %w", obj.GetName(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createManagedFieldPatch constructs a patch to combine managed fields.
|
||||||
|
// It returns nil if no patch is needed.
|
||||||
|
func (m *ManagedFieldsMigrator) createManagedFieldPatch(currentObject *unstructured.Unstructured) ([]byte, error) {
|
||||||
|
if currentObject == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
needPatch := false
|
||||||
|
fixedManagedFields := []metav1.ManagedFieldsEntry{}
|
||||||
|
for _, managedField := range currentObject.GetManagedFields() {
|
||||||
|
fixedManagedField := managedField.DeepCopy()
|
||||||
|
if managedField.Manager == "kubectl-edit" || managedField.Manager == "kubectl-client-side-apply" {
|
||||||
|
needPatch = true
|
||||||
|
fixedManagedField.Manager = m.NewManager
|
||||||
|
fixedManagedField.Operation = metav1.ManagedFieldsOperationApply
|
||||||
|
}
|
||||||
|
// In case we have an existing Update operation
|
||||||
|
if fixedManagedField.Manager == m.NewManager && fixedManagedField.Operation == "Update" {
|
||||||
|
needPatch = true
|
||||||
|
fixedManagedField.Operation = metav1.ManagedFieldsOperationApply
|
||||||
|
}
|
||||||
|
fixedManagedFields = append(fixedManagedFields, *fixedManagedField)
|
||||||
|
}
|
||||||
|
if !needPatch {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
merged, err := mergeFieldManagers(fixedManagedFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fixedManagedFields = merged
|
||||||
|
|
||||||
|
// Ensure patch is stable, mostly for tests
|
||||||
|
sort.Slice(fixedManagedFields, func(i, j int) bool {
|
||||||
|
if fixedManagedFields[i].Manager != fixedManagedFields[j].Manager {
|
||||||
|
return fixedManagedFields[i].Manager < fixedManagedFields[j].Manager
|
||||||
|
}
|
||||||
|
if fixedManagedFields[i].Subresource != fixedManagedFields[j].Subresource {
|
||||||
|
return fixedManagedFields[i].Subresource < fixedManagedFields[j].Subresource
|
||||||
|
}
|
||||||
|
if fixedManagedFields[i].Operation != fixedManagedFields[j].Operation {
|
||||||
|
return fixedManagedFields[i].Operation < fixedManagedFields[j].Operation
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
meta := &metav1.ObjectMeta{}
|
||||||
|
meta.SetManagedFields(fixedManagedFields)
|
||||||
|
patchObject := map[string]interface{}{
|
||||||
|
"metadata": meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalIndent is a little less efficient, but makes this much more readable (also helps tests)
|
||||||
|
jsonData, err := json.MarshalIndent(patchObject, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marsal %q into json: %w", currentObject.GetName(), err)
|
||||||
|
}
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldManagerKey is the primary key for a ManagedFieldEntry
|
||||||
|
type fieldManagerKey struct {
|
||||||
|
Manager string
|
||||||
|
Operation metav1.ManagedFieldsOperationType
|
||||||
|
Subresource string
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeFieldManagers merges the managed fields from identical field managers.
|
||||||
|
// If we don't do this, the apiserver will not currently construct the union for duplicate keys.
|
||||||
|
func mergeFieldManagers(managedFields []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) {
|
||||||
|
byKey := make(map[fieldManagerKey][]metav1.ManagedFieldsEntry)
|
||||||
|
for _, f := range managedFields {
|
||||||
|
k := fieldManagerKey{
|
||||||
|
Manager: f.Manager,
|
||||||
|
Operation: f.Operation,
|
||||||
|
Subresource: f.Subresource,
|
||||||
|
}
|
||||||
|
|
||||||
|
byKey[k] = append(byKey[k], f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []metav1.ManagedFieldsEntry
|
||||||
|
for k := range byKey {
|
||||||
|
managers := byKey[k]
|
||||||
|
if len(managers) > 1 {
|
||||||
|
fieldSet, err := mergeManagedFields(managers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encoded, err := fieldSet.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
managers[0].FieldsV1.Raw = encoded
|
||||||
|
}
|
||||||
|
result = append(result, managers[0])
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeManagedFields merges a set of ManagedFieldEntry managed fields, that are expected to have the same key.
|
||||||
|
func mergeManagedFields(managedFields []metav1.ManagedFieldsEntry) (*fieldpath.Set, error) {
|
||||||
|
if len(managedFields) == 0 {
|
||||||
|
return nil, fmt.Errorf("no managed fields supplied")
|
||||||
|
}
|
||||||
|
|
||||||
|
union, err := toFieldPathSet(&managedFields[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range managedFields {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := &managedFields[i]
|
||||||
|
if managedFields[0].APIVersion != m.APIVersion {
|
||||||
|
return nil, fmt.Errorf("cannot merge ManagedFieldsEntry apiVersion %q with apiVersion %q", managedFields[0].APIVersion, m.APIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
set, err := toFieldPathSet(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
union = union.Union(set)
|
||||||
|
}
|
||||||
|
return union, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toFieldPathSet converts an encoded ManagedFieldsEntry to a set of managed fields (a fieldpath.Set)
|
||||||
|
func toFieldPathSet(fields *metav1.ManagedFieldsEntry) (*fieldpath.Set, error) {
|
||||||
|
decoded, err := fieldmanager.DecodeManagedFields([]metav1.ManagedFieldsEntry{*fields})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(decoded.Fields()) != 1 {
|
||||||
|
return nil, fmt.Errorf("expected a single managed fields entry, but got %d", len(decoded.Fields()))
|
||||||
|
}
|
||||||
|
for _, fieldSet := range decoded.Fields() {
|
||||||
|
return fieldSet.Set(), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no fields were decoded")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 applyset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"k8s.io/kops/pkg/testutils/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManagedFieldsMigrator(t *testing.T) {
|
||||||
|
testdataBaseDir := "testdata/managedfields"
|
||||||
|
entries, err := os.ReadDir(testdataBaseDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read %q: %v", testdataBaseDir, err)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
testdataDir := filepath.Join(testdataBaseDir, entry.Name())
|
||||||
|
t.Run(entry.Name(), func(t *testing.T) {
|
||||||
|
p := filepath.Join(testdataDir, "object.yaml")
|
||||||
|
b, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read %q: %v", p, err)
|
||||||
|
}
|
||||||
|
obj := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal(b, &obj.Object); err != nil {
|
||||||
|
t.Fatalf("failed to parse %q: %v", p, err)
|
||||||
|
}
|
||||||
|
migrator := &ManagedFieldsMigrator{NewManager: "new-manager"}
|
||||||
|
patch, err := migrator.createManagedFieldPatch(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error from createManagedFieldPatch: %v", err)
|
||||||
|
}
|
||||||
|
patchString := ""
|
||||||
|
if patch != nil {
|
||||||
|
patchString = string(patch)
|
||||||
|
}
|
||||||
|
expectedPatch := filepath.Join(testdataDir, "patch.json")
|
||||||
|
golden.AssertMatchesFile(t, patchString, expectedPatch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: kube-dns
|
||||||
|
namespace: testmanagedfieldsmigrator
|
||||||
|
creationTimestamp: "2022-11-23T14:50:41Z"
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
addon.kops.k8s.io/name: coredns.addons.k8s.io
|
||||||
|
app.kubernetes.io/managed-by: kops
|
||||||
|
k8s-addon: coredns.addons.k8s.io
|
||||||
|
managedFields:
|
||||||
|
- apiVersion: policy/v1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:status":{"f:conditions":{".":{},"k:{\"type\":\"DisruptionAllowed\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:observedGeneration":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:desiredHealthy":{},"f:observedGeneration":{}}}
|
||||||
|
manager: kube-controller-manager
|
||||||
|
operation: Update
|
||||||
|
subresource: status
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
- apiVersion: policy/v1beta1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:addon.kops.k8s.io/name":{},"f:app.kubernetes.io/managed-by":{},"f:k8s-addon":{}}},"f:spec":{"f:minAvailable":{},"f:selector":{}}}
|
||||||
|
manager: kubectl-client-side-apply
|
||||||
|
operation: Update
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
spec:
|
||||||
|
minAvailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"creationTimestamp": null,
|
||||||
|
"managedFields": [
|
||||||
|
{
|
||||||
|
"manager": "kube-controller-manager",
|
||||||
|
"operation": "Update",
|
||||||
|
"apiVersion": "policy/v1",
|
||||||
|
"time": "2022-11-23T14:50:41Z",
|
||||||
|
"fieldsType": "FieldsV1",
|
||||||
|
"fieldsV1": {
|
||||||
|
"f:status": {
|
||||||
|
"f:conditions": {
|
||||||
|
".": {},
|
||||||
|
"k:{\"type\":\"DisruptionAllowed\"}": {
|
||||||
|
".": {},
|
||||||
|
"f:lastTransitionTime": {},
|
||||||
|
"f:message": {},
|
||||||
|
"f:observedGeneration": {},
|
||||||
|
"f:reason": {},
|
||||||
|
"f:status": {},
|
||||||
|
"f:type": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:desiredHealthy": {},
|
||||||
|
"f:observedGeneration": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subresource": "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manager": "new-manager",
|
||||||
|
"operation": "Apply",
|
||||||
|
"apiVersion": "policy/v1beta1",
|
||||||
|
"time": "2022-11-23T14:50:41Z",
|
||||||
|
"fieldsType": "FieldsV1",
|
||||||
|
"fieldsV1": {
|
||||||
|
"f:metadata": {
|
||||||
|
"f:annotations": {
|
||||||
|
".": {},
|
||||||
|
"f:kubectl.kubernetes.io/last-applied-configuration": {}
|
||||||
|
},
|
||||||
|
"f:labels": {
|
||||||
|
".": {},
|
||||||
|
"f:addon.kops.k8s.io/name": {},
|
||||||
|
"f:app.kubernetes.io/managed-by": {},
|
||||||
|
"f:k8s-addon": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:spec": {
|
||||||
|
"f:minAvailable": {},
|
||||||
|
"f:selector": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: kube-dns
|
||||||
|
namespace: testmanagedfieldsmigrator
|
||||||
|
creationTimestamp: "2022-11-23T14:50:41Z"
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
addon.kops.k8s.io/name: coredns.addons.k8s.io
|
||||||
|
app.kubernetes.io/managed-by: kops
|
||||||
|
k8s-addon: coredns.addons.k8s.io
|
||||||
|
managedFields:
|
||||||
|
- apiVersion: policy/v1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:status":{"f:conditions":{".":{},"k:{\"type\":\"DisruptionAllowed\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:observedGeneration":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:desiredHealthy":{},"f:observedGeneration":{}}}
|
||||||
|
manager: kube-controller-manager
|
||||||
|
operation: Update
|
||||||
|
subresource: status
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
- apiVersion: policy/v1beta1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:addon.kops.k8s.io/name":{},"f:app.kubernetes.io/managed-by":{},"f:k8s-addon":{}}}}
|
||||||
|
manager: kubectl-client-side-apply
|
||||||
|
operation: Update
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
- apiVersion: policy/v1beta1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{"f:minAvailable":{},"f:selector":{}}}
|
||||||
|
manager: kubectl-edit
|
||||||
|
operation: Update
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
spec:
|
||||||
|
minAvailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"creationTimestamp": null,
|
||||||
|
"managedFields": [
|
||||||
|
{
|
||||||
|
"manager": "kube-controller-manager",
|
||||||
|
"operation": "Update",
|
||||||
|
"apiVersion": "policy/v1",
|
||||||
|
"time": "2022-11-23T14:50:41Z",
|
||||||
|
"fieldsType": "FieldsV1",
|
||||||
|
"fieldsV1": {
|
||||||
|
"f:status": {
|
||||||
|
"f:conditions": {
|
||||||
|
".": {},
|
||||||
|
"k:{\"type\":\"DisruptionAllowed\"}": {
|
||||||
|
".": {},
|
||||||
|
"f:lastTransitionTime": {},
|
||||||
|
"f:message": {},
|
||||||
|
"f:observedGeneration": {},
|
||||||
|
"f:reason": {},
|
||||||
|
"f:status": {},
|
||||||
|
"f:type": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:desiredHealthy": {},
|
||||||
|
"f:observedGeneration": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subresource": "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manager": "new-manager",
|
||||||
|
"operation": "Apply",
|
||||||
|
"apiVersion": "policy/v1beta1",
|
||||||
|
"time": "2022-11-23T14:50:41Z",
|
||||||
|
"fieldsType": "FieldsV1",
|
||||||
|
"fieldsV1": {
|
||||||
|
"f:metadata": {
|
||||||
|
"f:annotations": {
|
||||||
|
".": {},
|
||||||
|
"f:kubectl.kubernetes.io/last-applied-configuration": {}
|
||||||
|
},
|
||||||
|
"f:labels": {
|
||||||
|
".": {},
|
||||||
|
"f:addon.kops.k8s.io/name": {},
|
||||||
|
"f:app.kubernetes.io/managed-by": {},
|
||||||
|
"f:k8s-addon": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:spec": {
|
||||||
|
"f:minAvailable": {},
|
||||||
|
"f:selector": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: kube-dns
|
||||||
|
namespace: testmanagedfieldsmigrator
|
||||||
|
creationTimestamp: "2022-11-23T14:50:41Z"
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
addon.kops.k8s.io/name: coredns.addons.k8s.io
|
||||||
|
app.kubernetes.io/managed-by: kops
|
||||||
|
k8s-addon: coredns.addons.k8s.io
|
||||||
|
managedFields:
|
||||||
|
- apiVersion: policy/v1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:status":{"f:conditions":{".":{},"k:{\"type\":\"DisruptionAllowed\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:observedGeneration":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:desiredHealthy":{},"f:observedGeneration":{}}}
|
||||||
|
manager: kube-controller-manager
|
||||||
|
operation: Update
|
||||||
|
subresource: status
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
- apiVersion: policy/v1beta1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:addon.kops.k8s.io/name":{},"f:app.kubernetes.io/managed-by":{},"f:k8s-addon":{}}},"f:spec":{"f:minAvailable":{},"f:selector":{}}}
|
||||||
|
manager: new-manager
|
||||||
|
operation: Apply
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
spec:
|
||||||
|
minAvailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: kube-dns
|
||||||
|
namespace: testmanagedfieldsmigrator
|
||||||
|
creationTimestamp: "2022-11-23T14:50:41Z"
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
addon.kops.k8s.io/name: coredns.addons.k8s.io
|
||||||
|
app.kubernetes.io/managed-by: kops
|
||||||
|
k8s-addon: coredns.addons.k8s.io
|
||||||
|
managedFields:
|
||||||
|
- apiVersion: policy/v1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:status":{"f:conditions":{".":{},"k:{\"type\":\"DisruptionAllowed\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:observedGeneration":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:desiredHealthy":{},"f:observedGeneration":{}}}
|
||||||
|
manager: kube-controller-manager
|
||||||
|
operation: Update
|
||||||
|
subresource: status
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
- apiVersion: policy/v1beta1
|
||||||
|
fieldsType: FieldsV1
|
||||||
|
fieldsV1: {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:addon.kops.k8s.io/name":{},"f:app.kubernetes.io/managed-by":{},"f:k8s-addon":{}}},"f:spec":{"f:minAvailable":{},"f:selector":{}}}
|
||||||
|
manager: some-other-manager
|
||||||
|
operation: Update
|
||||||
|
time: "2022-11-23T14:50:41Z"
|
||||||
|
spec:
|
||||||
|
minAvailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 applyset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnstructuredClient is a client that makes it easier to work with unstructured objects.
|
||||||
|
// It is similar to client.Client in controller-runtime, but is never cached.
|
||||||
|
type UnstructuredClient struct {
|
||||||
|
// client is the dynamic kubernetes client used to apply objects to the k8s cluster.
|
||||||
|
client dynamic.Interface
|
||||||
|
// restMapper is used to map object kind to resources, and to know if objects are cluster-scoped.
|
||||||
|
restMapper meta.RESTMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnstructuredClient constructs an UnstructuredClient
|
||||||
|
func NewUnstructuredClient(options Options) *UnstructuredClient {
|
||||||
|
return &UnstructuredClient{
|
||||||
|
client: options.Client,
|
||||||
|
restMapper: options.RESTMapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamicResource is a helper to get the resource for a gvk (with the namespace)
|
||||||
|
// It returns an error if a namespace is provided for a cluster-scoped resource,
|
||||||
|
// or no namespace is provided for a namespace-scoped resource.
|
||||||
|
func (c *UnstructuredClient) dynamicResource(ctx context.Context, gvk schema.GroupVersionKind, ns string) (dynamic.ResourceInterface, error) {
|
||||||
|
restMapping, err := c.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting rest mapping for %v: %w", gvk, err)
|
||||||
|
}
|
||||||
|
gvr := restMapping.Resource
|
||||||
|
|
||||||
|
switch restMapping.Scope.Name() {
|
||||||
|
case meta.RESTScopeNameNamespace:
|
||||||
|
if ns == "" {
|
||||||
|
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
||||||
|
return nil, fmt.Errorf("namespace was not provided for namespace-scoped object %v", gvk)
|
||||||
|
}
|
||||||
|
return c.client.Resource(gvr).Namespace(ns), nil
|
||||||
|
|
||||||
|
case meta.RESTScopeNameRoot:
|
||||||
|
if ns != "" {
|
||||||
|
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
||||||
|
return nil, fmt.Errorf("namespace %q was provided for cluster-scoped object %v", ns, gvk)
|
||||||
|
}
|
||||||
|
return c.client.Resource(gvr), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Internal error ... this is panic-level
|
||||||
|
return nil, fmt.Errorf("unknown scope for gvk %s: %q", gvk, restMapping.Scope.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch performs a Patch operation, used for server-side apply and client-side patch.
|
||||||
|
func (c *UnstructuredClient) Patch(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName, patchType types.PatchType, data []byte, opt metav1.PatchOptions) (*unstructured.Unstructured, error) {
|
||||||
|
dynamicResource, err := c.dynamicResource(ctx, gvk, nn.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := nn.Name
|
||||||
|
patched, err := dynamicResource.Patch(ctx, name, patchType, data, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error patching object: %w", err)
|
||||||
|
}
|
||||||
|
return patched, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update performs an Update operation on the object. Generally we should prefer server-side-apply.
|
||||||
|
func (c *UnstructuredClient) Update(ctx context.Context, obj *unstructured.Unstructured, opt metav1.UpdateOptions) (*unstructured.Unstructured, error) {
|
||||||
|
gvk := obj.GroupVersionKind()
|
||||||
|
dynamicResource, err := c.dynamicResource(ctx, gvk, obj.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := dynamicResource.Update(ctx, obj, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error updating object: %w", err)
|
||||||
|
}
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get reads the specified object.
|
||||||
|
func (c *UnstructuredClient) Get(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName) (*unstructured.Unstructured, error) {
|
||||||
|
dynamicResource, err := c.dynamicResource(ctx, gvk, nn.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := dynamicResource.Get(ctx, nn.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get existing object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store)
|
||||||
|
var NameMayNotBe = []string{".", ".."}
|
||||||
|
|
||||||
|
// NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store)
|
||||||
|
var NameMayNotContain = []string{"/", "%"}
|
||||||
|
|
||||||
|
// IsValidPathSegmentName validates the name can be safely encoded as a path segment
|
||||||
|
func IsValidPathSegmentName(name string) []string {
|
||||||
|
for _, illegalName := range NameMayNotBe {
|
||||||
|
if name == illegalName {
|
||||||
|
return []string{fmt.Sprintf(`may not be '%s'`, illegalName)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors []string
|
||||||
|
for _, illegalContent := range NameMayNotContain {
|
||||||
|
if strings.Contains(name, illegalContent) {
|
||||||
|
errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment
|
||||||
|
// It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid
|
||||||
|
func IsValidPathSegmentPrefix(name string) []string {
|
||||||
|
var errors []string
|
||||||
|
for _, illegalContent := range NameMayNotContain {
|
||||||
|
if strings.Contains(name, illegalContent) {
|
||||||
|
errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePathSegmentName validates the name can be safely encoded as a path segment
|
||||||
|
func ValidatePathSegmentName(name string, prefix bool) []string {
|
||||||
|
if prefix {
|
||||||
|
return IsValidPathSegmentPrefix(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsValidPathSegmentName(name)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attributesRecord struct {
|
||||||
|
kind schema.GroupVersionKind
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
resource schema.GroupVersionResource
|
||||||
|
subresource string
|
||||||
|
operation Operation
|
||||||
|
options runtime.Object
|
||||||
|
dryRun bool
|
||||||
|
object runtime.Object
|
||||||
|
oldObject runtime.Object
|
||||||
|
userInfo user.Info
|
||||||
|
|
||||||
|
// other elements are always accessed in single goroutine.
|
||||||
|
// But ValidatingAdmissionWebhook add annotations concurrently.
|
||||||
|
annotations map[string]annotation
|
||||||
|
annotationsLock sync.RWMutex
|
||||||
|
|
||||||
|
reinvocationContext ReinvocationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type annotation struct {
|
||||||
|
level auditinternal.Level
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
||||||
|
return &attributesRecord{
|
||||||
|
kind: kind,
|
||||||
|
namespace: namespace,
|
||||||
|
name: name,
|
||||||
|
resource: resource,
|
||||||
|
subresource: subresource,
|
||||||
|
operation: operation,
|
||||||
|
options: operationOptions,
|
||||||
|
dryRun: dryRun,
|
||||||
|
object: object,
|
||||||
|
oldObject: oldObject,
|
||||||
|
userInfo: userInfo,
|
||||||
|
reinvocationContext: &reinvocationContext{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetKind() schema.GroupVersionKind {
|
||||||
|
return record.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetNamespace() string {
|
||||||
|
return record.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetName() string {
|
||||||
|
return record.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetResource() schema.GroupVersionResource {
|
||||||
|
return record.resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetSubresource() string {
|
||||||
|
return record.subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetOperation() Operation {
|
||||||
|
return record.operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetOperationOptions() runtime.Object {
|
||||||
|
return record.options
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) IsDryRun() bool {
|
||||||
|
return record.dryRun
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetObject() runtime.Object {
|
||||||
|
return record.object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetOldObject() runtime.Object {
|
||||||
|
return record.oldObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetUserInfo() user.Info {
|
||||||
|
return record.userInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAnnotations implements privateAnnotationsGetter.It's a private method used
|
||||||
|
// by WithAudit decorator.
|
||||||
|
func (record *attributesRecord) getAnnotations(maxLevel auditinternal.Level) map[string]string {
|
||||||
|
record.annotationsLock.RLock()
|
||||||
|
defer record.annotationsLock.RUnlock()
|
||||||
|
|
||||||
|
if record.annotations == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cp := make(map[string]string, len(record.annotations))
|
||||||
|
for key, value := range record.annotations {
|
||||||
|
if value.level.Less(maxLevel) || value.level == maxLevel {
|
||||||
|
cp[key] = value.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAnnotation adds an annotation to attributesRecord with Metadata audit level
|
||||||
|
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
||||||
|
return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
|
||||||
|
if err := checkKeyFormat(key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
|
||||||
|
}
|
||||||
|
record.annotationsLock.Lock()
|
||||||
|
defer record.annotationsLock.Unlock()
|
||||||
|
|
||||||
|
if record.annotations == nil {
|
||||||
|
record.annotations = make(map[string]annotation)
|
||||||
|
}
|
||||||
|
annotation := annotation{level: level, value: value}
|
||||||
|
if v, ok := record.annotations[key]; ok && v != annotation {
|
||||||
|
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
|
||||||
|
}
|
||||||
|
record.annotations[key] = annotation
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetReinvocationContext() ReinvocationContext {
|
||||||
|
return record.reinvocationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type reinvocationContext struct {
|
||||||
|
// isReinvoke is true when admission plugins are being reinvoked
|
||||||
|
isReinvoke bool
|
||||||
|
// reinvokeRequested is true when an admission plugin requested a re-invocation of the chain
|
||||||
|
reinvokeRequested bool
|
||||||
|
// values stores reinvoke context values per plugin.
|
||||||
|
values map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) IsReinvoke() bool {
|
||||||
|
return rc.isReinvoke
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) SetIsReinvoke() {
|
||||||
|
rc.isReinvoke = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) ShouldReinvoke() bool {
|
||||||
|
return rc.reinvokeRequested
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) SetShouldReinvoke() {
|
||||||
|
rc.reinvokeRequested = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) SetValue(plugin string, v interface{}) {
|
||||||
|
if rc.values == nil {
|
||||||
|
rc.values = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
rc.values[plugin] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reinvocationContext) Value(plugin string) interface{} {
|
||||||
|
return rc.values[plugin]
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkKeyFormat(key string) error {
|
||||||
|
parts := strings.Split(key, "/")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("annotation key has invalid format, the right format is a DNS subdomain prefix and '/' and key name. (e.g. 'podsecuritypolicy.admission.k8s.io/admit-policy')")
|
||||||
|
}
|
||||||
|
if msgs := validation.IsQualifiedName(key); len(msgs) != 0 {
|
||||||
|
return fmt.Errorf("annotation key has invalid format %s. A qualified name like 'podsecuritypolicy.admission.k8s.io/admit-policy' is required.", strings.Join(msgs, ","))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// auditHandler logs annotations set by other admission handlers
|
||||||
|
type auditHandler struct {
|
||||||
|
Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Interface = &auditHandler{}
|
||||||
|
var _ MutationInterface = &auditHandler{}
|
||||||
|
var _ ValidationInterface = &auditHandler{}
|
||||||
|
|
||||||
|
// WithAudit is a decorator for a admission phase. It saves annotations
|
||||||
|
// of attribute into the audit event. Attributes passed to the Admit and
|
||||||
|
// Validate function must be instance of privateAnnotationsGetter or
|
||||||
|
// AnnotationsGetter, otherwise an error is returned.
|
||||||
|
func WithAudit(i Interface) Interface {
|
||||||
|
if i == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return &auditHandler{Interface: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
if !handler.Interface.Handles(a.GetOperation()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureAnnotationGetter(a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if mutator, ok := handler.Interface.(MutationInterface); ok {
|
||||||
|
err = mutator.Admit(ctx, a, o)
|
||||||
|
handler.logAnnotations(ctx, a)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *auditHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
if !handler.Interface.Handles(a.GetOperation()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureAnnotationGetter(a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if validator, ok := handler.Interface.(ValidationInterface); ok {
|
||||||
|
err = validator.Validate(ctx, a, o)
|
||||||
|
handler.logAnnotations(ctx, a)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureAnnotationGetter(a Attributes) error {
|
||||||
|
_, okPrivate := a.(privateAnnotationsGetter)
|
||||||
|
_, okPublic := a.(AnnotationsGetter)
|
||||||
|
if okPrivate || okPublic {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("attributes must be an instance of privateAnnotationsGetter or AnnotationsGetter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *auditHandler) logAnnotations(ctx context.Context, a Attributes) {
|
||||||
|
ae := audit.AuditEventFrom(ctx)
|
||||||
|
if ae == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var annotations map[string]string
|
||||||
|
switch a := a.(type) {
|
||||||
|
case privateAnnotationsGetter:
|
||||||
|
annotations = a.getAnnotations(ae.Level)
|
||||||
|
case AnnotationsGetter:
|
||||||
|
annotations = a.GetAnnotations(ae.Level)
|
||||||
|
default:
|
||||||
|
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
audit.AddAuditAnnotationsMap(ctx, annotations)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 admission
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
|
||||||
|
// a chain of admission handlers
|
||||||
|
type chainAdmissionHandler []Interface
|
||||||
|
|
||||||
|
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
||||||
|
func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
|
||||||
|
return chainAdmissionHandler(handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||||
|
func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
for _, handler := range admissionHandler {
|
||||||
|
if !handler.Handles(a.GetOperation()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mutator, ok := handler.(MutationInterface); ok {
|
||||||
|
err := mutator.Admit(ctx, a, o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||||
|
func (admissionHandler chainAdmissionHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
for _, handler := range admissionHandler {
|
||||||
|
if !handler.Handles(a.GetOperation()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if validator, ok := handler.(ValidationInterface); ok {
|
||||||
|
err := validator.Validate(ctx, a, o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles will return true if any of the handlers handles the given operation
|
||||||
|
func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool {
|
||||||
|
for _, handler := range admissionHandler {
|
||||||
|
if handler.Handles(operation) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeAbs(path, base string) (string, error) {
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
if len(base) == 0 || base == "." {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
base = cwd
|
||||||
|
}
|
||||||
|
return filepath.Join(base, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAdmissionConfiguration reads the admission configuration at the specified path.
|
||||||
|
// It returns the loaded admission configuration if the input file aligns with the required syntax.
|
||||||
|
// If it does not align with the provided syntax, it returns a default configuration for the enumerated
|
||||||
|
// set of pluginNames whose config location references the specified configFilePath.
|
||||||
|
// It does this to preserve backward compatibility when admission control files were opaque.
|
||||||
|
// It returns an error if the file did not exist.
|
||||||
|
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) {
|
||||||
|
if configFilePath == "" {
|
||||||
|
return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
|
||||||
|
}
|
||||||
|
// a file was provided, so we just read it.
|
||||||
|
data, err := ioutil.ReadFile(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
|
||||||
|
}
|
||||||
|
codecs := serializer.NewCodecFactory(configScheme)
|
||||||
|
decoder := codecs.UniversalDecoder()
|
||||||
|
decodedObj, err := runtime.Decode(decoder, data)
|
||||||
|
// we were able to decode the file successfully
|
||||||
|
if err == nil {
|
||||||
|
decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||||
|
}
|
||||||
|
baseDir := path.Dir(configFilePath)
|
||||||
|
for i := range decodedConfig.Plugins {
|
||||||
|
if decodedConfig.Plugins[i].Path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// we update relative file paths to absolute paths
|
||||||
|
absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decodedConfig.Plugins[i].Path = absPath
|
||||||
|
}
|
||||||
|
return configProvider{
|
||||||
|
config: decodedConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// we got an error where the decode wasn't related to a missing type
|
||||||
|
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only tolerate load errors if the file appears to be one of the two legacy plugin configs
|
||||||
|
unstructuredData := map[string]interface{}{}
|
||||||
|
if err2 := yaml.Unmarshal(data, &unstructuredData); err2 != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, isLegacyImagePolicy := unstructuredData["imagePolicy"]
|
||||||
|
_, isLegacyPodNodeSelector := unstructuredData["podNodeSelectorPluginConfig"]
|
||||||
|
if !isLegacyImagePolicy && !isLegacyPodNodeSelector {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the legacy format to the new admission control format
|
||||||
|
// in order to preserve backwards compatibility, we set plugins that
|
||||||
|
// previously read input from a non-versioned file configuration to the
|
||||||
|
// current input file.
|
||||||
|
legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector")
|
||||||
|
externalConfig := &apiserverv1.AdmissionConfiguration{}
|
||||||
|
for _, pluginName := range pluginNames {
|
||||||
|
if legacyPluginsWithUnversionedConfig.Has(pluginName) {
|
||||||
|
externalConfig.Plugins = append(externalConfig.Plugins,
|
||||||
|
apiserverv1.AdmissionPluginConfiguration{
|
||||||
|
Name: pluginName,
|
||||||
|
Path: configFilePath})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configScheme.Default(externalConfig)
|
||||||
|
internalConfig := &apiserver.AdmissionConfiguration{}
|
||||||
|
if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return configProvider{
|
||||||
|
config: internalConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type configProvider struct {
|
||||||
|
config *apiserver.AdmissionConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
|
||||||
|
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
|
||||||
|
// if there is a nest object, return it directly
|
||||||
|
if pluginCfg.Configuration != nil {
|
||||||
|
return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil
|
||||||
|
}
|
||||||
|
// there is nothing nested, so we delegate to path
|
||||||
|
if pluginCfg.Path != "" {
|
||||||
|
content, err := ioutil.ReadFile(pluginCfg.Path)
|
||||||
|
if err != nil {
|
||||||
|
klog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes.NewBuffer(content), nil
|
||||||
|
}
|
||||||
|
// there is no special config at all
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigFor returns a reader for the specified plugin.
|
||||||
|
// If no specific configuration is present, we return a nil reader.
|
||||||
|
func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
|
||||||
|
// there is no config, so there is no potential config
|
||||||
|
if p.config == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// look for matching plugin and get configuration
|
||||||
|
for _, pluginCfg := range p.config.Plugins {
|
||||||
|
if pluginName != pluginCfg.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pluginConfig, nil
|
||||||
|
}
|
||||||
|
// there is no registered config that matches on plugin name.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
type Decorator interface {
|
||||||
|
Decorate(handler Interface, name string) Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecoratorFunc func(handler Interface, name string) Interface
|
||||||
|
|
||||||
|
func (d DecoratorFunc) Decorate(handler Interface, name string) Interface {
|
||||||
|
return d(handler, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decorators []Decorator
|
||||||
|
|
||||||
|
// Decorate applies the decorator in inside-out order, i.e. the first decorator in the slice is first applied to the given handler.
|
||||||
|
func (d Decorators) Decorate(handler Interface, name string) Interface {
|
||||||
|
result := handler
|
||||||
|
for _, d := range d {
|
||||||
|
result = d.Decorate(result, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func extractResourceName(a Attributes) (name string, resource schema.GroupResource, err error) {
|
||||||
|
resource = a.GetResource().GroupResource()
|
||||||
|
|
||||||
|
if len(a.GetName()) > 0 {
|
||||||
|
return a.GetName(), resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "Unknown"
|
||||||
|
obj := a.GetObject()
|
||||||
|
if obj != nil {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
// not all object have ObjectMeta. If we don't, return a name with a slash (always illegal)
|
||||||
|
return "Unknown/errorGettingName", resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is necessary because name object name generation has not occurred yet
|
||||||
|
if len(accessor.GetName()) > 0 {
|
||||||
|
name = accessor.GetName()
|
||||||
|
} else if len(accessor.GetGenerateName()) > 0 {
|
||||||
|
name = accessor.GetGenerateName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForbidden is a utility function to return a well-formatted admission control error response
|
||||||
|
func NewForbidden(a Attributes, internalError error) error {
|
||||||
|
// do not double wrap an error of same type
|
||||||
|
if apierrors.IsForbidden(internalError) {
|
||||||
|
return internalError
|
||||||
|
}
|
||||||
|
name, resource, err := extractResourceName(a)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err}))
|
||||||
|
}
|
||||||
|
return apierrors.NewForbidden(resource, name, internalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotFound is a utility function to return a well-formatted admission control error response
|
||||||
|
func NewNotFound(a Attributes) error {
|
||||||
|
name, resource, err := extractResourceName(a)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
return apierrors.NewNotFound(resource, name)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// timeToWaitForReady is the amount of time to wait to let an admission controller to be ready to satisfy a request.
|
||||||
|
// this is useful when admission controllers need to warm their caches before letting requests through.
|
||||||
|
timeToWaitForReady = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadyFunc is a function that returns true if the admission controller is ready to handle requests.
|
||||||
|
type ReadyFunc func() bool
|
||||||
|
|
||||||
|
// Handler is a base for admission control handlers that
|
||||||
|
// support a predefined set of operations
|
||||||
|
type Handler struct {
|
||||||
|
operations sets.String
|
||||||
|
readyFunc ReadyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles returns true for methods that this handler supports
|
||||||
|
func (h *Handler) Handles(operation Operation) bool {
|
||||||
|
return h.operations.Has(string(operation))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new base handler that handles the passed
|
||||||
|
// in operations
|
||||||
|
func NewHandler(ops ...Operation) *Handler {
|
||||||
|
operations := sets.NewString()
|
||||||
|
for _, op := range ops {
|
||||||
|
operations.Insert(string(op))
|
||||||
|
}
|
||||||
|
return &Handler{
|
||||||
|
operations: operations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadyFunc allows late registration of a ReadyFunc to know if the handler is ready to process requests.
|
||||||
|
func (h *Handler) SetReadyFunc(readyFunc ReadyFunc) {
|
||||||
|
h.readyFunc = readyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForReady will wait for the readyFunc (if registered) to return ready, and in case of timeout, will return false.
|
||||||
|
func (h *Handler) WaitForReady() bool {
|
||||||
|
// there is no ready func configured, so we return immediately
|
||||||
|
if h.readyFunc == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.After(timeToWaitForReady)
|
||||||
|
for !h.readyFunc() {
|
||||||
|
select {
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
case <-timeout:
|
||||||
|
return h.readyFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an interface used by AdmissionController to get information about a request
|
||||||
|
// that is used to make an admission decision.
|
||||||
|
type Attributes interface {
|
||||||
|
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
|
||||||
|
// may omit name and rely on the server to generate the name. If that is the case, this method will return
|
||||||
|
// the empty string
|
||||||
|
GetName() string
|
||||||
|
// GetNamespace is the namespace associated with the request (if any)
|
||||||
|
GetNamespace() string
|
||||||
|
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
|
||||||
|
GetResource() schema.GroupVersionResource
|
||||||
|
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||||
|
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||||
|
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||||
|
GetSubresource() string
|
||||||
|
// GetOperation is the operation being performed
|
||||||
|
GetOperation() Operation
|
||||||
|
// GetOperationOptions is the options for the operation being performed
|
||||||
|
GetOperationOptions() runtime.Object
|
||||||
|
// IsDryRun indicates that modifications will definitely not be persisted for this request. This is to prevent
|
||||||
|
// admission controllers with side effects and a method of reconciliation from being overwhelmed.
|
||||||
|
// However, a value of false for this does not mean that the modification will be persisted, because it
|
||||||
|
// could still be rejected by a subsequent validation step.
|
||||||
|
IsDryRun() bool
|
||||||
|
// GetObject is the object from the incoming request prior to default values being applied
|
||||||
|
GetObject() runtime.Object
|
||||||
|
// GetOldObject is the existing object. Only populated for UPDATE and DELETE requests.
|
||||||
|
GetOldObject() runtime.Object
|
||||||
|
// GetKind is the type of object being manipulated. For example: Pod
|
||||||
|
GetKind() schema.GroupVersionKind
|
||||||
|
// GetUserInfo is information about the requesting user
|
||||||
|
GetUserInfo() user.Info
|
||||||
|
|
||||||
|
// AddAnnotation sets annotation according to key-value pair. The key should be qualified, e.g., podsecuritypolicy.admission.k8s.io/admit-policy, where
|
||||||
|
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
|
||||||
|
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
|
||||||
|
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
|
||||||
|
// By default, an annotation gets logged into audit event if the request's audit level is greater or
|
||||||
|
// equal to Metadata.
|
||||||
|
AddAnnotation(key, value string) error
|
||||||
|
|
||||||
|
// AddAnnotationWithLevel sets annotation according to key-value pair with additional intended audit level.
|
||||||
|
// An Annotation gets logged into audit event if the request's audit level is greater or equal to the
|
||||||
|
// intended audit level.
|
||||||
|
AddAnnotationWithLevel(key, value string, level auditinternal.Level) error
|
||||||
|
|
||||||
|
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
||||||
|
GetReinvocationContext() ReinvocationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectInterfaces is an interface used by AdmissionController to get object interfaces
|
||||||
|
// such as Converter or Defaulter. These interfaces are normally coming from Request Scope
|
||||||
|
// to handle special cases like CRDs.
|
||||||
|
type ObjectInterfaces interface {
|
||||||
|
// GetObjectCreater is the ObjectCreator appropriate for the requested object.
|
||||||
|
GetObjectCreater() runtime.ObjectCreater
|
||||||
|
// GetObjectTyper is the ObjectTyper appropriate for the requested object.
|
||||||
|
GetObjectTyper() runtime.ObjectTyper
|
||||||
|
// GetObjectDefaulter is the ObjectDefaulter appropriate for the requested object.
|
||||||
|
GetObjectDefaulter() runtime.ObjectDefaulter
|
||||||
|
// GetObjectConvertor is the ObjectConvertor appropriate for the requested object.
|
||||||
|
GetObjectConvertor() runtime.ObjectConvertor
|
||||||
|
// GetEquivalentResourceMapper is the EquivalentResourceMapper appropriate for finding equivalent resources and expected kind for the requested object.
|
||||||
|
GetEquivalentResourceMapper() runtime.EquivalentResourceMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
||||||
|
type privateAnnotationsGetter interface {
|
||||||
|
getAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
||||||
|
// this interface.
|
||||||
|
type AnnotationsGetter interface {
|
||||||
|
GetAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
||||||
|
type ReinvocationContext interface {
|
||||||
|
// IsReinvoke returns true if the current admission check is a re-invocation.
|
||||||
|
IsReinvoke() bool
|
||||||
|
// SetIsReinvoke sets the current admission check as a re-invocation.
|
||||||
|
SetIsReinvoke()
|
||||||
|
// ShouldReinvoke returns true if any plugin has requested a re-invocation.
|
||||||
|
ShouldReinvoke() bool
|
||||||
|
// SetShouldReinvoke signals that a re-invocation is desired.
|
||||||
|
SetShouldReinvoke()
|
||||||
|
// AddValue set a value for a plugin name, possibly overriding a previous value.
|
||||||
|
SetValue(plugin string, v interface{})
|
||||||
|
// Value reads a value for a webhook.
|
||||||
|
Value(plugin string) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||||
|
type Interface interface {
|
||||||
|
// Handles returns true if this admission controller can handle the given operation
|
||||||
|
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||||
|
Handles(operation Operation) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutationInterface interface {
|
||||||
|
Interface
|
||||||
|
|
||||||
|
// Admit makes an admission decision based on the request attributes.
|
||||||
|
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||||
|
Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
|
||||||
|
type ValidationInterface interface {
|
||||||
|
Interface
|
||||||
|
|
||||||
|
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
|
||||||
|
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||||
|
Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is the type of resource operation being checked for admission control
|
||||||
|
type Operation string
|
||||||
|
|
||||||
|
// Operation constants
|
||||||
|
const (
|
||||||
|
Create Operation = "CREATE"
|
||||||
|
Update Operation = "UPDATE"
|
||||||
|
Delete Operation = "DELETE"
|
||||||
|
Connect Operation = "CONNECT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginInitializer is used for initialization of shareable resources between admission plugins.
|
||||||
|
// After initialization the resources have to be set separately
|
||||||
|
type PluginInitializer interface {
|
||||||
|
Initialize(plugin Interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized
|
||||||
|
// shared resources and should be implemented on admission plugins
|
||||||
|
type InitializationValidator interface {
|
||||||
|
ValidateInitialization() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
|
||||||
|
type ConfigProvider interface {
|
||||||
|
ConfigFor(pluginName string) (io.Reader, error)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Factory is a function that returns an Interface for admission decisions.
|
||||||
|
// The config parameter provides an io.Reader handler to the factory in
|
||||||
|
// order to load specific configurations. If no configuration is provided
|
||||||
|
// the parameter is nil.
|
||||||
|
type Factory func(config io.Reader) (Interface, error)
|
||||||
|
|
||||||
|
type Plugins struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
registry map[string]Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPlugins() *Plugins {
|
||||||
|
return &Plugins{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All registered admission options.
|
||||||
|
var (
|
||||||
|
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
|
||||||
|
PluginEnabledFn = func(name string, config io.Reader) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled
|
||||||
|
type PluginEnabledFunc func(name string, config io.Reader) bool
|
||||||
|
|
||||||
|
// Registered enumerates the names of all registered plugins.
|
||||||
|
func (ps *Plugins) Registered() []string {
|
||||||
|
ps.lock.Lock()
|
||||||
|
defer ps.lock.Unlock()
|
||||||
|
keys := []string{}
|
||||||
|
for k := range ps.registry {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a plugin Factory by name. This
|
||||||
|
// is expected to happen during app startup.
|
||||||
|
func (ps *Plugins) Register(name string, plugin Factory) {
|
||||||
|
ps.lock.Lock()
|
||||||
|
defer ps.lock.Unlock()
|
||||||
|
if ps.registry != nil {
|
||||||
|
_, found := ps.registry[name]
|
||||||
|
if found {
|
||||||
|
klog.Fatalf("Admission plugin %q was registered twice", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ps.registry = map[string]Factory{}
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(1).InfoS("Registered admission plugin", "plugin", name)
|
||||||
|
ps.registry[name] = plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPlugin creates an instance of the named plugin. It returns `false` if the
|
||||||
|
// the name is not known. The error is returned only when the named provider was
|
||||||
|
// known but failed to initialize. The config parameter specifies the io.Reader
|
||||||
|
// handler of the configuration file for the cloud provider, or nil for no configuration.
|
||||||
|
func (ps *Plugins) getPlugin(name string, config io.Reader) (Interface, bool, error) {
|
||||||
|
ps.lock.Lock()
|
||||||
|
defer ps.lock.Unlock()
|
||||||
|
f, found := ps.registry[name]
|
||||||
|
if !found {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config1, config2, err := splitStream(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
if !PluginEnabledFn(name, config1) {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := f(config2)
|
||||||
|
return ret, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitStream reads the stream bytes and constructs two copies of it.
|
||||||
|
func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
|
||||||
|
if config == nil || reflect.ValueOf(config).IsNil() {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := ioutil.ReadAll(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
|
||||||
|
// the given plugins.
|
||||||
|
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
|
||||||
|
handlers := []Interface{}
|
||||||
|
mutationPlugins := []string{}
|
||||||
|
validationPlugins := []string{}
|
||||||
|
for _, pluginName := range pluginNames {
|
||||||
|
pluginConfig, err := configProvider.ConfigFor(pluginName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, err := ps.InitPlugin(pluginName, pluginConfig, pluginInitializer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if plugin != nil {
|
||||||
|
if decorator != nil {
|
||||||
|
handlers = append(handlers, decorator.Decorate(plugin, pluginName))
|
||||||
|
} else {
|
||||||
|
handlers = append(handlers, plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := plugin.(MutationInterface); ok {
|
||||||
|
mutationPlugins = append(mutationPlugins, pluginName)
|
||||||
|
}
|
||||||
|
if _, ok := plugin.(ValidationInterface); ok {
|
||||||
|
validationPlugins = append(validationPlugins, pluginName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(mutationPlugins) != 0 {
|
||||||
|
klog.Infof("Loaded %d mutating admission controller(s) successfully in the following order: %s.", len(mutationPlugins), strings.Join(mutationPlugins, ","))
|
||||||
|
}
|
||||||
|
if len(validationPlugins) != 0 {
|
||||||
|
klog.Infof("Loaded %d validating admission controller(s) successfully in the following order: %s.", len(validationPlugins), strings.Join(validationPlugins, ","))
|
||||||
|
}
|
||||||
|
return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitPlugin creates an instance of the named interface.
|
||||||
|
func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
|
||||||
|
if name == "" {
|
||||||
|
klog.Info("No admission plugin specified.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, found, err := ps.getPlugin(name, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't init admission plugin %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("unknown admission plugin: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginInitializer.Initialize(plugin)
|
||||||
|
// ensure that plugins have been properly initialized
|
||||||
|
if err := ValidateInitialization(plugin); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize admission plugin %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateInitialization will call the InitializationValidate function in each plugin if they implement
|
||||||
|
// the InitializationValidator interface.
|
||||||
|
func ValidateInitialization(plugin Interface) error {
|
||||||
|
if validater, ok := plugin.(InitializationValidator); ok {
|
||||||
|
err := validater.ValidateInitialization()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginInitializers []PluginInitializer
|
||||||
|
|
||||||
|
func (pp PluginInitializers) Initialize(plugin Interface) {
|
||||||
|
for _, p := range pp {
|
||||||
|
p.Initialize(plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 admission
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// newReinvocationHandler creates a handler that wraps the provided admission chain and reinvokes it
|
||||||
|
// if needed according to re-invocation policy of the webhooks.
|
||||||
|
func newReinvocationHandler(admissionChain Interface) Interface {
|
||||||
|
return &reinvoker{admissionChain}
|
||||||
|
}
|
||||||
|
|
||||||
|
type reinvoker struct {
|
||||||
|
admissionChain Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit performs an admission control check using the wrapped admission chain, reinvoking the
|
||||||
|
// admission chain if needed according to the reinvocation policy. Plugins are expected to check
|
||||||
|
// the admission attributes' reinvocation context against their reinvocation policy to decide if
|
||||||
|
// they should re-run, and to update the reinvocation context if they perform any mutations.
|
||||||
|
func (r *reinvoker) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
if mutator, ok := r.admissionChain.(MutationInterface); ok {
|
||||||
|
err := mutator.Admit(ctx, a, o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := a.GetReinvocationContext()
|
||||||
|
if s.ShouldReinvoke() {
|
||||||
|
s.SetIsReinvoke()
|
||||||
|
// Calling admit a second time will reinvoke all in-tree plugins
|
||||||
|
// as well as any webhook plugins that need to be reinvoked based on the
|
||||||
|
// reinvocation policy.
|
||||||
|
return mutator.Admit(ctx, a, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs an admission control check using the wrapped admission chain, and returns immediately on first error.
|
||||||
|
func (r *reinvoker) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||||
|
if validator, ok := r.admissionChain.(ValidationInterface); ok {
|
||||||
|
return validator.Validate(ctx, a, o)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles will return true if any of the admission chain handlers handle the given operation.
|
||||||
|
func (r *reinvoker) Handles(operation Operation) bool {
|
||||||
|
return r.admissionChain.Handles(operation)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 admission
|
||||||
|
|
||||||
|
import "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
type RuntimeObjectInterfaces struct {
|
||||||
|
runtime.ObjectCreater
|
||||||
|
runtime.ObjectTyper
|
||||||
|
runtime.ObjectDefaulter
|
||||||
|
runtime.ObjectConvertor
|
||||||
|
runtime.EquivalentResourceMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObjectInterfacesFromScheme(scheme *runtime.Scheme) ObjectInterfaces {
|
||||||
|
return &RuntimeObjectInterfaces{scheme, scheme, scheme, scheme, runtime.NewEquivalentResourceRegistry()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuntimeObjectInterfaces) GetObjectCreater() runtime.ObjectCreater {
|
||||||
|
return r.ObjectCreater
|
||||||
|
}
|
||||||
|
func (r *RuntimeObjectInterfaces) GetObjectTyper() runtime.ObjectTyper {
|
||||||
|
return r.ObjectTyper
|
||||||
|
}
|
||||||
|
func (r *RuntimeObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter {
|
||||||
|
return r.ObjectDefaulter
|
||||||
|
}
|
||||||
|
func (r *RuntimeObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor {
|
||||||
|
return r.ObjectConvertor
|
||||||
|
}
|
||||||
|
func (r *RuntimeObjectInterfaces) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper {
|
||||||
|
return r.EquivalentResourceMapper
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +groupName=apiserver.k8s.io
|
||||||
|
|
||||||
|
// Package apiserver is the internal version of the API.
|
||||||
|
package apiserver // import "k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LegacyGroupName = "apiserver.k8s.io"
|
||||||
|
const GroupName = "apiserver.config.k8s.io"
|
||||||
|
|
||||||
|
// LegacySchemeGroupVersion is group version used to register these objects
|
||||||
|
var LegacySchemeGroupVersion = schema.GroupVersion{Group: LegacyGroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
|
var (
|
||||||
|
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
// Adds the list of known types to the given scheme.
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(LegacySchemeGroupVersion,
|
||||||
|
&AdmissionConfiguration{},
|
||||||
|
&EgressSelectorConfiguration{},
|
||||||
|
)
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&AdmissionConfiguration{},
|
||||||
|
&EgressSelectorConfiguration{},
|
||||||
|
&TracingConfiguration{},
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// AdmissionConfiguration provides versioned configuration for admission controllers.
|
||||||
|
type AdmissionConfiguration struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// Plugins allows specifying a configuration per admission control plugin.
|
||||||
|
// +optional
|
||||||
|
Plugins []AdmissionPluginConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdmissionPluginConfiguration provides the configuration for a single plug-in.
|
||||||
|
type AdmissionPluginConfiguration struct {
|
||||||
|
// Name is the name of the admission controller.
|
||||||
|
// It must match the registered admission plugin name.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Path is the path to a configuration file that contains the plugin's
|
||||||
|
// configuration
|
||||||
|
// +optional
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// Configuration is an embedded configuration object to be used as the plugin's
|
||||||
|
// configuration. If present, it will be used instead of the path to the configuration file.
|
||||||
|
// +optional
|
||||||
|
Configuration *runtime.Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||||
|
type EgressSelectorConfiguration struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// EgressSelections contains a list of egress selection client configurations
|
||||||
|
EgressSelections []EgressSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressSelection provides the configuration for a single egress selection client.
|
||||||
|
type EgressSelection struct {
|
||||||
|
// Name is the name of the egress selection.
|
||||||
|
// Currently supported values are "controlplane", "etcd" and "cluster"
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Connection is the exact information used to configure the egress selection
|
||||||
|
Connection Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection provides the configuration for a single egress selection client.
|
||||||
|
type Connection struct {
|
||||||
|
// Protocol is the protocol used to connect from client to the konnectivity server.
|
||||||
|
ProxyProtocol ProtocolType
|
||||||
|
|
||||||
|
// Transport defines the transport configurations we use to dial to the konnectivity server.
|
||||||
|
// This is required if ProxyProtocol is HTTPConnect or GRPC.
|
||||||
|
// +optional
|
||||||
|
Transport *Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtocolType is a set of valid values for Connection.ProtocolType
|
||||||
|
type ProtocolType string
|
||||||
|
|
||||||
|
// Valid types for ProtocolType for konnectivity server
|
||||||
|
const (
|
||||||
|
// Use HTTPConnect to connect to konnectivity server
|
||||||
|
ProtocolHTTPConnect ProtocolType = "HTTPConnect"
|
||||||
|
// Use grpc to connect to konnectivity server
|
||||||
|
ProtocolGRPC ProtocolType = "GRPC"
|
||||||
|
// Connect directly (skip konnectivity server)
|
||||||
|
ProtocolDirect ProtocolType = "Direct"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport defines the transport configurations we use to dial to the konnectivity server
|
||||||
|
type Transport struct {
|
||||||
|
// TCP is the TCP configuration for communicating with the konnectivity server via TCP
|
||||||
|
// ProxyProtocol of GRPC is not supported with TCP transport at the moment
|
||||||
|
// Requires at least one of TCP or UDS to be set
|
||||||
|
// +optional
|
||||||
|
TCP *TCPTransport
|
||||||
|
|
||||||
|
// UDS is the UDS configuration for communicating with the konnectivity server via UDS
|
||||||
|
// Requires at least one of TCP or UDS to be set
|
||||||
|
// +optional
|
||||||
|
UDS *UDSTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPTransport provides the information to connect to konnectivity server via TCP
|
||||||
|
type TCPTransport struct {
|
||||||
|
// URL is the location of the konnectivity server to connect to.
|
||||||
|
// As an example it might be "https://127.0.0.1:8131"
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// TLSConfig is the config needed to use TLS when connecting to konnectivity server
|
||||||
|
// +optional
|
||||||
|
TLSConfig *TLSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDSTransport provides the information to connect to konnectivity server via UDS
|
||||||
|
type UDSTransport struct {
|
||||||
|
// UDSName is the name of the unix domain socket to connect to konnectivity server
|
||||||
|
// This does not use a unix:// prefix. (Eg: /etc/srv/kubernetes/konnectivity-server/konnectivity-server.socket)
|
||||||
|
UDSName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig provides the authentication information to connect to konnectivity server
|
||||||
|
// Only used with TCPTransport
|
||||||
|
type TLSConfig struct {
|
||||||
|
// caBundle is the file location of the CA to be used to determine trust with the konnectivity server.
|
||||||
|
// Must be absent/empty if TCPTransport.URL is prefixed with http://
|
||||||
|
// If absent while TCPTransport.URL is prefixed with https://, default to system trust roots.
|
||||||
|
// +optional
|
||||||
|
CABundle string
|
||||||
|
|
||||||
|
// clientKey is the file location of the client key to authenticate with the konnectivity server
|
||||||
|
// Must be absent/empty if TCPTransport.URL is prefixed with http://
|
||||||
|
// Must be configured if TCPTransport.URL is prefixed with https://
|
||||||
|
// +optional
|
||||||
|
ClientKey string
|
||||||
|
|
||||||
|
// clientCert is the file location of the client certificate to authenticate with the konnectivity server
|
||||||
|
// Must be absent/empty if TCPTransport.URL is prefixed with http://
|
||||||
|
// Must be configured if TCPTransport.URL is prefixed with https://
|
||||||
|
// +optional
|
||||||
|
ClientCert string
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// TracingConfiguration provides versioned configuration for tracing clients.
|
||||||
|
type TracingConfiguration struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
// Endpoint of the collector that's running on the control-plane node.
|
||||||
|
// The APIServer uses the egressType ControlPlane when sending data to the collector.
|
||||||
|
// The syntax is defined in https://github.com/grpc/grpc/blob/master/doc/naming.md.
|
||||||
|
// Defaults to the otlp grpc default, localhost:4317
|
||||||
|
// The connection is insecure, and does not currently support TLS.
|
||||||
|
Endpoint *string
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
// SamplingRatePerMillion is the number of samples to collect per million spans.
|
||||||
|
// Defaults to 0.
|
||||||
|
SamplingRatePerMillion *int32
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/apiserver
|
||||||
|
// +k8s:defaulter-gen=TypeMeta
|
||||||
|
// +groupName=apiserver.config.k8s.io
|
||||||
|
|
||||||
|
// Package v1 is the v1 version of the API.
|
||||||
|
package v1 // import "k8s.io/apiserver/pkg/apis/apiserver/v1"
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GroupName = "apiserver.config.k8s.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||||
|
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||||
|
SchemeBuilder runtime.SchemeBuilder
|
||||||
|
localSchemeBuilder = &SchemeBuilder
|
||||||
|
AddToScheme = localSchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// We only register manually written functions here. The registration of the
|
||||||
|
// generated functions takes place in the generated files. The separation
|
||||||
|
// makes the code compile even when the generated files are missing.
|
||||||
|
localSchemeBuilder.Register(addKnownTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the list of known types to the given scheme.
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&AdmissionConfiguration{},
|
||||||
|
)
|
||||||
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// AdmissionConfiguration provides versioned configuration for admission controllers.
|
||||||
|
type AdmissionConfiguration struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Plugins allows specifying a configuration per admission control plugin.
|
||||||
|
// +optional
|
||||||
|
Plugins []AdmissionPluginConfiguration `json:"plugins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdmissionPluginConfiguration provides the configuration for a single plug-in.
|
||||||
|
type AdmissionPluginConfiguration struct {
|
||||||
|
// Name is the name of the admission controller.
|
||||||
|
// It must match the registered admission plugin name.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Path is the path to a configuration file that contains the plugin's
|
||||||
|
// configuration
|
||||||
|
// +optional
|
||||||
|
Path string `json:"path"`
|
||||||
|
|
||||||
|
// Configuration is an embedded configuration object to be used as the plugin's
|
||||||
|
// configuration. If present, it will be used instead of the path to the configuration file.
|
||||||
|
// +optional
|
||||||
|
Configuration *runtime.Unknown `json:"configuration"`
|
||||||
|
}
|
||||||
104
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go
generated
vendored
Normal file
104
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.conversion.go
generated
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by conversion-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
apiserver "k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localSchemeBuilder.Register(RegisterConversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterConversions adds conversion functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
func RegisterConversions(s *runtime.Scheme) error {
|
||||||
|
if err := s.AddGeneratedConversionFunc((*AdmissionConfiguration)(nil), (*apiserver.AdmissionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_AdmissionConfiguration_To_apiserver_AdmissionConfiguration(a.(*AdmissionConfiguration), b.(*apiserver.AdmissionConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.AdmissionConfiguration)(nil), (*AdmissionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_AdmissionConfiguration_To_v1_AdmissionConfiguration(a.(*apiserver.AdmissionConfiguration), b.(*AdmissionConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*AdmissionPluginConfiguration)(nil), (*apiserver.AdmissionPluginConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_AdmissionPluginConfiguration_To_apiserver_AdmissionPluginConfiguration(a.(*AdmissionPluginConfiguration), b.(*apiserver.AdmissionPluginConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.AdmissionPluginConfiguration)(nil), (*AdmissionPluginConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration(a.(*apiserver.AdmissionPluginConfiguration), b.(*AdmissionPluginConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_AdmissionConfiguration_To_apiserver_AdmissionConfiguration(in *AdmissionConfiguration, out *apiserver.AdmissionConfiguration, s conversion.Scope) error {
|
||||||
|
out.Plugins = *(*[]apiserver.AdmissionPluginConfiguration)(unsafe.Pointer(&in.Plugins))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_AdmissionConfiguration_To_apiserver_AdmissionConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_v1_AdmissionConfiguration_To_apiserver_AdmissionConfiguration(in *AdmissionConfiguration, out *apiserver.AdmissionConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_AdmissionConfiguration_To_apiserver_AdmissionConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_AdmissionConfiguration_To_v1_AdmissionConfiguration(in *apiserver.AdmissionConfiguration, out *AdmissionConfiguration, s conversion.Scope) error {
|
||||||
|
out.Plugins = *(*[]AdmissionPluginConfiguration)(unsafe.Pointer(&in.Plugins))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_AdmissionConfiguration_To_v1_AdmissionConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_AdmissionConfiguration_To_v1_AdmissionConfiguration(in *apiserver.AdmissionConfiguration, out *AdmissionConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_AdmissionConfiguration_To_v1_AdmissionConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_AdmissionPluginConfiguration_To_apiserver_AdmissionPluginConfiguration(in *AdmissionPluginConfiguration, out *apiserver.AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||||
|
out.Name = in.Name
|
||||||
|
out.Path = in.Path
|
||||||
|
out.Configuration = (*runtime.Unknown)(unsafe.Pointer(in.Configuration))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_AdmissionPluginConfiguration_To_apiserver_AdmissionPluginConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_v1_AdmissionPluginConfiguration_To_apiserver_AdmissionPluginConfiguration(in *AdmissionPluginConfiguration, out *apiserver.AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_AdmissionPluginConfiguration_To_apiserver_AdmissionPluginConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration(in *apiserver.AdmissionPluginConfiguration, out *AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||||
|
out.Name = in.Name
|
||||||
|
out.Path = in.Path
|
||||||
|
out.Configuration = (*runtime.Unknown)(unsafe.Pointer(in.Configuration))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration(in *apiserver.AdmissionPluginConfiguration, out *AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_AdmissionPluginConfiguration_To_v1_AdmissionPluginConfiguration(in, out, s)
|
||||||
|
}
|
||||||
79
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
79
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Plugins != nil {
|
||||||
|
in, out := &in.Plugins, &out.Plugins
|
||||||
|
*out = make([]AdmissionPluginConfiguration, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionConfiguration.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopy() *AdmissionConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdmissionConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdmissionPluginConfiguration) DeepCopyInto(out *AdmissionPluginConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
if in.Configuration != nil {
|
||||||
|
in, out := &in.Configuration, &out.Configuration
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionPluginConfiguration.
|
||||||
|
func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdmissionPluginConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
33
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go
generated
vendored
Normal file
33
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/zz_generated.defaults.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
// All generated defaulters are covering - they call all nested defaulters.
|
||||||
|
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
263
vendor/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go
generated
vendored
Normal file
263
vendor/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Plugins != nil {
|
||||||
|
in, out := &in.Plugins, &out.Plugins
|
||||||
|
*out = make([]AdmissionPluginConfiguration, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionConfiguration.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopy() *AdmissionConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdmissionConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *AdmissionConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdmissionPluginConfiguration) DeepCopyInto(out *AdmissionPluginConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
if in.Configuration != nil {
|
||||||
|
in, out := &in.Configuration, &out.Configuration
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionPluginConfiguration.
|
||||||
|
func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdmissionPluginConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||||
|
*out = *in
|
||||||
|
if in.Transport != nil {
|
||||||
|
in, out := &in.Transport, &out.Transport
|
||||||
|
*out = new(Transport)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||||
|
func (in *Connection) DeepCopy() *Connection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Connection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||||
|
*out = *in
|
||||||
|
in.Connection.DeepCopyInto(&out.Connection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||||
|
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.EgressSelections != nil {
|
||||||
|
in, out := &in.EgressSelections, &out.EgressSelections
|
||||||
|
*out = make([]EgressSelection, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelectorConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TCPTransport) DeepCopyInto(out *TCPTransport) {
|
||||||
|
*out = *in
|
||||||
|
if in.TLSConfig != nil {
|
||||||
|
in, out := &in.TLSConfig, &out.TLSConfig
|
||||||
|
*out = new(TLSConfig)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPTransport.
|
||||||
|
func (in *TCPTransport) DeepCopy() *TCPTransport {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TCPTransport)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLSConfig) DeepCopyInto(out *TLSConfig) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig.
|
||||||
|
func (in *TLSConfig) DeepCopy() *TLSConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLSConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TracingConfiguration) DeepCopyInto(out *TracingConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Endpoint != nil {
|
||||||
|
in, out := &in.Endpoint, &out.Endpoint
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.SamplingRatePerMillion != nil {
|
||||||
|
in, out := &in.SamplingRatePerMillion, &out.SamplingRatePerMillion
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TracingConfiguration.
|
||||||
|
func (in *TracingConfiguration) DeepCopy() *TracingConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TracingConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TracingConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Transport) DeepCopyInto(out *Transport) {
|
||||||
|
*out = *in
|
||||||
|
if in.TCP != nil {
|
||||||
|
in, out := &in.TCP, &out.TCP
|
||||||
|
*out = new(TCPTransport)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.UDS != nil {
|
||||||
|
in, out := &in.UDS, &out.UDS
|
||||||
|
*out = new(UDSTransport)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Transport.
|
||||||
|
func (in *Transport) DeepCopy() *Transport {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Transport)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *UDSTransport) DeepCopyInto(out *UDSTransport) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDSTransport.
|
||||||
|
func (in *UDSTransport) DeepCopy() *UDSTransport {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(UDSTransport)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
# approval on api packages bubbles to api-approvers
|
||||||
|
reviewers:
|
||||||
|
- sig-auth-audit-approvers
|
||||||
|
- sig-auth-audit-reviewers
|
||||||
|
labels:
|
||||||
|
- sig/auth
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +groupName=audit.k8s.io
|
||||||
|
|
||||||
|
package audit // import "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
func ordLevel(l Level) int {
|
||||||
|
switch l {
|
||||||
|
case LevelMetadata:
|
||||||
|
return 1
|
||||||
|
case LevelRequest:
|
||||||
|
return 2
|
||||||
|
case LevelRequestResponse:
|
||||||
|
return 3
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Level) Less(b Level) bool {
|
||||||
|
return ordLevel(a) < ordLevel(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Level) GreaterOrEqual(b Level) bool {
|
||||||
|
return ordLevel(a) >= ordLevel(b)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the group name use in this package
|
||||||
|
const GroupName = "audit.k8s.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
|
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||||
|
func Kind(kind string) schema.GroupKind {
|
||||||
|
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||||
|
func Resource(resource string) schema.GroupResource {
|
||||||
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&Event{},
|
||||||
|
&EventList{},
|
||||||
|
&Policy{},
|
||||||
|
&PolicyList{},
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
authnv1 "k8s.io/api/authentication/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Header keys used by the audit system.
|
||||||
|
const (
|
||||||
|
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
|
||||||
|
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
|
||||||
|
// server or kube-aggregator).
|
||||||
|
//
|
||||||
|
// Audit ID is also returned to client by http response header.
|
||||||
|
// It's not guaranteed Audit-Id http header is sent for all requests. When kube-apiserver didn't
|
||||||
|
// audit the events according to the audit policy, no Audit-ID is returned. Also, for request to
|
||||||
|
// pods/exec, pods/attach, pods/proxy, kube-apiserver works like a proxy and redirect the request
|
||||||
|
// to kubelet node, users will only get http headers sent from kubelet node, so no Audit-ID is
|
||||||
|
// sent when users run command like "kubectl exec" or "kubectl attach".
|
||||||
|
HeaderAuditID = "Audit-ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level defines the amount of information logged during auditing
|
||||||
|
type Level string
|
||||||
|
|
||||||
|
// Valid audit levels
|
||||||
|
const (
|
||||||
|
// LevelNone disables auditing
|
||||||
|
LevelNone Level = "None"
|
||||||
|
// LevelMetadata provides the basic level of auditing.
|
||||||
|
LevelMetadata Level = "Metadata"
|
||||||
|
// LevelRequest provides Metadata level of auditing, and additionally
|
||||||
|
// logs the request object (does not apply for non-resource requests).
|
||||||
|
LevelRequest Level = "Request"
|
||||||
|
// LevelRequestResponse provides Request level of auditing, and additionally
|
||||||
|
// logs the response object (does not apply for non-resource requests).
|
||||||
|
LevelRequestResponse Level = "RequestResponse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stage defines the stages in request handling that audit events may be generated.
|
||||||
|
type Stage string
|
||||||
|
|
||||||
|
// Valid audit stages.
|
||||||
|
const (
|
||||||
|
// The stage for events generated as soon as the audit handler receives the request, and before it
|
||||||
|
// is delegated down the handler chain.
|
||||||
|
StageRequestReceived Stage = "RequestReceived"
|
||||||
|
// The stage for events generated once the response headers are sent, but before the response body
|
||||||
|
// is sent. This stage is only generated for long-running requests (e.g. watch).
|
||||||
|
StageResponseStarted Stage = "ResponseStarted"
|
||||||
|
// The stage for events generated once the response body has been completed, and no more bytes
|
||||||
|
// will be sent.
|
||||||
|
StageResponseComplete Stage = "ResponseComplete"
|
||||||
|
// The stage for events generated when a panic occurred.
|
||||||
|
StagePanic Stage = "Panic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Event captures all the information that can be included in an API audit log.
|
||||||
|
type Event struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// AuditLevel at which event was generated
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Unique audit ID, generated for each request.
|
||||||
|
AuditID types.UID
|
||||||
|
// Stage of the request handling when this event instance was generated.
|
||||||
|
Stage Stage
|
||||||
|
|
||||||
|
// RequestURI is the request URI as sent by the client to a server.
|
||||||
|
RequestURI string
|
||||||
|
// Verb is the kubernetes verb associated with the request.
|
||||||
|
// For non-resource requests, this is the lower-cased HTTP method.
|
||||||
|
Verb string
|
||||||
|
// Authenticated user information.
|
||||||
|
User authnv1.UserInfo
|
||||||
|
// Impersonated user information.
|
||||||
|
// +optional
|
||||||
|
ImpersonatedUser *authnv1.UserInfo
|
||||||
|
// Source IPs, from where the request originated and intermediate proxies.
|
||||||
|
// The source IPs are listed from (in order):
|
||||||
|
// 1. X-Forwarded-For request header IPs
|
||||||
|
// 2. X-Real-Ip header, if not present in the X-Forwarded-For list
|
||||||
|
// 3. The remote address for the connection, if it doesn't match the last
|
||||||
|
// IP in the list up to here (X-Forwarded-For or X-Real-Ip).
|
||||||
|
// Note: All but the last IP can be arbitrarily set by the client.
|
||||||
|
// +optional
|
||||||
|
SourceIPs []string
|
||||||
|
// UserAgent records the user agent string reported by the client.
|
||||||
|
// Note that the UserAgent is provided by the client, and must not be trusted.
|
||||||
|
// +optional
|
||||||
|
UserAgent string
|
||||||
|
// Object reference this request is targeted at.
|
||||||
|
// Does not apply for List-type requests, or non-resource requests.
|
||||||
|
// +optional
|
||||||
|
ObjectRef *ObjectReference
|
||||||
|
// The response status, populated even when the ResponseObject is not a Status type.
|
||||||
|
// For successful responses, this will only include the Code. For non-status type
|
||||||
|
// error responses, this will be auto-populated with the error Message.
|
||||||
|
// +optional
|
||||||
|
ResponseStatus *metav1.Status
|
||||||
|
|
||||||
|
// API object from the request, in JSON format. The RequestObject is recorded as-is in the request
|
||||||
|
// (possibly re-encoded as JSON), prior to version conversion, defaulting, admission or
|
||||||
|
// merging. It is an external versioned object type, and may not be a valid object on its own.
|
||||||
|
// Omitted for non-resource requests. Only logged at Request Level and higher.
|
||||||
|
// +optional
|
||||||
|
RequestObject *runtime.Unknown
|
||||||
|
// API object returned in the response, in JSON. The ResponseObject is recorded after conversion
|
||||||
|
// to the external type, and serialized as JSON. Omitted for non-resource requests. Only logged
|
||||||
|
// at Response Level.
|
||||||
|
// +optional
|
||||||
|
ResponseObject *runtime.Unknown
|
||||||
|
|
||||||
|
// Time the request reached the apiserver.
|
||||||
|
RequestReceivedTimestamp metav1.MicroTime
|
||||||
|
// Time the request reached current audit stage.
|
||||||
|
StageTimestamp metav1.MicroTime
|
||||||
|
|
||||||
|
// Annotations is an unstructured key value map stored with an audit event that may be set by
|
||||||
|
// plugins invoked in the request serving chain, including authentication, authorization and
|
||||||
|
// admission plugins. Note that these annotations are for the audit event, and do not correspond
|
||||||
|
// to the metadata.annotations of the submitted object. Keys should uniquely identify the informing
|
||||||
|
// component to avoid name collisions (e.g. podsecuritypolicy.admission.k8s.io/policy). Values
|
||||||
|
// should be short. Annotations are included in the Metadata level.
|
||||||
|
// +optional
|
||||||
|
Annotations map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// EventList is a list of audit Events.
|
||||||
|
type EventList struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta
|
||||||
|
|
||||||
|
Items []Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Policy defines the configuration of audit logging, and the rules for how different request
|
||||||
|
// categories are logged.
|
||||||
|
type Policy struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// ObjectMeta is included for interoperability with API infrastructure.
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// Rules specify the audit Level a request should be recorded at.
|
||||||
|
// A request may match multiple rules, in which case the FIRST matching rule is used.
|
||||||
|
// The default audit level is None, but can be overridden by a catch-all rule at the end of the list.
|
||||||
|
// PolicyRules are strictly ordered.
|
||||||
|
Rules []PolicyRule
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified per rule in which case the union of both are omitted.
|
||||||
|
// +optional
|
||||||
|
OmitStages []Stage
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// This is used as a global default - a value of 'true' will omit the managed fileds,
|
||||||
|
// otherwise the managed fields will be included in the API audit log.
|
||||||
|
// Note that this can also be specified per rule in which case the value specified
|
||||||
|
// in a rule will override the global default.
|
||||||
|
// +optional
|
||||||
|
OmitManagedFields bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// PolicyList is a list of audit Policies.
|
||||||
|
type PolicyList struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta
|
||||||
|
|
||||||
|
Items []Policy
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyRule maps requests based off metadata to an audit Level.
|
||||||
|
// Requests must match the rules of every field (an intersection of rules).
|
||||||
|
type PolicyRule struct {
|
||||||
|
// The Level that requests matching this rule are recorded at.
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// The users (by authenticated user name) this rule applies to.
|
||||||
|
// An empty list implies every user.
|
||||||
|
// +optional
|
||||||
|
Users []string
|
||||||
|
// The user groups this rule applies to. A user is considered matching
|
||||||
|
// if it is a member of any of the UserGroups.
|
||||||
|
// An empty list implies every user group.
|
||||||
|
// +optional
|
||||||
|
UserGroups []string
|
||||||
|
|
||||||
|
// The verbs that match this rule.
|
||||||
|
// An empty list implies every verb.
|
||||||
|
// +optional
|
||||||
|
Verbs []string
|
||||||
|
|
||||||
|
// Rules can apply to API resources (such as "pods" or "secrets"),
|
||||||
|
// non-resource URL paths (such as "/api"), or neither, but not both.
|
||||||
|
// If neither is specified, the rule is treated as a default for all URLs.
|
||||||
|
|
||||||
|
// Resources that this rule matches. An empty list implies all kinds in all API groups.
|
||||||
|
// +optional
|
||||||
|
Resources []GroupResources
|
||||||
|
// Namespaces that this rule matches.
|
||||||
|
// The empty string "" matches non-namespaced resources.
|
||||||
|
// An empty list implies every namespace.
|
||||||
|
// +optional
|
||||||
|
Namespaces []string
|
||||||
|
|
||||||
|
// NonResourceURLs is a set of URL paths that should be audited.
|
||||||
|
// *s are allowed, but only as the full, final step in the path.
|
||||||
|
// Examples:
|
||||||
|
// "/metrics" - Log requests for apiserver metrics
|
||||||
|
// "/healthz*" - Log all health checks
|
||||||
|
// +optional
|
||||||
|
NonResourceURLs []string
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified policy wide in which case the union of both are omitted.
|
||||||
|
// An empty list means no restrictions will apply.
|
||||||
|
// +optional
|
||||||
|
OmitStages []Stage
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// - a value of 'true' will drop the managed fields from the API audit log
|
||||||
|
// - a value of 'false' indicates that the managed fileds should be included
|
||||||
|
// in the API audit log
|
||||||
|
// Note that the value, if specified, in this rule will override the global default
|
||||||
|
// If a value is not specified then the global default specified in
|
||||||
|
// Policy.OmitManagedFields will stand.
|
||||||
|
// +optional
|
||||||
|
OmitManagedFields *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupResources represents resource kinds in an API group.
|
||||||
|
type GroupResources struct {
|
||||||
|
// Group is the name of the API group that contains the resources.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
Group string
|
||||||
|
// Resources is a list of resources this rule applies to.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// 'pods' matches pods.
|
||||||
|
// 'pods/log' matches the log subresource of pods.
|
||||||
|
// '*' matches all resources and their subresources.
|
||||||
|
// 'pods/*' matches all subresources of pods.
|
||||||
|
// '*/scale' matches all scale subresources.
|
||||||
|
//
|
||||||
|
// If wildcard is present, the validation rule will ensure resources do not
|
||||||
|
// overlap with each other.
|
||||||
|
//
|
||||||
|
// An empty list implies all resources and subresources in this API groups apply.
|
||||||
|
// +optional
|
||||||
|
Resources []string
|
||||||
|
// ResourceNames is a list of resource instance names that the policy matches.
|
||||||
|
// Using this field requires Resources to be specified.
|
||||||
|
// An empty list implies that every instance of the resource is matched.
|
||||||
|
// +optional
|
||||||
|
ResourceNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectReference contains enough information to let you inspect or modify the referred object.
|
||||||
|
type ObjectReference struct {
|
||||||
|
// +optional
|
||||||
|
Resource string
|
||||||
|
// +optional
|
||||||
|
Namespace string
|
||||||
|
// +optional
|
||||||
|
Name string
|
||||||
|
// +optional
|
||||||
|
UID types.UID
|
||||||
|
// APIGroup is the name of the API group that contains the referred object.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
APIGroup string
|
||||||
|
// APIVersion is the version of the API group that contains the referred object.
|
||||||
|
// +optional
|
||||||
|
APIVersion string
|
||||||
|
// +optional
|
||||||
|
ResourceVersion string
|
||||||
|
// +optional
|
||||||
|
Subresource string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +k8s:protobuf-gen=package
|
||||||
|
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
// +k8s:defaulter-gen=TypeMeta
|
||||||
|
|
||||||
|
// +groupName=audit.k8s.io
|
||||||
|
|
||||||
|
package v1 // import "k8s.io/apiserver/pkg/apis/audit/v1"
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,275 @@
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package k8s.io.apiserver.pkg.apis.audit.v1;
|
||||||
|
|
||||||
|
import "k8s.io/api/authentication/v1/generated.proto";
|
||||||
|
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
|
||||||
|
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
|
||||||
|
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||||
|
|
||||||
|
// Package-wide variables from generator "generated".
|
||||||
|
option go_package = "k8s.io/apiserver/pkg/apis/audit/v1";
|
||||||
|
|
||||||
|
// Event captures all the information that can be included in an API audit log.
|
||||||
|
message Event {
|
||||||
|
// AuditLevel at which event was generated
|
||||||
|
optional string level = 1;
|
||||||
|
|
||||||
|
// Unique audit ID, generated for each request.
|
||||||
|
optional string auditID = 2;
|
||||||
|
|
||||||
|
// Stage of the request handling when this event instance was generated.
|
||||||
|
optional string stage = 3;
|
||||||
|
|
||||||
|
// RequestURI is the request URI as sent by the client to a server.
|
||||||
|
optional string requestURI = 4;
|
||||||
|
|
||||||
|
// Verb is the kubernetes verb associated with the request.
|
||||||
|
// For non-resource requests, this is the lower-cased HTTP method.
|
||||||
|
optional string verb = 5;
|
||||||
|
|
||||||
|
// Authenticated user information.
|
||||||
|
optional k8s.io.api.authentication.v1.UserInfo user = 6;
|
||||||
|
|
||||||
|
// Impersonated user information.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.api.authentication.v1.UserInfo impersonatedUser = 7;
|
||||||
|
|
||||||
|
// Source IPs, from where the request originated and intermediate proxies.
|
||||||
|
// The source IPs are listed from (in order):
|
||||||
|
// 1. X-Forwarded-For request header IPs
|
||||||
|
// 2. X-Real-Ip header, if not present in the X-Forwarded-For list
|
||||||
|
// 3. The remote address for the connection, if it doesn't match the last
|
||||||
|
// IP in the list up to here (X-Forwarded-For or X-Real-Ip).
|
||||||
|
// Note: All but the last IP can be arbitrarily set by the client.
|
||||||
|
// +optional
|
||||||
|
repeated string sourceIPs = 8;
|
||||||
|
|
||||||
|
// UserAgent records the user agent string reported by the client.
|
||||||
|
// Note that the UserAgent is provided by the client, and must not be trusted.
|
||||||
|
// +optional
|
||||||
|
optional string userAgent = 16;
|
||||||
|
|
||||||
|
// Object reference this request is targeted at.
|
||||||
|
// Does not apply for List-type requests, or non-resource requests.
|
||||||
|
// +optional
|
||||||
|
optional ObjectReference objectRef = 9;
|
||||||
|
|
||||||
|
// The response status, populated even when the ResponseObject is not a Status type.
|
||||||
|
// For successful responses, this will only include the Code and StatusSuccess.
|
||||||
|
// For non-status type error responses, this will be auto-populated with the error Message.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.Status responseStatus = 10;
|
||||||
|
|
||||||
|
// API object from the request, in JSON format. The RequestObject is recorded as-is in the request
|
||||||
|
// (possibly re-encoded as JSON), prior to version conversion, defaulting, admission or
|
||||||
|
// merging. It is an external versioned object type, and may not be a valid object on its own.
|
||||||
|
// Omitted for non-resource requests. Only logged at Request Level and higher.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.runtime.Unknown requestObject = 11;
|
||||||
|
|
||||||
|
// API object returned in the response, in JSON. The ResponseObject is recorded after conversion
|
||||||
|
// to the external type, and serialized as JSON. Omitted for non-resource requests. Only logged
|
||||||
|
// at Response Level.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.runtime.Unknown responseObject = 12;
|
||||||
|
|
||||||
|
// Time the request reached the apiserver.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime requestReceivedTimestamp = 13;
|
||||||
|
|
||||||
|
// Time the request reached current audit stage.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime stageTimestamp = 14;
|
||||||
|
|
||||||
|
// Annotations is an unstructured key value map stored with an audit event that may be set by
|
||||||
|
// plugins invoked in the request serving chain, including authentication, authorization and
|
||||||
|
// admission plugins. Note that these annotations are for the audit event, and do not correspond
|
||||||
|
// to the metadata.annotations of the submitted object. Keys should uniquely identify the informing
|
||||||
|
// component to avoid name collisions (e.g. podsecuritypolicy.admission.k8s.io/policy). Values
|
||||||
|
// should be short. Annotations are included in the Metadata level.
|
||||||
|
// +optional
|
||||||
|
map<string, string> annotations = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventList is a list of audit Events.
|
||||||
|
message EventList {
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||||
|
|
||||||
|
repeated Event items = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupResources represents resource kinds in an API group.
|
||||||
|
message GroupResources {
|
||||||
|
// Group is the name of the API group that contains the resources.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
optional string group = 1;
|
||||||
|
|
||||||
|
// Resources is a list of resources this rule applies to.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// 'pods' matches pods.
|
||||||
|
// 'pods/log' matches the log subresource of pods.
|
||||||
|
// '*' matches all resources and their subresources.
|
||||||
|
// 'pods/*' matches all subresources of pods.
|
||||||
|
// '*/scale' matches all scale subresources.
|
||||||
|
//
|
||||||
|
// If wildcard is present, the validation rule will ensure resources do not
|
||||||
|
// overlap with each other.
|
||||||
|
//
|
||||||
|
// An empty list implies all resources and subresources in this API groups apply.
|
||||||
|
// +optional
|
||||||
|
repeated string resources = 2;
|
||||||
|
|
||||||
|
// ResourceNames is a list of resource instance names that the policy matches.
|
||||||
|
// Using this field requires Resources to be specified.
|
||||||
|
// An empty list implies that every instance of the resource is matched.
|
||||||
|
// +optional
|
||||||
|
repeated string resourceNames = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectReference contains enough information to let you inspect or modify the referred object.
|
||||||
|
message ObjectReference {
|
||||||
|
// +optional
|
||||||
|
optional string resource = 1;
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
optional string namespace = 2;
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
optional string name = 3;
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
optional string uid = 4;
|
||||||
|
|
||||||
|
// APIGroup is the name of the API group that contains the referred object.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
optional string apiGroup = 5;
|
||||||
|
|
||||||
|
// APIVersion is the version of the API group that contains the referred object.
|
||||||
|
// +optional
|
||||||
|
optional string apiVersion = 6;
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
optional string resourceVersion = 7;
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
optional string subresource = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy defines the configuration of audit logging, and the rules for how different request
|
||||||
|
// categories are logged.
|
||||||
|
message Policy {
|
||||||
|
// ObjectMeta is included for interoperability with API infrastructure.
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||||
|
|
||||||
|
// Rules specify the audit Level a request should be recorded at.
|
||||||
|
// A request may match multiple rules, in which case the FIRST matching rule is used.
|
||||||
|
// The default audit level is None, but can be overridden by a catch-all rule at the end of the list.
|
||||||
|
// PolicyRules are strictly ordered.
|
||||||
|
repeated PolicyRule rules = 2;
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified per rule in which case the union of both are omitted.
|
||||||
|
// +optional
|
||||||
|
repeated string omitStages = 3;
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// This is used as a global default - a value of 'true' will omit the managed fileds,
|
||||||
|
// otherwise the managed fields will be included in the API audit log.
|
||||||
|
// Note that this can also be specified per rule in which case the value specified
|
||||||
|
// in a rule will override the global default.
|
||||||
|
// +optional
|
||||||
|
optional bool omitManagedFields = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyList is a list of audit Policies.
|
||||||
|
message PolicyList {
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||||
|
|
||||||
|
repeated Policy items = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyRule maps requests based off metadata to an audit Level.
|
||||||
|
// Requests must match the rules of every field (an intersection of rules).
|
||||||
|
message PolicyRule {
|
||||||
|
// The Level that requests matching this rule are recorded at.
|
||||||
|
optional string level = 1;
|
||||||
|
|
||||||
|
// The users (by authenticated user name) this rule applies to.
|
||||||
|
// An empty list implies every user.
|
||||||
|
// +optional
|
||||||
|
repeated string users = 2;
|
||||||
|
|
||||||
|
// The user groups this rule applies to. A user is considered matching
|
||||||
|
// if it is a member of any of the UserGroups.
|
||||||
|
// An empty list implies every user group.
|
||||||
|
// +optional
|
||||||
|
repeated string userGroups = 3;
|
||||||
|
|
||||||
|
// The verbs that match this rule.
|
||||||
|
// An empty list implies every verb.
|
||||||
|
// +optional
|
||||||
|
repeated string verbs = 4;
|
||||||
|
|
||||||
|
// Resources that this rule matches. An empty list implies all kinds in all API groups.
|
||||||
|
// +optional
|
||||||
|
repeated GroupResources resources = 5;
|
||||||
|
|
||||||
|
// Namespaces that this rule matches.
|
||||||
|
// The empty string "" matches non-namespaced resources.
|
||||||
|
// An empty list implies every namespace.
|
||||||
|
// +optional
|
||||||
|
repeated string namespaces = 6;
|
||||||
|
|
||||||
|
// NonResourceURLs is a set of URL paths that should be audited.
|
||||||
|
// *s are allowed, but only as the full, final step in the path.
|
||||||
|
// Examples:
|
||||||
|
// "/metrics" - Log requests for apiserver metrics
|
||||||
|
// "/healthz*" - Log all health checks
|
||||||
|
// +optional
|
||||||
|
repeated string nonResourceURLs = 7;
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified policy wide in which case the union of both are omitted.
|
||||||
|
// An empty list means no restrictions will apply.
|
||||||
|
// +optional
|
||||||
|
repeated string omitStages = 8;
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// - a value of 'true' will drop the managed fields from the API audit log
|
||||||
|
// - a value of 'false' indicates that the managed fileds should be included
|
||||||
|
// in the API audit log
|
||||||
|
// Note that the value, if specified, in this rule will override the global default
|
||||||
|
// If a value is not specified then the global default specified in
|
||||||
|
// Policy.OmitManagedFields will stand.
|
||||||
|
// +optional
|
||||||
|
optional bool omitManagedFields = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the group name use in this package
|
||||||
|
const GroupName = "audit.k8s.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||||
|
|
||||||
|
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||||
|
func Resource(resource string) schema.GroupResource {
|
||||||
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
SchemeBuilder runtime.SchemeBuilder
|
||||||
|
localSchemeBuilder = &SchemeBuilder
|
||||||
|
AddToScheme = localSchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// We only register manually written functions here. The registration of the
|
||||||
|
// generated functions takes place in the generated files. The separation
|
||||||
|
// makes the code compile even when the generated files are missing.
|
||||||
|
localSchemeBuilder.Register(addKnownTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&Event{},
|
||||||
|
&EventList{},
|
||||||
|
&Policy{},
|
||||||
|
&PolicyList{},
|
||||||
|
)
|
||||||
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
authnv1 "k8s.io/api/authentication/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Header keys used by the audit system.
|
||||||
|
const (
|
||||||
|
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
|
||||||
|
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
|
||||||
|
// server or kube-aggregator).
|
||||||
|
HeaderAuditID = "Audit-ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level defines the amount of information logged during auditing
|
||||||
|
type Level string
|
||||||
|
|
||||||
|
// Valid audit levels
|
||||||
|
const (
|
||||||
|
// LevelNone disables auditing
|
||||||
|
LevelNone Level = "None"
|
||||||
|
// LevelMetadata provides the basic level of auditing.
|
||||||
|
LevelMetadata Level = "Metadata"
|
||||||
|
// LevelRequest provides Metadata level of auditing, and additionally
|
||||||
|
// logs the request object (does not apply for non-resource requests).
|
||||||
|
LevelRequest Level = "Request"
|
||||||
|
// LevelRequestResponse provides Request level of auditing, and additionally
|
||||||
|
// logs the response object (does not apply for non-resource requests).
|
||||||
|
LevelRequestResponse Level = "RequestResponse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stage defines the stages in request handling that audit events may be generated.
|
||||||
|
type Stage string
|
||||||
|
|
||||||
|
// Valid audit stages.
|
||||||
|
const (
|
||||||
|
// The stage for events generated as soon as the audit handler receives the request, and before it
|
||||||
|
// is delegated down the handler chain.
|
||||||
|
StageRequestReceived Stage = "RequestReceived"
|
||||||
|
// The stage for events generated once the response headers are sent, but before the response body
|
||||||
|
// is sent. This stage is only generated for long-running requests (e.g. watch).
|
||||||
|
StageResponseStarted Stage = "ResponseStarted"
|
||||||
|
// The stage for events generated once the response body has been completed, and no more bytes
|
||||||
|
// will be sent.
|
||||||
|
StageResponseComplete Stage = "ResponseComplete"
|
||||||
|
// The stage for events generated when a panic occurred.
|
||||||
|
StagePanic Stage = "Panic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Event captures all the information that can be included in an API audit log.
|
||||||
|
type Event struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// AuditLevel at which event was generated
|
||||||
|
Level Level `json:"level" protobuf:"bytes,1,opt,name=level,casttype=Level"`
|
||||||
|
|
||||||
|
// Unique audit ID, generated for each request.
|
||||||
|
AuditID types.UID `json:"auditID" protobuf:"bytes,2,opt,name=auditID,casttype=k8s.io/apimachinery/pkg/types.UID"`
|
||||||
|
// Stage of the request handling when this event instance was generated.
|
||||||
|
Stage Stage `json:"stage" protobuf:"bytes,3,opt,name=stage,casttype=Stage"`
|
||||||
|
|
||||||
|
// RequestURI is the request URI as sent by the client to a server.
|
||||||
|
RequestURI string `json:"requestURI" protobuf:"bytes,4,opt,name=requestURI"`
|
||||||
|
// Verb is the kubernetes verb associated with the request.
|
||||||
|
// For non-resource requests, this is the lower-cased HTTP method.
|
||||||
|
Verb string `json:"verb" protobuf:"bytes,5,opt,name=verb"`
|
||||||
|
// Authenticated user information.
|
||||||
|
User authnv1.UserInfo `json:"user" protobuf:"bytes,6,opt,name=user"`
|
||||||
|
// Impersonated user information.
|
||||||
|
// +optional
|
||||||
|
ImpersonatedUser *authnv1.UserInfo `json:"impersonatedUser,omitempty" protobuf:"bytes,7,opt,name=impersonatedUser"`
|
||||||
|
// Source IPs, from where the request originated and intermediate proxies.
|
||||||
|
// The source IPs are listed from (in order):
|
||||||
|
// 1. X-Forwarded-For request header IPs
|
||||||
|
// 2. X-Real-Ip header, if not present in the X-Forwarded-For list
|
||||||
|
// 3. The remote address for the connection, if it doesn't match the last
|
||||||
|
// IP in the list up to here (X-Forwarded-For or X-Real-Ip).
|
||||||
|
// Note: All but the last IP can be arbitrarily set by the client.
|
||||||
|
// +optional
|
||||||
|
SourceIPs []string `json:"sourceIPs,omitempty" protobuf:"bytes,8,rep,name=sourceIPs"`
|
||||||
|
// UserAgent records the user agent string reported by the client.
|
||||||
|
// Note that the UserAgent is provided by the client, and must not be trusted.
|
||||||
|
// +optional
|
||||||
|
UserAgent string `json:"userAgent,omitempty" protobuf:"bytes,16,opt,name=userAgent"`
|
||||||
|
// Object reference this request is targeted at.
|
||||||
|
// Does not apply for List-type requests, or non-resource requests.
|
||||||
|
// +optional
|
||||||
|
ObjectRef *ObjectReference `json:"objectRef,omitempty" protobuf:"bytes,9,opt,name=objectRef"`
|
||||||
|
// The response status, populated even when the ResponseObject is not a Status type.
|
||||||
|
// For successful responses, this will only include the Code and StatusSuccess.
|
||||||
|
// For non-status type error responses, this will be auto-populated with the error Message.
|
||||||
|
// +optional
|
||||||
|
ResponseStatus *metav1.Status `json:"responseStatus,omitempty" protobuf:"bytes,10,opt,name=responseStatus"`
|
||||||
|
|
||||||
|
// API object from the request, in JSON format. The RequestObject is recorded as-is in the request
|
||||||
|
// (possibly re-encoded as JSON), prior to version conversion, defaulting, admission or
|
||||||
|
// merging. It is an external versioned object type, and may not be a valid object on its own.
|
||||||
|
// Omitted for non-resource requests. Only logged at Request Level and higher.
|
||||||
|
// +optional
|
||||||
|
RequestObject *runtime.Unknown `json:"requestObject,omitempty" protobuf:"bytes,11,opt,name=requestObject"`
|
||||||
|
// API object returned in the response, in JSON. The ResponseObject is recorded after conversion
|
||||||
|
// to the external type, and serialized as JSON. Omitted for non-resource requests. Only logged
|
||||||
|
// at Response Level.
|
||||||
|
// +optional
|
||||||
|
ResponseObject *runtime.Unknown `json:"responseObject,omitempty" protobuf:"bytes,12,opt,name=responseObject"`
|
||||||
|
// Time the request reached the apiserver.
|
||||||
|
// +optional
|
||||||
|
RequestReceivedTimestamp metav1.MicroTime `json:"requestReceivedTimestamp" protobuf:"bytes,13,opt,name=requestReceivedTimestamp"`
|
||||||
|
// Time the request reached current audit stage.
|
||||||
|
// +optional
|
||||||
|
StageTimestamp metav1.MicroTime `json:"stageTimestamp" protobuf:"bytes,14,opt,name=stageTimestamp"`
|
||||||
|
|
||||||
|
// Annotations is an unstructured key value map stored with an audit event that may be set by
|
||||||
|
// plugins invoked in the request serving chain, including authentication, authorization and
|
||||||
|
// admission plugins. Note that these annotations are for the audit event, and do not correspond
|
||||||
|
// to the metadata.annotations of the submitted object. Keys should uniquely identify the informing
|
||||||
|
// component to avoid name collisions (e.g. podsecuritypolicy.admission.k8s.io/policy). Values
|
||||||
|
// should be short. Annotations are included in the Metadata level.
|
||||||
|
// +optional
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,15,rep,name=annotations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// EventList is a list of audit Events.
|
||||||
|
type EventList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
Items []Event `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Policy defines the configuration of audit logging, and the rules for how different request
|
||||||
|
// categories are logged.
|
||||||
|
type Policy struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// ObjectMeta is included for interoperability with API infrastructure.
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// Rules specify the audit Level a request should be recorded at.
|
||||||
|
// A request may match multiple rules, in which case the FIRST matching rule is used.
|
||||||
|
// The default audit level is None, but can be overridden by a catch-all rule at the end of the list.
|
||||||
|
// PolicyRules are strictly ordered.
|
||||||
|
Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"`
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified per rule in which case the union of both are omitted.
|
||||||
|
// +optional
|
||||||
|
OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,3,rep,name=omitStages"`
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// This is used as a global default - a value of 'true' will omit the managed fileds,
|
||||||
|
// otherwise the managed fields will be included in the API audit log.
|
||||||
|
// Note that this can also be specified per rule in which case the value specified
|
||||||
|
// in a rule will override the global default.
|
||||||
|
// +optional
|
||||||
|
OmitManagedFields bool `json:"omitManagedFields,omitempty" protobuf:"varint,4,opt,name=omitManagedFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// PolicyList is a list of audit Policies.
|
||||||
|
type PolicyList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
Items []Policy `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyRule maps requests based off metadata to an audit Level.
|
||||||
|
// Requests must match the rules of every field (an intersection of rules).
|
||||||
|
type PolicyRule struct {
|
||||||
|
// The Level that requests matching this rule are recorded at.
|
||||||
|
Level Level `json:"level" protobuf:"bytes,1,opt,name=level,casttype=Level"`
|
||||||
|
|
||||||
|
// The users (by authenticated user name) this rule applies to.
|
||||||
|
// An empty list implies every user.
|
||||||
|
// +optional
|
||||||
|
Users []string `json:"users,omitempty" protobuf:"bytes,2,rep,name=users"`
|
||||||
|
// The user groups this rule applies to. A user is considered matching
|
||||||
|
// if it is a member of any of the UserGroups.
|
||||||
|
// An empty list implies every user group.
|
||||||
|
// +optional
|
||||||
|
UserGroups []string `json:"userGroups,omitempty" protobuf:"bytes,3,rep,name=userGroups"`
|
||||||
|
|
||||||
|
// The verbs that match this rule.
|
||||||
|
// An empty list implies every verb.
|
||||||
|
// +optional
|
||||||
|
Verbs []string `json:"verbs,omitempty" protobuf:"bytes,4,rep,name=verbs"`
|
||||||
|
|
||||||
|
// Rules can apply to API resources (such as "pods" or "secrets"),
|
||||||
|
// non-resource URL paths (such as "/api"), or neither, but not both.
|
||||||
|
// If neither is specified, the rule is treated as a default for all URLs.
|
||||||
|
|
||||||
|
// Resources that this rule matches. An empty list implies all kinds in all API groups.
|
||||||
|
// +optional
|
||||||
|
Resources []GroupResources `json:"resources,omitempty" protobuf:"bytes,5,rep,name=resources"`
|
||||||
|
// Namespaces that this rule matches.
|
||||||
|
// The empty string "" matches non-namespaced resources.
|
||||||
|
// An empty list implies every namespace.
|
||||||
|
// +optional
|
||||||
|
Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,6,rep,name=namespaces"`
|
||||||
|
|
||||||
|
// NonResourceURLs is a set of URL paths that should be audited.
|
||||||
|
// *s are allowed, but only as the full, final step in the path.
|
||||||
|
// Examples:
|
||||||
|
// "/metrics" - Log requests for apiserver metrics
|
||||||
|
// "/healthz*" - Log all health checks
|
||||||
|
// +optional
|
||||||
|
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"`
|
||||||
|
|
||||||
|
// OmitStages is a list of stages for which no events are created. Note that this can also
|
||||||
|
// be specified policy wide in which case the union of both are omitted.
|
||||||
|
// An empty list means no restrictions will apply.
|
||||||
|
// +optional
|
||||||
|
OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,8,rep,name=omitStages"`
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
// - a value of 'true' will drop the managed fields from the API audit log
|
||||||
|
// - a value of 'false' indicates that the managed fileds should be included
|
||||||
|
// in the API audit log
|
||||||
|
// Note that the value, if specified, in this rule will override the global default
|
||||||
|
// If a value is not specified then the global default specified in
|
||||||
|
// Policy.OmitManagedFields will stand.
|
||||||
|
// +optional
|
||||||
|
OmitManagedFields *bool `json:"omitManagedFields,omitempty" protobuf:"varint,9,opt,name=omitManagedFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupResources represents resource kinds in an API group.
|
||||||
|
type GroupResources struct {
|
||||||
|
// Group is the name of the API group that contains the resources.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
|
||||||
|
// Resources is a list of resources this rule applies to.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// 'pods' matches pods.
|
||||||
|
// 'pods/log' matches the log subresource of pods.
|
||||||
|
// '*' matches all resources and their subresources.
|
||||||
|
// 'pods/*' matches all subresources of pods.
|
||||||
|
// '*/scale' matches all scale subresources.
|
||||||
|
//
|
||||||
|
// If wildcard is present, the validation rule will ensure resources do not
|
||||||
|
// overlap with each other.
|
||||||
|
//
|
||||||
|
// An empty list implies all resources and subresources in this API groups apply.
|
||||||
|
// +optional
|
||||||
|
Resources []string `json:"resources,omitempty" protobuf:"bytes,2,rep,name=resources"`
|
||||||
|
// ResourceNames is a list of resource instance names that the policy matches.
|
||||||
|
// Using this field requires Resources to be specified.
|
||||||
|
// An empty list implies that every instance of the resource is matched.
|
||||||
|
// +optional
|
||||||
|
ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,3,rep,name=resourceNames"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectReference contains enough information to let you inspect or modify the referred object.
|
||||||
|
type ObjectReference struct {
|
||||||
|
// +optional
|
||||||
|
Resource string `json:"resource,omitempty" protobuf:"bytes,1,opt,name=resource"`
|
||||||
|
// +optional
|
||||||
|
Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"`
|
||||||
|
// +optional
|
||||||
|
Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
|
||||||
|
// +optional
|
||||||
|
UID types.UID `json:"uid,omitempty" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"`
|
||||||
|
// APIGroup is the name of the API group that contains the referred object.
|
||||||
|
// The empty string represents the core API group.
|
||||||
|
// +optional
|
||||||
|
APIGroup string `json:"apiGroup,omitempty" protobuf:"bytes,5,opt,name=apiGroup"`
|
||||||
|
// APIVersion is the version of the API group that contains the referred object.
|
||||||
|
// +optional
|
||||||
|
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,6,opt,name=apiVersion"`
|
||||||
|
// +optional
|
||||||
|
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,7,opt,name=resourceVersion"`
|
||||||
|
// +optional
|
||||||
|
Subresource string `json:"subresource,omitempty" protobuf:"bytes,8,opt,name=subresource"`
|
||||||
|
}
|
||||||
327
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.conversion.go
generated
vendored
Normal file
327
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.conversion.go
generated
vendored
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by conversion-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
audit "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localSchemeBuilder.Register(RegisterConversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterConversions adds conversion functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
func RegisterConversions(s *runtime.Scheme) error {
|
||||||
|
if err := s.AddGeneratedConversionFunc((*Event)(nil), (*audit.Event)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_Event_To_audit_Event(a.(*Event), b.(*audit.Event), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.Event)(nil), (*Event)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_Event_To_v1_Event(a.(*audit.Event), b.(*Event), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*EventList)(nil), (*audit.EventList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_EventList_To_audit_EventList(a.(*EventList), b.(*audit.EventList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.EventList)(nil), (*EventList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_EventList_To_v1_EventList(a.(*audit.EventList), b.(*EventList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*GroupResources)(nil), (*audit.GroupResources)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_GroupResources_To_audit_GroupResources(a.(*GroupResources), b.(*audit.GroupResources), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.GroupResources)(nil), (*GroupResources)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_GroupResources_To_v1_GroupResources(a.(*audit.GroupResources), b.(*GroupResources), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*ObjectReference)(nil), (*audit.ObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_ObjectReference_To_audit_ObjectReference(a.(*ObjectReference), b.(*audit.ObjectReference), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.ObjectReference)(nil), (*ObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_ObjectReference_To_v1_ObjectReference(a.(*audit.ObjectReference), b.(*ObjectReference), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*Policy)(nil), (*audit.Policy)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_Policy_To_audit_Policy(a.(*Policy), b.(*audit.Policy), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.Policy)(nil), (*Policy)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_Policy_To_v1_Policy(a.(*audit.Policy), b.(*Policy), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*PolicyList)(nil), (*audit.PolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_PolicyList_To_audit_PolicyList(a.(*PolicyList), b.(*audit.PolicyList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.PolicyList)(nil), (*PolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_PolicyList_To_v1_PolicyList(a.(*audit.PolicyList), b.(*PolicyList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*PolicyRule)(nil), (*audit.PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_PolicyRule_To_audit_PolicyRule(a.(*PolicyRule), b.(*audit.PolicyRule), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*audit.PolicyRule)(nil), (*PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_audit_PolicyRule_To_v1_PolicyRule(a.(*audit.PolicyRule), b.(*PolicyRule), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_Event_To_audit_Event(in *Event, out *audit.Event, s conversion.Scope) error {
|
||||||
|
out.Level = audit.Level(in.Level)
|
||||||
|
out.AuditID = types.UID(in.AuditID)
|
||||||
|
out.Stage = audit.Stage(in.Stage)
|
||||||
|
out.RequestURI = in.RequestURI
|
||||||
|
out.Verb = in.Verb
|
||||||
|
out.User = in.User
|
||||||
|
out.ImpersonatedUser = (*authenticationv1.UserInfo)(unsafe.Pointer(in.ImpersonatedUser))
|
||||||
|
out.SourceIPs = *(*[]string)(unsafe.Pointer(&in.SourceIPs))
|
||||||
|
out.UserAgent = in.UserAgent
|
||||||
|
out.ObjectRef = (*audit.ObjectReference)(unsafe.Pointer(in.ObjectRef))
|
||||||
|
out.ResponseStatus = (*metav1.Status)(unsafe.Pointer(in.ResponseStatus))
|
||||||
|
out.RequestObject = (*runtime.Unknown)(unsafe.Pointer(in.RequestObject))
|
||||||
|
out.ResponseObject = (*runtime.Unknown)(unsafe.Pointer(in.ResponseObject))
|
||||||
|
out.RequestReceivedTimestamp = in.RequestReceivedTimestamp
|
||||||
|
out.StageTimestamp = in.StageTimestamp
|
||||||
|
out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_Event_To_audit_Event is an autogenerated conversion function.
|
||||||
|
func Convert_v1_Event_To_audit_Event(in *Event, out *audit.Event, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_Event_To_audit_Event(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_Event_To_v1_Event(in *audit.Event, out *Event, s conversion.Scope) error {
|
||||||
|
out.Level = Level(in.Level)
|
||||||
|
out.AuditID = types.UID(in.AuditID)
|
||||||
|
out.Stage = Stage(in.Stage)
|
||||||
|
out.RequestURI = in.RequestURI
|
||||||
|
out.Verb = in.Verb
|
||||||
|
out.User = in.User
|
||||||
|
out.ImpersonatedUser = (*authenticationv1.UserInfo)(unsafe.Pointer(in.ImpersonatedUser))
|
||||||
|
out.SourceIPs = *(*[]string)(unsafe.Pointer(&in.SourceIPs))
|
||||||
|
out.UserAgent = in.UserAgent
|
||||||
|
out.ObjectRef = (*ObjectReference)(unsafe.Pointer(in.ObjectRef))
|
||||||
|
out.ResponseStatus = (*metav1.Status)(unsafe.Pointer(in.ResponseStatus))
|
||||||
|
out.RequestObject = (*runtime.Unknown)(unsafe.Pointer(in.RequestObject))
|
||||||
|
out.ResponseObject = (*runtime.Unknown)(unsafe.Pointer(in.ResponseObject))
|
||||||
|
out.RequestReceivedTimestamp = in.RequestReceivedTimestamp
|
||||||
|
out.StageTimestamp = in.StageTimestamp
|
||||||
|
out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_Event_To_v1_Event is an autogenerated conversion function.
|
||||||
|
func Convert_audit_Event_To_v1_Event(in *audit.Event, out *Event, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_Event_To_v1_Event(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_EventList_To_audit_EventList(in *EventList, out *audit.EventList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]audit.Event)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_EventList_To_audit_EventList is an autogenerated conversion function.
|
||||||
|
func Convert_v1_EventList_To_audit_EventList(in *EventList, out *audit.EventList, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_EventList_To_audit_EventList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_EventList_To_v1_EventList(in *audit.EventList, out *EventList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]Event)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_EventList_To_v1_EventList is an autogenerated conversion function.
|
||||||
|
func Convert_audit_EventList_To_v1_EventList(in *audit.EventList, out *EventList, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_EventList_To_v1_EventList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_GroupResources_To_audit_GroupResources(in *GroupResources, out *audit.GroupResources, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||||
|
out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_GroupResources_To_audit_GroupResources is an autogenerated conversion function.
|
||||||
|
func Convert_v1_GroupResources_To_audit_GroupResources(in *GroupResources, out *audit.GroupResources, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_GroupResources_To_audit_GroupResources(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_GroupResources_To_v1_GroupResources(in *audit.GroupResources, out *GroupResources, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||||
|
out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_GroupResources_To_v1_GroupResources is an autogenerated conversion function.
|
||||||
|
func Convert_audit_GroupResources_To_v1_GroupResources(in *audit.GroupResources, out *GroupResources, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_GroupResources_To_v1_GroupResources(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_ObjectReference_To_audit_ObjectReference(in *ObjectReference, out *audit.ObjectReference, s conversion.Scope) error {
|
||||||
|
out.Resource = in.Resource
|
||||||
|
out.Namespace = in.Namespace
|
||||||
|
out.Name = in.Name
|
||||||
|
out.UID = types.UID(in.UID)
|
||||||
|
out.APIGroup = in.APIGroup
|
||||||
|
out.APIVersion = in.APIVersion
|
||||||
|
out.ResourceVersion = in.ResourceVersion
|
||||||
|
out.Subresource = in.Subresource
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_ObjectReference_To_audit_ObjectReference is an autogenerated conversion function.
|
||||||
|
func Convert_v1_ObjectReference_To_audit_ObjectReference(in *ObjectReference, out *audit.ObjectReference, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_ObjectReference_To_audit_ObjectReference(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_ObjectReference_To_v1_ObjectReference(in *audit.ObjectReference, out *ObjectReference, s conversion.Scope) error {
|
||||||
|
out.Resource = in.Resource
|
||||||
|
out.Namespace = in.Namespace
|
||||||
|
out.Name = in.Name
|
||||||
|
out.UID = types.UID(in.UID)
|
||||||
|
out.APIGroup = in.APIGroup
|
||||||
|
out.APIVersion = in.APIVersion
|
||||||
|
out.ResourceVersion = in.ResourceVersion
|
||||||
|
out.Subresource = in.Subresource
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_ObjectReference_To_v1_ObjectReference is an autogenerated conversion function.
|
||||||
|
func Convert_audit_ObjectReference_To_v1_ObjectReference(in *audit.ObjectReference, out *ObjectReference, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_ObjectReference_To_v1_ObjectReference(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error {
|
||||||
|
out.ObjectMeta = in.ObjectMeta
|
||||||
|
out.Rules = *(*[]audit.PolicyRule)(unsafe.Pointer(&in.Rules))
|
||||||
|
out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages))
|
||||||
|
out.OmitManagedFields = in.OmitManagedFields
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_Policy_To_audit_Policy is an autogenerated conversion function.
|
||||||
|
func Convert_v1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_Policy_To_audit_Policy(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_Policy_To_v1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error {
|
||||||
|
out.ObjectMeta = in.ObjectMeta
|
||||||
|
out.Rules = *(*[]PolicyRule)(unsafe.Pointer(&in.Rules))
|
||||||
|
out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages))
|
||||||
|
out.OmitManagedFields = in.OmitManagedFields
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_Policy_To_v1_Policy is an autogenerated conversion function.
|
||||||
|
func Convert_audit_Policy_To_v1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_Policy_To_v1_Policy(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_PolicyList_To_audit_PolicyList(in *PolicyList, out *audit.PolicyList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]audit.Policy)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_PolicyList_To_audit_PolicyList is an autogenerated conversion function.
|
||||||
|
func Convert_v1_PolicyList_To_audit_PolicyList(in *PolicyList, out *audit.PolicyList, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_PolicyList_To_audit_PolicyList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_PolicyList_To_v1_PolicyList(in *audit.PolicyList, out *PolicyList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]Policy)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_PolicyList_To_v1_PolicyList is an autogenerated conversion function.
|
||||||
|
func Convert_audit_PolicyList_To_v1_PolicyList(in *audit.PolicyList, out *PolicyList, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_PolicyList_To_v1_PolicyList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *audit.PolicyRule, s conversion.Scope) error {
|
||||||
|
out.Level = audit.Level(in.Level)
|
||||||
|
out.Users = *(*[]string)(unsafe.Pointer(&in.Users))
|
||||||
|
out.UserGroups = *(*[]string)(unsafe.Pointer(&in.UserGroups))
|
||||||
|
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
|
||||||
|
out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources))
|
||||||
|
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
|
||||||
|
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
|
||||||
|
out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages))
|
||||||
|
out.OmitManagedFields = (*bool)(unsafe.Pointer(in.OmitManagedFields))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_PolicyRule_To_audit_PolicyRule is an autogenerated conversion function.
|
||||||
|
func Convert_v1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *audit.PolicyRule, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_PolicyRule_To_audit_PolicyRule(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_audit_PolicyRule_To_v1_PolicyRule(in *audit.PolicyRule, out *PolicyRule, s conversion.Scope) error {
|
||||||
|
out.Level = Level(in.Level)
|
||||||
|
out.Users = *(*[]string)(unsafe.Pointer(&in.Users))
|
||||||
|
out.UserGroups = *(*[]string)(unsafe.Pointer(&in.UserGroups))
|
||||||
|
out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs))
|
||||||
|
out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources))
|
||||||
|
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
|
||||||
|
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
|
||||||
|
out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages))
|
||||||
|
out.OmitManagedFields = (*bool)(unsafe.Pointer(in.OmitManagedFields))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_audit_PolicyRule_To_v1_PolicyRule is an autogenerated conversion function.
|
||||||
|
func Convert_audit_PolicyRule_To_v1_PolicyRule(in *audit.PolicyRule, out *PolicyRule, s conversion.Scope) error {
|
||||||
|
return autoConvert_audit_PolicyRule_To_v1_PolicyRule(in, out, s)
|
||||||
|
}
|
||||||
297
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
297
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,297 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Event) DeepCopyInto(out *Event) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.User.DeepCopyInto(&out.User)
|
||||||
|
if in.ImpersonatedUser != nil {
|
||||||
|
in, out := &in.ImpersonatedUser, &out.ImpersonatedUser
|
||||||
|
*out = new(authenticationv1.UserInfo)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.SourceIPs != nil {
|
||||||
|
in, out := &in.SourceIPs, &out.SourceIPs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.ObjectRef != nil {
|
||||||
|
in, out := &in.ObjectRef, &out.ObjectRef
|
||||||
|
*out = new(ObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ResponseStatus != nil {
|
||||||
|
in, out := &in.ResponseStatus, &out.ResponseStatus
|
||||||
|
*out = new(metav1.Status)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.RequestObject != nil {
|
||||||
|
in, out := &in.RequestObject, &out.RequestObject
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ResponseObject != nil {
|
||||||
|
in, out := &in.ResponseObject, &out.ResponseObject
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
in.RequestReceivedTimestamp.DeepCopyInto(&out.RequestReceivedTimestamp)
|
||||||
|
in.StageTimestamp.DeepCopyInto(&out.StageTimestamp)
|
||||||
|
if in.Annotations != nil {
|
||||||
|
in, out := &in.Annotations, &out.Annotations
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event.
|
||||||
|
func (in *Event) DeepCopy() *Event {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Event)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Event) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Event, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList.
|
||||||
|
func (in *EventList) DeepCopy() *EventList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EventList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *EventList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *GroupResources) DeepCopyInto(out *GroupResources) {
|
||||||
|
*out = *in
|
||||||
|
if in.Resources != nil {
|
||||||
|
in, out := &in.Resources, &out.Resources
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.ResourceNames != nil {
|
||||||
|
in, out := &in.ResourceNames, &out.ResourceNames
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResources.
|
||||||
|
func (in *GroupResources) DeepCopy() *GroupResources {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(GroupResources)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ObjectReference) DeepCopyInto(out *ObjectReference) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference.
|
||||||
|
func (in *ObjectReference) DeepCopy() *ObjectReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ObjectReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Policy) DeepCopyInto(out *Policy) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
if in.Rules != nil {
|
||||||
|
in, out := &in.Rules, &out.Rules
|
||||||
|
*out = make([]PolicyRule, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.OmitStages != nil {
|
||||||
|
in, out := &in.OmitStages, &out.OmitStages
|
||||||
|
*out = make([]Stage, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
|
||||||
|
func (in *Policy) DeepCopy() *Policy {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Policy)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Policy) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Policy, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList.
|
||||||
|
func (in *PolicyList) DeepCopy() *PolicyList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PolicyList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *PolicyList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PolicyRule) DeepCopyInto(out *PolicyRule) {
|
||||||
|
*out = *in
|
||||||
|
if in.Users != nil {
|
||||||
|
in, out := &in.Users, &out.Users
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.UserGroups != nil {
|
||||||
|
in, out := &in.UserGroups, &out.UserGroups
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Verbs != nil {
|
||||||
|
in, out := &in.Verbs, &out.Verbs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Resources != nil {
|
||||||
|
in, out := &in.Resources, &out.Resources
|
||||||
|
*out = make([]GroupResources, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Namespaces != nil {
|
||||||
|
in, out := &in.Namespaces, &out.Namespaces
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.NonResourceURLs != nil {
|
||||||
|
in, out := &in.NonResourceURLs, &out.NonResourceURLs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.OmitStages != nil {
|
||||||
|
in, out := &in.OmitStages, &out.OmitStages
|
||||||
|
*out = make([]Stage, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.OmitManagedFields != nil {
|
||||||
|
in, out := &in.OmitManagedFields, &out.OmitManagedFields
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule.
|
||||||
|
func (in *PolicyRule) DeepCopy() *PolicyRule {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PolicyRule)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
33
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.defaults.go
generated
vendored
Normal file
33
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.defaults.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
// All generated defaulters are covering - they call all nested defaulters.
|
||||||
|
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/api/authentication/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Event) DeepCopyInto(out *Event) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.User.DeepCopyInto(&out.User)
|
||||||
|
if in.ImpersonatedUser != nil {
|
||||||
|
in, out := &in.ImpersonatedUser, &out.ImpersonatedUser
|
||||||
|
*out = new(v1.UserInfo)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.SourceIPs != nil {
|
||||||
|
in, out := &in.SourceIPs, &out.SourceIPs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.ObjectRef != nil {
|
||||||
|
in, out := &in.ObjectRef, &out.ObjectRef
|
||||||
|
*out = new(ObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ResponseStatus != nil {
|
||||||
|
in, out := &in.ResponseStatus, &out.ResponseStatus
|
||||||
|
*out = new(metav1.Status)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.RequestObject != nil {
|
||||||
|
in, out := &in.RequestObject, &out.RequestObject
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ResponseObject != nil {
|
||||||
|
in, out := &in.ResponseObject, &out.ResponseObject
|
||||||
|
*out = new(runtime.Unknown)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
in.RequestReceivedTimestamp.DeepCopyInto(&out.RequestReceivedTimestamp)
|
||||||
|
in.StageTimestamp.DeepCopyInto(&out.StageTimestamp)
|
||||||
|
if in.Annotations != nil {
|
||||||
|
in, out := &in.Annotations, &out.Annotations
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event.
|
||||||
|
func (in *Event) DeepCopy() *Event {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Event)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Event) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Event, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList.
|
||||||
|
func (in *EventList) DeepCopy() *EventList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EventList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *EventList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *GroupResources) DeepCopyInto(out *GroupResources) {
|
||||||
|
*out = *in
|
||||||
|
if in.Resources != nil {
|
||||||
|
in, out := &in.Resources, &out.Resources
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.ResourceNames != nil {
|
||||||
|
in, out := &in.ResourceNames, &out.ResourceNames
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResources.
|
||||||
|
func (in *GroupResources) DeepCopy() *GroupResources {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(GroupResources)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ObjectReference) DeepCopyInto(out *ObjectReference) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference.
|
||||||
|
func (in *ObjectReference) DeepCopy() *ObjectReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ObjectReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Policy) DeepCopyInto(out *Policy) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
if in.Rules != nil {
|
||||||
|
in, out := &in.Rules, &out.Rules
|
||||||
|
*out = make([]PolicyRule, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.OmitStages != nil {
|
||||||
|
in, out := &in.OmitStages, &out.OmitStages
|
||||||
|
*out = make([]Stage, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
|
||||||
|
func (in *Policy) DeepCopy() *Policy {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Policy)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Policy) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Policy, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList.
|
||||||
|
func (in *PolicyList) DeepCopy() *PolicyList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PolicyList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *PolicyList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PolicyRule) DeepCopyInto(out *PolicyRule) {
|
||||||
|
*out = *in
|
||||||
|
if in.Users != nil {
|
||||||
|
in, out := &in.Users, &out.Users
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.UserGroups != nil {
|
||||||
|
in, out := &in.UserGroups, &out.UserGroups
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Verbs != nil {
|
||||||
|
in, out := &in.Verbs, &out.Verbs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Resources != nil {
|
||||||
|
in, out := &in.Resources, &out.Resources
|
||||||
|
*out = make([]GroupResources, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Namespaces != nil {
|
||||||
|
in, out := &in.Namespaces, &out.Namespaces
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.NonResourceURLs != nil {
|
||||||
|
in, out := &in.NonResourceURLs, &out.NonResourceURLs
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.OmitStages != nil {
|
||||||
|
in, out := &in.OmitStages, &out.OmitStages
|
||||||
|
*out = make([]Stage, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.OmitManagedFields != nil {
|
||||||
|
in, out := &in.OmitManagedFields, &out.OmitManagedFields
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule.
|
||||||
|
func (in *PolicyRule) DeepCopy() *PolicyRule {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PolicyRule)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- sig-auth-audit-approvers
|
||||||
|
reviewers:
|
||||||
|
- sig-auth-audit-reviewers
|
||||||
|
labels:
|
||||||
|
- sig/auth
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
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 audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The key type is unexported to prevent collisions
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// auditAnnotationsKey is the context key for the audit annotations.
|
||||||
|
// TODO: consolidate all audit info under the AuditContext, rather than storing 3 separate keys.
|
||||||
|
auditAnnotationsKey key = iota
|
||||||
|
|
||||||
|
// auditKey is the context key for storing the audit event that is being
|
||||||
|
// captured and the evaluated policy that applies to the given request.
|
||||||
|
auditKey
|
||||||
|
|
||||||
|
// auditAnnotationsMutexKey is the context key for the audit annotations mutex.
|
||||||
|
auditAnnotationsMutexKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// annotations = *[]annotation instead of a map to preserve order of insertions
|
||||||
|
type annotation struct {
|
||||||
|
key, value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuditAnnotations returns a new context that can store audit annotations
|
||||||
|
// via the AddAuditAnnotation function. This function is meant to be called from
|
||||||
|
// an early request handler to allow all later layers to set audit annotations.
|
||||||
|
// This is required to support flows where handlers that come before WithAudit
|
||||||
|
// (such as WithAuthentication) wish to set audit annotations.
|
||||||
|
func WithAuditAnnotations(parent context.Context) context.Context {
|
||||||
|
// this should never really happen, but prevent double registration of this slice
|
||||||
|
if _, ok := parent.Value(auditAnnotationsKey).(*[]annotation); ok {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
parent = withAuditAnnotationsMutex(parent)
|
||||||
|
|
||||||
|
var annotations []annotation // avoid allocations until we actually need it
|
||||||
|
return genericapirequest.WithValue(parent, auditAnnotationsKey, &annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAuditAnnotation sets the audit annotation for the given key, value pair.
|
||||||
|
// It is safe to call at most parts of request flow that come after WithAuditAnnotations.
|
||||||
|
// The notable exception being that this function must not be called via a
|
||||||
|
// defer statement (i.e. after ServeHTTP) in a handler that runs before WithAudit
|
||||||
|
// as at that point the audit event has already been sent to the audit sink.
|
||||||
|
// Handlers that are unaware of their position in the overall request flow should
|
||||||
|
// prefer AddAuditAnnotation over LogAnnotation to avoid dropping annotations.
|
||||||
|
func AddAuditAnnotation(ctx context.Context, key, value string) {
|
||||||
|
mutex, ok := auditAnnotationsMutex(ctx)
|
||||||
|
if !ok {
|
||||||
|
// auditing is not enabled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
var ctxAnnotations *[]annotation
|
||||||
|
if ae == nil {
|
||||||
|
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
addAuditAnnotationLocked(ae, ctxAnnotations, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAuditAnnotations is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
|
||||||
|
// restrictions on when this can be called.
|
||||||
|
// keysAndValues are the key-value pairs to add, and must have an even number of items.
|
||||||
|
func AddAuditAnnotations(ctx context.Context, keysAndValues ...string) {
|
||||||
|
mutex, ok := auditAnnotationsMutex(ctx)
|
||||||
|
if !ok {
|
||||||
|
// auditing is not enabled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
var ctxAnnotations *[]annotation
|
||||||
|
if ae == nil {
|
||||||
|
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keysAndValues)%2 != 0 {
|
||||||
|
klog.Errorf("Dropping mismatched audit annotation %q", keysAndValues[len(keysAndValues)-1])
|
||||||
|
}
|
||||||
|
for i := 0; i < len(keysAndValues); i += 2 {
|
||||||
|
addAuditAnnotationLocked(ae, ctxAnnotations, keysAndValues[i], keysAndValues[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAuditAnnotationsMap is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
|
||||||
|
// restrictions on when this can be called.
|
||||||
|
func AddAuditAnnotationsMap(ctx context.Context, annotations map[string]string) {
|
||||||
|
mutex, ok := auditAnnotationsMutex(ctx)
|
||||||
|
if !ok {
|
||||||
|
// auditing is not enabled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
var ctxAnnotations *[]annotation
|
||||||
|
if ae == nil {
|
||||||
|
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range annotations {
|
||||||
|
addAuditAnnotationLocked(ae, ctxAnnotations, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addAuditAnnotationLocked is the shared code for recording an audit annotation. This method should
|
||||||
|
// only be called while the auditAnnotationsMutex is locked.
|
||||||
|
func addAuditAnnotationLocked(ae *auditinternal.Event, annotations *[]annotation, key, value string) {
|
||||||
|
if ae != nil {
|
||||||
|
logAnnotation(ae, key, value)
|
||||||
|
} else if annotations != nil {
|
||||||
|
*annotations = append(*annotations, annotation{key: key, value: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is private to prevent reads/write to the slice from outside of this package.
|
||||||
|
// The audit event should be directly read to get access to the annotations.
|
||||||
|
func addAuditAnnotationsFrom(ctx context.Context, ev *auditinternal.Event) {
|
||||||
|
mutex, ok := auditAnnotationsMutex(ctx)
|
||||||
|
if !ok {
|
||||||
|
// auditing is not enabled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
annotations, ok := ctx.Value(auditAnnotationsKey).(*[]annotation)
|
||||||
|
if !ok {
|
||||||
|
return // no annotations to copy
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range *annotations {
|
||||||
|
logAnnotation(ev, kv.key, kv.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogAnnotation fills in the Annotations according to the key value pair.
|
||||||
|
func logAnnotation(ae *auditinternal.Event, key, value string) {
|
||||||
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ae.Annotations == nil {
|
||||||
|
ae.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
if v, ok := ae.Annotations[key]; ok && v != value {
|
||||||
|
klog.Warningf("Failed to set annotations[%q] to %q for audit:%q, it has already been set to %q", key, value, ae.AuditID, ae.Annotations[key])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ae.Annotations[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuditContext returns a new context that stores the pair of the audit
|
||||||
|
// configuration object that applies to the given request and
|
||||||
|
// the audit event that is going to be written to the API audit log.
|
||||||
|
func WithAuditContext(parent context.Context, ev *AuditContext) context.Context {
|
||||||
|
parent = withAuditAnnotationsMutex(parent)
|
||||||
|
return genericapirequest.WithValue(parent, auditKey, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditEventFrom returns the audit event struct on the ctx
|
||||||
|
func AuditEventFrom(ctx context.Context) *auditinternal.Event {
|
||||||
|
if o := AuditContextFrom(ctx); o != nil {
|
||||||
|
return o.Event
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditContextFrom returns the pair of the audit configuration object
|
||||||
|
// that applies to the given request and the audit event that is going to
|
||||||
|
// be written to the API audit log.
|
||||||
|
func AuditContextFrom(ctx context.Context) *AuditContext {
|
||||||
|
ev, _ := ctx.Value(auditKey).(*AuditContext)
|
||||||
|
return ev
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuditAnnotationMutex adds a mutex for guarding context.AddAuditAnnotation.
|
||||||
|
func withAuditAnnotationsMutex(parent context.Context) context.Context {
|
||||||
|
if _, ok := parent.Value(auditAnnotationsMutexKey).(*sync.Mutex); ok {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
var mutex sync.Mutex
|
||||||
|
return genericapirequest.WithValue(parent, auditAnnotationsMutexKey, &mutex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditAnnotationsMutex returns the audit annotations mutex from the context.
|
||||||
|
func auditAnnotationsMutex(ctx context.Context) (*sync.Mutex, bool) {
|
||||||
|
mutex, ok := ctx.Value(auditAnnotationsMutexKey).(*sync.Mutex)
|
||||||
|
return mutex, ok
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditContext is a pair of the audit configuration object that applies to
|
||||||
|
// a given request and the audit Event object that is being captured.
|
||||||
|
// It's a convenient placeholder to store both these objects in the request context.
|
||||||
|
type AuditContext struct {
|
||||||
|
// RequestAuditConfig is the audit configuration that applies to the request
|
||||||
|
RequestAuditConfig RequestAuditConfig
|
||||||
|
|
||||||
|
// Event is the audit Event object that is being captured to be written in
|
||||||
|
// the API audit log. It is set to nil when the request is not being audited.
|
||||||
|
Event *audit.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAuditConfig is the evaluated audit configuration that is applicable to
|
||||||
|
// a given request. PolicyRuleEvaluator evaluates the audit policy against the
|
||||||
|
// authorizer attributes and returns a RequestAuditConfig that applies to the request.
|
||||||
|
type RequestAuditConfig struct {
|
||||||
|
// OmitStages is the stages that need to be omitted from being audited.
|
||||||
|
OmitStages []audit.Stage
|
||||||
|
|
||||||
|
// OmitManagedFields indicates whether to omit the managed fields of the request
|
||||||
|
// and response bodies from being written to the API audit log.
|
||||||
|
OmitManagedFields bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAuditConfigWithLevel includes Level at which the request is being audited.
|
||||||
|
// PolicyRuleEvaluator evaluates the audit configuration for a request
|
||||||
|
// against the authorizer attributes and returns an RequestAuditConfigWithLevel
|
||||||
|
// that applies to the request.
|
||||||
|
type RequestAuditConfigWithLevel struct {
|
||||||
|
RequestAuditConfig
|
||||||
|
|
||||||
|
// Level at which the request is being audited at
|
||||||
|
Level audit.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyRuleEvaluator exposes methods for evaluating the policy rules.
|
||||||
|
type PolicyRuleEvaluator interface {
|
||||||
|
// EvaluatePolicyRule evaluates the audit policy of the apiserver against
|
||||||
|
// the given authorizer attributes and returns the audit configuration that
|
||||||
|
// is applicable to the given equest.
|
||||||
|
EvaluatePolicyRule(authorizer.Attributes) RequestAuditConfigWithLevel
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventString creates a 1-line text representation of an audit event, using a subset of the
|
||||||
|
// information in the event struct.
|
||||||
|
func EventString(ev *auditinternal.Event) string {
|
||||||
|
username := "<none>"
|
||||||
|
groups := "<none>"
|
||||||
|
if len(ev.User.Username) > 0 {
|
||||||
|
username = ev.User.Username
|
||||||
|
if len(ev.User.Groups) > 0 {
|
||||||
|
groups = auditStringSlice(ev.User.Groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asuser := "<self>"
|
||||||
|
asgroups := "<lookup>"
|
||||||
|
if ev.ImpersonatedUser != nil {
|
||||||
|
asuser = ev.ImpersonatedUser.Username
|
||||||
|
if ev.ImpersonatedUser.Groups != nil {
|
||||||
|
asgroups = auditStringSlice(ev.ImpersonatedUser.Groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := "<none>"
|
||||||
|
if ev.ObjectRef != nil && len(ev.ObjectRef.Namespace) != 0 {
|
||||||
|
namespace = ev.ObjectRef.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
response := "<deferred>"
|
||||||
|
if ev.ResponseStatus != nil {
|
||||||
|
response = strconv.Itoa(int(ev.ResponseStatus.Code))
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := "<unknown>"
|
||||||
|
if len(ev.SourceIPs) > 0 {
|
||||||
|
ip = ev.SourceIPs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s AUDIT: id=%q stage=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q user-agent=%q namespace=%q uri=%q response=\"%s\"",
|
||||||
|
ev.RequestReceivedTimestamp.Format(time.RFC3339Nano), ev.AuditID, ev.Stage, ip, ev.Verb, username, groups, asuser, asgroups, ev.UserAgent, namespace, ev.RequestURI, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func auditStringSlice(inList []string) string {
|
||||||
|
quotedElements := make([]string, len(inList))
|
||||||
|
for i, in := range inList {
|
||||||
|
quotedElements[i] = fmt.Sprintf("%q", in)
|
||||||
|
}
|
||||||
|
return strings.Join(quotedElements, ",")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/component-base/metrics"
|
||||||
|
"k8s.io/component-base/metrics/legacyregistry"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
subsystem = "apiserver_audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default, all the following metrics are defined as falling under
|
||||||
|
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||||
|
*
|
||||||
|
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||||
|
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||||
|
* the metric stability policy.
|
||||||
|
*/
|
||||||
|
var (
|
||||||
|
eventCounter = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: "event_total",
|
||||||
|
Help: "Counter of audit events generated and sent to the audit backend.",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
})
|
||||||
|
errorCounter = metrics.NewCounterVec(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: "error_total",
|
||||||
|
Help: "Counter of audit events that failed to be audited properly. " +
|
||||||
|
"Plugin identifies the plugin affected by the error.",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"plugin"},
|
||||||
|
)
|
||||||
|
levelCounter = metrics.NewCounterVec(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: "level_total",
|
||||||
|
Help: "Counter of policy levels for audit events (1 per request).",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"level"},
|
||||||
|
)
|
||||||
|
|
||||||
|
ApiserverAuditDroppedCounter = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: "requests_rejected_total",
|
||||||
|
Help: "Counter of apiserver requests rejected due to an error " +
|
||||||
|
"in audit logging backend.",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
legacyregistry.MustRegister(eventCounter)
|
||||||
|
legacyregistry.MustRegister(errorCounter)
|
||||||
|
legacyregistry.MustRegister(levelCounter)
|
||||||
|
legacyregistry.MustRegister(ApiserverAuditDroppedCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserveEvent updates the relevant prometheus metrics for the generated audit event.
|
||||||
|
func ObserveEvent(ctx context.Context) {
|
||||||
|
eventCounter.WithContext(ctx).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObservePolicyLevel updates the relevant prometheus metrics with the audit level for a request.
|
||||||
|
func ObservePolicyLevel(ctx context.Context, level auditinternal.Level) {
|
||||||
|
levelCounter.WithContext(ctx).WithLabelValues(string(level)).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePluginError handles an error that occurred in an audit plugin. This method should only be
|
||||||
|
// used if the error may have prevented the audit event from being properly recorded. The events are
|
||||||
|
// logged to the debug log.
|
||||||
|
func HandlePluginError(plugin string, err error, impacted ...*auditinternal.Event) {
|
||||||
|
// Count the error.
|
||||||
|
errorCounter.WithLabelValues(plugin).Add(float64(len(impacted)))
|
||||||
|
|
||||||
|
// Log the audit events to the debug log.
|
||||||
|
msg := fmt.Sprintf("Error in audit plugin '%s' affecting %d audit events: %v\nImpacted events:\n",
|
||||||
|
plugin, len(impacted), err)
|
||||||
|
for _, ev := range impacted {
|
||||||
|
msg = msg + EventString(ev) + "\n"
|
||||||
|
}
|
||||||
|
klog.Error(msg)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
authnv1 "k8s.io/api/authentication/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxUserAgentLength = 1024
|
||||||
|
userAgentTruncateSuffix = "...TRUNCATED"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEventFromRequest(req *http.Request, requestReceivedTimestamp time.Time, level auditinternal.Level, attribs authorizer.Attributes) (*auditinternal.Event, error) {
|
||||||
|
ev := &auditinternal.Event{
|
||||||
|
RequestReceivedTimestamp: metav1.NewMicroTime(requestReceivedTimestamp),
|
||||||
|
Verb: attribs.GetVerb(),
|
||||||
|
RequestURI: req.URL.RequestURI(),
|
||||||
|
UserAgent: maybeTruncateUserAgent(req),
|
||||||
|
Level: level,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditID, found := request.AuditIDFrom(req.Context())
|
||||||
|
if !found {
|
||||||
|
auditID = types.UID(uuid.New().String())
|
||||||
|
}
|
||||||
|
ev.AuditID = auditID
|
||||||
|
|
||||||
|
ips := utilnet.SourceIPs(req)
|
||||||
|
ev.SourceIPs = make([]string, len(ips))
|
||||||
|
for i := range ips {
|
||||||
|
ev.SourceIPs[i] = ips[i].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if user := attribs.GetUser(); user != nil {
|
||||||
|
ev.User.Username = user.GetName()
|
||||||
|
ev.User.Extra = map[string]authnv1.ExtraValue{}
|
||||||
|
for k, v := range user.GetExtra() {
|
||||||
|
ev.User.Extra[k] = authnv1.ExtraValue(v)
|
||||||
|
}
|
||||||
|
ev.User.Groups = user.GetGroups()
|
||||||
|
ev.User.UID = user.GetUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
if attribs.IsResourceRequest() {
|
||||||
|
ev.ObjectRef = &auditinternal.ObjectReference{
|
||||||
|
Namespace: attribs.GetNamespace(),
|
||||||
|
Name: attribs.GetName(),
|
||||||
|
Resource: attribs.GetResource(),
|
||||||
|
Subresource: attribs.GetSubresource(),
|
||||||
|
APIGroup: attribs.GetAPIGroup(),
|
||||||
|
APIVersion: attribs.GetAPIVersion(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addAuditAnnotationsFrom(req.Context(), ev)
|
||||||
|
|
||||||
|
return ev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogImpersonatedUser fills in the impersonated user attributes into an audit event.
|
||||||
|
func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) {
|
||||||
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ae.ImpersonatedUser = &authnv1.UserInfo{
|
||||||
|
Username: user.GetName(),
|
||||||
|
}
|
||||||
|
ae.ImpersonatedUser.Groups = user.GetGroups()
|
||||||
|
ae.ImpersonatedUser.UID = user.GetUID()
|
||||||
|
ae.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{}
|
||||||
|
for k, v := range user.GetExtra() {
|
||||||
|
ae.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogRequestObject fills in the request object into an audit event. The passed runtime.Object
|
||||||
|
// will be converted to the given gv.
|
||||||
|
func LogRequestObject(ctx context.Context, obj runtime.Object, objGV schema.GroupVersion, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete ObjectRef
|
||||||
|
if ae.ObjectRef == nil {
|
||||||
|
ae.ObjectRef = &auditinternal.ObjectReference{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits
|
||||||
|
if meta, err := meta.Accessor(obj); err == nil {
|
||||||
|
if len(ae.ObjectRef.Namespace) == 0 {
|
||||||
|
ae.ObjectRef.Namespace = meta.GetNamespace()
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.Name) == 0 {
|
||||||
|
ae.ObjectRef.Name = meta.GetName()
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.UID) == 0 {
|
||||||
|
ae.ObjectRef.UID = meta.GetUID()
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.ResourceVersion) == 0 {
|
||||||
|
ae.ObjectRef.ResourceVersion = meta.GetResourceVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.APIVersion) == 0 {
|
||||||
|
ae.ObjectRef.APIGroup = gvr.Group
|
||||||
|
ae.ObjectRef.APIVersion = gvr.Version
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.Resource) == 0 {
|
||||||
|
ae.ObjectRef.Resource = gvr.Resource
|
||||||
|
}
|
||||||
|
if len(ae.ObjectRef.Subresource) == 0 {
|
||||||
|
ae.ObjectRef.Subresource = subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
if ae.Level.Less(auditinternal.LevelRequest) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldOmitManagedFields(ctx) {
|
||||||
|
copy, ok, err := copyWithoutManagedFields(obj)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("error while dropping managed fields from the request for %q error: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
obj = copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(audit): hook into the serializer to avoid double conversion
|
||||||
|
var err error
|
||||||
|
ae.RequestObject, err = encodeObject(obj, objGV, s)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(audit): add error slice to audit event struct
|
||||||
|
klog.Warningf("Auditing failed of %v request: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogRequestPatch fills in the given patch as the request object into an audit event.
|
||||||
|
func LogRequestPatch(ctx context.Context, patch []byte) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
if ae == nil || ae.Level.Less(auditinternal.LevelRequest) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.RequestObject = &runtime.Unknown{
|
||||||
|
Raw: patch,
|
||||||
|
ContentType: runtime.ContentTypeJSON,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogResponseObject fills in the response object into an audit event. The passed runtime.Object
|
||||||
|
// will be converted to the given gv.
|
||||||
|
func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status, ok := obj.(*metav1.Status); ok {
|
||||||
|
// selectively copy the bounded fields.
|
||||||
|
ae.ResponseStatus = &metav1.Status{
|
||||||
|
Status: status.Status,
|
||||||
|
Message: status.Message,
|
||||||
|
Reason: status.Reason,
|
||||||
|
Details: status.Details,
|
||||||
|
Code: status.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ae.Level.Less(auditinternal.LevelRequestResponse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldOmitManagedFields(ctx) {
|
||||||
|
copy, ok, err := copyWithoutManagedFields(obj)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("error while dropping managed fields from the response for %q error: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
obj = copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(audit): hook into the serializer to avoid double conversion
|
||||||
|
var err error
|
||||||
|
ae.ResponseObject, err = encodeObject(obj, gv, s)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("Audit failed for %q response: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) {
|
||||||
|
const mediaType = runtime.ContentTypeJSON
|
||||||
|
info, ok := runtime.SerializerInfoForMediaType(serializer.SupportedMediaTypes(), mediaType)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := serializer.EncoderForVersion(info.Serializer, gv)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := enc.Encode(obj, &buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("encoding failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &runtime.Unknown{
|
||||||
|
Raw: buf.Bytes(),
|
||||||
|
ContentType: mediaType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncate User-Agent if too long, otherwise return it directly.
|
||||||
|
func maybeTruncateUserAgent(req *http.Request) string {
|
||||||
|
ua := req.UserAgent()
|
||||||
|
if len(ua) > maxUserAgentLength {
|
||||||
|
ua = ua[:maxUserAgentLength] + userAgentTruncateSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return ua
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyWithoutManagedFields will make a deep copy of the specified object and
|
||||||
|
// will discard the managed fields from the copy.
|
||||||
|
// The specified object is expected to be a meta.Object or a "list".
|
||||||
|
// The specified object obj is treated as readonly and hence not mutated.
|
||||||
|
// On return, an error is set if the function runs into any error while
|
||||||
|
// removing the managed fields, the boolean value is true if the copy has
|
||||||
|
// been made successfully, otherwise false.
|
||||||
|
func copyWithoutManagedFields(obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
|
isAccessor := true
|
||||||
|
if _, err := meta.Accessor(obj); err != nil {
|
||||||
|
isAccessor = false
|
||||||
|
}
|
||||||
|
isList := meta.IsListType(obj)
|
||||||
|
_, isTable := obj.(*metav1.Table)
|
||||||
|
if !isAccessor && !isList && !isTable {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO a deep copy isn't really needed here, figure out how we can reliably
|
||||||
|
// use shallow copy here to omit the manageFields.
|
||||||
|
copy := obj.DeepCopyObject()
|
||||||
|
|
||||||
|
if isAccessor {
|
||||||
|
if err := removeManagedFields(copy); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList {
|
||||||
|
if err := meta.EachListItem(copy, removeManagedFields); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTable {
|
||||||
|
table := copy.(*metav1.Table)
|
||||||
|
for i := range table.Rows {
|
||||||
|
rowObj := table.Rows[i].Object
|
||||||
|
if err := removeManagedFields(rowObj.Object); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeManagedFields(obj runtime.Object) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessor.SetManagedFields(nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldOmitManagedFields(ctx context.Context) bool {
|
||||||
|
if auditContext := AuditContextFrom(ctx); auditContext != nil {
|
||||||
|
return auditContext.RequestAuditConfig.OmitManagedFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't decide, return false to maintain current behavior which is
|
||||||
|
// to retain the manage fields in the audit.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Delete this file if we generate a clientset.
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/apis/audit/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Scheme = runtime.NewScheme()
|
||||||
|
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||||
|
utilruntime.Must(v1.AddToScheme(Scheme))
|
||||||
|
utilruntime.Must(auditinternal.AddToScheme(Scheme))
|
||||||
|
utilruntime.Must(Scheme.SetVersionPriority(v1.SchemeGroupVersion))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sink interface {
|
||||||
|
// ProcessEvents handles events. Per audit ID it might be that ProcessEvents is called up to three times.
|
||||||
|
// Errors might be logged by the sink itself. If an error should be fatal, leading to an internal
|
||||||
|
// error, ProcessEvents is supposed to panic. The event must not be mutated and is reused by the caller
|
||||||
|
// after the call returns, i.e. the sink has to make a deepcopy to keep a copy around if necessary.
|
||||||
|
// Returns true on success, may return false on error.
|
||||||
|
ProcessEvents(events ...*auditinternal.Event) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Backend interface {
|
||||||
|
Sink
|
||||||
|
|
||||||
|
// Run will initialize the backend. It must not block, but may run go routines in the background. If
|
||||||
|
// stopCh is closed, it is supposed to stop them. Run will be called before the first call to ProcessEvents.
|
||||||
|
Run(stopCh <-chan struct{}) error
|
||||||
|
|
||||||
|
// Shutdown will synchronously shut down the backend while making sure that all pending
|
||||||
|
// events are delivered. It can be assumed that this method is called after
|
||||||
|
// the stopCh channel passed to the Run method has been closed.
|
||||||
|
Shutdown()
|
||||||
|
|
||||||
|
// Returns the backend PluginName.
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package audit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Union returns an audit Backend which logs events to a set of backends. The returned
|
||||||
|
// Sink implementation blocks in turn for each call to ProcessEvents.
|
||||||
|
func Union(backends ...Backend) Backend {
|
||||||
|
if len(backends) == 1 {
|
||||||
|
return backends[0]
|
||||||
|
}
|
||||||
|
return union{backends}
|
||||||
|
}
|
||||||
|
|
||||||
|
type union struct {
|
||||||
|
backends []Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u union) ProcessEvents(events ...*auditinternal.Event) bool {
|
||||||
|
success := true
|
||||||
|
for _, backend := range u.backends {
|
||||||
|
success = backend.ProcessEvents(events...) && success
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u union) Run(stopCh <-chan struct{}) error {
|
||||||
|
var funcs []func() error
|
||||||
|
for _, backend := range u.backends {
|
||||||
|
backend := backend
|
||||||
|
funcs = append(funcs, func() error {
|
||||||
|
return backend.Run(stopCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return errors.AggregateGoroutines(funcs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u union) Shutdown() {
|
||||||
|
for _, backend := range u.backends {
|
||||||
|
backend.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u union) String() string {
|
||||||
|
var backendStrings []string
|
||||||
|
for _, backend := range u.backends {
|
||||||
|
backendStrings = append(backendStrings, fmt.Sprintf("%s", backend))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("union[%s]", strings.Join(backendStrings, ","))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 user contains utilities for dealing with simple user exchange in the auth
|
||||||
|
// packages. The user.Info interface defines an interface for exchanging that info.
|
||||||
|
package user // import "k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 user
|
||||||
|
|
||||||
|
// Info describes a user that has been authenticated to the system.
|
||||||
|
type Info interface {
|
||||||
|
// GetName returns the name that uniquely identifies this user among all
|
||||||
|
// other active users.
|
||||||
|
GetName() string
|
||||||
|
// GetUID returns a unique value for a particular user that will change
|
||||||
|
// if the user is removed from the system and another user is added with
|
||||||
|
// the same name.
|
||||||
|
GetUID() string
|
||||||
|
// GetGroups returns the names of the groups the user is a member of
|
||||||
|
GetGroups() []string
|
||||||
|
|
||||||
|
// GetExtra can contain any additional information that the authenticator
|
||||||
|
// thought was interesting. One example would be scopes on a token.
|
||||||
|
// Keys in this map should be namespaced to the authenticator or
|
||||||
|
// authenticator/authorizer pair making use of them.
|
||||||
|
// For instance: "example.org/foo" instead of "foo"
|
||||||
|
// This is a map[string][]string because it needs to be serializeable into
|
||||||
|
// a SubjectAccessReviewSpec.authorization.k8s.io for proper authorization
|
||||||
|
// delegation flows
|
||||||
|
// In order to faithfully round-trip through an impersonation flow, these keys
|
||||||
|
// MUST be lowercase.
|
||||||
|
GetExtra() map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInfo provides a simple user information exchange object
|
||||||
|
// for components that implement the UserInfo interface.
|
||||||
|
type DefaultInfo struct {
|
||||||
|
Name string
|
||||||
|
UID string
|
||||||
|
Groups []string
|
||||||
|
Extra map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetName() string {
|
||||||
|
return i.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetUID() string {
|
||||||
|
return i.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetGroups() []string {
|
||||||
|
return i.Groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetExtra() map[string][]string {
|
||||||
|
return i.Extra
|
||||||
|
}
|
||||||
|
|
||||||
|
// well-known user and group names
|
||||||
|
const (
|
||||||
|
SystemPrivilegedGroup = "system:masters"
|
||||||
|
NodesGroup = "system:nodes"
|
||||||
|
MonitoringGroup = "system:monitoring"
|
||||||
|
AllUnauthenticated = "system:unauthenticated"
|
||||||
|
AllAuthenticated = "system:authenticated"
|
||||||
|
|
||||||
|
Anonymous = "system:anonymous"
|
||||||
|
APIServerUser = "system:apiserver"
|
||||||
|
|
||||||
|
// core kubernetes process identities
|
||||||
|
KubeProxy = "system:kube-proxy"
|
||||||
|
KubeControllerManager = "system:kube-controller-manager"
|
||||||
|
KubeScheduler = "system:kube-scheduler"
|
||||||
|
)
|
||||||
159
vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go
generated
vendored
Normal file
159
vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an interface used by an Authorizer to get information about a request
|
||||||
|
// that is used to make an authorization decision.
|
||||||
|
type Attributes interface {
|
||||||
|
// GetUser returns the user.Info object to authorize
|
||||||
|
GetUser() user.Info
|
||||||
|
|
||||||
|
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy),
|
||||||
|
// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
|
||||||
|
GetVerb() string
|
||||||
|
|
||||||
|
// When IsReadOnly() == true, the request has no side effects, other than
|
||||||
|
// caching, logging, and other incidentals.
|
||||||
|
IsReadOnly() bool
|
||||||
|
|
||||||
|
// The namespace of the object, if a request is for a REST object.
|
||||||
|
GetNamespace() string
|
||||||
|
|
||||||
|
// The kind of object, if a request is for a REST object.
|
||||||
|
GetResource() string
|
||||||
|
|
||||||
|
// GetSubresource returns the subresource being requested, if present
|
||||||
|
GetSubresource() string
|
||||||
|
|
||||||
|
// GetName returns the name of the object as parsed off the request. This will not be present for all request types, but
|
||||||
|
// will be present for: get, update, delete
|
||||||
|
GetName() string
|
||||||
|
|
||||||
|
// The group of the resource, if a request is for a REST object.
|
||||||
|
GetAPIGroup() string
|
||||||
|
|
||||||
|
// GetAPIVersion returns the version of the group requested, if a request is for a REST object.
|
||||||
|
GetAPIVersion() string
|
||||||
|
|
||||||
|
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
|
||||||
|
// and false for non-resource endpoints like /api, /healthz
|
||||||
|
IsResourceRequest() bool
|
||||||
|
|
||||||
|
// GetPath returns the path of the request
|
||||||
|
GetPath() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
|
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
|
||||||
|
// authorized, otherwise it returns an error.
|
||||||
|
type Authorizer interface {
|
||||||
|
Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizerFunc func(ctx context.Context, a Attributes) (Decision, string, error)
|
||||||
|
|
||||||
|
func (f AuthorizerFunc) Authorize(ctx context.Context, a Attributes) (Decision, string, error) {
|
||||||
|
return f(ctx, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
|
||||||
|
type RuleResolver interface {
|
||||||
|
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
|
||||||
|
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
|
||||||
|
type RequestAttributesGetter interface {
|
||||||
|
GetRequestAttributes(user.Info, *http.Request) Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttributesRecord implements Attributes interface.
|
||||||
|
type AttributesRecord struct {
|
||||||
|
User user.Info
|
||||||
|
Verb string
|
||||||
|
Namespace string
|
||||||
|
APIGroup string
|
||||||
|
APIVersion string
|
||||||
|
Resource string
|
||||||
|
Subresource string
|
||||||
|
Name string
|
||||||
|
ResourceRequest bool
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetUser() user.Info {
|
||||||
|
return a.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetVerb() string {
|
||||||
|
return a.Verb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsReadOnly() bool {
|
||||||
|
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetNamespace() string {
|
||||||
|
return a.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetResource() string {
|
||||||
|
return a.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetSubresource() string {
|
||||||
|
return a.Subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetName() string {
|
||||||
|
return a.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIGroup() string {
|
||||||
|
return a.APIGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIVersion() string {
|
||||||
|
return a.APIVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsResourceRequest() bool {
|
||||||
|
return a.ResourceRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetPath() string {
|
||||||
|
return a.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decision int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DecisionDeny means that an authorizer decided to deny the action.
|
||||||
|
DecisionDeny Decision = iota
|
||||||
|
// DecisionAllow means that an authorizer decided to allow the action.
|
||||||
|
DecisionAllow
|
||||||
|
// DecisionNoOpionion means that an authorizer has no opinion on whether
|
||||||
|
// to allow or deny an action.
|
||||||
|
DecisionNoOpinion
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package authorizer
|
||||||
|
|
||||||
|
type ResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetAPIGroups return the names of the APIGroup that contains the resources.
|
||||||
|
GetAPIGroups() []string
|
||||||
|
// GetResources return a list of resources the rule applies to.
|
||||||
|
GetResources() []string
|
||||||
|
// GetResourceNames return a white list of names that the rule applies to.
|
||||||
|
GetResourceNames() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultResourceRuleInfo holds information that describes a rule for the resource
|
||||||
|
type DefaultResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
APIGroups []string
|
||||||
|
Resources []string
|
||||||
|
ResourceNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
|
||||||
|
return i.APIGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResources() []string {
|
||||||
|
return i.Resources
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
|
||||||
|
return i.ResourceNames
|
||||||
|
}
|
||||||
|
|
||||||
|
type NonResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetNonResourceURLs return a set of partial urls that a user should have access to.
|
||||||
|
GetNonResourceURLs() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
|
||||||
|
type DefaultNonResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
NonResourceURLs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
|
||||||
|
return i.NonResourceURLs
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
approvers:
|
||||||
|
- apelisse
|
||||||
|
reviewers:
|
||||||
|
- kwiesmueller
|
||||||
|
emeritus_approvers:
|
||||||
|
- jennybuckley
|
||||||
89
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
generated
vendored
Normal file
89
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/warning"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvalidManagedFieldsAfterMutatingAdmissionWarningFormat is the warning that a client receives
|
||||||
|
// when a create/update/patch request results in invalid managedFields after going through the admission chain.
|
||||||
|
const InvalidManagedFieldsAfterMutatingAdmissionWarningFormat = ".metadata.managedFields was in an invalid state after admission; this could be caused by an outdated mutating admission controller; please fix your requests: %v"
|
||||||
|
|
||||||
|
// NewManagedFieldsValidatingAdmissionController validates the managedFields after calling
|
||||||
|
// the provided admission and resets them to their original state if they got changed to an invalid value
|
||||||
|
func NewManagedFieldsValidatingAdmissionController(wrap admission.Interface) admission.Interface {
|
||||||
|
if wrap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &managedFieldsValidatingAdmissionController{wrap: wrap}
|
||||||
|
}
|
||||||
|
|
||||||
|
type managedFieldsValidatingAdmissionController struct {
|
||||||
|
wrap admission.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ admission.Interface = &managedFieldsValidatingAdmissionController{}
|
||||||
|
var _ admission.MutationInterface = &managedFieldsValidatingAdmissionController{}
|
||||||
|
var _ admission.ValidationInterface = &managedFieldsValidatingAdmissionController{}
|
||||||
|
|
||||||
|
// Handles calls the wrapped admission.Interface if applicable
|
||||||
|
func (admit *managedFieldsValidatingAdmissionController) Handles(operation admission.Operation) bool {
|
||||||
|
return admit.wrap.Handles(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit calls the wrapped admission.Interface if applicable and resets the managedFields to their state before admission if they
|
||||||
|
// got modified in an invalid way
|
||||||
|
func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||||
|
mutationInterface, isMutationInterface := admit.wrap.(admission.MutationInterface)
|
||||||
|
if !isMutationInterface {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
objectMeta, err := meta.Accessor(a.GetObject())
|
||||||
|
if err != nil {
|
||||||
|
// the object we are dealing with doesn't have object metadata defined
|
||||||
|
// in that case we don't have to keep track of the managedField
|
||||||
|
// just call the wrapped admission
|
||||||
|
return mutationInterface.Admit(ctx, a, o)
|
||||||
|
}
|
||||||
|
managedFieldsBeforeAdmission := objectMeta.GetManagedFields()
|
||||||
|
if err := mutationInterface.Admit(ctx, a, o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
managedFieldsAfterAdmission := objectMeta.GetManagedFields()
|
||||||
|
if _, err := DecodeManagedFields(managedFieldsAfterAdmission); err != nil {
|
||||||
|
objectMeta.SetManagedFields(managedFieldsBeforeAdmission)
|
||||||
|
warning.AddWarning(ctx, "",
|
||||||
|
fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat,
|
||||||
|
err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate calls the wrapped admission.Interface if aplicable
|
||||||
|
func (admit *managedFieldsValidatingAdmissionController) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||||
|
if validationInterface, isValidationInterface := admit.wrap.(admission.ValidationInterface); isValidationInterface {
|
||||||
|
return validationInterface.Validate(ctx, a, o)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
75
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/buildmanagerinfo.go
generated
vendored
Normal file
75
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/buildmanagerinfo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type buildManagerInfoManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
subresource string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &buildManagerInfoManager{}
|
||||||
|
|
||||||
|
// NewBuildManagerInfoManager creates a new Manager that converts the manager name into a unique identifier
|
||||||
|
// combining operation and version for update requests, and just operation for apply requests.
|
||||||
|
func NewBuildManagerInfoManager(f Manager, gv schema.GroupVersion, subresource string) Manager {
|
||||||
|
return &buildManagerInfoManager{
|
||||||
|
fieldManager: f,
|
||||||
|
groupVersion: gv,
|
||||||
|
subresource: subresource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *buildManagerInfoManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *buildManagerInfoManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationApply)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
return f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
||||||
|
managerInfo := metav1.ManagedFieldsEntry{
|
||||||
|
Manager: prefix,
|
||||||
|
Operation: operation,
|
||||||
|
APIVersion: f.groupVersion.String(),
|
||||||
|
Subresource: f.subresource,
|
||||||
|
}
|
||||||
|
if managerInfo.Manager == "" {
|
||||||
|
managerInfo.Manager = "unknown"
|
||||||
|
}
|
||||||
|
return internal.BuildManagerIdentifier(&managerInfo)
|
||||||
|
}
|
||||||
134
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/capmanagers.go
generated
vendored
Normal file
134
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/capmanagers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type capManagersManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
maxUpdateManagers int
|
||||||
|
oldUpdatesManagerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &capManagersManager{}
|
||||||
|
|
||||||
|
// NewCapManagersManager creates a new wrapped FieldManager which ensures that the number of managers from updates
|
||||||
|
// does not exceed maxUpdateManagers, by merging some of the oldest entries on each update.
|
||||||
|
func NewCapManagersManager(fieldManager Manager, maxUpdateManagers int) Manager {
|
||||||
|
return &capManagersManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
maxUpdateManagers: maxUpdateManagers,
|
||||||
|
oldUpdatesManagerName: "ancient-changes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *capManagersManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
if err != nil {
|
||||||
|
return object, managed, err
|
||||||
|
}
|
||||||
|
if managed, err = f.capUpdateManagers(managed); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to cap update managers: %v", err)
|
||||||
|
}
|
||||||
|
return object, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *capManagersManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
// capUpdateManagers merges a number of the oldest update entries into versioned buckets,
|
||||||
|
// such that the number of entries from updates does not exceed f.maxUpdateManagers.
|
||||||
|
func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Managed, err error) {
|
||||||
|
// Gather all entries from updates
|
||||||
|
updaters := []string{}
|
||||||
|
for manager, fields := range managed.Fields() {
|
||||||
|
if !fields.Applied() {
|
||||||
|
updaters = append(updaters, manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(updaters) <= f.maxUpdateManagers {
|
||||||
|
return managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have more than the maximum, sort the update entries by time, oldest first.
|
||||||
|
sort.Slice(updaters, func(i, j int) bool {
|
||||||
|
iTime, jTime, iSeconds, jSeconds := managed.Times()[updaters[i]], managed.Times()[updaters[j]], int64(0), int64(0)
|
||||||
|
if iTime != nil {
|
||||||
|
iSeconds = iTime.Unix()
|
||||||
|
}
|
||||||
|
if jTime != nil {
|
||||||
|
jSeconds = jTime.Unix()
|
||||||
|
}
|
||||||
|
if iSeconds != jSeconds {
|
||||||
|
return iSeconds < jSeconds
|
||||||
|
}
|
||||||
|
return updaters[i] < updaters[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Merge the oldest updaters with versioned bucket managers until the number of updaters is under the cap
|
||||||
|
versionToFirstManager := map[string]string{}
|
||||||
|
for i, length := 0, len(updaters); i < len(updaters) && length > f.maxUpdateManagers; i++ {
|
||||||
|
manager := updaters[i]
|
||||||
|
vs := managed.Fields()[manager]
|
||||||
|
time := managed.Times()[manager]
|
||||||
|
version := string(vs.APIVersion())
|
||||||
|
|
||||||
|
// Create a new manager identifier for the versioned bucket entry.
|
||||||
|
// The version for this manager comes from the version of the update being merged into the bucket.
|
||||||
|
bucket, err := internal.BuildManagerIdentifier(&metav1.ManagedFieldsEntry{
|
||||||
|
Manager: f.oldUpdatesManagerName,
|
||||||
|
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||||
|
APIVersion: version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return managed, fmt.Errorf("failed to create bucket manager for version %v: %v", version, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the fieldets if this is not the first time the version was seen.
|
||||||
|
// Otherwise just record the manager name in versionToFirstManager
|
||||||
|
if first, ok := versionToFirstManager[version]; ok {
|
||||||
|
// If the bucket doesn't exists yet, create one.
|
||||||
|
if _, ok := managed.Fields()[bucket]; !ok {
|
||||||
|
s := managed.Fields()[first]
|
||||||
|
delete(managed.Fields(), first)
|
||||||
|
managed.Fields()[bucket] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
managed.Fields()[bucket] = fieldpath.NewVersionedSet(vs.Set().Union(managed.Fields()[bucket].Set()), vs.APIVersion(), vs.Applied())
|
||||||
|
delete(managed.Fields(), manager)
|
||||||
|
length--
|
||||||
|
|
||||||
|
// Use the time from the update being merged into the bucket, since it is more recent.
|
||||||
|
managed.Times()[bucket] = time
|
||||||
|
} else {
|
||||||
|
versionToFirstManager[version] = manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return managed, nil
|
||||||
|
}
|
||||||
7018
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/endpoints.yaml
generated
vendored
Normal file
7018
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/endpoints.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
180
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/equality.go
generated
vendored
Normal file
180
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/equality.go
generated
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func determineAvoidNoopTimestampUpdatesEnabled() bool {
|
||||||
|
if avoidNoopTimestampUpdatesString, exists := os.LookupEnv("KUBE_APISERVER_AVOID_NOOP_SSA_TIMESTAMP_UPDATES"); exists {
|
||||||
|
if ret, err := strconv.ParseBool(avoidNoopTimestampUpdatesString); err == nil {
|
||||||
|
return ret
|
||||||
|
} else {
|
||||||
|
klog.Errorf("failed to parse envar KUBE_APISERVER_AVOID_NOOP_SSA_TIMESTAMP_UPDATES: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enabled by default
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
avoidNoopTimestampUpdatesEnabled = determineAvoidNoopTimestampUpdatesEnabled()
|
||||||
|
)
|
||||||
|
|
||||||
|
var avoidTimestampEqualities = func() conversion.Equalities {
|
||||||
|
var eqs = equality.Semantic.Copy()
|
||||||
|
|
||||||
|
err := eqs.AddFunc(
|
||||||
|
func(a, b metav1.ManagedFieldsEntry) bool {
|
||||||
|
// Two objects' managed fields are equivalent if, ignoring timestamp,
|
||||||
|
// the objects are deeply equal.
|
||||||
|
a.Time = nil
|
||||||
|
b.Time = nil
|
||||||
|
return reflect.DeepEqual(a, b)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return eqs
|
||||||
|
}()
|
||||||
|
|
||||||
|
// IgnoreManagedFieldsTimestampsTransformer reverts timestamp updates
|
||||||
|
// if the non-managed parts of the object are equivalent
|
||||||
|
func IgnoreManagedFieldsTimestampsTransformer(
|
||||||
|
_ context.Context,
|
||||||
|
newObj runtime.Object,
|
||||||
|
oldObj runtime.Object,
|
||||||
|
) (res runtime.Object, err error) {
|
||||||
|
if !avoidNoopTimestampUpdatesEnabled {
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome := "unequal_objects_fast"
|
||||||
|
start := time.Now()
|
||||||
|
err = nil
|
||||||
|
res = nil
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
outcome = "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.RecordTimestampComparisonLatency(outcome, time.Since(start))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If managedFields modulo timestamps are unchanged
|
||||||
|
// and
|
||||||
|
// rest of object is unchanged
|
||||||
|
// then
|
||||||
|
// revert any changes to timestamps in managed fields
|
||||||
|
// (to prevent spurious ResourceVersion bump)
|
||||||
|
//
|
||||||
|
// Procecure:
|
||||||
|
// Do a quicker check to see if just managed fields modulo timestamps are
|
||||||
|
// unchanged. If so, then do the full, slower check.
|
||||||
|
//
|
||||||
|
// In most cases which actually update the object, the managed fields modulo
|
||||||
|
// timestamp check will fail, and we will be able to return early.
|
||||||
|
//
|
||||||
|
// In other cases, the managed fields may be exactly the same,
|
||||||
|
// except for timestamp, but the objects are the different. This is the
|
||||||
|
// slow path which checks the full object.
|
||||||
|
oldAccessor, err := meta.Accessor(oldObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to acquire accessor for oldObj: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor, err := meta.Accessor(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to acquire accessor for newObj: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldManagedFields := oldAccessor.GetManagedFields()
|
||||||
|
newManagedFields := accessor.GetManagedFields()
|
||||||
|
|
||||||
|
if len(oldManagedFields) != len(newManagedFields) {
|
||||||
|
// Return early if any managed fields entry was added/removed.
|
||||||
|
// We want to retain user expectation that even if they write to a field
|
||||||
|
// whose value did not change, they will still result as the field
|
||||||
|
// manager at the end.
|
||||||
|
return newObj, nil
|
||||||
|
} else if len(newManagedFields) == 0 {
|
||||||
|
// This transformation only makes sense when managedFields are
|
||||||
|
// non-empty
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This transformation only makes sense if the managed fields has at least one
|
||||||
|
// changed timestamp; and are otherwise equal. Return early if there are no
|
||||||
|
// changed timestamps.
|
||||||
|
allTimesUnchanged := true
|
||||||
|
for i, e := range newManagedFields {
|
||||||
|
if !e.Time.Equal(oldManagedFields[i].Time) {
|
||||||
|
allTimesUnchanged = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allTimesUnchanged {
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This condition ensures the managed fields are always compared first. If
|
||||||
|
// this check fails, the if statement will short circuit. If the check
|
||||||
|
// succeeds the slow path is taken which compares entire objects.
|
||||||
|
if !avoidTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(oldManagedFields, newManagedFields) {
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if avoidTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(newObj, oldObj) {
|
||||||
|
// Remove any changed timestamps, so that timestamp is not the only
|
||||||
|
// change seen by etcd.
|
||||||
|
//
|
||||||
|
// newManagedFields is known to be exactly pairwise equal to
|
||||||
|
// oldManagedFields except for timestamps.
|
||||||
|
//
|
||||||
|
// Simply replace possibly changed new timestamps with their old values.
|
||||||
|
for idx := 0; idx < len(oldManagedFields); idx++ {
|
||||||
|
newManagedFields[idx].Time = oldManagedFields[idx].Time
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor.SetManagedFields(newManagedFields)
|
||||||
|
outcome = "equal_objects"
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome = "unequal_objects_slow"
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
265
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
265
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates
|
||||||
|
// if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum.
|
||||||
|
// TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries.
|
||||||
|
const DefaultMaxUpdateManagers int = 10
|
||||||
|
|
||||||
|
// DefaultTrackOnCreateProbability defines the default probability that the field management of an object
|
||||||
|
// starts being tracked from the object's creation, instead of from the first time the object is applied to.
|
||||||
|
const DefaultTrackOnCreateProbability float32 = 1
|
||||||
|
|
||||||
|
var atMostEverySecond = internal.NewAtMostEvery(time.Second)
|
||||||
|
|
||||||
|
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||||
|
type Managed interface {
|
||||||
|
// Fields gets the fieldpath.ManagedFields.
|
||||||
|
Fields() fieldpath.ManagedFields
|
||||||
|
|
||||||
|
// Times gets the timestamps associated with each operation.
|
||||||
|
Times() map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager updates the managed fields and merges applied configurations.
|
||||||
|
type Manager interface {
|
||||||
|
// Update is used when the object has already been merged (non-apply
|
||||||
|
// use-case), and simply updates the managed fields in the output
|
||||||
|
// object.
|
||||||
|
// * `liveObj` is not mutated by this function
|
||||||
|
// * `newObj` may be mutated by this function
|
||||||
|
// Returns the new object with managedFields removed, and the object's new
|
||||||
|
// proposed managedFields separately.
|
||||||
|
Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error)
|
||||||
|
|
||||||
|
// Apply is used when server-side apply is called, as it merges the
|
||||||
|
// object and updates the managed fields.
|
||||||
|
// * `liveObj` is not mutated by this function
|
||||||
|
// * `newObj` may be mutated by this function
|
||||||
|
// Returns the new object with managedFields removed, and the object's new
|
||||||
|
// proposed managedFields separately.
|
||||||
|
Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldManager updates the managed fields and merge applied
|
||||||
|
// configurations.
|
||||||
|
type FieldManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
subresource string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields
|
||||||
|
// on update and apply requests.
|
||||||
|
func NewFieldManager(f Manager, subresource string) *FieldManager {
|
||||||
|
return &FieldManager{fieldManager: f, subresource: subresource}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultFieldManager creates a new FieldManager that merges apply requests
|
||||||
|
// and update managed fields for other types of requests.
|
||||||
|
func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) {
|
||||||
|
f, err := NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||||
|
}
|
||||||
|
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultCRDFieldManager creates a new FieldManager specifically for
|
||||||
|
// CRDs. This allows for the possibility of fields which are not defined
|
||||||
|
// in models, as well as having no models defined at all.
|
||||||
|
func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) {
|
||||||
|
f, err := NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||||
|
}
|
||||||
|
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic.
|
||||||
|
func newDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, subresource string) *FieldManager {
|
||||||
|
return NewFieldManager(
|
||||||
|
NewLastAppliedUpdater(
|
||||||
|
NewLastAppliedManager(
|
||||||
|
NewProbabilisticSkipNonAppliedManager(
|
||||||
|
NewCapManagersManager(
|
||||||
|
NewBuildManagerInfoManager(
|
||||||
|
NewManagedFieldsUpdater(
|
||||||
|
NewStripMetaManager(f),
|
||||||
|
), kind.GroupVersion(), subresource,
|
||||||
|
), DefaultMaxUpdateManagers,
|
||||||
|
), objectCreater, kind, DefaultTrackOnCreateProbability,
|
||||||
|
), typeConverter, objectConverter, kind.GroupVersion()),
|
||||||
|
), subresource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeManagedFields converts ManagedFields from the wire format (api format)
|
||||||
|
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||||
|
func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (Managed, error) {
|
||||||
|
return internal.DecodeManagedFields(encodedManagedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) {
|
||||||
|
liveAccessor, err := meta.Accessor(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We take the managedFields of the live object in case the request tries to
|
||||||
|
// manually set managedFields via a subresource.
|
||||||
|
if ignoreManagedFieldsFromRequestObject {
|
||||||
|
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object doesn't have metadata, we should just return without trying to
|
||||||
|
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||||
|
newAccessor, err := meta.Accessor(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isResetManagedFields(newAccessor.GetManagedFields()) {
|
||||||
|
return internal.NewEmptyManaged(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the managed field is empty or we failed to decode it,
|
||||||
|
// let's try the live object. This is to prevent clients who
|
||||||
|
// don't understand managedFields from deleting it accidentally.
|
||||||
|
managed, err := DecodeManagedFields(newAccessor.GetManagedFields())
|
||||||
|
if err != nil || len(managed.Fields()) == 0 {
|
||||||
|
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
|
||||||
|
}
|
||||||
|
return managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) {
|
||||||
|
if err != nil {
|
||||||
|
return internal.NewEmptyManaged(), nil
|
||||||
|
}
|
||||||
|
return managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is used when the object has already been merged (non-apply
|
||||||
|
// use-case), and simply updates the managed fields in the output
|
||||||
|
// object.
|
||||||
|
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) {
|
||||||
|
// First try to decode the managed fields provided in the update,
|
||||||
|
// This is necessary to allow directly updating managed fields.
|
||||||
|
isSubresource := f.subresource != ""
|
||||||
|
managed, err := decodeLiveOrNew(liveObj, newObj, isSubresource)
|
||||||
|
if err != nil {
|
||||||
|
return newObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.RemoveObjectManagedFields(newObj)
|
||||||
|
|
||||||
|
if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNoErrors is the same as Update, but it will not return
|
||||||
|
// errors. If an error happens, the object is returned with
|
||||||
|
// managedFields cleared.
|
||||||
|
func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object {
|
||||||
|
obj, err := f.Update(liveObj, newObj, manager)
|
||||||
|
if err != nil {
|
||||||
|
atMostEverySecond.Do(func() {
|
||||||
|
ns, name := "unknown", "unknown"
|
||||||
|
accessor, err := meta.Accessor(newObj)
|
||||||
|
if err == nil {
|
||||||
|
ns = accessor.GetNamespace()
|
||||||
|
name = accessor.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields", "VersionKind",
|
||||||
|
newObj.GetObjectKind().GroupVersionKind(), "namespace", ns, "name", name)
|
||||||
|
})
|
||||||
|
// Explicitly remove managedFields on failure, so that
|
||||||
|
// we can't have garbage in it.
|
||||||
|
internal.RemoveObjectManagedFields(newObj)
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the managedFields indicate that the user is trying to
|
||||||
|
// reset the managedFields, i.e. if the list is non-nil but empty, or if
|
||||||
|
// the list has one empty item.
|
||||||
|
func isResetManagedFields(managedFields []metav1.ManagedFieldsEntry) bool {
|
||||||
|
if len(managedFields) == 0 {
|
||||||
|
return managedFields != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(managedFields) == 1 {
|
||||||
|
return reflect.DeepEqual(managedFields[0], metav1.ManagedFieldsEntry{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply is used when server-side apply is called, as it merges the
|
||||||
|
// object and updates the managed fields.
|
||||||
|
func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) {
|
||||||
|
// If the object doesn't have metadata, apply isn't allowed.
|
||||||
|
accessor, err := meta.Accessor(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the managed fields in the live object, since it isn't allowed in the patch.
|
||||||
|
managed, err := DecodeManagedFields(accessor.GetManagedFields())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
|
||||||
|
if err != nil {
|
||||||
|
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||||
|
return nil, internal.NewConflictError(conflicts)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = internal.EncodeObjectManagedFields(object, managed); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
60
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/atmostevery.go
generated
vendored
Normal file
60
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/atmostevery.go
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
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 internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AtMostEvery will never run the method more than once every specified
|
||||||
|
// duration.
|
||||||
|
type AtMostEvery struct {
|
||||||
|
delay time.Duration
|
||||||
|
lastCall time.Time
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAtMostEvery creates a new AtMostEvery, that will run the method at
|
||||||
|
// most every given duration.
|
||||||
|
func NewAtMostEvery(delay time.Duration) *AtMostEvery {
|
||||||
|
return &AtMostEvery{
|
||||||
|
delay: delay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateLastCall returns true if the lastCall time has been updated,
|
||||||
|
// false if it was too early.
|
||||||
|
func (s *AtMostEvery) updateLastCall() bool {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
if time.Since(s.lastCall) < s.delay {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.lastCall = time.Now()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do will run the method if enough time has passed, and return true.
|
||||||
|
// Otherwise, it does nothing and returns false.
|
||||||
|
func (s *AtMostEvery) Do(fn func()) bool {
|
||||||
|
if !s.updateLastCall() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
return true
|
||||||
|
}
|
||||||
89
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
89
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewConflictError returns an error including details on the requests apply conflicts
|
||||||
|
func NewConflictError(conflicts merge.Conflicts) *errors.StatusError {
|
||||||
|
causes := []metav1.StatusCause{}
|
||||||
|
for _, conflict := range conflicts {
|
||||||
|
causes = append(causes, metav1.StatusCause{
|
||||||
|
Type: metav1.CauseTypeFieldManagerConflict,
|
||||||
|
Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)),
|
||||||
|
Field: conflict.Path.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return errors.NewApplyConflict(causes, getConflictMessage(conflicts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConflictMessage(conflicts merge.Conflicts) string {
|
||||||
|
if len(conflicts) == 1 {
|
||||||
|
return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string][]fieldpath.Path{}
|
||||||
|
for _, conflict := range conflicts {
|
||||||
|
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueManagers := []string{}
|
||||||
|
for manager := range m {
|
||||||
|
uniqueManagers = append(uniqueManagers, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print conflicts by sorted managers.
|
||||||
|
sort.Strings(uniqueManagers)
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
for _, manager := range uniqueManagers {
|
||||||
|
messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager)))
|
||||||
|
for _, path := range m[manager] {
|
||||||
|
messages = append(messages, fmt.Sprintf("- %v", path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printManager(manager string) string {
|
||||||
|
encodedManager := &metav1.ManagedFieldsEntry{}
|
||||||
|
if err := json.Unmarshal([]byte(manager), encodedManager); err != nil {
|
||||||
|
return fmt.Sprintf("%q", manager)
|
||||||
|
}
|
||||||
|
managerStr := fmt.Sprintf("%q", encodedManager.Manager)
|
||||||
|
if encodedManager.Subresource != "" {
|
||||||
|
managerStr = fmt.Sprintf("%s with subresource %q", managerStr, encodedManager.Subresource)
|
||||||
|
}
|
||||||
|
if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate {
|
||||||
|
if encodedManager.Time == nil {
|
||||||
|
return fmt.Sprintf("%s using %v", managerStr, encodedManager.APIVersion)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s using %v at %v", managerStr, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
return managerStr
|
||||||
|
}
|
||||||
47
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmptyFields represents a set with no paths
|
||||||
|
// It looks like metav1.Fields{Raw: []byte("{}")}
|
||||||
|
var EmptyFields = func() metav1.FieldsV1 {
|
||||||
|
f, err := SetToFields(*fieldpath.NewSet())
|
||||||
|
if err != nil {
|
||||||
|
panic("should never happen")
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
// FieldsToSet creates a set paths from an input trie of fields
|
||||||
|
func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) {
|
||||||
|
err = s.FromJSON(bytes.NewReader(f.Raw))
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToFields creates a trie of fields from an input set of paths
|
||||||
|
func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) {
|
||||||
|
f.Raw, err = s.ToJSON()
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
248
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
248
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManagedInterface groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||||
|
type ManagedInterface interface {
|
||||||
|
// Fields gets the fieldpath.ManagedFields.
|
||||||
|
Fields() fieldpath.ManagedFields
|
||||||
|
|
||||||
|
// Times gets the timestamps associated with each operation.
|
||||||
|
Times() map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type managedStruct struct {
|
||||||
|
fields fieldpath.ManagedFields
|
||||||
|
times map[string]*metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ManagedInterface = &managedStruct{}
|
||||||
|
|
||||||
|
// Fields implements ManagedInterface.
|
||||||
|
func (m *managedStruct) Fields() fieldpath.ManagedFields {
|
||||||
|
return m.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times implements ManagedInterface.
|
||||||
|
func (m *managedStruct) Times() map[string]*metav1.Time {
|
||||||
|
return m.times
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEmptyManaged creates an empty ManagedInterface.
|
||||||
|
func NewEmptyManaged() ManagedInterface {
|
||||||
|
return NewManaged(fieldpath.ManagedFields{}, map[string]*metav1.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation.
|
||||||
|
func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface {
|
||||||
|
return &managedStruct{
|
||||||
|
fields: f,
|
||||||
|
times: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveObjectManagedFields removes the ManagedFields from the object
|
||||||
|
// before we merge so that it doesn't appear in the ManagedFields
|
||||||
|
// recursively.
|
||||||
|
func RemoveObjectManagedFields(obj runtime.Object) {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
accessor.SetManagedFields(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
||||||
|
func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) error {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedManagedFields, err := encodeManagedFields(managed)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert back managed fields to API: %v", err)
|
||||||
|
}
|
||||||
|
accessor.SetManagedFields(encodedManagedFields)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeManagedFields converts ManagedFields from the wire format (api format)
|
||||||
|
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||||
|
func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (ManagedInterface, error) {
|
||||||
|
managed := managedStruct{}
|
||||||
|
managed.fields = make(fieldpath.ManagedFields, len(encodedManagedFields))
|
||||||
|
managed.times = make(map[string]*metav1.Time, len(encodedManagedFields))
|
||||||
|
|
||||||
|
for i, encodedVersionedSet := range encodedManagedFields {
|
||||||
|
switch encodedVersionedSet.Operation {
|
||||||
|
case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("operation must be `Apply` or `Update`")
|
||||||
|
}
|
||||||
|
if len(encodedVersionedSet.APIVersion) < 1 {
|
||||||
|
return nil, fmt.Errorf("apiVersion must not be empty")
|
||||||
|
}
|
||||||
|
switch encodedVersionedSet.FieldsType {
|
||||||
|
case "FieldsV1":
|
||||||
|
// Valid case.
|
||||||
|
case "":
|
||||||
|
return nil, fmt.Errorf("missing fieldsType in managed fields entry %d", i)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid fieldsType %q in managed fields entry %d", encodedVersionedSet.FieldsType, i)
|
||||||
|
}
|
||||||
|
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||||
|
}
|
||||||
|
managed.fields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||||
|
}
|
||||||
|
managed.times[manager] = encodedVersionedSet.Time
|
||||||
|
}
|
||||||
|
return &managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
|
||||||
|
func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||||
|
encodedManagerCopy := *encodedManager
|
||||||
|
|
||||||
|
// Never include fields type in the manager identifier
|
||||||
|
encodedManagerCopy.FieldsType = ""
|
||||||
|
|
||||||
|
// Never include the fields in the manager identifier
|
||||||
|
encodedManagerCopy.FieldsV1 = nil
|
||||||
|
|
||||||
|
// Never include the time in the manager identifier
|
||||||
|
encodedManagerCopy.Time = nil
|
||||||
|
|
||||||
|
// For appliers, don't include the APIVersion in the manager identifier,
|
||||||
|
// so it will always have the same manager identifier each time it applied.
|
||||||
|
if encodedManager.Operation == metav1.ManagedFieldsOperationApply {
|
||||||
|
encodedManagerCopy.APIVersion = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the remaining fields to build the manager identifier
|
||||||
|
b, err := json.Marshal(&encodedManagerCopy)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error marshalling manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet fieldpath.VersionedSet, err error) {
|
||||||
|
fields := EmptyFields
|
||||||
|
if encodedVersionedSet.FieldsV1 != nil {
|
||||||
|
fields = *encodedVersionedSet.FieldsV1
|
||||||
|
}
|
||||||
|
set, err := FieldsToSet(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding set: %v", err)
|
||||||
|
}
|
||||||
|
return fieldpath.NewVersionedSet(&set, fieldpath.APIVersion(encodedVersionedSet.APIVersion), encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeManagedFields converts ManagedFields from the format used by
|
||||||
|
// sigs.k8s.io/structured-merge-diff to the wire format (api format)
|
||||||
|
func encodeManagedFields(managed ManagedInterface) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||||
|
if len(managed.Fields()) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
||||||
|
for manager := range managed.Fields() {
|
||||||
|
versionedSet := managed.Fields()[manager]
|
||||||
|
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
||||||
|
}
|
||||||
|
if t, ok := managed.Times()[manager]; ok {
|
||||||
|
v.Time = t
|
||||||
|
}
|
||||||
|
encodedManagedFields = append(encodedManagedFields, *v)
|
||||||
|
}
|
||||||
|
return sortEncodedManagedFields(encodedManagedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||||
|
sort.Slice(encodedManagedFields, func(i, j int) bool {
|
||||||
|
p, q := encodedManagedFields[i], encodedManagedFields[j]
|
||||||
|
|
||||||
|
if p.Operation != q.Operation {
|
||||||
|
return p.Operation < q.Operation
|
||||||
|
}
|
||||||
|
|
||||||
|
pSeconds, qSeconds := int64(0), int64(0)
|
||||||
|
if p.Time != nil {
|
||||||
|
pSeconds = p.Time.Unix()
|
||||||
|
}
|
||||||
|
if q.Time != nil {
|
||||||
|
qSeconds = q.Time.Unix()
|
||||||
|
}
|
||||||
|
if pSeconds != qSeconds {
|
||||||
|
return pSeconds < qSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Manager != q.Manager {
|
||||||
|
return p.Manager < q.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.APIVersion != q.APIVersion {
|
||||||
|
return p.APIVersion < q.APIVersion
|
||||||
|
}
|
||||||
|
return p.Subresource < q.Subresource
|
||||||
|
})
|
||||||
|
|
||||||
|
return encodedManagedFields, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeManagerVersionedSet(manager string, versionedSet fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) {
|
||||||
|
encodedVersionedSet = &metav1.ManagedFieldsEntry{}
|
||||||
|
|
||||||
|
// Get as many fields as we can from the manager identifier
|
||||||
|
err = json.Unmarshal([]byte(manager), encodedVersionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the APIVersion, Operation, and Fields from the VersionedSet
|
||||||
|
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion())
|
||||||
|
if versionedSet.Applied() {
|
||||||
|
encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply
|
||||||
|
}
|
||||||
|
encodedVersionedSet.FieldsType = "FieldsV1"
|
||||||
|
fields, err := SetToFields(*versionedSet.Set())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding set: %v", err)
|
||||||
|
}
|
||||||
|
encodedVersionedSet.FieldsV1 = &fields
|
||||||
|
|
||||||
|
return encodedVersionedSet, nil
|
||||||
|
}
|
||||||
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Field indicates that the content of this path element is a field's name
|
||||||
|
Field = "f"
|
||||||
|
|
||||||
|
// Value indicates that the content of this path element is a field's value
|
||||||
|
Value = "v"
|
||||||
|
|
||||||
|
// Index indicates that the content of this path element is an index in an array
|
||||||
|
Index = "i"
|
||||||
|
|
||||||
|
// Key indicates that the content of this path element is a key value map
|
||||||
|
Key = "k"
|
||||||
|
|
||||||
|
// Separator separates the type of a path element from the contents
|
||||||
|
Separator = ":"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPathElement parses a serialized path element
|
||||||
|
func NewPathElement(s string) (fieldpath.PathElement, error) {
|
||||||
|
split := strings.SplitN(s, Separator, 2)
|
||||||
|
if len(split) < 2 {
|
||||||
|
return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s)
|
||||||
|
}
|
||||||
|
switch split[0] {
|
||||||
|
case Field:
|
||||||
|
return fieldpath.PathElement{
|
||||||
|
FieldName: &split[1],
|
||||||
|
}, nil
|
||||||
|
case Value:
|
||||||
|
val, err := value.FromJSON([]byte(split[1]))
|
||||||
|
if err != nil {
|
||||||
|
return fieldpath.PathElement{}, err
|
||||||
|
}
|
||||||
|
return fieldpath.PathElement{
|
||||||
|
Value: &val,
|
||||||
|
}, nil
|
||||||
|
case Index:
|
||||||
|
i, err := strconv.Atoi(split[1])
|
||||||
|
if err != nil {
|
||||||
|
return fieldpath.PathElement{}, err
|
||||||
|
}
|
||||||
|
return fieldpath.PathElement{
|
||||||
|
Index: &i,
|
||||||
|
}, nil
|
||||||
|
case Key:
|
||||||
|
kv := map[string]json.RawMessage{}
|
||||||
|
err := json.Unmarshal([]byte(split[1]), &kv)
|
||||||
|
if err != nil {
|
||||||
|
return fieldpath.PathElement{}, err
|
||||||
|
}
|
||||||
|
fields := value.FieldList{}
|
||||||
|
for k, v := range kv {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return fieldpath.PathElement{}, err
|
||||||
|
}
|
||||||
|
val, err := value.FromJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
return fieldpath.PathElement{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = append(fields, value.Field{
|
||||||
|
Name: k,
|
||||||
|
Value: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return fieldpath.PathElement{
|
||||||
|
Key: &fields,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
// Ignore unknown key types
|
||||||
|
return fieldpath.PathElement{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathElementString serializes a path element
|
||||||
|
func PathElementString(pe fieldpath.PathElement) (string, error) {
|
||||||
|
switch {
|
||||||
|
case pe.FieldName != nil:
|
||||||
|
return Field + Separator + *pe.FieldName, nil
|
||||||
|
case pe.Key != nil:
|
||||||
|
kv := map[string]json.RawMessage{}
|
||||||
|
for _, k := range *pe.Key {
|
||||||
|
b, err := value.ToJSON(k.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m := json.RawMessage{}
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
kv[k.Name] = m
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(kv)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return Key + ":" + string(b), nil
|
||||||
|
case pe.Value != nil:
|
||||||
|
b, err := value.ToJSON(*pe.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return Value + ":" + string(b), nil
|
||||||
|
case pe.Index != nil:
|
||||||
|
return Index + ":" + strconv.Itoa(*pe.Index), nil
|
||||||
|
default:
|
||||||
|
return "", errors.New("Invalid type of path element")
|
||||||
|
}
|
||||||
|
}
|
||||||
172
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedmanager.go
generated
vendored
Normal file
172
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedmanager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lastAppliedManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
typeConverter TypeConverter
|
||||||
|
objectConverter runtime.ObjectConvertor
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &lastAppliedManager{}
|
||||||
|
|
||||||
|
// NewLastAppliedManager converts the client-side apply annotation to
|
||||||
|
// server-side apply managed fields
|
||||||
|
func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager {
|
||||||
|
return &lastAppliedManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
typeConverter: typeConverter,
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
groupVersion: groupVersion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply will consider the last-applied annotation
|
||||||
|
// for upgrading an object managed by client-side apply to server-side apply
|
||||||
|
// without conflicts.
|
||||||
|
func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
|
||||||
|
// Upgrade the client-side apply annotation only from kubectl server-side-apply.
|
||||||
|
// To opt-out of this behavior, users may specify a different field manager.
|
||||||
|
if manager != "kubectl" {
|
||||||
|
return newLiveObj, newManaged, newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have conflicts
|
||||||
|
if newErr == nil {
|
||||||
|
return newLiveObj, newManaged, newErr
|
||||||
|
}
|
||||||
|
conflicts, ok := newErr.(merge.Conflicts)
|
||||||
|
if !ok {
|
||||||
|
return newLiveObj, newManaged, newErr
|
||||||
|
}
|
||||||
|
conflictSet := conflictsToSet(conflicts)
|
||||||
|
|
||||||
|
// Check if conflicts are allowed due to client-side apply,
|
||||||
|
// and if so, then force apply
|
||||||
|
allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return newLiveObj, newManaged, newErr
|
||||||
|
}
|
||||||
|
if !conflictSet.Difference(allowedConflictSet).Empty() {
|
||||||
|
newConflicts := conflictsDifference(conflicts, allowedConflictSet)
|
||||||
|
return newLiveObj, newManaged, newConflicts
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.fieldManager.Apply(liveObj, newObj, managed, manager, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) {
|
||||||
|
var accessor, err = meta.Accessor(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no client-side apply annotation, then there is nothing to do
|
||||||
|
var annotations = accessor.GetAnnotations()
|
||||||
|
if annotations == nil {
|
||||||
|
return nil, fmt.Errorf("no last applied annotation")
|
||||||
|
}
|
||||||
|
var lastApplied, ok = annotations[corev1.LastAppliedConfigAnnotation]
|
||||||
|
if !ok || lastApplied == "" {
|
||||||
|
return nil, fmt.Errorf("no last applied annotation")
|
||||||
|
}
|
||||||
|
|
||||||
|
liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert live obj to typed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
|
err = json.Unmarshal([]byte(lastApplied), lastAppliedObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() {
|
||||||
|
return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert last applied to typed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
comparison, err := lastAppliedObjTyped.Compare(liveObjTyped)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove fields in last applied that are different, added, or missing in
|
||||||
|
// the live object.
|
||||||
|
// Because last-applied fields don't match the live object fields,
|
||||||
|
// then we don't own these fields.
|
||||||
|
lastAppliedObjFieldSet = lastAppliedObjFieldSet.
|
||||||
|
Difference(comparison.Modified).
|
||||||
|
Difference(comparison.Added).
|
||||||
|
Difference(comparison.Removed)
|
||||||
|
|
||||||
|
return lastAppliedObjFieldSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace with merge.Conflicts.ToSet()
|
||||||
|
func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set {
|
||||||
|
conflictSet := fieldpath.NewSet()
|
||||||
|
for _, conflict := range []merge.Conflict(conflicts) {
|
||||||
|
conflictSet.Insert(conflict.Path)
|
||||||
|
}
|
||||||
|
return conflictSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts {
|
||||||
|
newConflicts := []merge.Conflict{}
|
||||||
|
for _, conflict := range []merge.Conflict(conflicts) {
|
||||||
|
if !s.Has(conflict.Path) {
|
||||||
|
newConflicts = append(newConflicts, conflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newConflicts
|
||||||
|
}
|
||||||
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedupdater.go
generated
vendored
Normal file
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedupdater.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lastAppliedUpdater struct {
|
||||||
|
fieldManager Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &lastAppliedUpdater{}
|
||||||
|
|
||||||
|
// NewLastAppliedUpdater sets the client-side apply annotation up to date with
|
||||||
|
// server-side apply managed fields
|
||||||
|
func NewLastAppliedUpdater(fieldManager Manager) Manager {
|
||||||
|
return &lastAppliedUpdater{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *lastAppliedUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server-side apply managed fields
|
||||||
|
func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
liveObj, managed, err := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
|
||||||
|
if err != nil {
|
||||||
|
return liveObj, managed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the client-side apply annotation only from kubectl server-side apply.
|
||||||
|
// To opt-out of this behavior, users may specify a different field manager.
|
||||||
|
//
|
||||||
|
// If the client-side apply annotation doesn't exist,
|
||||||
|
// then continue because we have no annotation to update
|
||||||
|
if manager == "kubectl" && hasLastApplied(liveObj) {
|
||||||
|
lastAppliedValue, err := buildLastApplied(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err)
|
||||||
|
}
|
||||||
|
err = setLastApplied(liveObj, lastAppliedValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return liveObj, managed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasLastApplied(obj runtime.Object) bool {
|
||||||
|
var accessor, err = meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
var annotations = accessor.GetAnnotations()
|
||||||
|
if annotations == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lastApplied, ok := annotations[corev1.LastAppliedConfigAnnotation]
|
||||||
|
return ok && len(lastApplied) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLastApplied(obj runtime.Object, value string) error {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
var annotations = accessor.GetAnnotations()
|
||||||
|
if annotations == nil {
|
||||||
|
annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
annotations[corev1.LastAppliedConfigAnnotation] = value
|
||||||
|
if err := apimachineryvalidation.ValidateAnnotationsSize(annotations); err != nil {
|
||||||
|
delete(annotations, corev1.LastAppliedConfigAnnotation)
|
||||||
|
}
|
||||||
|
accessor.SetAnnotations(annotations)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLastApplied(obj runtime.Object) (string, error) {
|
||||||
|
obj = obj.DeepCopyObject()
|
||||||
|
|
||||||
|
var accessor, err = meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the annotation from the object before encoding the object
|
||||||
|
var annotations = accessor.GetAnnotations()
|
||||||
|
delete(annotations, corev1.LastAppliedConfigAnnotation)
|
||||||
|
accessor.SetAnnotations(annotations)
|
||||||
|
|
||||||
|
lastApplied, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("couldn't encode object into last applied annotation: %v", err)
|
||||||
|
}
|
||||||
|
return string(lastApplied), nil
|
||||||
|
}
|
||||||
83
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go
generated
vendored
Normal file
83
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go
generated
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type managedFieldsUpdater struct {
|
||||||
|
fieldManager Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &managedFieldsUpdater{}
|
||||||
|
|
||||||
|
// NewManagedFieldsUpdater is responsible for updating the managedfields
|
||||||
|
// in the object, updating the time of the operation as necessary. For
|
||||||
|
// updates, it uses a hard-coded manager to detect if things have
|
||||||
|
// changed, and swaps back the correct manager after the operation is
|
||||||
|
// done.
|
||||||
|
func NewManagedFieldsUpdater(fieldManager Manager) Manager {
|
||||||
|
return &managedFieldsUpdater{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
self := "current-operation"
|
||||||
|
object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self)
|
||||||
|
if err != nil {
|
||||||
|
return object, managed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current operation took any fields from anything, it means the object changed,
|
||||||
|
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
|
||||||
|
if vs, ok := managed.Fields()[self]; ok {
|
||||||
|
delete(managed.Fields(), self)
|
||||||
|
|
||||||
|
if previous, ok := managed.Fields()[manager]; ok {
|
||||||
|
managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
|
||||||
|
} else {
|
||||||
|
managed.Fields()[manager] = vs
|
||||||
|
}
|
||||||
|
|
||||||
|
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
object, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
|
||||||
|
if err != nil {
|
||||||
|
return object, managed, err
|
||||||
|
}
|
||||||
|
if object != nil {
|
||||||
|
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()}
|
||||||
|
} else {
|
||||||
|
object = liveObj.DeepCopyObject()
|
||||||
|
internal.RemoveObjectManagedFields(object)
|
||||||
|
}
|
||||||
|
return object, managed, nil
|
||||||
|
}
|
||||||
261
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/node.yaml
generated
vendored
Normal file
261
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/node.yaml
generated
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Node
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
container.googleapis.com/instance_id: "123456789321654789"
|
||||||
|
node.alpha.kubernetes.io/ttl: "0"
|
||||||
|
volumes.kubernetes.io/controller-managed-attach-detach: "true"
|
||||||
|
creationTimestamp: "2019-07-09T16:17:29Z"
|
||||||
|
labels:
|
||||||
|
kubernetes.io/arch: amd64
|
||||||
|
beta.kubernetes.io/fluentd-ds-ready: "true"
|
||||||
|
beta.kubernetes.io/instance-type: n1-standard-4
|
||||||
|
kubernetes.io/os: linux
|
||||||
|
cloud.google.com/gke-nodepool: default-pool
|
||||||
|
cloud.google.com/gke-os-distribution: cos
|
||||||
|
failure-domain.beta.kubernetes.io/region: us-central1
|
||||||
|
failure-domain.beta.kubernetes.io/zone: us-central1-b
|
||||||
|
topology.kubernetes.io/region: us-central1
|
||||||
|
topology.kubernetes.io/zone: us-central1-b
|
||||||
|
kubernetes.io/hostname: node-default-pool-something
|
||||||
|
name: node-default-pool-something
|
||||||
|
resourceVersion: "211582541"
|
||||||
|
selfLink: /api/v1/nodes/node-default-pool-something
|
||||||
|
uid: 0c24d0e1-a265-11e9-abe4-42010a80026b
|
||||||
|
spec:
|
||||||
|
podCIDR: 10.0.0.1/24
|
||||||
|
providerID: some-provider-id-of-some-sort
|
||||||
|
status:
|
||||||
|
addresses:
|
||||||
|
- address: 10.0.0.1
|
||||||
|
type: InternalIP
|
||||||
|
- address: 192.168.0.1
|
||||||
|
type: ExternalIP
|
||||||
|
- address: node-default-pool-something
|
||||||
|
type: Hostname
|
||||||
|
allocatable:
|
||||||
|
cpu: 3920m
|
||||||
|
ephemeral-storage: "104638878617"
|
||||||
|
hugepages-2Mi: "0"
|
||||||
|
memory: 12700100Ki
|
||||||
|
pods: "110"
|
||||||
|
capacity:
|
||||||
|
cpu: "4"
|
||||||
|
ephemeral-storage: 202086868Ki
|
||||||
|
hugepages-2Mi: "0"
|
||||||
|
memory: 15399364Ki
|
||||||
|
pods: "110"
|
||||||
|
conditions:
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:22:08Z"
|
||||||
|
message: containerd is functioning properly
|
||||||
|
reason: FrequentContainerdRestart
|
||||||
|
status: "False"
|
||||||
|
type: FrequentContainerdRestart
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:22:06Z"
|
||||||
|
message: docker overlay2 is functioning properly
|
||||||
|
reason: CorruptDockerOverlay2
|
||||||
|
status: "False"
|
||||||
|
type: CorruptDockerOverlay2
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:22:06Z"
|
||||||
|
message: node is functioning properly
|
||||||
|
reason: UnregisterNetDevice
|
||||||
|
status: "False"
|
||||||
|
type: FrequentUnregisterNetDevice
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:04Z"
|
||||||
|
message: kernel has no deadlock
|
||||||
|
reason: KernelHasNoDeadlock
|
||||||
|
status: "False"
|
||||||
|
type: KernelDeadlock
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:04Z"
|
||||||
|
message: Filesystem is not read-only
|
||||||
|
reason: FilesystemIsNotReadOnly
|
||||||
|
status: "False"
|
||||||
|
type: ReadonlyFilesystem
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:22:05Z"
|
||||||
|
message: kubelet is functioning properly
|
||||||
|
reason: FrequentKubeletRestart
|
||||||
|
status: "False"
|
||||||
|
type: FrequentKubeletRestart
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:08Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:22:06Z"
|
||||||
|
message: docker is functioning properly
|
||||||
|
reason: FrequentDockerRestart
|
||||||
|
status: "False"
|
||||||
|
type: FrequentDockerRestart
|
||||||
|
- lastHeartbeatTime: "2019-07-09T16:17:47Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:47Z"
|
||||||
|
message: RouteController created a route
|
||||||
|
reason: RouteCreated
|
||||||
|
status: "False"
|
||||||
|
type: NetworkUnavailable
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:29Z"
|
||||||
|
message: kubelet has sufficient disk space available
|
||||||
|
reason: KubeletHasSufficientDisk
|
||||||
|
status: "False"
|
||||||
|
type: OutOfDisk
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:29Z"
|
||||||
|
message: kubelet has sufficient memory available
|
||||||
|
reason: KubeletHasSufficientMemory
|
||||||
|
status: "False"
|
||||||
|
type: MemoryPressure
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:29Z"
|
||||||
|
message: kubelet has no disk pressure
|
||||||
|
reason: KubeletHasNoDiskPressure
|
||||||
|
status: "False"
|
||||||
|
type: DiskPressure
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:29Z"
|
||||||
|
message: kubelet has sufficient PID available
|
||||||
|
reason: KubeletHasSufficientPID
|
||||||
|
status: "False"
|
||||||
|
type: PIDPressure
|
||||||
|
- lastHeartbeatTime: "2019-09-20T19:32:50Z"
|
||||||
|
lastTransitionTime: "2019-07-09T16:17:49Z"
|
||||||
|
message: kubelet is posting ready status. AppArmor enabled
|
||||||
|
reason: KubeletReady
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
||||||
|
daemonEndpoints:
|
||||||
|
kubeletEndpoint:
|
||||||
|
Port: 10250
|
||||||
|
images:
|
||||||
|
- names:
|
||||||
|
- grafana/grafana@sha256:80e5e113a984d74836aa16f5b4524012099436b1a50df293f00ac6377fb512c8
|
||||||
|
- grafana/grafana:4.4.2
|
||||||
|
sizeBytes: 287008013
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/node-problem-detector@sha256:f95cab985c26b2f46e9bd43283e0bfa88860c14e0fb0649266babe8b65e9eb2b
|
||||||
|
- registry.k8s.io/node-problem-detector:v0.4.1
|
||||||
|
sizeBytes: 286572743
|
||||||
|
- names:
|
||||||
|
- grafana/grafana@sha256:7ff7f9b2501a5d55b55ce3f58d21771b1c5af1f2a4ab7dbf11bef7142aae7033
|
||||||
|
- grafana/grafana:4.2.0
|
||||||
|
sizeBytes: 277940263
|
||||||
|
- names:
|
||||||
|
- influxdb@sha256:7dddf03376348876ed4bdf33d6dfa3326f45a2bae0930dbd80781a374eb519bc
|
||||||
|
- influxdb:1.2.2
|
||||||
|
sizeBytes: 223948571
|
||||||
|
- names:
|
||||||
|
- gcr.io/stackdriver-agents/stackdriver-logging-agent@sha256:f8d5231b67b9c53f60068b535a11811d29d1b3efd53d2b79f2a2591ea338e4f2
|
||||||
|
- gcr.io/stackdriver-agents/stackdriver-logging-agent:0.6-1.6.0-1
|
||||||
|
sizeBytes: 223242132
|
||||||
|
- names:
|
||||||
|
- nginx@sha256:35779791c05d119df4fe476db8f47c0bee5943c83eba5656a15fc046db48178b
|
||||||
|
- nginx:1.10.1
|
||||||
|
sizeBytes: 180708613
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/fluentd-elasticsearch@sha256:b8c94527b489fb61d3d81ce5ad7f3ddbb7be71e9620a3a36e2bede2f2e487d73
|
||||||
|
- registry.k8s.io/fluentd-elasticsearch:v2.0.4
|
||||||
|
sizeBytes: 135716379
|
||||||
|
- names:
|
||||||
|
- nginx@sha256:00be67d6ba53d5318cd91c57771530f5251cfbe028b7be2c4b70526f988cfc9f
|
||||||
|
- nginx:latest
|
||||||
|
sizeBytes: 109357355
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/kubernetes-dashboard-amd64@sha256:dc4026c1b595435ef5527ca598e1e9c4343076926d7d62b365c44831395adbd0
|
||||||
|
- registry.k8s.io/kubernetes-dashboard-amd64:v1.8.3
|
||||||
|
sizeBytes: 102319441
|
||||||
|
- names:
|
||||||
|
- gcr.io/google_containers/kube-proxy:v1.11.10-gke.5
|
||||||
|
- registry.k8s.io/kube-proxy:v1.11.10-gke.5
|
||||||
|
sizeBytes: 102279340
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/event-exporter@sha256:7f9cd7cb04d6959b0aa960727d04fa86759008048c785397b7b0d9dff0007516
|
||||||
|
- registry.k8s.io/event-exporter:v0.2.3
|
||||||
|
sizeBytes: 94171943
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/prometheus-to-sd@sha256:6c0c742475363d537ff059136e5d5e4ab1f512ee0fd9b7ca42ea48bc309d1662
|
||||||
|
- registry.k8s.io/prometheus-to-sd:v0.3.1
|
||||||
|
sizeBytes: 88077694
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/fluentd-gcp-scaler@sha256:a5ace7506d393c4ed65eb2cbb6312c64ab357fcea16dff76b9055bc6e498e5ff
|
||||||
|
- registry.k8s.io/fluentd-gcp-scaler:0.5.1
|
||||||
|
sizeBytes: 86637208
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/heapster-amd64@sha256:9fae0af136ce0cf4f88393b3670f7139ffc464692060c374d2ae748e13144521
|
||||||
|
- registry.k8s.io/heapster-amd64:v1.6.0-beta.1
|
||||||
|
sizeBytes: 76016169
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/ingress-glbc-amd64@sha256:31d36bbd9c44caffa135fc78cf0737266fcf25e3cf0cd1c2fcbfbc4f7309cc52
|
||||||
|
- registry.k8s.io/ingress-glbc-amd64:v1.1.1
|
||||||
|
sizeBytes: 67801919
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/kube-addon-manager@sha256:d53486c3a0b49ebee019932878dc44232735d5622a51dbbdcec7124199020d09
|
||||||
|
- registry.k8s.io/kube-addon-manager:v8.7
|
||||||
|
sizeBytes: 63322109
|
||||||
|
- names:
|
||||||
|
- nginx@sha256:4aacdcf186934dcb02f642579314075910f1855590fd3039d8fa4c9f96e48315
|
||||||
|
- nginx:1.10-alpine
|
||||||
|
sizeBytes: 54042627
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/cpvpa-amd64@sha256:cfe7b0a11c9c8e18c87b1eb34fef9a7cbb8480a8da11fc2657f78dbf4739f869
|
||||||
|
- registry.k8s.io/cpvpa-amd64:v0.6.0
|
||||||
|
sizeBytes: 51785854
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/cluster-proportional-autoscaler-amd64@sha256:003f98d9f411ddfa6ff6d539196355e03ddd69fa4ed38c7ffb8fec6f729afe2d
|
||||||
|
- registry.k8s.io/cluster-proportional-autoscaler-amd64:1.1.2-r2
|
||||||
|
sizeBytes: 49648481
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/ip-masq-agent-amd64@sha256:1ffda57d87901bc01324c82ceb2145fe6a0448d3f0dd9cb65aa76a867cd62103
|
||||||
|
- registry.k8s.io/ip-masq-agent-amd64:v2.1.1
|
||||||
|
sizeBytes: 49612505
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/k8s-dns-kube-dns-amd64@sha256:b99fc3eee2a9f052f7eb4cc00f15eb12fc405fa41019baa2d6b79847ae7284a8
|
||||||
|
- registry.k8s.io/k8s-dns-kube-dns-amd64:1.14.10
|
||||||
|
sizeBytes: 49549457
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/rescheduler@sha256:156cfbfd05a5a815206fd2eeb6cbdaf1596d71ea4b415d3a6c43071dd7b99450
|
||||||
|
- registry.k8s.io/rescheduler:v0.4.0
|
||||||
|
sizeBytes: 48973149
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/event-exporter@sha256:16ca66e2b5dc7a1ce6a5aafcb21d0885828b75cdfc08135430480f7ad2364adc
|
||||||
|
- registry.k8s.io/event-exporter:v0.2.4
|
||||||
|
sizeBytes: 47261019
|
||||||
|
- names:
|
||||||
|
- registry.k8s.io/coredns@sha256:db2bf53126ed1c761d5a41f24a1b82a461c85f736ff6e90542e9522be4757848
|
||||||
|
- registry.k8s.io/coredns:1.1.3
|
||||||
|
sizeBytes: 45587362
|
||||||
|
- names:
|
||||||
|
- prom/prometheus@sha256:483f4c9d7733699ba79facca9f8bcce1cef1af43dfc3e7c5a1882aa85f53cb74
|
||||||
|
- prom/prometheus:v1.1.3
|
||||||
|
sizeBytes: 45493941
|
||||||
|
nodeInfo:
|
||||||
|
architecture: amd64
|
||||||
|
bootID: a32eca78-4ad4-4b76-9252-f143d6c2ae61
|
||||||
|
containerRuntimeVersion: docker://17.3.2
|
||||||
|
kernelVersion: 4.14.127+
|
||||||
|
kubeProxyVersion: v1.11.10-gke.5
|
||||||
|
kubeletVersion: v1.11.10-gke.5
|
||||||
|
machineID: 1739555e5b231057f0f9a0b5fa29511b
|
||||||
|
operatingSystem: linux
|
||||||
|
osImage: Container-Optimized OS from Google
|
||||||
|
systemUUID: 1739555E-5B23-1057-F0F9-A0B5FA29511B
|
||||||
|
volumesAttached:
|
||||||
|
- devicePath: /dev/disk/by-id/b9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049
|
||||||
|
- devicePath: /dev/disk/by-id/b9772-pvc-8895a852-fd42-11e6-94d4-42010a800049
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049
|
||||||
|
- devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049
|
||||||
|
- devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049
|
||||||
|
- devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049
|
||||||
|
- devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283
|
||||||
|
name: kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283
|
||||||
|
volumesInUse:
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049
|
||||||
|
- kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049
|
||||||
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/pod.yaml
generated
vendored
Normal file
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/pod.yaml
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: some-app
|
||||||
|
plugin1: some-value
|
||||||
|
plugin2: some-value
|
||||||
|
plugin3: some-value
|
||||||
|
plugin4: some-value
|
||||||
|
name: some-name
|
||||||
|
namespace: default
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: ReplicaSet
|
||||||
|
name: some-name
|
||||||
|
uid: 0a9d2b9e-779e-11e7-b422-42010a8001be
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
- three
|
||||||
|
- four
|
||||||
|
- five
|
||||||
|
- six
|
||||||
|
- seven
|
||||||
|
- eight
|
||||||
|
- nine
|
||||||
|
env:
|
||||||
|
- name: VAR_3
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: some-other-key
|
||||||
|
name: some-oher-name
|
||||||
|
- name: VAR_2
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: other-key
|
||||||
|
name: other-name
|
||||||
|
- name: VAR_1
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: some-key
|
||||||
|
name: some-name
|
||||||
|
image: some-image-name
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: some-name
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: '0'
|
||||||
|
terminationMessagePath: /dev/termination-log
|
||||||
|
terminationMessagePolicy: File
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
|
||||||
|
name: default-token-hu5jz
|
||||||
|
readOnly: true
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
nodeName: node-name
|
||||||
|
priority: 0
|
||||||
|
restartPolicy: Always
|
||||||
|
schedulerName: default-scheduler
|
||||||
|
securityContext: {}
|
||||||
|
serviceAccount: default
|
||||||
|
serviceAccountName: default
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
tolerations:
|
||||||
|
- effect: NoExecute
|
||||||
|
key: node.kubernetes.io/not-ready
|
||||||
|
operator: Exists
|
||||||
|
tolerationSeconds: 300
|
||||||
|
- effect: NoExecute
|
||||||
|
key: node.kubernetes.io/unreachable
|
||||||
|
operator: Exists
|
||||||
|
tolerationSeconds: 300
|
||||||
|
volumes:
|
||||||
|
- name: default-token-hu5jz
|
||||||
|
secret:
|
||||||
|
defaultMode: 420
|
||||||
|
secretName: default-token-hu5jz
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- lastProbeTime: null
|
||||||
|
lastTransitionTime: '2019-07-08T09:31:18Z'
|
||||||
|
status: 'True'
|
||||||
|
type: Initialized
|
||||||
|
- lastProbeTime: null
|
||||||
|
lastTransitionTime: '2019-07-08T09:41:59Z'
|
||||||
|
status: 'True'
|
||||||
|
type: Ready
|
||||||
|
- lastProbeTime: null
|
||||||
|
lastTransitionTime: null
|
||||||
|
status: 'True'
|
||||||
|
type: ContainersReady
|
||||||
|
- lastProbeTime: null
|
||||||
|
lastTransitionTime: '2019-07-08T09:31:18Z'
|
||||||
|
status: 'True'
|
||||||
|
type: PodScheduled
|
||||||
|
containerStatuses:
|
||||||
|
- containerID: docker://885e82a1ed0b7356541bb410a0126921ac42439607c09875cd8097dd5d7b5376
|
||||||
|
image: some-image-name
|
||||||
|
imageID: docker-pullable://some-image-id
|
||||||
|
lastState:
|
||||||
|
terminated:
|
||||||
|
containerID: docker://d57290f9e00fad626b20d2dd87a3cf69bbc22edae07985374f86a8b2b4e39565
|
||||||
|
exitCode: 255
|
||||||
|
finishedAt: '2019-07-08T09:39:09Z'
|
||||||
|
reason: Error
|
||||||
|
startedAt: '2019-07-08T09:38:54Z'
|
||||||
|
name: name
|
||||||
|
ready: true
|
||||||
|
restartCount: 6
|
||||||
|
state:
|
||||||
|
running:
|
||||||
|
startedAt: '2019-07-08T09:41:59Z'
|
||||||
|
hostIP: 10.0.0.1
|
||||||
|
phase: Running
|
||||||
|
podIP: 10.0.0.1
|
||||||
|
qosClass: BestEffort
|
||||||
|
startTime: '2019-07-08T09:31:18Z'
|
||||||
174
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/scalehandler.go
generated
vendored
Normal file
174
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/scalehandler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scaleGroupVersion = schema.GroupVersion{Group: "autoscaling", Version: "v1"}
|
||||||
|
replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourcePathMappings maps a group/version to its replicas path. The
|
||||||
|
// assumption is that all the paths correspond to leaf fields.
|
||||||
|
type ResourcePathMappings map[string]fieldpath.Path
|
||||||
|
|
||||||
|
// ScaleHandler manages the conversion of managed fields between a main
|
||||||
|
// resource and the scale subresource
|
||||||
|
type ScaleHandler struct {
|
||||||
|
parentEntries []metav1.ManagedFieldsEntry
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
mappings ResourcePathMappings
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScaleHandler creates a new ScaleHandler
|
||||||
|
func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler {
|
||||||
|
return &ScaleHandler{
|
||||||
|
parentEntries: parentEntries,
|
||||||
|
groupVersion: groupVersion,
|
||||||
|
mappings: mappings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSubresource filter the managed fields of the main resource and convert
|
||||||
|
// them so that they can be handled by scale.
|
||||||
|
// For the managed fields that have a replicas path it performs two changes:
|
||||||
|
// 1. APIVersion is changed to the APIVersion of the scale subresource
|
||||||
|
// 2. Replicas path of the main resource is transformed to the replicas path of
|
||||||
|
// the scale subresource
|
||||||
|
func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) {
|
||||||
|
managed, err := DecodeManagedFields(h.parentEntries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fieldpath.ManagedFields{}
|
||||||
|
t := map[string]*metav1.Time{}
|
||||||
|
for manager, versionedSet := range managed.Fields() {
|
||||||
|
path, ok := h.mappings[string(versionedSet.APIVersion())]
|
||||||
|
// Skip the entry if the APIVersion is unknown
|
||||||
|
if !ok || path == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionedSet.Set().Has(path) {
|
||||||
|
newVersionedSet := fieldpath.NewVersionedSet(
|
||||||
|
fieldpath.NewSet(replicasPathInScale),
|
||||||
|
fieldpath.APIVersion(scaleGroupVersion.String()),
|
||||||
|
versionedSet.Applied(),
|
||||||
|
)
|
||||||
|
|
||||||
|
f[manager] = newVersionedSet
|
||||||
|
t[manager] = managed.Times()[manager]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return managedFieldsEntries(internal.NewManaged(f, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToParent merges `scaleEntries` with the entries of the main resource and
|
||||||
|
// transforms them accordingly
|
||||||
|
func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) {
|
||||||
|
decodedParentEntries, err := DecodeManagedFields(h.parentEntries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parentFields := decodedParentEntries.Fields()
|
||||||
|
|
||||||
|
decodedScaleEntries, err := DecodeManagedFields(scaleEntries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
scaleFields := decodedScaleEntries.Fields()
|
||||||
|
|
||||||
|
f := fieldpath.ManagedFields{}
|
||||||
|
t := map[string]*metav1.Time{}
|
||||||
|
|
||||||
|
for manager, versionedSet := range parentFields {
|
||||||
|
// Get the main resource "replicas" path
|
||||||
|
path, ok := h.mappings[string(versionedSet.APIVersion())]
|
||||||
|
// Drop the entry if the APIVersion is unknown.
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the parent entry does not have the replicas path or it is nil, just
|
||||||
|
// keep it as it is. The path is nil for Custom Resources without scale
|
||||||
|
// subresource.
|
||||||
|
if path == nil || !versionedSet.Set().Has(path) {
|
||||||
|
f[manager] = versionedSet
|
||||||
|
t[manager] = decodedParentEntries.Times()[manager]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := scaleFields[manager]; !ok {
|
||||||
|
// "Steal" the replicas path from the main resource entry
|
||||||
|
newSet := versionedSet.Set().Difference(fieldpath.NewSet(path))
|
||||||
|
|
||||||
|
if !newSet.Empty() {
|
||||||
|
newVersionedSet := fieldpath.NewVersionedSet(
|
||||||
|
newSet,
|
||||||
|
versionedSet.APIVersion(),
|
||||||
|
versionedSet.Applied(),
|
||||||
|
)
|
||||||
|
f[manager] = newVersionedSet
|
||||||
|
t[manager] = decodedParentEntries.Times()[manager]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Field wasn't stolen, let's keep the entry as it is.
|
||||||
|
f[manager] = versionedSet
|
||||||
|
t[manager] = decodedParentEntries.Times()[manager]
|
||||||
|
delete(scaleFields, manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for manager, versionedSet := range scaleFields {
|
||||||
|
if !versionedSet.Set().Has(replicasPathInScale) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newVersionedSet := fieldpath.NewVersionedSet(
|
||||||
|
fieldpath.NewSet(h.mappings[h.groupVersion.String()]),
|
||||||
|
fieldpath.APIVersion(h.groupVersion.String()),
|
||||||
|
versionedSet.Applied(),
|
||||||
|
)
|
||||||
|
f[manager] = newVersionedSet
|
||||||
|
t[manager] = decodedParentEntries.Times()[manager]
|
||||||
|
}
|
||||||
|
|
||||||
|
return managedFieldsEntries(internal.NewManaged(f, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) {
|
||||||
|
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
|
if err := internal.EncodeObjectManagedFields(obj, entries); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||||
|
}
|
||||||
|
return accessor.GetManagedFields(), nil
|
||||||
|
}
|
||||||
91
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/skipnonapplied.go
generated
vendored
Normal file
91
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/skipnonapplied.go
generated
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type skipNonAppliedManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
objectCreater runtime.ObjectCreater
|
||||||
|
gvk schema.GroupVersionKind
|
||||||
|
beforeApplyManagerName string
|
||||||
|
probability float32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &skipNonAppliedManager{}
|
||||||
|
|
||||||
|
// NewSkipNonAppliedManager creates a new wrapped FieldManager that only starts tracking managers after the first apply.
|
||||||
|
func NewSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater, gvk schema.GroupVersionKind) Manager {
|
||||||
|
return NewProbabilisticSkipNonAppliedManager(fieldManager, objectCreater, gvk, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProbabilisticSkipNonAppliedManager creates a new wrapped FieldManager that starts tracking managers after the first apply,
|
||||||
|
// or starts tracking on create with p probability.
|
||||||
|
func NewProbabilisticSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater, gvk schema.GroupVersionKind, p float32) Manager {
|
||||||
|
return &skipNonAppliedManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
objectCreater: objectCreater,
|
||||||
|
gvk: gvk,
|
||||||
|
beforeApplyManagerName: "before-first-apply",
|
||||||
|
probability: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
accessor, err := meta.Accessor(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If managed fields is empty, we need to determine whether to skip tracking managed fields.
|
||||||
|
if len(managed.Fields()) == 0 {
|
||||||
|
// Check if the operation is a create, by checking whether lastObj's UID is empty.
|
||||||
|
// If the operation is create, P(tracking managed fields) = f.probability
|
||||||
|
// If the operation is update, skip tracking managed fields, since we already know managed fields is empty.
|
||||||
|
if len(accessor.GetUID()) == 0 {
|
||||||
|
if f.probability <= rand.Float32() {
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *skipNonAppliedManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
if len(managed.Fields()) == 0 {
|
||||||
|
emptyObj, err := f.objectCreater.New(f.gvk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create empty object of type %v: %v", f.gvk, err)
|
||||||
|
}
|
||||||
|
liveObj, managed, err = f.fieldManager.Update(emptyObj, liveObj, managed, f.beforeApplyManagerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create manager for existing fields: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
|
||||||
|
}
|
||||||
90
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/stripmeta.go
generated
vendored
Normal file
90
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/stripmeta.go
generated
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stripMetaManager struct {
|
||||||
|
fieldManager Manager
|
||||||
|
|
||||||
|
// stripSet is the list of fields that should never be part of a mangedFields.
|
||||||
|
stripSet *fieldpath.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &stripMetaManager{}
|
||||||
|
|
||||||
|
// NewStripMetaManager creates a new Manager that strips metadata and typemeta fields from the manager's fieldset.
|
||||||
|
func NewStripMetaManager(fieldManager Manager) Manager {
|
||||||
|
return &stripMetaManager{
|
||||||
|
fieldManager: fieldManager,
|
||||||
|
stripSet: fieldpath.NewSet(
|
||||||
|
fieldpath.MakePathOrDie("apiVersion"),
|
||||||
|
fieldpath.MakePathOrDie("kind"),
|
||||||
|
fieldpath.MakePathOrDie("metadata"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "name"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "namespace"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "selfLink"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "uid"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "clusterName"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "generation"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "managedFields"),
|
||||||
|
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *stripMetaManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
newObj, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
f.stripFields(managed.Fields(), manager)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *stripMetaManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
newObj, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
f.stripFields(managed.Fields(), manager)
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripFields removes a predefined set of paths found in typed from managed
|
||||||
|
func (f *stripMetaManager) stripFields(managed fieldpath.ManagedFields, manager string) {
|
||||||
|
vs, ok := managed[manager]
|
||||||
|
if ok {
|
||||||
|
if vs == nil {
|
||||||
|
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
|
||||||
|
}
|
||||||
|
newSet := vs.Set().Difference(f.stripSet)
|
||||||
|
if newSet.Empty() {
|
||||||
|
delete(managed, manager)
|
||||||
|
} else {
|
||||||
|
managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go
generated
vendored
Normal file
184
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go
generated
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
type structuredMergeManager struct {
|
||||||
|
typeConverter TypeConverter
|
||||||
|
objectConverter runtime.ObjectConvertor
|
||||||
|
objectDefaulter runtime.ObjectDefaulter
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
hubVersion schema.GroupVersion
|
||||||
|
updater merge.Updater
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Manager = &structuredMergeManager{}
|
||||||
|
|
||||||
|
// NewStructuredMergeManager creates a new Manager that merges apply requests
|
||||||
|
// and update managed fields for other types of requests.
|
||||||
|
func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (Manager, error) {
|
||||||
|
return &structuredMergeManager{
|
||||||
|
typeConverter: typeConverter,
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
objectDefaulter: objectDefaulter,
|
||||||
|
groupVersion: gv,
|
||||||
|
hubVersion: hub,
|
||||||
|
updater: merge.Updater{
|
||||||
|
Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s
|
||||||
|
IgnoredFields: resetFields,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCRDStructuredMergeManager creates a new Manager specifically for
|
||||||
|
// CRDs. This allows for the possibility of fields which are not defined
|
||||||
|
// in models, as well as having no models defined at all.
|
||||||
|
func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ Manager, err error) {
|
||||||
|
return &structuredMergeManager{
|
||||||
|
typeConverter: typeConverter,
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
objectDefaulter: objectDefaulter,
|
||||||
|
groupVersion: gv,
|
||||||
|
hubVersion: hub,
|
||||||
|
updater: merge.Updater{
|
||||||
|
Converter: newCRDVersionConverter(typeConverter, objectConverter, hub),
|
||||||
|
IgnoredFields: resetFields,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectGVKNN(obj runtime.Object) string {
|
||||||
|
name := "<unknown>"
|
||||||
|
namespace := "<unknown>"
|
||||||
|
if accessor, err := meta.Accessor(obj); err == nil {
|
||||||
|
name = accessor.GetName()
|
||||||
|
namespace = accessor.GetNamespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v/%v; %v", namespace, name, obj.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements Manager.
|
||||||
|
func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||||
|
newObjVersioned, err := f.toVersioned(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version (%v): %v", objectGVKNN(newObj), f.groupVersion, err)
|
||||||
|
}
|
||||||
|
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err)
|
||||||
|
}
|
||||||
|
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new object (%v) to smd typed: %v", objectGVKNN(newObjVersioned), err)
|
||||||
|
}
|
||||||
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert live object (%v) to smd typed: %v", objectGVKNN(liveObjVersioned), err)
|
||||||
|
}
|
||||||
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
|
||||||
|
// TODO(apelisse) use the first return value when unions are implemented
|
||||||
|
_, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), manager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to update ManagedFields (%v): %v", objectGVKNN(newObjVersioned), err)
|
||||||
|
}
|
||||||
|
managed = internal.NewManaged(managedFields, managed.Times())
|
||||||
|
|
||||||
|
return newObj, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Manager.
|
||||||
|
func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||||
|
// Check that the patch object has the same version as the live object
|
||||||
|
if patchVersion := patchObj.GetObjectKind().GroupVersionKind().GroupVersion(); patchVersion != f.groupVersion {
|
||||||
|
return nil, nil,
|
||||||
|
errors.NewBadRequest(
|
||||||
|
fmt.Sprintf("Incorrect version specified in apply patch. "+
|
||||||
|
"Specified patch version: %s, expected: %s",
|
||||||
|
patchVersion, f.groupVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
patchObjMeta, err := meta.Accessor(patchObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||||
|
}
|
||||||
|
if patchObjMeta.GetManagedFields() != nil {
|
||||||
|
return nil, nil, errors.NewBadRequest("metadata.managedFields must be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchObjTyped, err := f.typeConverter.ObjectToTyped(patchObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create typed patch object (%v): %v", objectGVKNN(patchObj), err)
|
||||||
|
}
|
||||||
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create typed live object (%v): %v", objectGVKNN(liveObjVersioned), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields(), manager, force)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
managed = internal.NewManaged(managedFields, managed.Times())
|
||||||
|
|
||||||
|
if newObjTyped == nil {
|
||||||
|
return nil, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new typed object (%v) to object: %v", objectGVKNN(patchObj), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newObjVersioned, err := f.toVersioned(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version: %v", objectGVKNN(patchObj), err)
|
||||||
|
}
|
||||||
|
f.objectDefaulter.Default(newObjVersioned)
|
||||||
|
|
||||||
|
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to convert to unversioned (%v): %v", objectGVKNN(patchObj), err)
|
||||||
|
}
|
||||||
|
return newObjUnversioned, managed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *structuredMergeManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *structuredMergeManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
||||||
|
}
|
||||||
130
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go
generated
vendored
Normal file
130
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||||
|
"k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypeConverter allows you to convert from runtime.Object to
|
||||||
|
// typed.TypedValue and the other way around.
|
||||||
|
type TypeConverter interface {
|
||||||
|
ObjectToTyped(runtime.Object) (*typed.TypedValue, error)
|
||||||
|
TypedToObject(*typed.TypedValue) (runtime.Object, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
|
||||||
|
// schema. It does implement the same interface though (and create the
|
||||||
|
// same types of objects), so that everything can still work the same.
|
||||||
|
// CRDs are merged with all their fields being "atomic" (lists
|
||||||
|
// included).
|
||||||
|
//
|
||||||
|
// Note that this is not going to be sufficient for converting to/from
|
||||||
|
// CRDs that have a schema defined (we don't support that schema yet).
|
||||||
|
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
|
||||||
|
type DeducedTypeConverter struct{}
|
||||||
|
|
||||||
|
var _ TypeConverter = DeducedTypeConverter{}
|
||||||
|
|
||||||
|
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
|
||||||
|
func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
||||||
|
switch o := obj.(type) {
|
||||||
|
case *unstructured.Unstructured:
|
||||||
|
return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent())
|
||||||
|
default:
|
||||||
|
return typed.DeducedParseableType.FromStructured(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypedToObject transforms the typed value into a runtime.Object. That
|
||||||
|
// is not specific to deduced type.
|
||||||
|
func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
|
||||||
|
return valueToObject(value.AsValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeConverter struct {
|
||||||
|
parser *managedfields.GvkParser
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ TypeConverter = &typeConverter{}
|
||||||
|
|
||||||
|
// NewTypeConverter builds a TypeConverter from a proto.Models. This
|
||||||
|
// will automatically find the proper version of the object, and the
|
||||||
|
// corresponding schema information.
|
||||||
|
func NewTypeConverter(models proto.Models, preserveUnknownFields bool) (TypeConverter, error) {
|
||||||
|
parser, err := managedfields.NewGVKParser(models, preserveUnknownFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &typeConverter{parser: parser}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
||||||
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||||
|
t := c.parser.Type(gvk)
|
||||||
|
if t == nil {
|
||||||
|
return nil, newNoCorrespondingTypeError(gvk)
|
||||||
|
}
|
||||||
|
switch o := obj.(type) {
|
||||||
|
case *unstructured.Unstructured:
|
||||||
|
return t.FromUnstructured(o.UnstructuredContent())
|
||||||
|
default:
|
||||||
|
return t.FromStructured(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
|
||||||
|
return valueToObject(value.AsValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToObject(val value.Value) (runtime.Object, error) {
|
||||||
|
vu := val.Unstructured()
|
||||||
|
switch o := vu.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return &unstructured.Unstructured{Object: o}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noCorrespondingTypeErr struct {
|
||||||
|
gvk schema.GroupVersionKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNoCorrespondingTypeError(gvk schema.GroupVersionKind) error {
|
||||||
|
return &noCorrespondingTypeErr{gvk: gvk}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *noCorrespondingTypeErr) Error() string {
|
||||||
|
return fmt.Sprintf("no corresponding type for %v", k.gvk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNoCorrespondingTypeError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := err.(*noCorrespondingTypeErr)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/versionconverter.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/versionconverter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fieldmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// versionConverter is an implementation of
|
||||||
|
// sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||||
|
type versionConverter struct {
|
||||||
|
typeConverter TypeConverter
|
||||||
|
objectConvertor runtime.ObjectConvertor
|
||||||
|
hubGetter func(from schema.GroupVersion) schema.GroupVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ merge.Converter = &versionConverter{}
|
||||||
|
|
||||||
|
// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor.
|
||||||
|
func newVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||||
|
return &versionConverter{
|
||||||
|
typeConverter: t,
|
||||||
|
objectConvertor: o,
|
||||||
|
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||||
|
return schema.GroupVersion{
|
||||||
|
Group: from.Group,
|
||||||
|
Version: h.Version,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor.
|
||||||
|
func newCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||||
|
return &versionConverter{
|
||||||
|
typeConverter: t,
|
||||||
|
objectConvertor: o,
|
||||||
|
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||||
|
return h
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||||
|
func (v *versionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
|
||||||
|
// Convert the smd typed value to a kubernetes object.
|
||||||
|
objectToConvert, err := v.typeConverter.TypedToObject(object)
|
||||||
|
if err != nil {
|
||||||
|
return object, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the target groupVersion.
|
||||||
|
groupVersion, err := schema.ParseGroupVersion(string(version))
|
||||||
|
if err != nil {
|
||||||
|
return object, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If attempting to convert to the same version as we already have, just return it.
|
||||||
|
fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion()
|
||||||
|
if fromVersion == groupVersion {
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to internal
|
||||||
|
internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion))
|
||||||
|
if err != nil {
|
||||||
|
return object, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the object into the target version
|
||||||
|
convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion)
|
||||||
|
if err != nil {
|
||||||
|
return object, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the object back to a smd typed value and return it.
|
||||||
|
return v.typeConverter.ObjectToTyped(convertedObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMissingVersionError
|
||||||
|
func (v *versionConverter) IsMissingVersionError(err error) bool {
|
||||||
|
return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
reviewers:
|
||||||
|
- wojtek-t
|
||||||
|
approvers:
|
||||||
|
- logicalhan
|
||||||
|
|
@ -0,0 +1,798 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
restful "github.com/emicklei/go-restful/v3"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
utilsets "k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/apiserver/pkg/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||||
|
"k8s.io/apiserver/pkg/features"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
compbasemetrics "k8s.io/component-base/metrics"
|
||||||
|
"k8s.io/component-base/metrics/legacyregistry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resettableCollector is the interface implemented by prometheus.MetricVec
|
||||||
|
// that can be used by Prometheus to collect metrics and reset their values.
|
||||||
|
type resettableCollector interface {
|
||||||
|
compbasemetrics.Registerable
|
||||||
|
Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
APIServerComponent string = "apiserver"
|
||||||
|
OtherRequestMethod string = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default, all the following metrics are defined as falling under
|
||||||
|
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||||
|
*
|
||||||
|
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||||
|
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||||
|
* the metric stability policy.
|
||||||
|
*/
|
||||||
|
var (
|
||||||
|
deprecatedRequestGauge = compbasemetrics.NewGaugeVec(
|
||||||
|
&compbasemetrics.GaugeOpts{
|
||||||
|
Name: "apiserver_requested_deprecated_apis",
|
||||||
|
Help: "Gauge of deprecated APIs that have been requested, broken out by API group, version, resource, subresource, and removed_release.",
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"group", "version", "resource", "subresource", "removed_release"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(a-robinson): Add unit tests for the handling of these metrics once
|
||||||
|
// the upstream library supports it.
|
||||||
|
requestCounter = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_request_total",
|
||||||
|
Help: "Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code.",
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component", "code"},
|
||||||
|
)
|
||||||
|
longRunningRequestsGauge = compbasemetrics.NewGaugeVec(
|
||||||
|
&compbasemetrics.GaugeOpts{
|
||||||
|
Name: "apiserver_longrunning_requests",
|
||||||
|
Help: "Gauge of all active long-running apiserver requests broken out by verb, group, version, resource, scope and component. Not all requests are tracked this way.",
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||||
|
)
|
||||||
|
requestLatencies = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_request_duration_seconds",
|
||||||
|
Help: "Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope and component.",
|
||||||
|
// This metric is used for verifying api call latencies SLO,
|
||||||
|
// as well as tracking regressions in this aspects.
|
||||||
|
// Thus we customize buckets significantly, to empower both usecases.
|
||||||
|
Buckets: []float64{0.005, 0.025, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3,
|
||||||
|
4, 5, 6, 8, 10, 15, 20, 30, 45, 60},
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component"},
|
||||||
|
)
|
||||||
|
requestSloLatencies = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_request_slo_duration_seconds",
|
||||||
|
Help: "Response latency distribution (not counting webhook duration) in seconds for each verb, group, version, resource, subresource, scope and component.",
|
||||||
|
// This metric is supplementary to the requestLatencies metric.
|
||||||
|
// It measures request duration excluding webhooks as they are mostly
|
||||||
|
// dependant on user configuration.
|
||||||
|
Buckets: []float64{0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3,
|
||||||
|
4, 5, 6, 8, 10, 15, 20, 30, 45, 60},
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||||
|
)
|
||||||
|
fieldValidationRequestLatencies = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "field_validation_request_duration_seconds",
|
||||||
|
Help: "Response latency distribution in seconds for each field validation value and whether field validation is enabled or not",
|
||||||
|
// This metric is supplementary to the requestLatencies metric.
|
||||||
|
// It measures request durations for the various field validation
|
||||||
|
// values.
|
||||||
|
Buckets: []float64{0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3,
|
||||||
|
4, 5, 6, 8, 10, 15, 20, 30, 45, 60},
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"field_validation", "enabled"},
|
||||||
|
)
|
||||||
|
responseSizes = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_response_sizes",
|
||||||
|
Help: "Response size distribution in bytes for each group, version, verb, resource, subresource, scope and component.",
|
||||||
|
// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
|
||||||
|
Buckets: compbasemetrics.ExponentialBuckets(1000, 10.0, 7),
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||||
|
)
|
||||||
|
// TLSHandshakeErrors is a number of requests dropped with 'TLS handshake error from' error
|
||||||
|
TLSHandshakeErrors = compbasemetrics.NewCounter(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_tls_handshake_errors_total",
|
||||||
|
Help: "Number of requests dropped with 'TLS handshake error from' error",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
WatchEvents = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_watch_events_total",
|
||||||
|
Help: "Number of events sent in watch clients",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"group", "version", "kind"},
|
||||||
|
)
|
||||||
|
WatchEventsSizes = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_watch_events_sizes",
|
||||||
|
Help: "Watch event size distribution in bytes",
|
||||||
|
Buckets: compbasemetrics.ExponentialBuckets(1024, 2.0, 8), // 1K, 2K, 4K, 8K, ..., 128K.
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"group", "version", "kind"},
|
||||||
|
)
|
||||||
|
// Because of volatility of the base metric this is pre-aggregated one. Instead of reporting current usage all the time
|
||||||
|
// it reports maximal usage during the last second.
|
||||||
|
currentInflightRequests = compbasemetrics.NewGaugeVec(
|
||||||
|
&compbasemetrics.GaugeOpts{
|
||||||
|
Name: "apiserver_current_inflight_requests",
|
||||||
|
Help: "Maximal number of currently used inflight request limit of this apiserver per request kind in last second.",
|
||||||
|
StabilityLevel: compbasemetrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"request_kind"},
|
||||||
|
)
|
||||||
|
currentInqueueRequests = compbasemetrics.NewGaugeVec(
|
||||||
|
&compbasemetrics.GaugeOpts{
|
||||||
|
Name: "apiserver_current_inqueue_requests",
|
||||||
|
Help: "Maximal number of queued requests in this apiserver per request kind in last second.",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"request_kind"},
|
||||||
|
)
|
||||||
|
|
||||||
|
requestTerminationsTotal = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_request_terminations_total",
|
||||||
|
Help: "Number of requests which apiserver terminated in self-defense.",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component", "code"},
|
||||||
|
)
|
||||||
|
|
||||||
|
apiSelfRequestCounter = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_selfrequest_total",
|
||||||
|
Help: "Counter of apiserver self-requests broken out for each verb, API resource and subresource.",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"verb", "resource", "subresource"},
|
||||||
|
)
|
||||||
|
|
||||||
|
requestFilterDuration = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_request_filter_duration_seconds",
|
||||||
|
Help: "Request filter latency distribution in seconds, for each filter type",
|
||||||
|
Buckets: []float64{0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0, 5.0},
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"filter"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestAbortsTotal is a number of aborted requests with http.ErrAbortHandler
|
||||||
|
requestAbortsTotal = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_request_aborts_total",
|
||||||
|
Help: "Number of requests which apiserver aborted possibly due to a timeout, for each group, version, verb, resource, subresource and scope",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"verb", "group", "version", "resource", "subresource", "scope"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestPostTimeoutTotal tracks the activity of the executing request handler after the associated request
|
||||||
|
// has been timed out by the apiserver.
|
||||||
|
// source: the name of the handler that is recording this metric. Currently, we have two:
|
||||||
|
// - timeout-handler: the "executing" handler returns after the timeout filter times out the request.
|
||||||
|
// - rest-handler: the "executing" handler returns after the rest layer times out the request.
|
||||||
|
// status: whether the handler panicked or threw an error, possible values:
|
||||||
|
// - 'panic': the handler panicked
|
||||||
|
// - 'error': the handler return an error
|
||||||
|
// - 'ok': the handler returned a result (no error and no panic)
|
||||||
|
// - 'pending': the handler is still running in the background and it did not return
|
||||||
|
// within the wait threshold.
|
||||||
|
requestPostTimeoutTotal = compbasemetrics.NewCounterVec(
|
||||||
|
&compbasemetrics.CounterOpts{
|
||||||
|
Name: "apiserver_request_post_timeout_total",
|
||||||
|
Help: "Tracks the activity of the request handlers after the associated requests have been timed out by the apiserver",
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"source", "status"},
|
||||||
|
)
|
||||||
|
|
||||||
|
requestTimestampComparisonDuration = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "apiserver_request_timestamp_comparison_time",
|
||||||
|
Help: "Time taken for comparison of old vs new objects in UPDATE or PATCH requests",
|
||||||
|
Buckets: []float64{0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0, 5.0},
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
// Path the code takes to reach a conclusion:
|
||||||
|
// i.e. unequalObjectsFast, unequalObjectsSlow, equalObjectsSlow
|
||||||
|
[]string{"code_path"},
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics = []resettableCollector{
|
||||||
|
deprecatedRequestGauge,
|
||||||
|
requestCounter,
|
||||||
|
longRunningRequestsGauge,
|
||||||
|
requestLatencies,
|
||||||
|
requestSloLatencies,
|
||||||
|
fieldValidationRequestLatencies,
|
||||||
|
responseSizes,
|
||||||
|
TLSHandshakeErrors,
|
||||||
|
WatchEvents,
|
||||||
|
WatchEventsSizes,
|
||||||
|
currentInflightRequests,
|
||||||
|
currentInqueueRequests,
|
||||||
|
requestTerminationsTotal,
|
||||||
|
apiSelfRequestCounter,
|
||||||
|
requestFilterDuration,
|
||||||
|
requestAbortsTotal,
|
||||||
|
requestPostTimeoutTotal,
|
||||||
|
requestTimestampComparisonDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the valid request methods which we report in our metrics. Any other request methods
|
||||||
|
// will be aggregated under 'unknown'
|
||||||
|
validRequestMethods = utilsets.NewString(
|
||||||
|
"APPLY",
|
||||||
|
"CONNECT",
|
||||||
|
"CREATE",
|
||||||
|
"DELETE",
|
||||||
|
"DELETECOLLECTION",
|
||||||
|
"GET",
|
||||||
|
"LIST",
|
||||||
|
"PATCH",
|
||||||
|
"POST",
|
||||||
|
"PROXY",
|
||||||
|
"PUT",
|
||||||
|
"UPDATE",
|
||||||
|
"WATCH",
|
||||||
|
"WATCHLIST")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ReadOnlyKind is a string identifying read only request kind
|
||||||
|
ReadOnlyKind = "readOnly"
|
||||||
|
// MutatingKind is a string identifying mutating request kind
|
||||||
|
MutatingKind = "mutating"
|
||||||
|
|
||||||
|
// WaitingPhase is the phase value for a request waiting in a queue
|
||||||
|
WaitingPhase = "waiting"
|
||||||
|
// ExecutingPhase is the phase value for an executing request
|
||||||
|
ExecutingPhase = "executing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// deprecatedAnnotationKey is a key for an audit annotation set to
|
||||||
|
// "true" on requests made to deprecated API versions
|
||||||
|
deprecatedAnnotationKey = "k8s.io/deprecated"
|
||||||
|
// removedReleaseAnnotationKey is a key for an audit annotation set to
|
||||||
|
// the target removal release, in "<major>.<minor>" format,
|
||||||
|
// on requests made to deprecated API versions with a target removal release
|
||||||
|
removedReleaseAnnotationKey = "k8s.io/removed-release"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The source that is recording the apiserver_request_post_timeout_total metric.
|
||||||
|
// The "executing" request handler returns after the timeout filter times out the request.
|
||||||
|
PostTimeoutSourceTimeoutHandler = "timeout-handler"
|
||||||
|
|
||||||
|
// The source that is recording the apiserver_request_post_timeout_total metric.
|
||||||
|
// The "executing" request handler returns after the rest layer times out the request.
|
||||||
|
PostTimeoutSourceRestHandler = "rest-handler"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The executing request handler panicked after the request had
|
||||||
|
// been timed out by the apiserver.
|
||||||
|
PostTimeoutHandlerPanic = "panic"
|
||||||
|
|
||||||
|
// The executing request handler has returned an error to the post-timeout
|
||||||
|
// receiver after the request had been timed out by the apiserver.
|
||||||
|
PostTimeoutHandlerError = "error"
|
||||||
|
|
||||||
|
// The executing request handler has returned a result to the post-timeout
|
||||||
|
// receiver after the request had been timed out by the apiserver.
|
||||||
|
PostTimeoutHandlerOK = "ok"
|
||||||
|
|
||||||
|
// The executing request handler has not panicked or returned any error/result to
|
||||||
|
// the post-timeout receiver yet after the request had been timed out by the apiserver.
|
||||||
|
// The post-timeout receiver gives up after waiting for certain threshold and if the
|
||||||
|
// executing request handler has not returned yet we use the following label.
|
||||||
|
PostTimeoutHandlerPending = "pending"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registerMetrics sync.Once
|
||||||
|
|
||||||
|
// Register all metrics.
|
||||||
|
func Register() {
|
||||||
|
registerMetrics.Do(func() {
|
||||||
|
for _, metric := range metrics {
|
||||||
|
legacyregistry.MustRegister(metric)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all metrics.
|
||||||
|
func Reset() {
|
||||||
|
for _, metric := range metrics {
|
||||||
|
metric.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInflightRequestMetrics reports concurrency metrics classified by
|
||||||
|
// mutating vs Readonly.
|
||||||
|
func UpdateInflightRequestMetrics(phase string, nonmutating, mutating int) {
|
||||||
|
for _, kc := range []struct {
|
||||||
|
kind string
|
||||||
|
count int
|
||||||
|
}{{ReadOnlyKind, nonmutating}, {MutatingKind, mutating}} {
|
||||||
|
if phase == ExecutingPhase {
|
||||||
|
currentInflightRequests.WithLabelValues(kc.kind).Set(float64(kc.count))
|
||||||
|
} else {
|
||||||
|
currentInqueueRequests.WithLabelValues(kc.kind).Set(float64(kc.count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordFilterLatency(ctx context.Context, name string, elapsed time.Duration) {
|
||||||
|
requestFilterDuration.WithContext(ctx).WithLabelValues(name).Observe(elapsed.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordTimestampComparisonLatency(codePath string, elapsed time.Duration) {
|
||||||
|
requestTimestampComparisonDuration.WithLabelValues(codePath).Observe(elapsed.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordRequestPostTimeout(source string, status string) {
|
||||||
|
requestPostTimeoutTotal.WithLabelValues(source, status).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordRequestAbort records that the request was aborted possibly due to a timeout.
|
||||||
|
func RecordRequestAbort(req *http.Request, requestInfo *request.RequestInfo) {
|
||||||
|
if requestInfo == nil {
|
||||||
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := CleanScope(requestInfo)
|
||||||
|
reportedVerb := cleanVerb(CanonicalVerb(strings.ToUpper(req.Method), scope), getVerbIfWatch(req), req)
|
||||||
|
resource := requestInfo.Resource
|
||||||
|
subresource := requestInfo.Subresource
|
||||||
|
group := requestInfo.APIGroup
|
||||||
|
version := requestInfo.APIVersion
|
||||||
|
|
||||||
|
requestAbortsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordDroppedRequest records that the request was rejected via http.TooManyRequests.
|
||||||
|
func RecordDroppedRequest(req *http.Request, requestInfo *request.RequestInfo, component string, isMutatingRequest bool) {
|
||||||
|
if requestInfo == nil {
|
||||||
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
|
}
|
||||||
|
scope := CleanScope(requestInfo)
|
||||||
|
dryRun := cleanDryRun(req.URL)
|
||||||
|
|
||||||
|
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||||
|
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||||
|
// list of verbs (different than those translated to RequestInfo).
|
||||||
|
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||||
|
reportedVerb := cleanVerb(CanonicalVerb(strings.ToUpper(req.Method), scope), getVerbIfWatch(req), req)
|
||||||
|
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
requestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component, codeToString(http.StatusTooManyRequests)).Inc()
|
||||||
|
} else {
|
||||||
|
requestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, "", "", "", requestInfo.Subresource, scope, component, codeToString(http.StatusTooManyRequests)).Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordRequestTermination records that the request was terminated early as part of a resource
|
||||||
|
// preservation or apiserver self-defense mechanism (e.g. timeouts, maxinflight throttling,
|
||||||
|
// proxyHandler errors). RecordRequestTermination should only be called zero or one times
|
||||||
|
// per request.
|
||||||
|
func RecordRequestTermination(req *http.Request, requestInfo *request.RequestInfo, component string, code int) {
|
||||||
|
if requestInfo == nil {
|
||||||
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
|
}
|
||||||
|
scope := CleanScope(requestInfo)
|
||||||
|
|
||||||
|
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||||
|
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||||
|
// list of verbs (different than those translated to RequestInfo).
|
||||||
|
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||||
|
reportedVerb := cleanVerb(CanonicalVerb(strings.ToUpper(req.Method), scope), getVerbIfWatch(req), req)
|
||||||
|
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
requestTerminationsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component, codeToString(code)).Inc()
|
||||||
|
} else {
|
||||||
|
requestTerminationsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component, codeToString(code)).Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordLongRunning tracks the execution of a long running request against the API server. It provides an accurate count
|
||||||
|
// of the total number of open long running requests. requestInfo may be nil if the caller is not in the normal request flow.
|
||||||
|
func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, component string, fn func()) {
|
||||||
|
if requestInfo == nil {
|
||||||
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
|
}
|
||||||
|
var g compbasemetrics.GaugeMetric
|
||||||
|
scope := CleanScope(requestInfo)
|
||||||
|
|
||||||
|
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||||
|
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||||
|
// list of verbs (different than those translated to RequestInfo).
|
||||||
|
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||||
|
reportedVerb := cleanVerb(CanonicalVerb(strings.ToUpper(req.Method), scope), getVerbIfWatch(req), req)
|
||||||
|
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
g = longRunningRequestsGauge.WithContext(req.Context()).WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component)
|
||||||
|
} else {
|
||||||
|
g = longRunningRequestsGauge.WithContext(req.Context()).WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component)
|
||||||
|
}
|
||||||
|
g.Inc()
|
||||||
|
defer g.Dec()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
|
||||||
|
// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
|
||||||
|
func MonitorRequest(req *http.Request, verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, httpCode, respSize int, elapsed time.Duration) {
|
||||||
|
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||||
|
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||||
|
// list of verbs (different than those translated to RequestInfo).
|
||||||
|
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||||
|
reportedVerb := cleanVerb(CanonicalVerb(strings.ToUpper(req.Method), scope), verb, req)
|
||||||
|
|
||||||
|
dryRun := cleanDryRun(req.URL)
|
||||||
|
elapsedSeconds := elapsed.Seconds()
|
||||||
|
requestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component, codeToString(httpCode)).Inc()
|
||||||
|
// MonitorRequest happens after authentication, so we can trust the username given by the request
|
||||||
|
info, ok := request.UserFrom(req.Context())
|
||||||
|
if ok && info.GetName() == user.APIServerUser {
|
||||||
|
apiSelfRequestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, resource, subresource).Inc()
|
||||||
|
}
|
||||||
|
if deprecated {
|
||||||
|
deprecatedRequestGauge.WithContext(req.Context()).WithLabelValues(group, version, resource, subresource, removedRelease).Set(1)
|
||||||
|
audit.AddAuditAnnotation(req.Context(), deprecatedAnnotationKey, "true")
|
||||||
|
if len(removedRelease) > 0 {
|
||||||
|
audit.AddAuditAnnotation(req.Context(), removedReleaseAnnotationKey, removedRelease)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
||||||
|
fieldValidation := cleanFieldValidation(req.URL)
|
||||||
|
fieldValidationEnabled := strconv.FormatBool(utilfeature.DefaultFeatureGate.Enabled(features.ServerSideFieldValidation))
|
||||||
|
fieldValidationRequestLatencies.WithContext(req.Context()).WithLabelValues(fieldValidation, fieldValidationEnabled)
|
||||||
|
|
||||||
|
if wd, ok := request.LatencyTrackersFrom(req.Context()); ok {
|
||||||
|
sloLatency := elapsedSeconds - (wd.MutatingWebhookTracker.GetLatency() + wd.ValidatingWebhookTracker.GetLatency()).Seconds()
|
||||||
|
requestSloLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(sloLatency)
|
||||||
|
}
|
||||||
|
// We are only interested in response sizes of read requests.
|
||||||
|
if verb == "GET" || verb == "LIST" {
|
||||||
|
responseSizes.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(float64(respSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
|
||||||
|
// the go-restful RouteFunction instead of a HandlerFunc plus some Kubernetes endpoint specific information.
|
||||||
|
func InstrumentRouteFunc(verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
||||||
|
return restful.RouteFunction(func(req *restful.Request, response *restful.Response) {
|
||||||
|
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(req.Request.Context())
|
||||||
|
if !ok {
|
||||||
|
requestReceivedTimestamp = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate := &ResponseWriterDelegator{ResponseWriter: response.ResponseWriter}
|
||||||
|
|
||||||
|
rw := responsewriter.WrapForHTTP1Or2(delegate)
|
||||||
|
response.ResponseWriter = rw
|
||||||
|
|
||||||
|
routeFunc(req, response)
|
||||||
|
|
||||||
|
MonitorRequest(req.Request, verb, group, version, resource, subresource, scope, component, deprecated, removedRelease, delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerFunc works like Prometheus' InstrumentHandlerFunc but adds some Kubernetes endpoint specific information.
|
||||||
|
func InstrumentHandlerFunc(verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(req.Context())
|
||||||
|
if !ok {
|
||||||
|
requestReceivedTimestamp = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate := &ResponseWriterDelegator{ResponseWriter: w}
|
||||||
|
w = responsewriter.WrapForHTTP1Or2(delegate)
|
||||||
|
|
||||||
|
handler(w, req)
|
||||||
|
|
||||||
|
MonitorRequest(req, verb, group, version, resource, subresource, scope, component, deprecated, removedRelease, delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanScope returns the scope of the request.
|
||||||
|
func CleanScope(requestInfo *request.RequestInfo) string {
|
||||||
|
if requestInfo.Name != "" || requestInfo.Verb == "create" {
|
||||||
|
return "resource"
|
||||||
|
}
|
||||||
|
if requestInfo.Namespace != "" {
|
||||||
|
return "namespace"
|
||||||
|
}
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
return "cluster"
|
||||||
|
}
|
||||||
|
// this is the empty scope
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalVerb distinguishes LISTs from GETs (and HEADs). It assumes verb is
|
||||||
|
// UPPERCASE.
|
||||||
|
func CanonicalVerb(verb string, scope string) string {
|
||||||
|
switch verb {
|
||||||
|
case "GET", "HEAD":
|
||||||
|
if scope != "resource" && scope != "" {
|
||||||
|
return "LIST"
|
||||||
|
}
|
||||||
|
return "GET"
|
||||||
|
default:
|
||||||
|
return verb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanVerb returns a normalized verb, so that it is easy to tell WATCH from
|
||||||
|
// LIST and APPLY from PATCH.
|
||||||
|
func CleanVerb(verb string, request *http.Request) string {
|
||||||
|
reportedVerb := verb
|
||||||
|
if suggestedVerb := getVerbIfWatch(request); suggestedVerb == "WATCH" {
|
||||||
|
reportedVerb = "WATCH"
|
||||||
|
}
|
||||||
|
// normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
|
||||||
|
if verb == "WATCHLIST" {
|
||||||
|
reportedVerb = "WATCH"
|
||||||
|
}
|
||||||
|
if verb == "PATCH" && request.Header.Get("Content-Type") == string(types.ApplyPatchType) && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||||
|
reportedVerb = "APPLY"
|
||||||
|
}
|
||||||
|
return reportedVerb
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanVerb additionally ensures that unknown verbs don't clog up the metrics.
|
||||||
|
func cleanVerb(verb, suggestedVerb string, request *http.Request) string {
|
||||||
|
// CanonicalVerb (being an input for this function) doesn't handle correctly the
|
||||||
|
// deprecated path pattern for watch of:
|
||||||
|
// GET /api/{version}/watch/{resource}
|
||||||
|
// We correct it manually based on the pass verb from the installer.
|
||||||
|
var reportedVerb string
|
||||||
|
if suggestedVerb == "WATCH" || suggestedVerb == "WATCHLIST" {
|
||||||
|
reportedVerb = "WATCH"
|
||||||
|
} else {
|
||||||
|
reportedVerb = CleanVerb(verb, request)
|
||||||
|
}
|
||||||
|
if validRequestMethods.Has(reportedVerb) {
|
||||||
|
return reportedVerb
|
||||||
|
}
|
||||||
|
return OtherRequestMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVerbIfWatch additionally ensures that GET or List would be transformed to WATCH
|
||||||
|
func getVerbIfWatch(req *http.Request) string {
|
||||||
|
if strings.ToUpper(req.Method) == "GET" || strings.ToUpper(req.Method) == "LIST" {
|
||||||
|
// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
|
||||||
|
if values := req.URL.Query()["watch"]; len(values) > 0 {
|
||||||
|
if value := strings.ToLower(values[0]); value != "0" && value != "false" {
|
||||||
|
return "WATCH"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanDryRun(u *url.URL) string {
|
||||||
|
// avoid allocating when we don't see dryRun in the query
|
||||||
|
if !strings.Contains(u.RawQuery, "dryRun") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
dryRun := u.Query()["dryRun"]
|
||||||
|
if errs := validation.ValidateDryRun(nil, dryRun); len(errs) > 0 {
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
// Since dryRun could be valid with any arbitrarily long length
|
||||||
|
// we have to dedup and sort the elements before joining them together
|
||||||
|
// TODO: this is a fairly large allocation for what it does, consider
|
||||||
|
// a sort and dedup in a single pass
|
||||||
|
return strings.Join(utilsets.NewString(dryRun...).List(), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanFieldValidation(u *url.URL) string {
|
||||||
|
// avoid allocating when we don't see dryRun in the query
|
||||||
|
if !strings.Contains(u.RawQuery, "fieldValidation") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
fieldValidation := u.Query()["fieldValidation"]
|
||||||
|
if len(fieldValidation) != 1 {
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
if errs := validation.ValidateFieldValidation(nil, fieldValidation[0]); len(errs) > 0 {
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
return fieldValidation[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = (*ResponseWriterDelegator)(nil)
|
||||||
|
var _ responsewriter.UserProvidedDecorator = (*ResponseWriterDelegator)(nil)
|
||||||
|
|
||||||
|
// ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
|
||||||
|
type ResponseWriterDelegator struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
|
||||||
|
status int
|
||||||
|
written int64
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseWriterDelegator) Unwrap() http.ResponseWriter {
|
||||||
|
return r.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseWriterDelegator) WriteHeader(code int) {
|
||||||
|
r.status = code
|
||||||
|
r.wroteHeader = true
|
||||||
|
r.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseWriterDelegator) Write(b []byte) (int, error) {
|
||||||
|
if !r.wroteHeader {
|
||||||
|
r.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
n, err := r.ResponseWriter.Write(b)
|
||||||
|
r.written += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseWriterDelegator) Status() int {
|
||||||
|
return r.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseWriterDelegator) ContentLength() int {
|
||||||
|
return int(r.written)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small optimization over Itoa
|
||||||
|
func codeToString(s int) string {
|
||||||
|
switch s {
|
||||||
|
case 100:
|
||||||
|
return "100"
|
||||||
|
case 101:
|
||||||
|
return "101"
|
||||||
|
|
||||||
|
case 200:
|
||||||
|
return "200"
|
||||||
|
case 201:
|
||||||
|
return "201"
|
||||||
|
case 202:
|
||||||
|
return "202"
|
||||||
|
case 203:
|
||||||
|
return "203"
|
||||||
|
case 204:
|
||||||
|
return "204"
|
||||||
|
case 205:
|
||||||
|
return "205"
|
||||||
|
case 206:
|
||||||
|
return "206"
|
||||||
|
|
||||||
|
case 300:
|
||||||
|
return "300"
|
||||||
|
case 301:
|
||||||
|
return "301"
|
||||||
|
case 302:
|
||||||
|
return "302"
|
||||||
|
case 304:
|
||||||
|
return "304"
|
||||||
|
case 305:
|
||||||
|
return "305"
|
||||||
|
case 307:
|
||||||
|
return "307"
|
||||||
|
|
||||||
|
case 400:
|
||||||
|
return "400"
|
||||||
|
case 401:
|
||||||
|
return "401"
|
||||||
|
case 402:
|
||||||
|
return "402"
|
||||||
|
case 403:
|
||||||
|
return "403"
|
||||||
|
case 404:
|
||||||
|
return "404"
|
||||||
|
case 405:
|
||||||
|
return "405"
|
||||||
|
case 406:
|
||||||
|
return "406"
|
||||||
|
case 407:
|
||||||
|
return "407"
|
||||||
|
case 408:
|
||||||
|
return "408"
|
||||||
|
case 409:
|
||||||
|
return "409"
|
||||||
|
case 410:
|
||||||
|
return "410"
|
||||||
|
case 411:
|
||||||
|
return "411"
|
||||||
|
case 412:
|
||||||
|
return "412"
|
||||||
|
case 413:
|
||||||
|
return "413"
|
||||||
|
case 414:
|
||||||
|
return "414"
|
||||||
|
case 415:
|
||||||
|
return "415"
|
||||||
|
case 416:
|
||||||
|
return "416"
|
||||||
|
case 417:
|
||||||
|
return "417"
|
||||||
|
case 418:
|
||||||
|
return "418"
|
||||||
|
|
||||||
|
case 500:
|
||||||
|
return "500"
|
||||||
|
case 501:
|
||||||
|
return "501"
|
||||||
|
case 502:
|
||||||
|
return "502"
|
||||||
|
case 503:
|
||||||
|
return "503"
|
||||||
|
case 504:
|
||||||
|
return "504"
|
||||||
|
case 505:
|
||||||
|
return "505"
|
||||||
|
|
||||||
|
case 428:
|
||||||
|
return "428"
|
||||||
|
case 429:
|
||||||
|
return "429"
|
||||||
|
case 431:
|
||||||
|
return "431"
|
||||||
|
case 511:
|
||||||
|
return "511"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return strconv.Itoa(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
reviewers:
|
||||||
|
- sttts
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type auditIDKeyType int
|
||||||
|
|
||||||
|
// auditIDKey is the key to associate the Audit-ID value of a request.
|
||||||
|
const auditIDKey auditIDKeyType = iota
|
||||||
|
|
||||||
|
// WithAuditID returns a copy of the parent context into which the Audit-ID
|
||||||
|
// associated with the request is set.
|
||||||
|
//
|
||||||
|
// If the specified auditID is empty, no value is set and the parent context is returned as is.
|
||||||
|
func WithAuditID(parent context.Context, auditID types.UID) context.Context {
|
||||||
|
if auditID == "" {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
return WithValue(parent, auditIDKey, auditID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditIDFrom returns the value of the audit ID from the request context.
|
||||||
|
func AuditIDFrom(ctx context.Context) (types.UID, bool) {
|
||||||
|
auditID, ok := ctx.Value(auditIDKey).(types.UID)
|
||||||
|
return auditID, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuditIDTruncated returns the audit ID (truncated) from the request context.
|
||||||
|
// If the length of the Audit-ID value exceeds the limit, we truncate it to keep
|
||||||
|
// the first N (maxAuditIDLength) characters.
|
||||||
|
// This is intended to be used in logging only.
|
||||||
|
func GetAuditIDTruncated(ctx context.Context) string {
|
||||||
|
auditID, ok := AuditIDFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user has specified a very long audit ID then we will use the first N characters
|
||||||
|
// Note: assuming Audit-ID header is in ASCII
|
||||||
|
const maxAuditIDLength = 64
|
||||||
|
if len(auditID) > maxAuditIDLength {
|
||||||
|
auditID = auditID[0:maxAuditIDLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(auditID)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The key type is unexported to prevent collisions
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// namespaceKey is the context key for the request namespace.
|
||||||
|
namespaceKey key = iota
|
||||||
|
|
||||||
|
// userKey is the context key for the request user.
|
||||||
|
userKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewContext instantiates a base context object for request flows.
|
||||||
|
func NewContext() context.Context {
|
||||||
|
return context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultContext instantiates a base context object for request flows in the default namespace
|
||||||
|
func NewDefaultContext() context.Context {
|
||||||
|
return WithNamespace(NewContext(), metav1.NamespaceDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is val.
|
||||||
|
func WithValue(parent context.Context, key interface{}, val interface{}) context.Context {
|
||||||
|
return context.WithValue(parent, key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamespace returns a copy of parent in which the namespace value is set
|
||||||
|
func WithNamespace(parent context.Context, namespace string) context.Context {
|
||||||
|
return WithValue(parent, namespaceKey, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceFrom returns the value of the namespace key on the ctx
|
||||||
|
func NamespaceFrom(ctx context.Context) (string, bool) {
|
||||||
|
namespace, ok := ctx.Value(namespaceKey).(string)
|
||||||
|
return namespace, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
|
||||||
|
func NamespaceValue(ctx context.Context) string {
|
||||||
|
namespace, _ := NamespaceFrom(ctx)
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUser returns a copy of parent in which the user value is set
|
||||||
|
func WithUser(parent context.Context, user user.Info) context.Context {
|
||||||
|
return WithValue(parent, userKey, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserFrom returns the value of the user key on the ctx
|
||||||
|
func UserFrom(ctx context.Context) (user.Info, bool) {
|
||||||
|
user, ok := ctx.Value(userKey).(user.Info)
|
||||||
|
return user, ok
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package request contains everything around extracting info from
|
||||||
|
// a http request object.
|
||||||
|
// TODO: this package is temporary. Handlers must move into pkg/apiserver/handlers to avoid dependency cycle
|
||||||
|
package request // import "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestReceivedTimestampKeyType int
|
||||||
|
|
||||||
|
// requestReceivedTimestampKey is the ReceivedTimestamp (the time the request reached the apiserver)
|
||||||
|
// key for the context.
|
||||||
|
const requestReceivedTimestampKey requestReceivedTimestampKeyType = iota
|
||||||
|
|
||||||
|
// WithReceivedTimestamp returns a copy of parent context in which the ReceivedTimestamp
|
||||||
|
// (the time the request reached the apiserver) is set.
|
||||||
|
//
|
||||||
|
// If the specified ReceivedTimestamp is zero, no value is set and the parent context is returned as is.
|
||||||
|
func WithReceivedTimestamp(parent context.Context, receivedTimestamp time.Time) context.Context {
|
||||||
|
if receivedTimestamp.IsZero() {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
return WithValue(parent, requestReceivedTimestampKey, receivedTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceivedTimestampFrom returns the value of the ReceivedTimestamp key from the specified context.
|
||||||
|
func ReceivedTimestampFrom(ctx context.Context) (time.Time, bool) {
|
||||||
|
info, ok := ctx.Value(requestReceivedTimestampKey).(time.Time)
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||||
|
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
|
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
||||||
|
type LongRunningRequestCheck func(r *http.Request, requestInfo *RequestInfo) bool
|
||||||
|
|
||||||
|
type RequestInfoResolver interface {
|
||||||
|
NewRequestInfo(req *http.Request) (*RequestInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInfo holds information parsed from the http.Request
|
||||||
|
type RequestInfo struct {
|
||||||
|
// IsResourceRequest indicates whether or not the request is for an API resource or subresource
|
||||||
|
IsResourceRequest bool
|
||||||
|
// Path is the URL path of the request
|
||||||
|
Path string
|
||||||
|
// Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
|
||||||
|
// for non-resource requests, this is the lowercase http verb
|
||||||
|
Verb string
|
||||||
|
|
||||||
|
APIPrefix string
|
||||||
|
APIGroup string
|
||||||
|
APIVersion string
|
||||||
|
Namespace string
|
||||||
|
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
||||||
|
Resource string
|
||||||
|
// Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||||
|
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||||
|
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||||
|
Subresource string
|
||||||
|
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
||||||
|
Name string
|
||||||
|
// Parts are the path parts for the request, always starting with /{resource}/{name}
|
||||||
|
Parts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||||
|
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||||
|
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
|
||||||
|
// master's Mux.
|
||||||
|
var specialVerbs = sets.NewString("proxy", "watch")
|
||||||
|
|
||||||
|
// specialVerbsNoSubresources contains root verbs which do not allow subresources
|
||||||
|
var specialVerbsNoSubresources = sets.NewString("proxy")
|
||||||
|
|
||||||
|
// namespaceSubresources contains subresources of namespace
|
||||||
|
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||||
|
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||||
|
|
||||||
|
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/controlplane/master_test.go, so we never drift
|
||||||
|
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||||
|
|
||||||
|
type RequestInfoFactory struct {
|
||||||
|
APIPrefixes sets.String // without leading and trailing slashes
|
||||||
|
GrouplessAPIPrefixes sets.String // without leading and trailing slashes
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
|
||||||
|
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
||||||
|
// It handles both resource and non-resource requests and fills in all the pertinent information for each.
|
||||||
|
// Valid Inputs:
|
||||||
|
// Resource paths
|
||||||
|
// /apis/{api-group}/{version}/namespaces
|
||||||
|
// /api/{version}/namespaces
|
||||||
|
// /api/{version}/namespaces/{namespace}
|
||||||
|
// /api/{version}/namespaces/{namespace}/{resource}
|
||||||
|
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
|
// /api/{version}/{resource}
|
||||||
|
// /api/{version}/{resource}/{resourceName}
|
||||||
|
//
|
||||||
|
// Special verbs without subresources:
|
||||||
|
// /api/{version}/proxy/{resource}/{resourceName}
|
||||||
|
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
|
//
|
||||||
|
// Special verbs with subresources:
|
||||||
|
// /api/{version}/watch/{resource}
|
||||||
|
// /api/{version}/watch/namespaces/{namespace}/{resource}
|
||||||
|
//
|
||||||
|
// NonResource paths
|
||||||
|
// /apis/{api-group}/{version}
|
||||||
|
// /apis/{api-group}
|
||||||
|
// /apis
|
||||||
|
// /api/{version}
|
||||||
|
// /api
|
||||||
|
// /healthz
|
||||||
|
// /
|
||||||
|
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
|
||||||
|
// start with a non-resource request until proven otherwise
|
||||||
|
requestInfo := RequestInfo{
|
||||||
|
IsResourceRequest: false,
|
||||||
|
Path: req.URL.Path,
|
||||||
|
Verb: strings.ToLower(req.Method),
|
||||||
|
}
|
||||||
|
|
||||||
|
currentParts := splitPath(req.URL.Path)
|
||||||
|
if len(currentParts) < 3 {
|
||||||
|
// return a non-resource request
|
||||||
|
return &requestInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||||
|
// return a non-resource request
|
||||||
|
return &requestInfo, nil
|
||||||
|
}
|
||||||
|
requestInfo.APIPrefix = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
|
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||||
|
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||||
|
if len(currentParts) < 3 {
|
||||||
|
// return a non-resource request
|
||||||
|
return &requestInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInfo.APIGroup = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInfo.IsResourceRequest = true
|
||||||
|
requestInfo.APIVersion = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
|
// handle input of form /{specialVerb}/*
|
||||||
|
if specialVerbs.Has(currentParts[0]) {
|
||||||
|
if len(currentParts) < 2 {
|
||||||
|
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInfo.Verb = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
|
} else {
|
||||||
|
switch req.Method {
|
||||||
|
case "POST":
|
||||||
|
requestInfo.Verb = "create"
|
||||||
|
case "GET", "HEAD":
|
||||||
|
requestInfo.Verb = "get"
|
||||||
|
case "PUT":
|
||||||
|
requestInfo.Verb = "update"
|
||||||
|
case "PATCH":
|
||||||
|
requestInfo.Verb = "patch"
|
||||||
|
case "DELETE":
|
||||||
|
requestInfo.Verb = "delete"
|
||||||
|
default:
|
||||||
|
requestInfo.Verb = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||||
|
if currentParts[0] == "namespaces" {
|
||||||
|
if len(currentParts) > 1 {
|
||||||
|
requestInfo.Namespace = currentParts[1]
|
||||||
|
|
||||||
|
// if there is another step after the namespace name and it is not a known namespace subresource
|
||||||
|
// move currentParts to include it as a resource in its own right
|
||||||
|
if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
|
||||||
|
currentParts = currentParts[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestInfo.Namespace = metav1.NamespaceNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsing successful, so we now know the proper value for .Parts
|
||||||
|
requestInfo.Parts = currentParts
|
||||||
|
|
||||||
|
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
||||||
|
switch {
|
||||||
|
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
|
||||||
|
requestInfo.Subresource = requestInfo.Parts[2]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) >= 2:
|
||||||
|
requestInfo.Name = requestInfo.Parts[1]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) >= 1:
|
||||||
|
requestInfo.Resource = requestInfo.Parts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
|
||||||
|
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
|
||||||
|
opts := metainternalversion.ListOptions{}
|
||||||
|
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
|
||||||
|
// An error in parsing request will result in default to "list" and not setting "name" field.
|
||||||
|
klog.ErrorS(err, "Couldn't parse request", "Request", req.URL.Query())
|
||||||
|
// Reset opts to not rely on partial results from parsing.
|
||||||
|
// However, if watch is set, let's report it.
|
||||||
|
opts = metainternalversion.ListOptions{}
|
||||||
|
if values := req.URL.Query()["watch"]; len(values) > 0 {
|
||||||
|
switch strings.ToLower(values[0]) {
|
||||||
|
case "false", "0":
|
||||||
|
default:
|
||||||
|
opts.Watch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Watch {
|
||||||
|
requestInfo.Verb = "watch"
|
||||||
|
} else {
|
||||||
|
requestInfo.Verb = "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.FieldSelector != nil {
|
||||||
|
if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
|
||||||
|
if len(path.IsValidPathSegmentName(name)) == 0 {
|
||||||
|
requestInfo.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
|
||||||
|
if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
|
||||||
|
requestInfo.Verb = "deletecollection"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requestInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestInfoKeyType int
|
||||||
|
|
||||||
|
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
|
||||||
|
// keys are interfaces and interfaces are equal when the type and the value is equal, this
|
||||||
|
// does not conflict with the keys defined in pkg/api.
|
||||||
|
const requestInfoKey requestInfoKeyType = iota
|
||||||
|
|
||||||
|
// WithRequestInfo returns a copy of parent in which the request info value is set
|
||||||
|
func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context {
|
||||||
|
return WithValue(parent, requestInfoKey, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInfoFrom returns the value of the RequestInfo key on the ctx
|
||||||
|
func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) {
|
||||||
|
info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitPath returns the segments for a URL path.
|
||||||
|
func splitPath(path string) []string {
|
||||||
|
path = strings.Trim(path, "/")
|
||||||
|
if path == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return strings.Split(path, "/")
|
||||||
|
}
|
||||||
272
vendor/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go
generated
vendored
Normal file
272
vendor/k8s.io/apiserver/pkg/endpoints/request/webhook_duration.go
generated
vendored
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/utils/clock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sumDuration(d1 time.Duration, d2 time.Duration) time.Duration {
|
||||||
|
return d1 + d2
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxDuration(d1 time.Duration, d2 time.Duration) time.Duration {
|
||||||
|
if d1 > d2 {
|
||||||
|
return d1
|
||||||
|
}
|
||||||
|
return d2
|
||||||
|
}
|
||||||
|
|
||||||
|
// DurationTracker is a simple interface for tracking functions duration,
|
||||||
|
// it is safe for concurrent use by multiple goroutines.
|
||||||
|
type DurationTracker interface {
|
||||||
|
// Track measures time spent in the given function f and
|
||||||
|
// aggregates measured duration using aggregateFunction.
|
||||||
|
// if Track is invoked with f from multiple goroutines concurrently,
|
||||||
|
// then f must be safe to be invoked concurrently by multiple goroutines.
|
||||||
|
Track(f func())
|
||||||
|
|
||||||
|
// TrackDuration tracks latency from the specified duration
|
||||||
|
// and aggregate it using aggregateFunction
|
||||||
|
TrackDuration(time.Duration)
|
||||||
|
|
||||||
|
// GetLatency returns the total latency incurred so far
|
||||||
|
GetLatency() time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationTracker implements DurationTracker by measuring function time
|
||||||
|
// using given clock and aggregates the duration using given aggregate function
|
||||||
|
type durationTracker struct {
|
||||||
|
clock clock.Clock
|
||||||
|
latency time.Duration
|
||||||
|
mu sync.Mutex
|
||||||
|
aggregateFunction func(time.Duration, time.Duration) time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track measures time spent in given function and aggregates measured
|
||||||
|
// duration using aggregateFunction
|
||||||
|
func (t *durationTracker) Track(f func()) {
|
||||||
|
startedAt := t.clock.Now()
|
||||||
|
defer func() {
|
||||||
|
duration := t.clock.Since(startedAt)
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
t.latency = t.aggregateFunction(t.latency, duration)
|
||||||
|
}()
|
||||||
|
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackDuration tracks latency from the given duration
|
||||||
|
// using aggregateFunction
|
||||||
|
func (t *durationTracker) TrackDuration(d time.Duration) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
t.latency = t.aggregateFunction(t.latency, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatency returns aggregated latency tracked by a tracker
|
||||||
|
func (t *durationTracker) GetLatency() time.Duration {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
return t.latency
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSumLatencyTracker(c clock.Clock) DurationTracker {
|
||||||
|
return &durationTracker{
|
||||||
|
clock: c,
|
||||||
|
aggregateFunction: sumDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMaxLatencyTracker(c clock.Clock) DurationTracker {
|
||||||
|
return &durationTracker{
|
||||||
|
clock: c,
|
||||||
|
aggregateFunction: maxDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatencyTrackers stores trackers used to measure latecny incurred in
|
||||||
|
// components within the apiserver.
|
||||||
|
type LatencyTrackers struct {
|
||||||
|
// MutatingWebhookTracker tracks the latency incurred in mutating webhook(s).
|
||||||
|
// Since mutating webhooks are done sequentially, latency
|
||||||
|
// is aggregated using sum function.
|
||||||
|
MutatingWebhookTracker DurationTracker
|
||||||
|
|
||||||
|
// ValidatingWebhookTracker tracks the latency incurred in validating webhook(s).
|
||||||
|
// Validate webhooks are done in parallel, so max function is used.
|
||||||
|
ValidatingWebhookTracker DurationTracker
|
||||||
|
|
||||||
|
// StorageTracker tracks the latency incurred inside the storage layer,
|
||||||
|
// it accounts for the time it takes to send data to the underlying
|
||||||
|
// storage layer (etcd) and get the complete response back.
|
||||||
|
// If a request involves N (N>=1) round trips to the underlying
|
||||||
|
// stogare layer, the latency will account for the total duration
|
||||||
|
// from these N round trips.
|
||||||
|
// It does not include the time incurred in admission, or validation.
|
||||||
|
StorageTracker DurationTracker
|
||||||
|
|
||||||
|
// TransformTracker tracks the latency incurred in transforming the
|
||||||
|
// response object(s) returned from the underlying storage layer.
|
||||||
|
// This includes transforming the object to user's desired form
|
||||||
|
// (ie. as Table), and also setting appropriate API level fields.
|
||||||
|
// This does not include the latency incurred in serialization
|
||||||
|
// (json or protobuf) of the response object or writing
|
||||||
|
// of it to the http ResponseWriter object.
|
||||||
|
TransformTracker DurationTracker
|
||||||
|
|
||||||
|
// SerializationTracker tracks the latency incurred in serialization
|
||||||
|
// (json or protobuf) of the response object.
|
||||||
|
// NOTE: serialization and writing of the serialized raw bytes to the
|
||||||
|
// associated http ResponseWriter object are interleaved, and hence
|
||||||
|
// the latency measured here will include the time spent writing the
|
||||||
|
// serialized raw bytes to the http ResponseWriter object.
|
||||||
|
SerializationTracker DurationTracker
|
||||||
|
|
||||||
|
// ResponseWriteTracker tracks the latency incurred in writing the
|
||||||
|
// serialized raw bytes to the http ResponseWriter object (via the
|
||||||
|
// Write method) associated with the request.
|
||||||
|
// The Write method can be invoked multiple times, so we use a
|
||||||
|
// latency tracker that sums up the duration from each call.
|
||||||
|
ResponseWriteTracker DurationTracker
|
||||||
|
}
|
||||||
|
|
||||||
|
type latencyTrackersKeyType int
|
||||||
|
|
||||||
|
// latencyTrackersKey is the key that associates a LatencyTrackers
|
||||||
|
// instance with the request context.
|
||||||
|
const latencyTrackersKey latencyTrackersKeyType = iota
|
||||||
|
|
||||||
|
// WithLatencyTrackers returns a copy of parent context to which an
|
||||||
|
// instance of LatencyTrackers is added.
|
||||||
|
func WithLatencyTrackers(parent context.Context) context.Context {
|
||||||
|
return WithLatencyTrackersAndCustomClock(parent, clock.RealClock{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLatencyTrackersAndCustomClock returns a copy of parent context to which
|
||||||
|
// an instance of LatencyTrackers is added. Tracers use given clock.
|
||||||
|
func WithLatencyTrackersAndCustomClock(parent context.Context, c clock.Clock) context.Context {
|
||||||
|
return WithValue(parent, latencyTrackersKey, &LatencyTrackers{
|
||||||
|
MutatingWebhookTracker: newSumLatencyTracker(c),
|
||||||
|
ValidatingWebhookTracker: newMaxLatencyTracker(c),
|
||||||
|
StorageTracker: newSumLatencyTracker(c),
|
||||||
|
TransformTracker: newSumLatencyTracker(c),
|
||||||
|
SerializationTracker: newSumLatencyTracker(c),
|
||||||
|
ResponseWriteTracker: newSumLatencyTracker(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatencyTrackersFrom returns the associated LatencyTrackers instance
|
||||||
|
// from the specified context.
|
||||||
|
func LatencyTrackersFrom(ctx context.Context) (*LatencyTrackers, bool) {
|
||||||
|
wd, ok := ctx.Value(latencyTrackersKey).(*LatencyTrackers)
|
||||||
|
return wd, ok && wd != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackTransformResponseObjectLatency is used to track latency incurred
|
||||||
|
// inside the function that takes an object returned from the underlying
|
||||||
|
// storage layer (etcd) and performs any necessary transformations
|
||||||
|
// of the response object. This does not include the latency incurred in
|
||||||
|
// serialization (json or protobuf) of the response object or writing of
|
||||||
|
// it to the http ResponseWriter object.
|
||||||
|
// When called multiple times, the latency incurred inside the
|
||||||
|
// transform func each time will be summed up.
|
||||||
|
func TrackTransformResponseObjectLatency(ctx context.Context, transform func()) {
|
||||||
|
if tracker, ok := LatencyTrackersFrom(ctx); ok {
|
||||||
|
tracker.TransformTracker.Track(transform)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
transform()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackStorageLatency is used to track latency incurred
|
||||||
|
// inside the underlying storage layer.
|
||||||
|
// When called multiple times, the latency provided will be summed up.
|
||||||
|
func TrackStorageLatency(ctx context.Context, d time.Duration) {
|
||||||
|
if tracker, ok := LatencyTrackersFrom(ctx); ok {
|
||||||
|
tracker.StorageTracker.TrackDuration(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackSerializeResponseObjectLatency is used to track latency incurred in
|
||||||
|
// serialization (json or protobuf) of the response object.
|
||||||
|
// When called multiple times, the latency provided will be summed up.
|
||||||
|
func TrackSerializeResponseObjectLatency(ctx context.Context, f func()) {
|
||||||
|
if tracker, ok := LatencyTrackersFrom(ctx); ok {
|
||||||
|
tracker.SerializationTracker.Track(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackResponseWriteLatency is used to track latency incurred in writing
|
||||||
|
// the serialized raw bytes to the http ResponseWriter object (via the
|
||||||
|
// Write method) associated with the request.
|
||||||
|
// When called multiple times, the latency provided will be summed up.
|
||||||
|
func TrackResponseWriteLatency(ctx context.Context, d time.Duration) {
|
||||||
|
if tracker, ok := LatencyTrackersFrom(ctx); ok {
|
||||||
|
tracker.ResponseWriteTracker.TrackDuration(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditAnnotationsFromLatencyTrackers will inspect each latency tracker
|
||||||
|
// associated with the request context and return a set of audit
|
||||||
|
// annotations that can be added to the API audit entry.
|
||||||
|
func AuditAnnotationsFromLatencyTrackers(ctx context.Context) map[string]string {
|
||||||
|
const (
|
||||||
|
transformLatencyKey = "apiserver.latency.k8s.io/transform-response-object"
|
||||||
|
storageLatencyKey = "apiserver.latency.k8s.io/etcd"
|
||||||
|
serializationLatencyKey = "apiserver.latency.k8s.io/serialize-response-object"
|
||||||
|
responseWriteLatencyKey = "apiserver.latency.k8s.io/response-write"
|
||||||
|
mutatingWebhookLatencyKey = "apiserver.latency.k8s.io/mutating-webhook"
|
||||||
|
validatingWebhookLatencyKey = "apiserver.latency.k8s.io/validating-webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
tracker, ok := LatencyTrackersFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations := map[string]string{}
|
||||||
|
if latency := tracker.TransformTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[transformLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
if latency := tracker.StorageTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[storageLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
if latency := tracker.SerializationTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[serializationLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
if latency := tracker.ResponseWriteTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[responseWriteLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
if latency := tracker.MutatingWebhookTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[mutatingWebhookLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
if latency := tracker.ValidatingWebhookTracker.GetLatency(); latency != 0 {
|
||||||
|
annotations[validatingWebhookLatencyKey] = latency.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 responsewriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = &FakeResponseWriter{}
|
||||||
|
|
||||||
|
// FakeResponseWriter implements http.ResponseWriter,
|
||||||
|
// it is used for testing purpose only
|
||||||
|
type FakeResponseWriter struct{}
|
||||||
|
|
||||||
|
func (fw *FakeResponseWriter) Header() http.Header { return http.Header{} }
|
||||||
|
func (fw *FakeResponseWriter) WriteHeader(code int) {}
|
||||||
|
func (fw *FakeResponseWriter) Write(bs []byte) (int, error) { return len(bs), nil }
|
||||||
|
|
||||||
|
// For HTTP2 an http.ResponseWriter object implements
|
||||||
|
// http.Flusher and http.CloseNotifier.
|
||||||
|
// It is used for testing purpose only
|
||||||
|
type FakeResponseWriterFlusherCloseNotifier struct {
|
||||||
|
*FakeResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *FakeResponseWriterFlusherCloseNotifier) Flush() {}
|
||||||
|
func (fw *FakeResponseWriterFlusherCloseNotifier) CloseNotify() <-chan bool { return nil }
|
||||||
|
|
||||||
|
// For HTTP/1.x an http.ResponseWriter object implements
|
||||||
|
// http.Flusher, http.CloseNotifier and http.Hijacker.
|
||||||
|
// It is used for testing purpose only
|
||||||
|
type FakeResponseWriterFlusherCloseNotifierHijacker struct {
|
||||||
|
*FakeResponseWriterFlusherCloseNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *FakeResponseWriterFlusherCloseNotifierHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 responsewriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserProvidedDecorator represensts a user (client that uses this package)
|
||||||
|
// provided decorator that wraps an inner http.ResponseWriter object.
|
||||||
|
// The user-provided decorator object must return the inner (decorated)
|
||||||
|
// http.ResponseWriter object via the Unwrap function.
|
||||||
|
type UserProvidedDecorator interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
|
||||||
|
// Unwrap returns the inner http.ResponseWriter object associated
|
||||||
|
// with the user-provided decorator.
|
||||||
|
Unwrap() http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapForHTTP1Or2 accepts a user-provided decorator of an "inner" http.responseWriter
|
||||||
|
// object and potentially wraps the user-provided decorator with a new http.ResponseWriter
|
||||||
|
// object that implements http.CloseNotifier, http.Flusher, and/or http.Hijacker by
|
||||||
|
// delegating to the user-provided decorator (if it implements the relevant method) or
|
||||||
|
// the inner http.ResponseWriter (otherwise), so that the returned http.ResponseWriter
|
||||||
|
// object implements the same subset of those interfaces as the inner http.ResponseWriter.
|
||||||
|
//
|
||||||
|
// This function handles the following three casses.
|
||||||
|
// - The inner ResponseWriter implements `http.CloseNotifier`, `http.Flusher`,
|
||||||
|
// and `http.Hijacker` (an HTTP/1.1 sever provides such a ResponseWriter).
|
||||||
|
// - The inner ResponseWriter implements `http.CloseNotifier` and `http.Flusher`
|
||||||
|
// but not `http.Hijacker` (an HTTP/2 server provides such a ResponseWriter).
|
||||||
|
// - All the other cases collapse to this one, in which the given ResponseWriter is returned.
|
||||||
|
//
|
||||||
|
// There are three applicable terms:
|
||||||
|
// - "outer": this is the ResponseWriter object returned by the WrapForHTTP1Or2 function.
|
||||||
|
// - "user-provided decorator" or "middle": this is the user-provided decorator
|
||||||
|
// that decorates an inner ResponseWriter object. A user-provided decorator
|
||||||
|
// implements the UserProvidedDecorator interface. A user-provided decorator
|
||||||
|
// may or may not implement http.CloseNotifier, http.Flusher or http.Hijacker.
|
||||||
|
// - "inner": the ResponseWriter that the user-provided decorator extends.
|
||||||
|
func WrapForHTTP1Or2(decorator UserProvidedDecorator) http.ResponseWriter {
|
||||||
|
// from go net/http documentation:
|
||||||
|
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations support Flusher
|
||||||
|
// Handlers should always test for this ability at runtime.
|
||||||
|
//
|
||||||
|
// The Hijacker interface is implemented by ResponseWriters that allow an HTTP handler
|
||||||
|
// to take over the connection.
|
||||||
|
// The default ResponseWriter for HTTP/1.x connections supports Hijacker, but HTTP/2 connections
|
||||||
|
// intentionally do not. ResponseWriter wrappers may also not support Hijacker.
|
||||||
|
// Handlers should always test for this ability at runtime
|
||||||
|
//
|
||||||
|
// The CloseNotifier interface is implemented by ResponseWriters which allow detecting
|
||||||
|
// when the underlying connection has gone away.
|
||||||
|
// Deprecated: the CloseNotifier interface predates Go's context package.
|
||||||
|
// New code should use Request.Context instead.
|
||||||
|
inner := decorator.Unwrap()
|
||||||
|
if innerNotifierFlusher, ok := inner.(CloseNotifierFlusher); ok {
|
||||||
|
// for HTTP/2 request, the default ResponseWriter object (http2responseWriter)
|
||||||
|
// implements Flusher and CloseNotifier.
|
||||||
|
outerHTTP2 := outerWithCloseNotifyAndFlush{
|
||||||
|
UserProvidedDecorator: decorator,
|
||||||
|
InnerCloseNotifierFlusher: innerNotifierFlusher,
|
||||||
|
}
|
||||||
|
|
||||||
|
if innerHijacker, hijackable := inner.(http.Hijacker); hijackable {
|
||||||
|
// for HTTP/1.x request the default implementation of ResponseWriter
|
||||||
|
// also implement CloseNotifier, Flusher and Hijacker
|
||||||
|
return &outerWithCloseNotifyFlushAndHijack{
|
||||||
|
outerWithCloseNotifyAndFlush: outerHTTP2,
|
||||||
|
InnerHijacker: innerHijacker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outerHTTP2
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should never be here for either http/1.x or http2 request
|
||||||
|
return decorator
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotifierFlusher is a combination of http.CloseNotifier and http.Flusher
|
||||||
|
// This applies to both http/1.x and http2 requests.
|
||||||
|
type CloseNotifierFlusher interface {
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Flusher
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOriginal goes through the chain of wrapped http.ResponseWriter objects
|
||||||
|
// and returns the original http.ResponseWriter object provided to the first
|
||||||
|
// request handler in the filter chain.
|
||||||
|
func GetOriginal(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
decorator, ok := w.(UserProvidedDecorator)
|
||||||
|
if !ok {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
inner := decorator.Unwrap()
|
||||||
|
if inner == w {
|
||||||
|
// infinite cycle here, we should never be here though.
|
||||||
|
panic("http.ResponseWriter decorator chain has a cycle")
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetOriginal(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:staticcheck // SA1019
|
||||||
|
var _ http.CloseNotifier = outerWithCloseNotifyAndFlush{}
|
||||||
|
var _ http.Flusher = outerWithCloseNotifyAndFlush{}
|
||||||
|
var _ http.ResponseWriter = outerWithCloseNotifyAndFlush{}
|
||||||
|
var _ UserProvidedDecorator = outerWithCloseNotifyAndFlush{}
|
||||||
|
|
||||||
|
// outerWithCloseNotifyAndFlush is the outer object that extends the
|
||||||
|
// user provied decorator with http.CloseNotifier and http.Flusher only.
|
||||||
|
type outerWithCloseNotifyAndFlush struct {
|
||||||
|
// UserProvidedDecorator is the user-provided object, it decorates
|
||||||
|
// an inner ResponseWriter object.
|
||||||
|
UserProvidedDecorator
|
||||||
|
|
||||||
|
// http.CloseNotifier and http.Flusher for the inner object
|
||||||
|
InnerCloseNotifierFlusher CloseNotifierFlusher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr outerWithCloseNotifyAndFlush) CloseNotify() <-chan bool {
|
||||||
|
if notifier, ok := wr.UserProvidedDecorator.(http.CloseNotifier); ok {
|
||||||
|
return notifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr.InnerCloseNotifierFlusher.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr outerWithCloseNotifyAndFlush) Flush() {
|
||||||
|
if flusher, ok := wr.UserProvidedDecorator.(http.Flusher); ok {
|
||||||
|
flusher.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wr.InnerCloseNotifierFlusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
//lint:file-ignore SA1019 Keep supporting deprecated http.CloseNotifier
|
||||||
|
var _ http.CloseNotifier = outerWithCloseNotifyFlushAndHijack{}
|
||||||
|
var _ http.Flusher = outerWithCloseNotifyFlushAndHijack{}
|
||||||
|
var _ http.Hijacker = outerWithCloseNotifyFlushAndHijack{}
|
||||||
|
var _ http.ResponseWriter = outerWithCloseNotifyFlushAndHijack{}
|
||||||
|
var _ UserProvidedDecorator = outerWithCloseNotifyFlushAndHijack{}
|
||||||
|
|
||||||
|
// outerWithCloseNotifyFlushAndHijack is the outer object that extends the
|
||||||
|
// user-provided decorator with http.CloseNotifier, http.Flusher and http.Hijacker.
|
||||||
|
// This applies to http/1.x requests only.
|
||||||
|
type outerWithCloseNotifyFlushAndHijack struct {
|
||||||
|
outerWithCloseNotifyAndFlush
|
||||||
|
|
||||||
|
// http.Hijacker for the inner object
|
||||||
|
InnerHijacker http.Hijacker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr outerWithCloseNotifyFlushAndHijack) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if hijacker, ok := wr.UserProvidedDecorator.(http.Hijacker); ok {
|
||||||
|
return hijacker.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr.InnerHijacker.Hijack()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- feature-approvers
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Every feature gate should add method here following this template:
|
||||||
|
//
|
||||||
|
// // owner: @username
|
||||||
|
// // alpha: v1.4
|
||||||
|
// MyFeature featuregate.Feature = "MyFeature"
|
||||||
|
//
|
||||||
|
// Feature gates should be listed in alphabetical, case-sensitive
|
||||||
|
// (upper before any lower case character) order. This reduces the risk
|
||||||
|
// of code conflicts because changes are more likely to be scattered
|
||||||
|
// across the file.
|
||||||
|
|
||||||
|
// owner: @smarterclayton
|
||||||
|
// alpha: v1.8
|
||||||
|
// beta: v1.9
|
||||||
|
//
|
||||||
|
// Allow API clients to retrieve resource lists in chunks rather than
|
||||||
|
// all at once.
|
||||||
|
APIListChunking featuregate.Feature = "APIListChunking"
|
||||||
|
|
||||||
|
// owner: @MikeSpreitzer @yue9944882
|
||||||
|
// alpha: v1.18
|
||||||
|
// beta: v1.20
|
||||||
|
//
|
||||||
|
// Enables managing request concurrency with prioritization and fairness at each server.
|
||||||
|
// The FeatureGate was introduced in release 1.15 but the feature
|
||||||
|
// was not really implemented before 1.18.
|
||||||
|
APIPriorityAndFairness featuregate.Feature = "APIPriorityAndFairness"
|
||||||
|
|
||||||
|
// owner: @ilackams
|
||||||
|
// alpha: v1.7
|
||||||
|
// beta: v1.16
|
||||||
|
//
|
||||||
|
// Enables compression of REST responses (GET and LIST only)
|
||||||
|
APIResponseCompression featuregate.Feature = "APIResponseCompression"
|
||||||
|
|
||||||
|
// owner: @roycaihw
|
||||||
|
// alpha: v1.20
|
||||||
|
//
|
||||||
|
// Assigns each kube-apiserver an ID in a cluster.
|
||||||
|
APIServerIdentity featuregate.Feature = "APIServerIdentity"
|
||||||
|
|
||||||
|
// owner: @dashpole
|
||||||
|
// alpha: v1.22
|
||||||
|
//
|
||||||
|
// Add support for distributed tracing in the API Server
|
||||||
|
APIServerTracing featuregate.Feature = "APIServerTracing"
|
||||||
|
|
||||||
|
// owner: @tallclair
|
||||||
|
// alpha: v1.7
|
||||||
|
// beta: v1.8
|
||||||
|
// GA: v1.12
|
||||||
|
//
|
||||||
|
// AdvancedAuditing enables a much more general API auditing pipeline, which includes support for
|
||||||
|
// pluggable output backends and an audit policy specifying how different requests should be
|
||||||
|
// audited.
|
||||||
|
AdvancedAuditing featuregate.Feature = "AdvancedAuditing"
|
||||||
|
|
||||||
|
// owner: @cici37
|
||||||
|
// kep: http://kep.k8s.io/2876
|
||||||
|
// alpha: v1.23
|
||||||
|
// beta: v1.25
|
||||||
|
//
|
||||||
|
// Enables expression validation for Custom Resource
|
||||||
|
CustomResourceValidationExpressions featuregate.Feature = "CustomResourceValidationExpressions"
|
||||||
|
|
||||||
|
// owner: @apelisse
|
||||||
|
// alpha: v1.12
|
||||||
|
// beta: v1.13
|
||||||
|
// stable: v1.18
|
||||||
|
//
|
||||||
|
// Allow requests to be processed but not stored, so that
|
||||||
|
// validation, merging, mutation can be tested without
|
||||||
|
// committing.
|
||||||
|
DryRun featuregate.Feature = "DryRun"
|
||||||
|
|
||||||
|
// owner: @wojtek-t
|
||||||
|
// alpha: v1.20
|
||||||
|
// beta: v1.21
|
||||||
|
// GA: v1.24
|
||||||
|
//
|
||||||
|
// Allows for updating watchcache resource version with progress notify events.
|
||||||
|
EfficientWatchResumption featuregate.Feature = "EfficientWatchResumption"
|
||||||
|
|
||||||
|
// owner: @aramase
|
||||||
|
// kep: http://kep.k8s.io/3299
|
||||||
|
// alpha: v1.25
|
||||||
|
//
|
||||||
|
// Enables KMS v2 API for encryption at rest.
|
||||||
|
KMSv2 featuregate.Feature = "KMSv2"
|
||||||
|
|
||||||
|
// owner: @jiahuif
|
||||||
|
// kep: http://kep.k8s.io/2887
|
||||||
|
// alpha: v1.23
|
||||||
|
// beta: v1.24
|
||||||
|
//
|
||||||
|
// Enables populating "enum" field of OpenAPI schemas
|
||||||
|
// in the spec returned from kube-apiserver.
|
||||||
|
OpenAPIEnums featuregate.Feature = "OpenAPIEnums"
|
||||||
|
|
||||||
|
// owner: @jefftree
|
||||||
|
// kep: http://kep.k8s.io/2896
|
||||||
|
// alpha: v1.23
|
||||||
|
// beta: v1.24
|
||||||
|
//
|
||||||
|
// Enables kubernetes to publish OpenAPI v3
|
||||||
|
OpenAPIV3 featuregate.Feature = "OpenAPIV3"
|
||||||
|
|
||||||
|
// owner: @caesarxuchao
|
||||||
|
// alpha: v1.15
|
||||||
|
// beta: v1.16
|
||||||
|
//
|
||||||
|
// Allow apiservers to show a count of remaining items in the response
|
||||||
|
// to a chunking list request.
|
||||||
|
RemainingItemCount featuregate.Feature = "RemainingItemCount"
|
||||||
|
|
||||||
|
// owner: @wojtek-t
|
||||||
|
// alpha: v1.16
|
||||||
|
// beta: v1.20
|
||||||
|
// GA: v1.24
|
||||||
|
//
|
||||||
|
// Deprecates and removes SelfLink from ObjectMeta and ListMeta.
|
||||||
|
RemoveSelfLink featuregate.Feature = "RemoveSelfLink"
|
||||||
|
|
||||||
|
// owner: @apelisse, @lavalamp
|
||||||
|
// alpha: v1.14
|
||||||
|
// beta: v1.16
|
||||||
|
// stable: v1.22
|
||||||
|
//
|
||||||
|
// Server-side apply. Merging happens on the server.
|
||||||
|
ServerSideApply featuregate.Feature = "ServerSideApply"
|
||||||
|
|
||||||
|
// owner: @kevindelgado
|
||||||
|
// kep: http://kep.k8s.io/2885
|
||||||
|
// alpha: v1.23
|
||||||
|
// beta: v1.24
|
||||||
|
//
|
||||||
|
// Enables server-side field validation.
|
||||||
|
ServerSideFieldValidation featuregate.Feature = "ServerSideFieldValidation"
|
||||||
|
|
||||||
|
// owner: @caesarxuchao @roycaihw
|
||||||
|
// alpha: v1.20
|
||||||
|
//
|
||||||
|
// Enable the storage version API.
|
||||||
|
StorageVersionAPI featuregate.Feature = "StorageVersionAPI"
|
||||||
|
|
||||||
|
// owner: @caesarxuchao
|
||||||
|
// alpha: v1.14
|
||||||
|
// beta: v1.15
|
||||||
|
//
|
||||||
|
// Allow apiservers to expose the storage version hash in the discovery
|
||||||
|
// document.
|
||||||
|
StorageVersionHash featuregate.Feature = "StorageVersionHash"
|
||||||
|
|
||||||
|
// owner: @wojtek-t
|
||||||
|
// alpha: v1.15
|
||||||
|
// beta: v1.16
|
||||||
|
// GA: v1.17
|
||||||
|
//
|
||||||
|
// Enables support for watch bookmark events.
|
||||||
|
WatchBookmark featuregate.Feature = "WatchBookmark"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
|
||||||
|
// To add a new feature, define a key for it above and add it here. The features will be
|
||||||
|
// available throughout Kubernetes binaries.
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
APIListChunking: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
APIPriorityAndFairness: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
APIResponseCompression: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
APIServerIdentity: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
APIServerTracing: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
AdvancedAuditing: {Default: true, PreRelease: featuregate.GA},
|
||||||
|
|
||||||
|
CustomResourceValidationExpressions: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
DryRun: {Default: true, PreRelease: featuregate.GA},
|
||||||
|
|
||||||
|
EfficientWatchResumption: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
|
||||||
|
KMSv2: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
OpenAPIEnums: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
OpenAPIV3: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
RemainingItemCount: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
RemoveSelfLink: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
|
||||||
|
ServerSideApply: {Default: true, PreRelease: featuregate.GA},
|
||||||
|
|
||||||
|
ServerSideFieldValidation: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
StorageVersionAPI: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
StorageVersionHash: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
WatchBookmark: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue