Merge branch 'release-1.10' into cp-backup-result-1.10
commit
b192665024
|
@ -0,0 +1 @@
|
|||
Restore finalizer and managedFields of metadata during the restoration
|
|
@ -0,0 +1 @@
|
|||
Add labels for velero installed namespace to support PSA.
|
|
@ -146,3 +146,10 @@ func WithGenerateName(val string) func(obj metav1.Object) {
|
|||
obj.SetGenerateName(val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithManagedFields is a functional option that applies the specified managed fields to an object.
|
||||
func WithManagedFields(val []metav1.ManagedFieldsEntry) func(obj metav1.Object) {
|
||||
return func(obj metav1.Object) {
|
||||
obj.SetManagedFields(val)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,13 +136,18 @@ func ClusterRoleBinding(namespace string) *rbacv1.ClusterRoleBinding {
|
|||
}
|
||||
|
||||
func Namespace(namespace string) *corev1.Namespace {
|
||||
return &corev1.Namespace{
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: objectMeta("", namespace),
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Namespace",
|
||||
APIVersion: corev1.SchemeGroupVersion.String(),
|
||||
},
|
||||
}
|
||||
|
||||
ns.Labels["pod-security.kubernetes.io/enforce"] = "privileged"
|
||||
ns.Labels["pod-security.kubernetes.io/enforce-version"] = "latest"
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
func BackupStorageLocation(namespace, provider, bucket, prefix string, config map[string]string, caCert []byte) *velerov1api.BackupStorageLocation {
|
||||
|
|
|
@ -40,6 +40,11 @@ func TestResources(t *testing.T) {
|
|||
ns := Namespace("velero")
|
||||
|
||||
assert.Equal(t, "velero", ns.Name)
|
||||
// For k8s version v1.25 and later, need to add the following labels to make
|
||||
// velero installation namespace has privileged version to work with
|
||||
// PSA(Pod Security Admission) and PSS(Pod Security Standards).
|
||||
assert.Equal(t, ns.Labels["pod-security.kubernetes.io/enforce"], "privileged")
|
||||
assert.Equal(t, ns.Labels["pod-security.kubernetes.io/enforce-version"], "latest")
|
||||
|
||||
crb := ClusterRoleBinding(DefaultVeleroNamespace)
|
||||
// The CRB is a cluster-scoped resource
|
||||
|
|
|
@ -1422,6 +1422,24 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
|||
}
|
||||
}
|
||||
|
||||
// restore the managedFields
|
||||
withoutManagedFields := createdObj.DeepCopy()
|
||||
createdObj.SetManagedFields(obj.GetManagedFields())
|
||||
patchBytes, err := generatePatch(withoutManagedFields, createdObj)
|
||||
if err != nil {
|
||||
ctx.log.Errorf("error generating patch for managed fields %s: %v", kube.NamespaceAndName(obj), err)
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
if patchBytes != nil {
|
||||
if _, err = resourceClient.Patch(name, patchBytes); err != nil {
|
||||
ctx.log.Errorf("error patch for managed fields %s: %v", kube.NamespaceAndName(obj), err)
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
ctx.log.Infof("the managed fields for %s is patched", kube.NamespaceAndName(obj))
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.Pods {
|
||||
pod := new(v1.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
|
@ -1721,8 +1739,8 @@ func resetMetadata(obj *unstructured.Unstructured) (*unstructured.Unstructured,
|
|||
|
||||
for k := range metadata {
|
||||
switch k {
|
||||
case "name", "namespace", "labels", "annotations":
|
||||
default:
|
||||
case "generateName", "selfLink", "uid", "resourceVersion", "generation", "creationTimestamp", "deletionTimestamp",
|
||||
"deletionGracePeriodSeconds", "ownerReferences":
|
||||
delete(metadata, k)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -868,7 +868,7 @@ func TestRestoreItems(t *testing.T) {
|
|||
want []*test.APIResource
|
||||
}{
|
||||
{
|
||||
name: "metadata other than namespace/name/labels/annotations gets removed",
|
||||
name: "metadata uid/resourceVersion/etc. gets removed",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
|
@ -878,6 +878,7 @@ func TestRestoreItems(t *testing.T) {
|
|||
builder.WithLabels("key-1", "val-1"),
|
||||
builder.WithAnnotations("key-1", "val-1"),
|
||||
builder.WithFinalizers("finalizer-1"),
|
||||
builder.WithUID("uid"),
|
||||
).
|
||||
Result(),
|
||||
).
|
||||
|
@ -891,6 +892,7 @@ func TestRestoreItems(t *testing.T) {
|
|||
ObjectMeta(
|
||||
builder.WithLabels("key-1", "val-1", "velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
builder.WithAnnotations("key-1", "val-1"),
|
||||
builder.WithFinalizers("finalizer-1"),
|
||||
).
|
||||
Result(),
|
||||
),
|
||||
|
@ -1108,6 +1110,53 @@ func TestRestoreItems(t *testing.T) {
|
|||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "metadata managedFields gets restored",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("pods",
|
||||
builder.ForPod("ns-1", "pod-1").
|
||||
ObjectMeta(
|
||||
builder.WithManagedFields([]metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "kubectl",
|
||||
Operation: "Apply",
|
||||
APIVersion: "v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:data": {"f:key":{}}}`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
).
|
||||
Result(),
|
||||
).
|
||||
Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(),
|
||||
},
|
||||
want: []*test.APIResource{
|
||||
test.Pods(
|
||||
builder.ForPod("ns-1", "pod-1").
|
||||
ObjectMeta(
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
builder.WithManagedFields([]metav1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "kubectl",
|
||||
Operation: "Apply",
|
||||
APIVersion: "v1",
|
||||
FieldsType: "FieldsV1",
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:data": {"f:key":{}}}`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
).
|
||||
Result(),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
@ -2824,10 +2873,16 @@ func TestResetMetadata(t *testing.T) {
|
|||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "keep name, namespace, labels, annotations only",
|
||||
obj: NewTestUnstructured().WithMetadata("name", "blah", "namespace", "labels", "annotations", "foo").Unstructured,
|
||||
name: "keep name, namespace, labels, annotations, managedFields, finalizers",
|
||||
obj: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations", "managedFields", "finalizers").Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured,
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations", "managedFields", "finalizers").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "remove uid, ownerReferences",
|
||||
obj: NewTestUnstructured().WithMetadata("name", "namespace", "uid", "ownerReferences").Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "keep status",
|
||||
|
|
|
@ -18,6 +18,7 @@ package restore
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -83,10 +84,10 @@ func deleteNodePorts(service *corev1api.Service) error {
|
|||
// find any NodePorts whose values were explicitly specified according
|
||||
// to the last-applied-config annotation. We'll retain these values, and
|
||||
// clear out any other (presumably auto-assigned) NodePort values.
|
||||
explicitNodePorts := sets.NewString()
|
||||
unnamedPortInts := sets.NewInt()
|
||||
lastAppliedConfig, ok := service.Annotations[annotationLastAppliedConfig]
|
||||
if ok {
|
||||
explicitNodePorts := sets.NewString()
|
||||
unnamedPortInts := sets.NewInt()
|
||||
appliedServiceUnstructured := new(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(lastAppliedConfig), appliedServiceUnstructured); err != nil {
|
||||
return errors.WithStack(err)
|
||||
|
@ -134,19 +135,58 @@ func deleteNodePorts(service *corev1api.Service) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, port := range service.Spec.Ports {
|
||||
if port.Name != "" {
|
||||
if !explicitNodePorts.Has(port.Name) {
|
||||
service.Spec.Ports[i].NodePort = 0
|
||||
}
|
||||
} else {
|
||||
if !unnamedPortInts.Has(int(port.NodePort)) {
|
||||
service.Spec.Ports[i].NodePort = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, port := range service.Spec.Ports {
|
||||
if port.Name != "" {
|
||||
if !explicitNodePorts.Has(port.Name) {
|
||||
service.Spec.Ports[i].NodePort = 0
|
||||
explicitNodePorts := sets.NewString()
|
||||
for _, entry := range service.GetManagedFields() {
|
||||
if entry.FieldsV1 == nil {
|
||||
continue
|
||||
}
|
||||
fields := new(map[string]interface{})
|
||||
if err := json.Unmarshal(entry.FieldsV1.Raw, fields); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
ports, exist, err := unstructured.NestedMap(*fields, "f:spec", "f:ports")
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
for key, port := range ports {
|
||||
p, ok := port.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !unnamedPortInts.Has(int(port.NodePort)) {
|
||||
service.Spec.Ports[i].NodePort = 0
|
||||
if _, exist := p["f:nodePort"]; exist {
|
||||
explicitNodePorts.Insert(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, port := range service.Spec.Ports {
|
||||
k := portKey(port)
|
||||
if !explicitNodePorts.Has(k) {
|
||||
service.Spec.Ports[i].NodePort = 0
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func portKey(port corev1api.ServicePort) string {
|
||||
return fmt.Sprintf(`k:{"port":%d,"protocol":"%s"}`, port.Port, port.Protocol)
|
||||
}
|
||||
|
|
|
@ -368,6 +368,124 @@ func TestServiceActionExecute(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nodePort should be delete when not specified in managedFields",
|
||||
obj: corev1api.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc-1",
|
||||
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1api.ServiceSpec{
|
||||
Ports: []corev1api.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 443,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").Result(),
|
||||
expectedRes: corev1api.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc-1",
|
||||
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1api.ServiceSpec{
|
||||
Ports: []corev1api.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
NodePort: 0,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 443,
|
||||
NodePort: 0,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nodePort should be preserved when specified in managedFields",
|
||||
obj: corev1api.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc-1",
|
||||
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1api.ServiceSpec{
|
||||
Ports: []corev1api.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
NodePort: 30000,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 443,
|
||||
NodePort: 30002,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").Result(),
|
||||
expectedRes: corev1api.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc-1",
|
||||
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
FieldsV1: &metav1.FieldsV1{
|
||||
Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1api.ServiceSpec{
|
||||
Ports: []corev1api.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
NodePort: 30000,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 443,
|
||||
NodePort: 30002,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
Loading…
Reference in New Issue