diff --git a/api/v4/app_types.go b/api/v4/app_types.go new file mode 100644 index 000000000..5cacd8875 --- /dev/null +++ b/api/v4/app_types.go @@ -0,0 +1,138 @@ +// Copyright (c) 2018-2026 Splunk Inc. All rights reserved. + +// 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 v4 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // AppPausedAnnotation is the annotation that pauses the reconciliation (triggers + // an immediate requeue) + AppPausedAnnotation = "app.enterprise.splunk.com/paused" +) + +// AppTargetRef defines the target environment the app should bind to. +type AppTargetRef struct { + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Kind string `json:"kind"` + + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` +} + +// AppSourceRef defines the AppSource backing this App - for now its a stub implementation. +type AppSourceRef struct { + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` +} + +// AppPackageSpec defines the package location within the source. +type AppPackageSpec struct { + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Path string `json:"path"` +} + +// AppSpec defines the desired state of App. +type AppSpec struct { + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + AppID string `json:"appID"` + + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Version string `json:"version"` + + // +kubebuilder:validation:Required + TargetRef AppTargetRef `json:"targetRef"` + + // +kubebuilder:validation:Required + SourceRef AppSourceRef `json:"sourceRef"` + + // +kubebuilder:validation:Required + Package AppPackageSpec `json:"package"` + + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Scope string `json:"scope"` +} + +// AppStatus defines the observed state of App. +type AppStatus struct { + // Phase of the app resource + Phase Phase `json:"phase,omitempty"` + + // Auxillary message describing CR status + Message string `json:"message,omitempty"` + + // InstalledVersion is the app version installed on target + InstalledVersion string `json:"installedVersion,omitempty"` + + // Artifact tracks the resolved app package details + Artifact *AppArtifactStatus `json:"artifact,omitempty"` + + // ObservedGeneration tracks the latest reconciled generation + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Conditions represent the latest available observations of the app + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// AppArtifactStatus defines resolved app artifact details. +type AppArtifactStatus struct { + // ResolvedPath is the resolved artifact path within the source + ResolvedPath string `json:"resolvedPath,omitempty"` + + // Etag is the artifact hash or ETag used for change detection + Etag string `json:"etag,omitempty"` + + // LastFetchedTime is the last time the artifact was fetched + LastFetchedTime metav1.Time `json:"lastFetchedTime,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// App is the Schema for the apps API. +// +k8s:openapi-gen=true +// +kubebuilder:resource:path=apps,scope=Namespaced +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Status of app" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age of app resource" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.message",description="Auxillary message describing CR status" +// +kubebuilder:storageversion +type App struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + Spec AppSpec `json:"spec"` + Status AppStatus `json:"status,omitempty,omitzero"` +} + +// +kubebuilder:object:root=true + +// AppList contains a list of App. +type AppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []App `json:"items"` +} + +func init() { + SchemeBuilder.Register(&App{}, &AppList{}) +} diff --git a/api/v4/zz_generated.deepcopy.go b/api/v4/zz_generated.deepcopy.go index 7ae136536..31f7817c8 100644 --- a/api/v4/zz_generated.deepcopy.go +++ b/api/v4/zz_generated.deepcopy.go @@ -21,10 +21,54 @@ limitations under the License. package v4 import ( - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *App) DeepCopyInto(out *App) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new App. +func (in *App) DeepCopy() *App { + if in == nil { + return nil + } + out := new(App) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *App) 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 *AppArtifactStatus) DeepCopyInto(out *AppArtifactStatus) { + *out = *in + in.LastFetchedTime.DeepCopyInto(&out.LastFetchedTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppArtifactStatus. +func (in *AppArtifactStatus) DeepCopy() *AppArtifactStatus { + if in == nil { + return nil + } + out := new(AppArtifactStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppDeploymentContext) DeepCopyInto(out *AppDeploymentContext) { *out = *in @@ -96,6 +140,53 @@ func (in *AppFrameworkSpec) DeepCopy() *AppFrameworkSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppList) DeepCopyInto(out *AppList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]App, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppList. +func (in *AppList) DeepCopy() *AppList { + if in == nil { + return nil + } + out := new(AppList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppList) 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 *AppPackageSpec) DeepCopyInto(out *AppPackageSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppPackageSpec. +func (in *AppPackageSpec) DeepCopy() *AppPackageSpec { + if in == nil { + return nil + } + out := new(AppPackageSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppSourceDefaultSpec) DeepCopyInto(out *AppSourceDefaultSpec) { *out = *in @@ -112,6 +203,21 @@ func (in *AppSourceDefaultSpec) DeepCopy() *AppSourceDefaultSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppSourceRef) DeepCopyInto(out *AppSourceRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppSourceRef. +func (in *AppSourceRef) DeepCopy() *AppSourceRef { + if in == nil { + return nil + } + out := new(AppSourceRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppSourceSpec) DeepCopyInto(out *AppSourceSpec) { *out = *in @@ -128,6 +234,24 @@ func (in *AppSourceSpec) DeepCopy() *AppSourceSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppSpec) DeepCopyInto(out *AppSpec) { + *out = *in + out.TargetRef = in.TargetRef + out.SourceRef = in.SourceRef + out.Package = in.Package +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppSpec. +func (in *AppSpec) DeepCopy() *AppSpec { + if in == nil { + return nil + } + out := new(AppSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppSrcDeployInfo) DeepCopyInto(out *AppSrcDeployInfo) { *out = *in @@ -150,6 +274,48 @@ func (in *AppSrcDeployInfo) DeepCopy() *AppSrcDeployInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppStatus) DeepCopyInto(out *AppStatus) { + *out = *in + if in.Artifact != nil { + in, out := &in.Artifact, &out.Artifact + *out = new(AppArtifactStatus) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppStatus. +func (in *AppStatus) DeepCopy() *AppStatus { + if in == nil { + return nil + } + out := new(AppStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppTargetRef) DeepCopyInto(out *AppTargetRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppTargetRef. +func (in *AppTargetRef) DeepCopy() *AppTargetRef { + if in == nil { + return nil + } + out := new(AppTargetRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BundlePushInfo) DeepCopyInto(out *BundlePushInfo) { *out = *in @@ -306,7 +472,7 @@ func (in *CommonSplunkSpec) DeepCopyInto(out *CommonSplunkSpec) { out.VarVolumeStorageConfig = in.VarVolumeStorageConfig if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes - *out = make([]v1.Volume, len(*in)) + *out = make([]corev1.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -318,7 +484,7 @@ func (in *CommonSplunkSpec) DeepCopyInto(out *CommonSplunkSpec) { out.MonitoringConsoleRef = in.MonitoringConsoleRef if in.ExtraEnv != nil { in, out := &in.ExtraEnv, &out.ExtraEnv - *out = make([]v1.EnvVar, len(*in)) + *out = make([]corev1.EnvVar, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -340,7 +506,7 @@ func (in *CommonSplunkSpec) DeepCopyInto(out *CommonSplunkSpec) { } if in.ImagePullSecrets != nil { in, out := &in.ImagePullSecrets, &out.ImagePullSecrets - *out = make([]v1.LocalObjectReference, len(*in)) + *out = make([]corev1.LocalObjectReference, len(*in)) copy(*out, *in) } } @@ -1198,7 +1364,7 @@ func (in *SearchHeadClusterSpec) DeepCopyInto(out *SearchHeadClusterSpec) { in.DeployerResourceSpec.DeepCopyInto(&out.DeployerResourceSpec) if in.DeployerNodeAffinity != nil { in, out := &in.DeployerNodeAffinity, &out.DeployerNodeAffinity - *out = new(v1.NodeAffinity) + *out = new(corev1.NodeAffinity) (*in).DeepCopyInto(*out) } } @@ -1284,7 +1450,7 @@ func (in *Spec) DeepCopyInto(out *Spec) { in.Affinity.DeepCopyInto(&out.Affinity) if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) + *out = make([]corev1.Toleration, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -1293,7 +1459,7 @@ func (in *Spec) DeepCopyInto(out *Spec) { in.ServiceTemplate.DeepCopyInto(&out.ServiceTemplate) if in.TopologySpreadConstraints != nil { in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints - *out = make([]v1.TopologySpreadConstraint, len(*in)) + *out = make([]corev1.TopologySpreadConstraint, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/config/crd/bases/enterprise.splunk.com_apps.yaml b/config/crd/bases/enterprise.splunk.com_apps.yaml new file mode 100644 index 000000000..694aa6d6c --- /dev/null +++ b/config/crd/bases/enterprise.splunk.com_apps.yaml @@ -0,0 +1,210 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: apps.enterprise.splunk.com +spec: + group: enterprise.splunk.com + names: + kind: App + listKind: AppList + plural: apps + singular: app + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status of app + jsonPath: .status.phase + name: Phase + type: string + - description: Age of app resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Auxillary message describing CR status + jsonPath: .status.message + name: Message + type: string + name: v4 + schema: + openAPIV3Schema: + description: App is the Schema for the apps API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: AppSpec defines the desired state of App. + properties: + appID: + minLength: 1 + type: string + package: + description: AppPackageSpec defines the package location within the + source. + properties: + path: + minLength: 1 + type: string + required: + - path + type: object + scope: + minLength: 1 + type: string + sourceRef: + description: AppSourceRef defines the AppSource backing this App - + for now its a stub implementation. + properties: + name: + minLength: 1 + type: string + required: + - name + type: object + targetRef: + description: AppTargetRef defines the target environment the app should + bind to. + properties: + kind: + minLength: 1 + type: string + name: + minLength: 1 + type: string + required: + - kind + - name + type: object + version: + minLength: 1 + type: string + required: + - appID + - package + - scope + - sourceRef + - targetRef + - version + type: object + status: + description: AppStatus defines the observed state of App. + properties: + artifact: + description: Artifact tracks the resolved app package details + properties: + etag: + description: Etag is the artifact hash or ETag used for change + detection + type: string + lastFetchedTime: + description: LastFetchedTime is the last time the artifact was + fetched + format: date-time + type: string + resolvedPath: + description: ResolvedPath is the resolved artifact path within + the source + type: string + type: object + conditions: + description: Conditions represent the latest available observations + of the app + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + installedVersion: + description: InstalledVersion is the app version installed on target + type: string + message: + description: Auxillary message describing CR status + type: string + observedGeneration: + description: ObservedGeneration tracks the latest reconciled generation + format: int64 + type: integer + phase: + description: Phase of the app resource + enum: + - Pending + - Ready + - Updating + - ScalingUp + - ScalingDown + - Terminating + - Error + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 21dd480ce..6c7ead86d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,6 +2,7 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: +- bases/enterprise.splunk.com_apps.yaml - bases/enterprise.splunk.com_clustermanagers.yaml - bases/enterprise.splunk.com_clustermasters.yaml - bases/enterprise.splunk.com_indexerclusters.yaml diff --git a/config/samples/enterprise_v4_app.yaml b/config/samples/enterprise_v4_app.yaml new file mode 100644 index 000000000..ccb78da34 --- /dev/null +++ b/config/samples/enterprise_v4_app.yaml @@ -0,0 +1,15 @@ +apiVersion: enterprise.splunk.com/v4 +kind: App +metadata: + name: app-sample +spec: + appID: sample-app + version: "1.0.0" + targetRef: + kind: Standalone + name: standalone-sample + sourceRef: + name: appsource-sample + package: + path: apps/sample-app.tgz + scope: local diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 34c05ab05..6684180f8 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,5 +1,6 @@ ## Append samples you want in your CSV to this file as resources ## resources: +- enterprise_v4_app.yaml - enterprise_v3_clustermaster.yaml - enterprise_v3_indexercluster.yaml - enterprise_v3_licensemaster.yaml