Merge pull request #14626 from justinsb/set_apply_operation

Apply: Migration from CSA to SSA; set operation and merge
This commit is contained in:
Kubernetes Prow Robot 2022-11-27 01:48:05 -08:00 committed by GitHub
commit 8695cb4559
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 22077 additions and 73 deletions

3
go.mod
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": {}
}
}
}
]
}
}

View File

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

View File

@ -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": {}
}
}
}
]
}
}

View File

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

View File

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

View File

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

View File

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

202
vendor/k8s.io/apiserver/LICENSE generated vendored Normal file
View File

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

211
vendor/k8s.io/apiserver/pkg/admission/attributes.go generated vendored Normal file
View File

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

102
vendor/k8s.io/apiserver/pkg/admission/audit.go generated vendored Normal file
View File

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

70
vendor/k8s.io/apiserver/pkg/admission/chain.go generated vendored Normal file
View File

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

175
vendor/k8s.io/apiserver/pkg/admission/config.go generated vendored Normal file
View File

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

39
vendor/k8s.io/apiserver/pkg/admission/decorator.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package 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
}

72
vendor/k8s.io/apiserver/pkg/admission/errors.go generated vendored Normal file
View File

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

79
vendor/k8s.io/apiserver/pkg/admission/handler.go generated vendored Normal file
View File

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

172
vendor/k8s.io/apiserver/pkg/admission/interfaces.go generated vendored Normal file
View File

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

208
vendor/k8s.io/apiserver/pkg/admission/plugins.go generated vendored Normal file
View File

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

64
vendor/k8s.io/apiserver/pkg/admission/reinvocation.go generated vendored Normal file
View File

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

47
vendor/k8s.io/apiserver/pkg/admission/util.go generated vendored Normal file
View File

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

21
vendor/k8s.io/apiserver/pkg/apis/apiserver/doc.go generated vendored Normal file
View File

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

50
vendor/k8s.io/apiserver/pkg/apis/apiserver/register.go generated vendored Normal file
View File

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

168
vendor/k8s.io/apiserver/pkg/apis/apiserver/types.go generated vendored Normal file
View File

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

23
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/doc.go generated vendored Normal file
View File

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

View File

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

50
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1/types.go generated vendored Normal file
View File

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

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

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

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

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

8
vendor/k8s.io/apiserver/pkg/apis/audit/OWNERS generated vendored Normal file
View File

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

20
vendor/k8s.io/apiserver/pkg/apis/audit/doc.go generated vendored Normal file
View File

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

38
vendor/k8s.io/apiserver/pkg/apis/audit/helpers.go generated vendored Normal file
View File

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

53
vendor/k8s.io/apiserver/pkg/apis/audit/register.go generated vendored Normal file
View File

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

312
vendor/k8s.io/apiserver/pkg/apis/audit/types.go generated vendored Normal file
View File

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

25
vendor/k8s.io/apiserver/pkg/apis/audit/v1/doc.go generated vendored Normal file
View File

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

3231
vendor/k8s.io/apiserver/pkg/apis/audit/v1/generated.pb.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

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

58
vendor/k8s.io/apiserver/pkg/apis/audit/v1/register.go generated vendored Normal file
View File

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

306
vendor/k8s.io/apiserver/pkg/apis/audit/v1/types.go generated vendored Normal file
View File

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

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

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

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

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

8
vendor/k8s.io/apiserver/pkg/audit/OWNERS generated vendored Normal file
View File

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

225
vendor/k8s.io/apiserver/pkg/audit/context.go generated vendored Normal file
View File

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

65
vendor/k8s.io/apiserver/pkg/audit/evaluator.go generated vendored Normal file
View File

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

73
vendor/k8s.io/apiserver/pkg/audit/format.go generated vendored Normal file
View File

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

111
vendor/k8s.io/apiserver/pkg/audit/metrics.go generated vendored Normal file
View File

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

323
vendor/k8s.io/apiserver/pkg/audit/request.go generated vendored Normal file
View File

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

38
vendor/k8s.io/apiserver/pkg/audit/scheme.go generated vendored Normal file
View File

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

46
vendor/k8s.io/apiserver/pkg/audit/types.go generated vendored Normal file
View File

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

71
vendor/k8s.io/apiserver/pkg/audit/union.go generated vendored Normal file
View File

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

19
vendor/k8s.io/apiserver/pkg/authentication/user/doc.go generated vendored Normal file
View File

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

View File

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

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

View File

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

View File

@ -0,0 +1,6 @@
approvers:
- apelisse
reviewers:
- kwiesmueller
emeritus_approvers:
- jennybuckley

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

View 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")
}
}

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

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

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

View 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

View 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'

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

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

View 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())
}
}
}

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

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

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

6
vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS generated vendored Normal file
View File

@ -0,0 +1,6 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- wojtek-t
approvers:
- logicalhan

View File

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

4
vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS generated vendored Normal file
View File

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- sttts

View File

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

View File

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

20
vendor/k8s.io/apiserver/pkg/endpoints/request/doc.go generated vendored Normal file
View File

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

View File

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

View File

@ -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, "/")
}

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

View File

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

View 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 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()
}

4
vendor/k8s.io/apiserver/pkg/features/OWNERS generated vendored Normal file
View File

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- feature-approvers

234
vendor/k8s.io/apiserver/pkg/features/kube_features.go generated vendored Normal file
View File

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