|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See LICENSE in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | + |
| 6 | +package v2 |
| 7 | + |
| 8 | +import ( |
| 9 | + "context" |
| 10 | + "reflect" |
| 11 | + "strings" |
| 12 | + |
| 13 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 14 | + "k8s.io/apimachinery/pkg/runtime" |
| 15 | + "k8s.io/apimachinery/pkg/runtime/schema" |
| 16 | + "k8s.io/apimachinery/pkg/types" |
| 17 | + "k8s.io/apimachinery/pkg/util/validation/field" |
| 18 | + |
| 19 | + apiserver_resource "github.com/tilt-dev/tilt-apiserver/pkg/server/builder/resource" |
| 20 | + apiserver_resourcerest "github.com/tilt-dev/tilt-apiserver/pkg/server/builder/resource/resourcerest" |
| 21 | + apiserver_resourcestrategy "github.com/tilt-dev/tilt-apiserver/pkg/server/builder/resource/resourcestrategy" |
| 22 | + |
| 23 | + "github.com/microsoft/dcp/pkg/commonapi" |
| 24 | +) |
| 25 | + |
| 26 | +// PhysicalContainerVolumePhase describes the lifecycle phase of a PhysicalContainerVolume. |
| 27 | +type PhysicalContainerVolumePhase string |
| 28 | + |
| 29 | +const ( |
| 30 | + // PhysicalContainerVolumePhasePending indicates that the volume is waiting for prerequisites. |
| 31 | + PhysicalContainerVolumePhasePending PhysicalContainerVolumePhase = "Pending" |
| 32 | + |
| 33 | + // PhysicalContainerVolumePhaseReady indicates that the runtime volume is available. |
| 34 | + PhysicalContainerVolumePhaseReady PhysicalContainerVolumePhase = "Ready" |
| 35 | + |
| 36 | + // PhysicalContainerVolumePhaseMissing indicates that the referenced runtime volume was not found. |
| 37 | + PhysicalContainerVolumePhaseMissing PhysicalContainerVolumePhase = "Missing" |
| 38 | + |
| 39 | + // PhysicalContainerVolumePhaseFailed indicates that creating or inspecting the runtime volume failed. |
| 40 | + PhysicalContainerVolumePhaseFailed PhysicalContainerVolumePhase = "Failed" |
| 41 | +) |
| 42 | + |
| 43 | +const ( |
| 44 | + // PhysicalContainerVolumeReasonPending indicates that the volume is waiting for prerequisites. |
| 45 | + PhysicalContainerVolumeReasonPending string = "Pending" |
| 46 | + |
| 47 | + // PhysicalContainerVolumeReasonCreating indicates that runtime volume creation is in progress. |
| 48 | + PhysicalContainerVolumeReasonCreating string = "Creating" |
| 49 | + |
| 50 | + // PhysicalContainerVolumeReasonCreated indicates that runtime volume creation completed. |
| 51 | + PhysicalContainerVolumeReasonCreated string = "Created" |
| 52 | + |
| 53 | + // PhysicalContainerVolumeReasonCreateFailed indicates that runtime volume creation failed. |
| 54 | + PhysicalContainerVolumeReasonCreateFailed string = "CreateFailed" |
| 55 | + |
| 56 | + // PhysicalContainerVolumeReasonVolumeReady indicates that the runtime volume is available. |
| 57 | + PhysicalContainerVolumeReasonVolumeReady string = "VolumeReady" |
| 58 | + |
| 59 | + // PhysicalContainerVolumeReasonRuntimeVolumeMissing indicates that the runtime volume was not found. |
| 60 | + PhysicalContainerVolumeReasonRuntimeVolumeMissing string = "RuntimeVolumeMissing" |
| 61 | + |
| 62 | + // PhysicalContainerVolumeReasonReconciliationFailed indicates that reconciliation failed outside a specific progress gate. |
| 63 | + PhysicalContainerVolumeReasonReconciliationFailed string = "ReconciliationFailed" |
| 64 | +) |
| 65 | + |
| 66 | +// PhysicalContainerVolumeSpec describes either an existing runtime volume or how to create one. |
| 67 | +// +k8s:openapi-gen=true |
| 68 | +type PhysicalContainerVolumeSpec struct { |
| 69 | + // VolumeID identifies an existing runtime volume to track. Container runtimes commonly use |
| 70 | + // the volume name as its identifier. When set, creation fields are forbidden. |
| 71 | + VolumeID string `json:"volumeID,omitempty"` |
| 72 | + |
| 73 | + // VolumeName is the runtime name to use when creating a new volume. Required when volumeID is omitted. |
| 74 | + VolumeName string `json:"volumeName,omitempty"` |
| 75 | + |
| 76 | + // PreserveOnDeletion keeps the runtime volume in place when this resource is deleted. |
| 77 | + // By default the runtime volume is removed, including when this resource only tracks a |
| 78 | + // volume it did not create. |
| 79 | + PreserveOnDeletion bool `json:"preserveOnDeletion,omitempty"` |
| 80 | + |
| 81 | + // Labels contains labels to apply to a newly-created runtime volume. |
| 82 | + // +listType=map |
| 83 | + // +listMapKey=key |
| 84 | + Labels []commonapi.Label `json:"labels,omitempty"` |
| 85 | +} |
| 86 | + |
| 87 | +// PhysicalContainerVolumeStatus describes the observed runtime volume. |
| 88 | +// +k8s:openapi-gen=true |
| 89 | +type PhysicalContainerVolumeStatus struct { |
| 90 | + // Phase summarizes whether the runtime volume is available. |
| 91 | + // +kubebuilder:validation:Enum=Pending;Ready;Missing;Failed |
| 92 | + // +optional |
| 93 | + Phase PhysicalContainerVolumePhase `json:"phase,omitempty"` |
| 94 | + |
| 95 | + // VolumeID is the runtime identifier being tracked. |
| 96 | + VolumeID string `json:"volumeID,omitempty"` |
| 97 | + |
| 98 | + // VolumeName is the runtime volume name. |
| 99 | + VolumeName string `json:"volumeName,omitempty"` |
| 100 | + |
| 101 | + // Driver is the runtime volume driver. |
| 102 | + Driver string `json:"driver,omitempty"` |
| 103 | + |
| 104 | + // MountPoint is the host path where the runtime stores volume data, when exposed by the runtime. |
| 105 | + MountPoint string `json:"mountPoint,omitempty"` |
| 106 | + |
| 107 | + // Scope is the runtime volume scope. |
| 108 | + Scope string `json:"scope,omitempty"` |
| 109 | + |
| 110 | + // CreatedAt is the runtime volume creation timestamp. |
| 111 | + CreatedAt metav1.MicroTime `json:"createdAt,omitempty"` |
| 112 | + |
| 113 | + // Conditions describe readiness and reconciliation progress. |
| 114 | + // +listType=map |
| 115 | + // +listMapKey=type |
| 116 | + // +optional |
| 117 | + Conditions []metav1.Condition `json:"conditions,omitempty"` |
| 118 | +} |
| 119 | + |
| 120 | +func (pcvs PhysicalContainerVolumeStatus) CopyTo(dest apiserver_resource.ObjectWithStatusSubResource) { |
| 121 | + pcvs.DeepCopyInto(&dest.(*PhysicalContainerVolume).Status) |
| 122 | +} |
| 123 | + |
| 124 | +// PhysicalContainerVolume represents one runtime container volume in a DCP V2 namespace. |
| 125 | +// +kubebuilder:object:root=true |
| 126 | +// +kubebuilder:subresource:status |
| 127 | +// +k8s:openapi-gen=true |
| 128 | +// +kubebuilder:resource:scope=Namespaced,path=physicalcontainervolumes,shortName=pcv |
| 129 | +type PhysicalContainerVolume struct { |
| 130 | + metav1.TypeMeta `json:",inline"` |
| 131 | + metav1.ObjectMeta `json:"metadata,omitempty"` |
| 132 | + |
| 133 | + Spec PhysicalContainerVolumeSpec `json:"spec,omitempty"` |
| 134 | + Status PhysicalContainerVolumeStatus `json:"status,omitempty"` |
| 135 | +} |
| 136 | + |
| 137 | +func (pv *PhysicalContainerVolume) GetGroupVersionResource() schema.GroupVersionResource { |
| 138 | + return schema.GroupVersionResource{ |
| 139 | + Group: GroupVersion.Group, |
| 140 | + Version: GroupVersion.Version, |
| 141 | + Resource: "physicalcontainervolumes", |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +func (pv *PhysicalContainerVolume) GetObjectMeta() *metav1.ObjectMeta { |
| 146 | + return &pv.ObjectMeta |
| 147 | +} |
| 148 | + |
| 149 | +func (pv *PhysicalContainerVolume) GetStatus() apiserver_resource.StatusSubResource { |
| 150 | + return pv.Status |
| 151 | +} |
| 152 | + |
| 153 | +func (pv *PhysicalContainerVolume) New() runtime.Object { |
| 154 | + return &PhysicalContainerVolume{} |
| 155 | +} |
| 156 | + |
| 157 | +func (pv *PhysicalContainerVolume) NewList() runtime.Object { |
| 158 | + return &PhysicalContainerVolumeList{} |
| 159 | +} |
| 160 | + |
| 161 | +func (pv *PhysicalContainerVolume) IsStorageVersion() bool { |
| 162 | + return true |
| 163 | +} |
| 164 | + |
| 165 | +func (pv *PhysicalContainerVolume) NamespaceScoped() bool { |
| 166 | + return true |
| 167 | +} |
| 168 | + |
| 169 | +func (pv *PhysicalContainerVolume) ShortNames() []string { |
| 170 | + return []string{"pcv"} |
| 171 | +} |
| 172 | + |
| 173 | +func (pv *PhysicalContainerVolume) NamespacedName() types.NamespacedName { |
| 174 | + return NamespacedName(pv) |
| 175 | +} |
| 176 | + |
| 177 | +func (pv *PhysicalContainerVolume) Validate(ctx context.Context) field.ErrorList { |
| 178 | + errorList := ValidateNamespacedResourceMetadata(pv) |
| 179 | + specPath := field.NewPath("spec") |
| 180 | + |
| 181 | + if commonapi.ResourceCreationProhibited.Load() && pv.DeletionTimestamp.IsZero() { |
| 182 | + errorList = append(errorList, field.Forbidden(nil, commonapi.ErrResourceCreationProhibited.Error())) |
| 183 | + } |
| 184 | + |
| 185 | + errorList = append(errorList, commonapi.ValidateAnnotationsSize(pv.Annotations, field.NewPath("metadata", "annotations"))...) |
| 186 | + |
| 187 | + if pv.Spec.VolumeID != "" { |
| 188 | + if strings.TrimSpace(pv.Spec.VolumeID) != pv.Spec.VolumeID { |
| 189 | + errorList = append(errorList, field.Invalid(specPath.Child("volumeID"), pv.Spec.VolumeID, "volumeID must not have leading or trailing whitespace")) |
| 190 | + } |
| 191 | + if pv.Spec.VolumeName != "" { |
| 192 | + errorList = append(errorList, field.Forbidden(specPath.Child("volumeName"), "volumeName cannot be set when volumeID is set")) |
| 193 | + } |
| 194 | + if len(pv.Spec.Labels) > 0 { |
| 195 | + errorList = append(errorList, field.Forbidden(specPath.Child("labels"), "labels cannot be set when volumeID is set")) |
| 196 | + } |
| 197 | + return errorList |
| 198 | + } |
| 199 | + |
| 200 | + if strings.TrimSpace(pv.Spec.VolumeName) == "" { |
| 201 | + errorList = append(errorList, field.Required(specPath.Child("volumeName"), "volumeName must be set when volumeID is omitted")) |
| 202 | + } else if strings.TrimSpace(pv.Spec.VolumeName) != pv.Spec.VolumeName { |
| 203 | + errorList = append(errorList, field.Invalid(specPath.Child("volumeName"), pv.Spec.VolumeName, "volumeName must not have leading or trailing whitespace")) |
| 204 | + } |
| 205 | + |
| 206 | + errorList = append(errorList, validateLabels(pv.Spec.Labels, specPath.Child("labels"))...) |
| 207 | + return errorList |
| 208 | +} |
| 209 | + |
| 210 | +// ValidateUpdate freezes the spec of an existing PhysicalContainerVolume. |
| 211 | +func (pv *PhysicalContainerVolume) ValidateUpdate(ctx context.Context, old runtime.Object) field.ErrorList { |
| 212 | + errorList := field.ErrorList{} |
| 213 | + |
| 214 | + oldPhysicalContainerVolume := old.(*PhysicalContainerVolume) |
| 215 | + if !reflect.DeepEqual(oldPhysicalContainerVolume.Spec, pv.Spec) { |
| 216 | + errorList = append(errorList, field.Forbidden(field.NewPath("spec"), "spec is immutable")) |
| 217 | + } |
| 218 | + |
| 219 | + return errorList |
| 220 | +} |
| 221 | + |
| 222 | +// PhysicalContainerVolumeList contains a list of PhysicalContainerVolume instances. |
| 223 | +// +k8s:openapi-gen=true |
| 224 | +// +kubebuilder:object:root=true |
| 225 | +type PhysicalContainerVolumeList struct { |
| 226 | + metav1.TypeMeta `json:",inline"` |
| 227 | + metav1.ListMeta `json:"metadata,omitempty"` |
| 228 | + Items []PhysicalContainerVolume `json:"items"` |
| 229 | +} |
| 230 | + |
| 231 | +func (pvl *PhysicalContainerVolumeList) GetListMeta() *metav1.ListMeta { |
| 232 | + return &pvl.ListMeta |
| 233 | +} |
| 234 | + |
| 235 | +func (pvl *PhysicalContainerVolumeList) ItemCount() uint32 { |
| 236 | + return uint32(len(pvl.Items)) |
| 237 | +} |
| 238 | + |
| 239 | +func (pvl *PhysicalContainerVolumeList) GetItems() []*PhysicalContainerVolume { |
| 240 | + retval := make([]*PhysicalContainerVolume, len(pvl.Items)) |
| 241 | + for i := range pvl.Items { |
| 242 | + retval[i] = &pvl.Items[i] |
| 243 | + } |
| 244 | + return retval |
| 245 | +} |
| 246 | + |
| 247 | +func init() { |
| 248 | + SchemeBuilder.Register(&PhysicalContainerVolume{}, &PhysicalContainerVolumeList{}) |
| 249 | +} |
| 250 | + |
| 251 | +// Ensure types support interfaces expected by our API server. |
| 252 | +var _ apiserver_resource.Object = (*PhysicalContainerVolume)(nil) |
| 253 | +var _ apiserver_resource.ObjectWithStatusSubResource = (*PhysicalContainerVolume)(nil) |
| 254 | +var _ apiserver_resource.StatusSubResource = (*PhysicalContainerVolumeStatus)(nil) |
| 255 | +var _ apiserver_resource.ObjectList = (*PhysicalContainerVolumeList)(nil) |
| 256 | +var _ commonapi.ListWithObjectItems[PhysicalContainerVolume, *PhysicalContainerVolume] = (*PhysicalContainerVolumeList)(nil) |
| 257 | +var _ apiserver_resourcerest.ShortNamesProvider = (*PhysicalContainerVolume)(nil) |
| 258 | +var _ apiserver_resourcestrategy.Validater = (*PhysicalContainerVolume)(nil) |
| 259 | +var _ apiserver_resourcestrategy.ValidateUpdater = (*PhysicalContainerVolume)(nil) |
0 commit comments