diff --git a/PROJECT b/PROJECT index d212824f..8882d095 100644 --- a/PROJECT +++ b/PROJECT @@ -509,4 +509,12 @@ resources: kind: StandaloneDatabase path: github.com/kloudlite/operator/apis/mongodb.msvc/v1 version: v1 +- api: + crdVersion: v1 + namespaced: true + domain: kloudlite.io + group: crds + kind: ServiceIntercept + path: github.com/kloudlite/operator/apis/crds/v1 + version: v1 version: "3" diff --git a/apis/crds/v1/serviceintercept_types.go b/apis/crds/v1/serviceintercept_types.go new file mode 100644 index 00000000..1d0af0bc --- /dev/null +++ b/apis/crds/v1/serviceintercept_types.go @@ -0,0 +1,74 @@ +package v1 + +import ( + "github.com/kloudlite/operator/pkg/constants" + rApi "github.com/kloudlite/operator/pkg/operator" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SvcInterceptPortMappings struct { + ContainerPort uint16 `json:"containerPort"` + ServicePort uint16 `json:"servicePort"` +} + +type ServiceInterceptSpec struct { + ToAddr string `json:"toAddress"` + PortMappings []SvcInterceptPortMappings `json:"portMappings"` +} + +type ServiceInterceptStatus struct { + rApi.Status `json:",inline"` + Selector map[string]string `json:"selector,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:JSONPath=".status.lastReconcileTime",name=Seen,type=date +// +kubebuilder:printcolumn:JSONPath=".metadata.annotations.kloudlite\\.io\\/operator\\.checks",name=Checks,type=string +// +kubebuilder:printcolumn:JSONPath=".metadata.annotations.kloudlite\\.io\\/operator\\.resource\\.ready",name=Ready,type=string +// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name=Age,type=date +// ServiceIntercept is the Schema for the serviceintercepts API +type ServiceIntercept struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ServiceInterceptSpec `json:"spec,omitempty"` + Status ServiceInterceptStatus `json:"status,omitempty" graphql:"noinput"` +} + +func (svci *ServiceIntercept) EnsureGVK() { + if svci != nil { + svci.SetGroupVersionKind(GroupVersion.WithKind("ServiceIntercept")) + } +} + +func (svci *ServiceIntercept) GetStatus() *rApi.Status { + return &svci.Status.Status +} + +func (svci *ServiceIntercept) GetEnsuredLabels() map[string]string { + m := map[string]string{ + "kloudlite.io/svci.name": svci.Name, + } + + return m +} + +func (svci *ServiceIntercept) GetEnsuredAnnotations() map[string]string { + return map[string]string{ + constants.AnnotationKeys.GroupVersionKind: GroupVersion.WithKind("ServiceIntercept").String(), + } +} + +//+kubebuilder:object:root=true + +// ServiceInterceptList contains a list of ServiceIntercept +type ServiceInterceptList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ServiceIntercept `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ServiceIntercept{}, &ServiceInterceptList{}) +} diff --git a/apis/crds/v1/zz_generated.deepcopy.go b/apis/crds/v1/zz_generated.deepcopy.go index 18f8fba9..1bebffd3 100644 --- a/apis/crds/v1/zz_generated.deepcopy.go +++ b/apis/crds/v1/zz_generated.deepcopy.go @@ -1816,6 +1816,108 @@ func (in *RouterSpec) DeepCopy() *RouterSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceIntercept) DeepCopyInto(out *ServiceIntercept) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceIntercept. +func (in *ServiceIntercept) DeepCopy() *ServiceIntercept { + if in == nil { + return nil + } + out := new(ServiceIntercept) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceIntercept) 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 *ServiceInterceptList) DeepCopyInto(out *ServiceInterceptList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ServiceIntercept, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceInterceptList. +func (in *ServiceInterceptList) DeepCopy() *ServiceInterceptList { + if in == nil { + return nil + } + out := new(ServiceInterceptList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceInterceptList) 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 *ServiceInterceptSpec) DeepCopyInto(out *ServiceInterceptSpec) { + *out = *in + if in.PortMappings != nil { + in, out := &in.PortMappings, &out.PortMappings + *out = make([]SvcInterceptPortMappings, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceInterceptSpec. +func (in *ServiceInterceptSpec) DeepCopy() *ServiceInterceptSpec { + if in == nil { + return nil + } + out := new(ServiceInterceptSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceInterceptStatus) DeepCopyInto(out *ServiceInterceptStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceInterceptStatus. +func (in *ServiceInterceptStatus) DeepCopy() *ServiceInterceptStatus { + if in == nil { + return nil + } + out := new(ServiceInterceptStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceTemplate) DeepCopyInto(out *ServiceTemplate) { *out = *in @@ -1858,6 +1960,21 @@ func (in *ShellProbe) DeepCopy() *ShellProbe { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SvcInterceptPortMappings) DeepCopyInto(out *SvcInterceptPortMappings) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SvcInterceptPortMappings. +func (in *SvcInterceptPortMappings) DeepCopy() *SvcInterceptPortMappings { + if in == nil { + return nil + } + out := new(SvcInterceptPortMappings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TcpProbe) DeepCopyInto(out *TcpProbe) { *out = *in diff --git a/cmd/agent-operator/main.go b/cmd/agent-operator/main.go index 011a48c7..c17e718f 100644 --- a/cmd/agent-operator/main.go +++ b/cmd/agent-operator/main.go @@ -20,6 +20,7 @@ import ( routers "github.com/kloudlite/operator/operators/routers/controller" // nodepool "github.com/kloudlite/operator/operators/nodepool/controller" // wireguard "github.com/kloudlite/operator/operators/wireguard/controller" + serviceIntercept "github.com/kloudlite/operator/operators/service-intercept/controller" ) func main() { @@ -50,5 +51,7 @@ func main() { networkingv1.RegisterInto(mgr) + serviceIntercept.RegisterInto(mgr) + mgr.Start() } diff --git a/cmd/platform-operator/main.go b/cmd/platform-operator/main.go index f1163fb0..c085c449 100644 --- a/cmd/platform-operator/main.go +++ b/cmd/platform-operator/main.go @@ -24,6 +24,7 @@ import ( msvcMysql "github.com/kloudlite/operator/operators/msvc-mysql/controller" msvcPostgres "github.com/kloudlite/operator/operators/msvc-postgres/controller" msvcRedis "github.com/kloudlite/operator/operators/msvc-redis/controller" + serviceIntercept "github.com/kloudlite/operator/operators/service-intercept/controller" ) func main() { @@ -42,6 +43,8 @@ func main() { lifecycle.RegisterInto(mgr) + serviceIntercept.RegisterInto(mgr) + // clusters.RegisterInto(mgr) // nodepool.RegisterInto(mgr) // MIGRATE // virtualMachine.RegisterInto(mgr) diff --git a/config/crd/bases/crds.kloudlite.io_serviceintercepts.yaml b/config/crd/bases/crds.kloudlite.io_serviceintercepts.yaml new file mode 100644 index 00000000..c0eb6901 --- /dev/null +++ b/config/crd/bases/crds.kloudlite.io_serviceintercepts.yaml @@ -0,0 +1,157 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: serviceintercepts.crds.kloudlite.io +spec: + group: crds.kloudlite.io + names: + kind: ServiceIntercept + listKind: ServiceInterceptList + plural: serviceintercepts + singular: serviceintercept + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastReconcileTime + name: Seen + type: date + - jsonPath: .metadata.annotations.kloudlite\.io\/operator\.checks + name: Checks + type: string + - jsonPath: .metadata.annotations.kloudlite\.io\/operator\.resource\.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ServiceIntercept is the Schema for the serviceintercepts 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: + properties: + portMappings: + items: + properties: + containerPort: + type: integer + servicePort: + type: integer + required: + - containerPort + - servicePort + type: object + type: array + toAddress: + type: string + required: + - portMappings + - toAddress + type: object + status: + properties: + checkList: + items: + properties: + debug: + type: boolean + description: + type: string + hide: + type: boolean + name: + type: string + title: + type: string + required: + - name + - title + type: object + type: array + checks: + additionalProperties: + properties: + debug: + type: string + error: + type: string + generation: + format: int64 + type: integer + info: + type: string + message: + type: string + startedAt: + format: date-time + type: string + state: + type: string + status: + type: boolean + required: + - status + type: object + type: object + isReady: + type: boolean + lastReadyGeneration: + format: int64 + type: integer + lastReconcileTime: + format: date-time + type: string + message: + type: object + x-kubernetes-preserve-unknown-fields: true + resources: + items: + 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 + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + type: array + selector: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index bacd51c9..27859474 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -56,6 +56,7 @@ resources: - bases/postgres.msvc.kloudlite.io_standalonedatabases.yaml - bases/mysql.msvc.kloudlite.io_standalonedatabases.yaml - bases/mongodb.msvc.kloudlite.io_standalonedatabases.yaml +- bases/crds.kloudlite.io_serviceintercepts.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -85,6 +86,7 @@ patchesStrategicMerge: #- patches/webhook_in_standalones.yaml #- patches/webhook_in_databases.yaml #- patches/webhook_in_standalonedatabases.yaml +#- patches/webhook_in_serviceintercepts.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -111,6 +113,7 @@ patchesStrategicMerge: #- patches/webhook_in_standalones.yaml #- patches/webhook_in_databases.yaml #- patches/webhook_in_standalonedatabases.yaml +#- patches/webhook_in_serviceintercepts.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # - patches/serverless.kloudlite.io_lambdas.yaml # - patches/cainjection_in_lambdas.yaml @@ -137,6 +140,7 @@ patchesStrategicMerge: #- patches/cainjection_in_standalones.yaml #- patches/cainjection_in_databases.yaml #- patches/cainjection_in_standalonedatabases.yaml +#- patches/cainjection_in_serviceintercepts.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_crds_serviceintercepts.yaml b/config/crd/patches/cainjection_in_crds_serviceintercepts.yaml new file mode 100644 index 00000000..c186160a --- /dev/null +++ b/config/crd/patches/cainjection_in_crds_serviceintercepts.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: serviceintercepts.crds.kloudlite.io diff --git a/config/crd/patches/webhook_in_crds_serviceintercepts.yaml b/config/crd/patches/webhook_in_crds_serviceintercepts.yaml new file mode 100644 index 00000000..1f500e80 --- /dev/null +++ b/config/crd/patches/webhook_in_crds_serviceintercepts.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: serviceintercepts.crds.kloudlite.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/crds_serviceintercept_editor_role.yaml b/config/rbac/crds_serviceintercept_editor_role.yaml new file mode 100644 index 00000000..bc0bf237 --- /dev/null +++ b/config/rbac/crds_serviceintercept_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit serviceintercepts. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: serviceintercept-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: app + app.kubernetes.io/part-of: app + app.kubernetes.io/managed-by: kustomize + name: serviceintercept-editor-role +rules: +- apiGroups: + - crds.kloudlite.io + resources: + - serviceintercepts + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - crds.kloudlite.io + resources: + - serviceintercepts/status + verbs: + - get diff --git a/config/rbac/crds_serviceintercept_viewer_role.yaml b/config/rbac/crds_serviceintercept_viewer_role.yaml new file mode 100644 index 00000000..48d209a6 --- /dev/null +++ b/config/rbac/crds_serviceintercept_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view serviceintercepts. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: serviceintercept-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: app + app.kubernetes.io/part-of: app + app.kubernetes.io/managed-by: kustomize + name: serviceintercept-viewer-role +rules: +- apiGroups: + - crds.kloudlite.io + resources: + - serviceintercepts + verbs: + - get + - list + - watch +- apiGroups: + - crds.kloudlite.io + resources: + - serviceintercepts/status + verbs: + - get diff --git a/config/samples/crds_v1_serviceintercept.yaml b/config/samples/crds_v1_serviceintercept.yaml new file mode 100644 index 00000000..ce64a1c6 --- /dev/null +++ b/config/samples/crds_v1_serviceintercept.yaml @@ -0,0 +1,12 @@ +apiVersion: crds.kloudlite.io/v1 +kind: ServiceIntercept +metadata: + labels: + app.kubernetes.io/name: serviceintercept + app.kubernetes.io/instance: serviceintercept-sample + app.kubernetes.io/part-of: app + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: app + name: serviceintercept-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4480faaa..e0903d2c 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -107,4 +107,5 @@ resources: - postgres.msvc_v1_standalonedatabase.yaml - mysql.msvc_v1_standalonedatabase.yaml - mongodb.msvc_v1_standalonedatabase.yaml +- crds_v1_serviceintercept.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/grpc-interfaces/grpc/messages/messages.pb.go b/grpc-interfaces/grpc/messages/messages.pb.go index dd2a3380..dc2ba41b 100644 --- a/grpc-interfaces/grpc/messages/messages.pb.go +++ b/grpc-interfaces/grpc/messages/messages.pb.go @@ -562,6 +562,53 @@ func (*Empty) Descriptor() ([]byte, []int) { return file_messages_proto_rawDescGZIP(), []int{8} } +type GatewayResource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Gateway []byte `protobuf:"bytes,1,opt,name=gateway,proto3" json:"gateway,omitempty"` +} + +func (x *GatewayResource) Reset() { + *x = GatewayResource{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GatewayResource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GatewayResource) ProtoMessage() {} + +func (x *GatewayResource) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GatewayResource.ProtoReflect.Descriptor instead. +func (*GatewayResource) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{9} +} + +func (x *GatewayResource) GetGateway() []byte { + if x != nil { + return x.Gateway + } + return nil +} + var File_messages_proto protoreflect.FileDescriptor var file_messages_proto_rawDesc = []byte{ @@ -623,40 +670,46 @@ var file_messages_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1c, 0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x32, 0xf5, 0x03, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x13, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x1a, 0x17, 0x2e, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x4f, 0x75, 0x74, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x11, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x1a, 0x12, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x4f, 0x75, 0x74, 0x22, 0x00, - 0x12, 0x22, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x07, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x00, 0x30, 0x01, 0x12, 0x24, 0x0a, 0x0c, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0a, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x61, 0x74, 0x61, - 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x1c, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x06, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x1f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x49, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x22, 0x2b, 0x0a, 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x32, 0xaf, 0x04, + 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x13, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x16, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x1a, 0x17, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x4f, 0x75, 0x74, + 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x11, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x1a, 0x12, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x4f, 0x75, 0x74, 0x22, 0x00, 0x12, 0x22, 0x0a, + 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x06, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x07, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x30, + 0x01, 0x12, 0x38, 0x0a, 0x1a, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x00, 0x12, 0x24, 0x0a, 0x0c, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0a, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x39, 0x0a, 0x1c, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x1f, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x49, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x1a, 0x52, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x49, 0x6e, - 0x66, 0x72, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x1e, - 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0f, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, - 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x1d, 0x0a, 0x04, 0x50, 0x69, 0x6e, - 0x67, 0x12, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x50, 0x69, 0x6e, 0x67, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x67, 0x72, 0x70, 0x63, - 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x1e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x43, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x1d, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x0b, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x00, 0x42, + 0x0f, 0x5a, 0x0d, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -671,7 +724,7 @@ func file_messages_proto_rawDescGZIP() []byte { return file_messages_proto_rawDescData } -var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_messages_proto_goTypes = []interface{}{ (*ValidateAccessTokenIn)(nil), // 0: ValidateAccessTokenIn (*ValidateAccessTokenOut)(nil), // 1: ValidateAccessTokenOut @@ -682,31 +735,34 @@ var file_messages_proto_goTypes = []interface{}{ (*ResourceUpdate)(nil), // 6: ResourceUpdate (*PingOutput)(nil), // 7: PingOutput (*Empty)(nil), // 8: Empty + (*GatewayResource)(nil), // 9: GatewayResource } var file_messages_proto_depIdxs = []int32{ - 0, // 0: MessageDispatchService.ValidateAccessToken:input_type -> ValidateAccessTokenIn - 2, // 1: MessageDispatchService.GetAccessToken:input_type -> GetAccessTokenIn - 8, // 2: MessageDispatchService.SendActions:input_type -> Empty - 5, // 3: MessageDispatchService.ReceiveError:input_type -> ErrorData - 6, // 4: MessageDispatchService.ReceiveConsoleResourceUpdate:input_type -> ResourceUpdate - 6, // 5: MessageDispatchService.ReceiveIotConsoleResourceUpdate:input_type -> ResourceUpdate - 6, // 6: MessageDispatchService.ReceiveInfraResourceUpdate:input_type -> ResourceUpdate - 6, // 7: MessageDispatchService.ReceiveContainerRegistryUpdate:input_type -> ResourceUpdate - 8, // 8: MessageDispatchService.Ping:input_type -> Empty - 1, // 9: MessageDispatchService.ValidateAccessToken:output_type -> ValidateAccessTokenOut - 3, // 10: MessageDispatchService.GetAccessToken:output_type -> GetAccessTokenOut - 4, // 11: MessageDispatchService.SendActions:output_type -> Action - 8, // 12: MessageDispatchService.ReceiveError:output_type -> Empty - 8, // 13: MessageDispatchService.ReceiveConsoleResourceUpdate:output_type -> Empty - 8, // 14: MessageDispatchService.ReceiveIotConsoleResourceUpdate:output_type -> Empty - 8, // 15: MessageDispatchService.ReceiveInfraResourceUpdate:output_type -> Empty - 8, // 16: MessageDispatchService.ReceiveContainerRegistryUpdate:output_type -> Empty - 7, // 17: MessageDispatchService.Ping:output_type -> PingOutput - 9, // [9:18] is the sub-list for method output_type - 0, // [0:9] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: MessageDispatchService.ValidateAccessToken:input_type -> ValidateAccessTokenIn + 2, // 1: MessageDispatchService.GetAccessToken:input_type -> GetAccessTokenIn + 8, // 2: MessageDispatchService.SendActions:input_type -> Empty + 8, // 3: MessageDispatchService.SendClusterGatewayResource:input_type -> Empty + 5, // 4: MessageDispatchService.ReceiveError:input_type -> ErrorData + 6, // 5: MessageDispatchService.ReceiveConsoleResourceUpdate:input_type -> ResourceUpdate + 6, // 6: MessageDispatchService.ReceiveIotConsoleResourceUpdate:input_type -> ResourceUpdate + 6, // 7: MessageDispatchService.ReceiveInfraResourceUpdate:input_type -> ResourceUpdate + 6, // 8: MessageDispatchService.ReceiveContainerRegistryUpdate:input_type -> ResourceUpdate + 8, // 9: MessageDispatchService.Ping:input_type -> Empty + 1, // 10: MessageDispatchService.ValidateAccessToken:output_type -> ValidateAccessTokenOut + 3, // 11: MessageDispatchService.GetAccessToken:output_type -> GetAccessTokenOut + 4, // 12: MessageDispatchService.SendActions:output_type -> Action + 9, // 13: MessageDispatchService.SendClusterGatewayResource:output_type -> GatewayResource + 8, // 14: MessageDispatchService.ReceiveError:output_type -> Empty + 8, // 15: MessageDispatchService.ReceiveConsoleResourceUpdate:output_type -> Empty + 8, // 16: MessageDispatchService.ReceiveIotConsoleResourceUpdate:output_type -> Empty + 8, // 17: MessageDispatchService.ReceiveInfraResourceUpdate:output_type -> Empty + 8, // 18: MessageDispatchService.ReceiveContainerRegistryUpdate:output_type -> Empty + 7, // 19: MessageDispatchService.Ping:output_type -> PingOutput + 10, // [10:20] is the sub-list for method output_type + 0, // [0:10] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_messages_proto_init() } @@ -823,6 +879,18 @@ func file_messages_proto_init() { return nil } } + file_messages_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GatewayResource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -830,7 +898,7 @@ func file_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_messages_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/grpc-interfaces/grpc/messages/messages_grpc.pb.go b/grpc-interfaces/grpc/messages/messages_grpc.pb.go index 802e98e1..df3d914a 100644 --- a/grpc-interfaces/grpc/messages/messages_grpc.pb.go +++ b/grpc-interfaces/grpc/messages/messages_grpc.pb.go @@ -22,6 +22,7 @@ const ( MessageDispatchService_ValidateAccessToken_FullMethodName = "/MessageDispatchService/ValidateAccessToken" MessageDispatchService_GetAccessToken_FullMethodName = "/MessageDispatchService/GetAccessToken" MessageDispatchService_SendActions_FullMethodName = "/MessageDispatchService/SendActions" + MessageDispatchService_SendClusterGatewayResource_FullMethodName = "/MessageDispatchService/SendClusterGatewayResource" MessageDispatchService_ReceiveError_FullMethodName = "/MessageDispatchService/ReceiveError" MessageDispatchService_ReceiveConsoleResourceUpdate_FullMethodName = "/MessageDispatchService/ReceiveConsoleResourceUpdate" MessageDispatchService_ReceiveIotConsoleResourceUpdate_FullMethodName = "/MessageDispatchService/ReceiveIotConsoleResourceUpdate" @@ -37,6 +38,7 @@ type MessageDispatchServiceClient interface { ValidateAccessToken(ctx context.Context, in *ValidateAccessTokenIn, opts ...grpc.CallOption) (*ValidateAccessTokenOut, error) GetAccessToken(ctx context.Context, in *GetAccessTokenIn, opts ...grpc.CallOption) (*GetAccessTokenOut, error) SendActions(ctx context.Context, in *Empty, opts ...grpc.CallOption) (MessageDispatchService_SendActionsClient, error) + SendClusterGatewayResource(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GatewayResource, error) ReceiveError(ctx context.Context, in *ErrorData, opts ...grpc.CallOption) (*Empty, error) ReceiveConsoleResourceUpdate(ctx context.Context, in *ResourceUpdate, opts ...grpc.CallOption) (*Empty, error) ReceiveIotConsoleResourceUpdate(ctx context.Context, in *ResourceUpdate, opts ...grpc.CallOption) (*Empty, error) @@ -103,6 +105,15 @@ func (x *messageDispatchServiceSendActionsClient) Recv() (*Action, error) { return m, nil } +func (c *messageDispatchServiceClient) SendClusterGatewayResource(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GatewayResource, error) { + out := new(GatewayResource) + err := c.cc.Invoke(ctx, MessageDispatchService_SendClusterGatewayResource_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *messageDispatchServiceClient) ReceiveError(ctx context.Context, in *ErrorData, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, MessageDispatchService_ReceiveError_FullMethodName, in, out, opts...) @@ -164,6 +175,7 @@ type MessageDispatchServiceServer interface { ValidateAccessToken(context.Context, *ValidateAccessTokenIn) (*ValidateAccessTokenOut, error) GetAccessToken(context.Context, *GetAccessTokenIn) (*GetAccessTokenOut, error) SendActions(*Empty, MessageDispatchService_SendActionsServer) error + SendClusterGatewayResource(context.Context, *Empty) (*GatewayResource, error) ReceiveError(context.Context, *ErrorData) (*Empty, error) ReceiveConsoleResourceUpdate(context.Context, *ResourceUpdate) (*Empty, error) ReceiveIotConsoleResourceUpdate(context.Context, *ResourceUpdate) (*Empty, error) @@ -186,6 +198,9 @@ func (UnimplementedMessageDispatchServiceServer) GetAccessToken(context.Context, func (UnimplementedMessageDispatchServiceServer) SendActions(*Empty, MessageDispatchService_SendActionsServer) error { return status.Errorf(codes.Unimplemented, "method SendActions not implemented") } +func (UnimplementedMessageDispatchServiceServer) SendClusterGatewayResource(context.Context, *Empty) (*GatewayResource, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendClusterGatewayResource not implemented") +} func (UnimplementedMessageDispatchServiceServer) ReceiveError(context.Context, *ErrorData) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method ReceiveError not implemented") } @@ -275,6 +290,24 @@ func (x *messageDispatchServiceSendActionsServer) Send(m *Action) error { return x.ServerStream.SendMsg(m) } +func _MessageDispatchService_SendClusterGatewayResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessageDispatchServiceServer).SendClusterGatewayResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MessageDispatchService_SendClusterGatewayResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessageDispatchServiceServer).SendClusterGatewayResource(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _MessageDispatchService_ReceiveError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ErrorData) if err := dec(in); err != nil { @@ -398,6 +431,10 @@ var MessageDispatchService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetAccessToken", Handler: _MessageDispatchService_GetAccessToken_Handler, }, + { + MethodName: "SendClusterGatewayResource", + Handler: _MessageDispatchService_SendClusterGatewayResource_Handler, + }, { MethodName: "ReceiveError", Handler: _MessageDispatchService_ReceiveError_Handler, diff --git a/grpc-interfaces/messages.proto b/grpc-interfaces/messages.proto index 04cd15e4..fc1343d9 100644 --- a/grpc-interfaces/messages.proto +++ b/grpc-interfaces/messages.proto @@ -8,6 +8,8 @@ service MessageDispatchService { rpc SendActions (Empty) returns (stream Action) {} + rpc SendClusterGatewayResource(Empty) returns (GatewayResource) {} + rpc ReceiveError(ErrorData) returns (Empty) {} rpc ReceiveConsoleResourceUpdate (ResourceUpdate) returns (Empty) {} @@ -75,3 +77,7 @@ message PingOutput { } message Empty {} + +message GatewayResource { + bytes gateway = 1; +} diff --git a/main.go b/main.go index 6fa5fbb4..2f6355b3 100644 --- a/main.go +++ b/main.go @@ -5,4 +5,3 @@ import "fmt" func main() { fmt.Println("Operator-SDK") } - diff --git a/operators/app-n-lambda/internal/controllers/app/controller.go b/operators/app-n-lambda/internal/controllers/app/controller.go index e6d1bf2d..df3f1691 100644 --- a/operators/app-n-lambda/internal/controllers/app/controller.go +++ b/operators/app-n-lambda/internal/controllers/app/controller.go @@ -61,8 +61,7 @@ const ( AppInterceptCreated string = "app-intercept-created" - DefaultsPatched string = "defaults-patched" - AppRouterReady string = "app-router-ready" + AppRouterReady string = "app-router-ready" ) var DeleteChecklist = []rApi.CheckMeta{ @@ -101,7 +100,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. return "Deployment Created" }(), Hide: req.Object.IsInterceptEnabled()}, {Name: DeploymentReady, Title: "Deployment Ready", Hide: req.Object.IsInterceptEnabled()}, - {Name: HPAConfigured, Title: "Horizontal pod autoscaling configured", Hide: req.Object.IsInterceptEnabled() || req.Object.IsHPAEnabled()}, + {Name: HPAConfigured, Title: "Horizontal pod autoscaling configured", Hide: req.Object.IsInterceptEnabled() || !req.Object.IsHPAEnabled()}, {Name: AppInterceptCreated, Title: "App Intercept Created", Hide: !req.Object.IsInterceptEnabled()}, {Name: AppRouterReady, Title: "App Router Ready", Hide: req.Object.Spec.Router == nil}, }); !step.ShouldProceed() { @@ -191,6 +190,16 @@ func (r *Reconciler) reconLabellingImages(req *rApi.Request[*crdsv1.App]) stepRe return check.Completed() } +func getServiceAccountName(obj *crdsv1.App) string { + if obj.Spec.ServiceAccount != "" { + return obj.Spec.ServiceAccount + } + if _, ok := obj.GetLabels()[constants.EnvNameKey]; ok { + return "kloudlite-env-sa" + } + return "" +} + func (r *Reconciler) ensureDeploymentThings(req *rApi.Request[*crdsv1.App]) stepResult.Result { ctx, obj := req.Context(), req.Object check := rApi.NewRunningCheck(DeploymentSvcCreated, req) @@ -199,14 +208,18 @@ func (r *Reconciler) ensureDeploymentThings(req *rApi.Request[*crdsv1.App]) step b, err := templates.ParseBytes( r.appDeploymentTemplate, map[string]any{ - "object": obj, - "volumes": volumes, - "volume-mounts": vMounts, - "owner-refs": []metav1.OwnerReference{fn.AsOwner(obj, true)}, - "account-name": obj.GetAnnotations()[constants.AccountNameKey], - "pod-labels": fn.MapFilterWithPrefix(obj.GetLabels(), "kloudlite.io/"), + "object": obj, + "volumes": volumes, + "volume-mounts": vMounts, + "service-account-name": getServiceAccountName(obj), + "owner-refs": []metav1.OwnerReference{fn.AsOwner(obj, true)}, + "account-name": obj.GetAnnotations()[constants.AccountNameKey], + "pod-labels": fn.MapFilterWithPrefix(obj.GetLabels(), "kloudlite.io/"), "pod-annotations": fn.MapFilter(obj.GetAnnotations(), func(k string, _ string) bool { + if k == "kloudlite.io/last-applied" { + return false + } return strings.HasPrefix(k, "kloudlite.io/") && !strings.HasPrefix(k, "kloudlite.io/operator.") }, ), @@ -323,10 +336,6 @@ func (r *Reconciler) ensureHPA(req *rApi.Request[*crdsv1.App]) stepResult.Result check := rApi.NewRunningCheck(HPAConfigured, req) - // if obj.IsInterceptEnabled() { - // return check.Completed() - // } - hpaVars := templates.HPATemplateVars{ Metadata: metav1.ObjectMeta{ Name: obj.Name, @@ -338,17 +347,7 @@ func (r *Reconciler) ensureHPA(req *rApi.Request[*crdsv1.App]) stepResult.Result HPA: obj.Spec.Hpa, } - // if obj.Spec.Intercept != nil && obj.Spec.Intercept.Enabled { - // if obj.Spec.Hpa != nil && obj.Spec.Hpa.Enabled { - // hpaVars.HPA.MinReplicas = 0 - // hpaVars.HPA.MaxReplicas = 0 - // } - // } - - isInterceptEnabled := obj.Spec.Intercept != nil && obj.Spec.Intercept.Enabled - isHPAEnabled := obj.Spec.Hpa != nil && obj.Spec.Hpa.Enabled - - if isInterceptEnabled || !isHPAEnabled { + if obj.IsInterceptEnabled() || !obj.IsHPAEnabled() { hpa, err := rApi.Get(ctx, r.Client, fn.NN(hpaVars.Metadata.Namespace, hpaVars.Metadata.Name), &autoscalingv2.HorizontalPodAutoscaler{}) if err != nil { if apiErrors.IsNotFound(err) { @@ -391,6 +390,10 @@ func (r *Reconciler) checkDeploymentReady(req *rApi.Request[*crdsv1.App]) stepRe return check.Failed(err) } + if deployment.Spec.Template.Spec.ServiceAccountName != getServiceAccountName(obj) { + return check.StillRunning(r.Delete(ctx, deployment)) + } + for _, c := range deployment.Status.Conditions { switch c.Type { case appsv1.DeploymentAvailable: diff --git a/operators/app-n-lambda/internal/templates/app-deployment-svc-hpa.yml.tpl b/operators/app-n-lambda/internal/templates/app-deployment-svc-hpa.yml.tpl index 73276b6d..73091ea9 100644 --- a/operators/app-n-lambda/internal/templates/app-deployment-svc-hpa.yml.tpl +++ b/operators/app-n-lambda/internal/templates/app-deployment-svc-hpa.yml.tpl @@ -8,6 +8,8 @@ {{- /* {{- $clusterDnsSuffix := get . "cluster-dns-suffix" | default "cluster.local"}} */}} +{{- $serviceAccountName := get . "service-account-name" | default "" }} + {{- with $obj }} {{- $isIntercepted := (and .Spec.Intercept .Spec.Intercept.Enabled) }} @@ -40,7 +42,7 @@ spec: {{ $podLabels | toYAML | nindent 8 }} annotations: {{$podAnnotations | toYAML | nindent 8 }} spec: - serviceAccountName: {{.Spec.ServiceAccount}} + serviceAccountName: {{$serviceAccountName}} nodeSelector: {{if .Spec.NodeSelector}}{{ .Spec.NodeSelector | toYAML | nindent 8 }}{{end}} {{- if .Spec.Region}} kloudlite.io/region: {{.Spec.Region | squote}} @@ -60,20 +62,6 @@ spec: dnsPolicy: ClusterFirst - {{- /* affinity: */ -}} - {{- /* nodeAffinity: */ -}} - {{- /* preferredDuringSchedulingIgnoredDuringExecution: */ -}} - {{- /* {{- $nWeight := 30 -}} */ -}} - {{- /* {{- range $weight := Iterate $nWeight }} */ -}} - {{- /* - weight: {{ sub $nWeight $weight }} */ -}} - {{- /* preference: */ -}} - {{- /* matchExpressions: */ -}} - {{- /* - key: kloudlite.io/node-index */ -}} - {{- /* operator: In */ -}} - {{- /* values: */ -}} - {{- /* - {{$weight | squote}} */ -}} - {{- /* {{- end }} */ -}} - {{- if .Spec.Containers }} {{- $myDict := dict "containers" .Spec.Containers "volumeMounts" $vMounts }} containers: {{- include "TemplateContainer" $myDict | nindent 8 }} diff --git a/operators/networking/internal/cmd/ip-binding-controller/pod-pinger/controller.go b/operators/networking/internal/cmd/ip-binding-controller/pod-pinger/controller.go index e0b794a7..4e2ad027 100644 --- a/operators/networking/internal/cmd/ip-binding-controller/pod-pinger/controller.go +++ b/operators/networking/internal/cmd/ip-binding-controller/pod-pinger/controller.go @@ -5,17 +5,18 @@ import ( "fmt" "log/slog" "os" - "os/exec" "strings" "time" networkingv1 "github.com/kloudlite/operator/apis/networking/v1" "github.com/kloudlite/operator/operators/networking/internal/cmd/ip-binding-controller/env" "github.com/kloudlite/operator/pkg/constants" + "github.com/kloudlite/operator/pkg/errors" fn "github.com/kloudlite/operator/pkg/functions" "github.com/kloudlite/operator/pkg/kubectl" "github.com/kloudlite/operator/pkg/logging" rApi "github.com/kloudlite/operator/pkg/operator" + "github.com/prometheus-community/pro-bing" corev1 "k8s.io/api/core/v1" apiLabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -156,12 +157,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. return requeue(fmt.Errorf("multiple pod bindings with same reservation found, exiting")) } - if out, err := exec.CommandContext(ctx, "timeout", "5", "ping", "-c", "1", pblist.Items[0].Spec.GlobalIP).CombinedOutput(); err != nil { - logger.Error("failed to ping", "global-ip", pblist.Items[0].Spec.GlobalIP, "output", string(out)) + pinger, err := probing.NewPinger(pblist.Items[0].Spec.GlobalIP) + if err != nil { + return requeue(errors.NewEf(err, "failed to create pinger")) + } + + pinger.ResolveTimeout = 2 * time.Second + pinger.Timeout = 2 * time.Second + pinger.Count = 1 + pctx, cf := context.WithTimeout(ctx, pinger.Timeout) + defer cf() + + if err := pinger.RunWithContext(pctx); err != nil { + logger.Error("failed to ping", "global-ip", pblist.Items[0].Spec.GlobalIP) return deletePod(fmt.Sprintf("ping failed, got err: %s", err.Error())) } - logger.Debug("ping success, requeing after 5s") + logger.Debug("ping success, requeing after 5s", "ping.packets-received", pinger.Statistics().PacketsRecv) return requeue(nil) } diff --git a/operators/networking/internal/cmd/webhook/main.go b/operators/networking/internal/cmd/webhook/main.go index fdcda56c..620fecce 100644 --- a/operators/networking/internal/cmd/webhook/main.go +++ b/operators/networking/internal/cmd/webhook/main.go @@ -198,6 +198,9 @@ while true; do wg-quick up kloudlite-wg %s echo "[SUCCESS] wireguard is up" + + echo "search $POD_NAMESPACE.svc.cluster.local svc.cluster.local cluster.local" >> /etc/resolv.conf + echo "options ndots:1" >> /etc/resolv.conf exit 0 fi echo "[RETRY] wireguard configuration could not be fetched from gateway ip-manager, retrying in 1 seconds" diff --git a/operators/networking/internal/gateway/templates/gateway-deployment.yml.tpl b/operators/networking/internal/gateway/templates/gateway-deployment.yml.tpl index 7b0c7b60..249278e1 100644 --- a/operators/networking/internal/gateway/templates/gateway-deployment.yml.tpl +++ b/operators/networking/internal/gateway/templates/gateway-deployment.yml.tpl @@ -43,6 +43,10 @@ spec: kloudlite.io/gateway-extra-peers-hash: {{.GatewayWgExtraPeersHash}} spec: serviceAccountName: {{.ServiceAccountName}} + securityContext: + sysctls: + - name: net.ipv4.ping_group_range + value: "0 2147483647" initContainers: - name: wg-hostnames image: ghcr.io/kloudlite/hub/wireguard:latest diff --git a/operators/service-intercept/.dockerignore b/operators/service-intercept/.dockerignore new file mode 100644 index 00000000..b30fec26 --- /dev/null +++ b/operators/service-intercept/.dockerignore @@ -0,0 +1,3 @@ +** +!main.go +!internal diff --git a/operators/service-intercept/Containerfile.local b/operators/service-intercept/Containerfile.local new file mode 100644 index 00000000..7a004be2 --- /dev/null +++ b/operators/service-intercept/Containerfile.local @@ -0,0 +1,6 @@ +FROM gcr.io/distroless/static:nonroot +ARG binpath +COPY --from=local-builder ${binpath} /manager +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/operators/service-intercept/Dockerfile b/operators/service-intercept/Dockerfile new file mode 100644 index 00000000..0e655598 --- /dev/null +++ b/operators/service-intercept/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1.4 +FROM golang:1.18-alpine as builder +RUN apk add curl +WORKDIR /workspace +#RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" > \ +# ./kubectl && chmod +x ./kubectl +# +COPY --from=project go.mod go.sum ./ +COPY --from=project lib ./lib +COPY --from=project apis ./apis +COPY --from=project ./operator ./operator + +#COPY ./go.mod ./go.sum ./ +#COPY ./pkg ./pkg +#COPY ./apis ./apis +#COPY ./operator ./operator +RUN go mod download -x + +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 + +ARG name +RUN mkdir -p ./operators/$name +COPY ./ ./operators/$name +RUN go build -o manager ./operators/$name/main.go + +#RUN ls -al +#COPY ./operators/$name ./operators/$name +#RUN go build -o manager ./operators/$name/main.go + +FROM gcr.io/distroless/static:nonroot +#COPY --from=builder /workspace/kubectl /usr/local/bin/kubectl +COPY --from=builder /workspace/manager /manager +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/operators/service-intercept/controller/register.go b/operators/service-intercept/controller/register.go new file mode 100644 index 00000000..ded01304 --- /dev/null +++ b/operators/service-intercept/controller/register.go @@ -0,0 +1,16 @@ +package controller + +import ( + crdsv1 "github.com/kloudlite/operator/apis/crds/v1" + "github.com/kloudlite/operator/operator" + "github.com/kloudlite/operator/operators/service-intercept/internal/controllers/svci" + "github.com/kloudlite/operator/operators/service-intercept/internal/env" +) + +func RegisterInto(mgr operator.Operator) { + ev := env.GetEnvOrDie() + mgr.AddToSchemes(crdsv1.AddToScheme) + mgr.RegisterControllers( + &svci.Reconciler{Name: "service-intercept", Env: ev}, + ) +} diff --git a/operators/service-intercept/internal/cmd/webhook/Dockerfile b/operators/service-intercept/internal/cmd/webhook/Dockerfile new file mode 100644 index 00000000..7710ff88 --- /dev/null +++ b/operators/service-intercept/internal/cmd/webhook/Dockerfile @@ -0,0 +1,4 @@ +FROM gcr.io/distroless/static:nonroot +ARG BINARY TARGETARCH +COPY ${BINARY}-${TARGETARCH} /webhook-server +ENTRYPOINT ["/webhook-server"] diff --git a/operators/service-intercept/internal/cmd/webhook/Taskfile.yml b/operators/service-intercept/internal/cmd/webhook/Taskfile.yml new file mode 100644 index 00000000..63f57716 --- /dev/null +++ b/operators/service-intercept/internal/cmd/webhook/Taskfile.yml @@ -0,0 +1,49 @@ +version: 3 + +includes: + go: ../../../../.tools/taskfiles/go-build.Taskfile.yml + docker: ../../../../.tools/taskfiles/docker.Taskfile.yml + +vars: + app: svci-mutation-webhook + binary: "./bin/{{.app}}" + +tasks: + build: + cmds: + - task: go:build + vars: + Out: "{{.binary}}-{{.GOARCH}}" + GOARCH: "{{.GOARCH}}" + run: + env: + # KEY=value + cmds: + - go run ./main.go --addr ":8443" + + debug: + env: + # KEY=value + cmds: + - |+ + go build -o ./__debug-{{.app}} . + trap "echo closing; rm -f ./__debug-{{.app}}" EXIT + ./__debug-{{.app}} --debug --addr ":8443" + + container:build-and-push: + preconditions: + - sh: '[[ -n "{{.image}}" ]]' + msg: "var image must have a value, of format 'image_repository:image_tag'" + cmds: + - task: build + vars: + GOARCH: amd64 + - task: build + vars: + GOARCH: arm64 + + - task: docker:build-and-push + vars: + image: "{{.image}}" + args: "--platform linux/amd64,linux/arm64 --build-arg BINARY={{.binary}} ." + override: "{{.override}}" diff --git a/operators/service-intercept/internal/cmd/webhook/main.go b/operators/service-intercept/internal/cmd/webhook/main.go new file mode 100644 index 00000000..5b39ddb9 --- /dev/null +++ b/operators/service-intercept/internal/cmd/webhook/main.go @@ -0,0 +1,320 @@ +package webhook + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "log/slog" + "net/http" + "strings" + "time" + + crdsv1 "github.com/kloudlite/operator/apis/crds/v1" + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + + "github.com/codingconcepts/env" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/kloudlite/operator/common" + "github.com/kloudlite/operator/pkg/errors" + "github.com/kloudlite/operator/pkg/logging" + + fn "github.com/kloudlite/operator/pkg/functions" + "k8s.io/client-go/dynamic" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +type Resource string + +const ( + ResourcePod Resource = "pod" +) + +const ( + debugWebhookAnnotation string = "kloudlite.io/networking.webhook.debug" +) + +type Env struct { + KubernetesApiProxy string `env:"KUBERNETES_API_PROXY"` +} + +type HandlerContext struct { + context.Context + Resource + *slog.Logger + + client *dynamic.DynamicClient + CreatedForLabel string +} + +type RunArgs struct { + Addr string + LogLevel string + KubeRestConfig *rest.Config + CreatedForLabel string + + TLSCertFile string + TLSKeyFile string +} + +func Run(args RunArgs) error { + start := time.Now() + + if args.CreatedForLabel == "" { + args.CreatedForLabel = "kloudlite.io/created-for" + } + + if args.TLSCertFile == "" || args.TLSKeyFile == "" { + return fmt.Errorf("must provide TLSCertFile and TLSKeyFile") + } + + logger := logging.NewSlogLogger(logging.SlogOptions{ + Prefix: "[webhook]", + ShowCaller: true, + ShowDebugLogs: strings.ToLower(args.LogLevel) == "debug", + }) + + r := chi.NewRouter() + r.Use(middleware.RequestID) + + httpLogger := logging.NewHttpLogger(logging.HttpLoggerOptions{}) + r.Use(httpLogger.Use) + + dclient, err := dynamic.NewForConfig(args.KubeRestConfig) + if err != nil { + return errors.NewEf(err, "creating kubernetes dynamic client") + } + + r.HandleFunc("/mutate/pod", func(w http.ResponseWriter, r *http.Request) { + requestID := middleware.GetReqID(r.Context()) + handleMutate(HandlerContext{ + client: dclient, + Context: r.Context(), + Resource: ResourcePod, + Logger: logger.With("request-id", requestID), + CreatedForLabel: args.CreatedForLabel, + }, w, r) + }) + + server := &http.Server{ + Addr: args.Addr, + Handler: r, + } + logger.Info("starting http server", "addr", args.Addr) + + common.PrintReadyBanner2(time.Since(start)) + + // return server.ListenAndServeTLS("/tmp/tls/tls.crt", "/tmp/tls/tls.key") + return server.ListenAndServeTLS(args.TLSCertFile, args.TLSKeyFile) +} + +func main() { + var ev Env + if err := env.Set(&ev); err != nil { + panic(err) + } + + var addr string + flag.StringVar(&addr, "addr", "", "--addr ") + + var logLevel string + flag.StringVar(&logLevel, "log-level", "info", "--log-level ") + + flag.Parse() + + kubeConfig, err := func() (*rest.Config, error) { + if ev.KubernetesApiProxy == "" { + return &rest.Config{Host: ev.KubernetesApiProxy}, nil + } + return rest.InClusterConfig() + }() + if err != nil { + panic(err) + } + + if err := Run(RunArgs{ + Addr: addr, + LogLevel: logLevel, + KubeRestConfig: kubeConfig, + TLSCertFile: "/tmp/tls/tls.crt", + TLSKeyFile: "/tmp/tls/tls.key", + }); err != nil { + panic(err) + } +} + +func handleMutate(ctx HandlerContext, w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "could not read request body", http.StatusBadRequest) + return + } + + review := admissionv1.AdmissionReview{} + deserializer := codecs.UniversalDeserializer() + if _, _, err = deserializer.Decode(body, nil, &review); err != nil { + http.Error(w, "could not decode admission review", http.StatusBadRequest) + return + } + + var response admissionv1.AdmissionReview + + response = processPodAdmission(ctx, review) + responseBytes, err := json.Marshal(response) + if err != nil { + http.Error(w, "could not marshal response", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(responseBytes) +} + +func processPodAdmission(ctx HandlerContext, review admissionv1.AdmissionReview) admissionv1.AdmissionReview { + ctx.InfoContext(ctx, "pod admission", "ref", review.Request.UID, "op", review.Request.Operation) + + pod := corev1.Pod{} + + switch review.Request.Operation { + case admissionv1.Create, admissionv1.Delete: + err := json.Unmarshal(review.Request.Object.Raw, &pod) + if err != nil { + return errResponse(ctx, err, review.Request.UID) + } + default: + { + return mutateAndAllow(review, nil) + } + } + + if _, ok := pod.Labels[ctx.CreatedForLabel]; ok { + return mutateAndAllow(review, nil) + } + + ctx.InfoContext(ctx, "pod-info", "name", pod.Name, "namespace", pod.Namespace) + + gcr := crdsv1.GroupVersion.WithResource("serviceintercepts") + ul, err := ctx.client.Resource(gcr).Namespace(pod.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return errResponse(ctx, err, review.Request.UID) + } + + var svciList crdsv1.ServiceInterceptList + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(ul.UnstructuredContent(), &svciList); err != nil { + return errResponse(ctx, err, review.Request.UID) + } + + isMatched, err := func() (bool, error) { + for _, si := range svciList.Items { + if si.DeletionTimestamp != nil { + continue + } + + s, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: pod.Labels}) + if err != nil { + return false, err + } + + if !s.Matches(labels.Set(si.Status.Selector)) { + return true, nil + } + } + + return false, nil + }() + if err != nil { + return errResponse(ctx, err, review.Request.UID) + } + + if !isMatched { + return mutateAndAllow(review, nil) + } + + switch review.Request.Operation { + case admissionv1.Create: + { + ctx.Info("[INCOMING] pod", "op", review.Request.Operation, "uid", review.Request.UID, "name", review.Request.Name, "namespace", review.Request.Namespace) + + pod.Spec.NodeSelector = fn.MapMerge(pod.Spec.NodeSelector, map[string]string{"kloudlite.io/no-schedule": "true"}) + + lb := pod.GetLabels() + if lb == nil { + lb = make(map[string]string, 1) + } + + lb[ctx.CreatedForLabel] = "intercept" + + patchBytes, err := json.Marshal([]map[string]any{ + { + "op": "add", + "path": "/metadata/labels", + "value": lb, + }, + { + "op": "add", + "path": "/spec", + "value": pod.Spec, + }, + }) + if err != nil { + return errResponse(ctx, err, review.Request.UID) + } + + return mutateAndAllow(review, patchBytes) + } + default: + { + return mutateAndAllow(review, nil) + } + } +} + +func errResponse(ctx HandlerContext, err error, uid types.UID) admissionv1.AdmissionReview { + ctx.Error("encountered error", "err", err) + return admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: "admission.k8s.io/v1", + }, + Response: &admissionv1.AdmissionResponse{ + UID: uid, + Allowed: false, + Result: &metav1.Status{ + Message: err.Error(), + }, + }, + } +} + +func mutateAndAllow(review admissionv1.AdmissionReview, patch []byte) admissionv1.AdmissionReview { + patchType := admissionv1.PatchTypeJSONPatch + + resp := admissionv1.AdmissionResponse{ + UID: review.Request.UID, + Allowed: true, + } + + if patch != nil { + resp.Patch = patch + resp.PatchType = &patchType + } + + return admissionv1.AdmissionReview{ + TypeMeta: review.TypeMeta, + Response: &resp, + } +} diff --git a/operators/service-intercept/internal/controllers/svci/controller.go b/operators/service-intercept/internal/controllers/svci/controller.go new file mode 100644 index 00000000..92639ce0 --- /dev/null +++ b/operators/service-intercept/internal/controllers/svci/controller.go @@ -0,0 +1,392 @@ +package svci + +import ( + "context" + "fmt" + "os" + "time" + + "k8s.io/client-go/tools/record" + + crdsv1 "github.com/kloudlite/operator/apis/crds/v1" + "github.com/kloudlite/operator/operators/service-intercept/internal/cmd/webhook" + "github.com/kloudlite/operator/operators/service-intercept/internal/env" + "github.com/kloudlite/operator/operators/service-intercept/internal/templates" + "github.com/kloudlite/operator/pkg/constants" + "github.com/kloudlite/operator/pkg/errors" + fn "github.com/kloudlite/operator/pkg/functions" + "github.com/kloudlite/operator/pkg/kubectl" + "github.com/kloudlite/operator/pkg/logging" + rApi "github.com/kloudlite/operator/pkg/operator" + stepResult "github.com/kloudlite/operator/pkg/operator/step-result" + "github.com/kloudlite/operator/pkg/tls_utils" + corev1 "k8s.io/api/core/v1" + apiErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" +) + +type Reconciler struct { + client.Client + Scheme *runtime.Scheme + Logger logging.Logger + Name string + Env *env.Env + + yamlClient kubectl.YAMLClient + recorder record.EventRecorder + + svcInterceptTemplate []byte + templateWebhook []byte +} + +func (r *Reconciler) GetName() string { + return r.Name +} + +const ( + CreatedForLabel string = "kloudlite.io/created-for" + + CreateIntercept string = "create-intercept" + InterceptClosePerformed string = "cleanup" + TrackInterceptedSvc string = "tracks-intercept-svc" +) + +var DeleteChecklist = []rApi.CheckMeta{ + {Name: CreateIntercept, Title: "Intercept close performed"}, +} + +// +kubebuilder:rbac:groups=crds.kloudlite.io,resources=apps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=crds.kloudlite.io,resources=apps/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=crds.kloudlite.io,resources=apps/finalizers,verbs=update + +func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + req, err := rApi.NewRequest(rApi.NewReconcilerCtx(ctx, r.Logger), r.Client, request.NamespacedName, &crdsv1.ServiceIntercept{}) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + req.PreReconcile() + defer req.PostReconcile() + + if req.Object.GetDeletionTimestamp() != nil { + if x := r.finalize(req); !x.ShouldProceed() { + return x.ReconcilerResponse() + } + return ctrl.Result{}, nil + } + + if step := req.ClearStatusIfAnnotated(); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := req.EnsureCheckList([]rApi.CheckMeta{ + {Name: CreateIntercept, Title: func() string { + return "Intercept performed" + }(), Hide: false}, + }); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := req.RestartIfAnnotated(); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := req.EnsureLabelsAndAnnotations(); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := req.EnsureFinalizers(constants.ForegroundFinalizer, constants.CommonFinalizer); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := r.createSvcIntercept(req); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + if step := r.trackInterceptSvc(req); !step.ShouldProceed() { + return step.ReconcilerResponse() + } + + req.Object.Status.IsReady = true + return ctrl.Result{}, nil +} + +func (r *Reconciler) finalize(req *rApi.Request[*crdsv1.ServiceIntercept]) stepResult.Result { + if step := req.EnsureCheckList(DeleteChecklist); !step.ShouldProceed() { + return step + } + + ctx, obj := req.Context(), req.Object + check := rApi.NewRunningCheck(InterceptClosePerformed, req) + + svc, err := rApi.Get(ctx, r.Client, fn.NN(obj.Namespace, obj.Name), &corev1.Service{}) + if err != nil { + return check.Failed(err) + } + + var podList corev1.PodList + if err := r.List(ctx, &podList, &client.ListOptions{ + LabelSelector: labels.SelectorFromValidatedSet(fn.MapMerge(svc.Spec.Selector, map[string]string{ + CreatedForLabel: "intercept", + })), + Namespace: obj.Namespace, + }); err != nil { + return check.Failed(err) + } + + for _, p := range podList.Items { + if err := r.Delete(ctx, &p); err != nil { + return check.Failed(err) + } + } + + if step := req.CleanupOwnedResourcesV2(check); !step.ShouldProceed() { + return step + } + + return req.Finalize() +} + +const ( + ServiceInterceptServiceName string = "service-intercept" +) + +type SetupWebhookArgs struct { + Client client.Client + YAMLClient kubectl.YAMLClient + Env *env.Env + templateWebhook []byte +} + +func setupAdmissionWebhook(ctx context.Context, args SetupWebhookArgs) (map[string][]byte, error) { + certSecretName := ServiceInterceptServiceName + "-webhook-cert" + certSecretNamespace := args.Env.KloudliteNamespace + + webhookCert, err := rApi.Get(ctx, args.Client, fn.NN(certSecretNamespace, certSecretName), &corev1.Secret{}) + if err != nil { + if !apiErrors.IsNotFound(err) { + return nil, err + } + + caBundle, cert, key, err := tls_utils.GenTLSCert(tls_utils.GenTLSCertArgs{ + DNSNames: []string{ + fmt.Sprintf("%s.%s.svc", ServiceInterceptServiceName, args.Env.KloudliteNamespace), + fmt.Sprintf("%s.%s.svc.cluster.local", ServiceInterceptServiceName, args.Env.KloudliteNamespace), + }, + CertificateLabel: "service intercept webhook cert", + }) + if err != nil { + return nil, errors.NewEf(err, "failed to generate TLS certificates") + } + + webhookCert = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: certSecretName, + Namespace: certSecretNamespace, + }, + Data: map[string][]byte{ + "ca.crt": caBundle, + "tls.crt": cert, + "tls.key": key, + }, + } + if err := args.Client.Create(ctx, webhookCert); err != nil { + return nil, err + } + } + + b, err := templates.ParseBytes(args.templateWebhook, templates.WebhookTemplateArgs{ + CaBundle: string(webhookCert.Data["ca.crt"]), + ServiceName: ServiceInterceptServiceName, + ServiceNamespace: certSecretNamespace, + ServiceHTTPSPort: 9443, + ServiceSelector: args.Env.ServiceInterceptWebhookServiceSelector, + + NamespaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "kloudlite.io/gateway.enabled": "true", + }, + }, + }) + if err != nil { + return nil, err + } + + if _, err := args.YAMLClient.ApplyYAML(ctx, b); err != nil { + return nil, err + } + + return webhookCert.Data, nil +} + +func (r *Reconciler) trackInterceptSvc(req *rApi.Request[*crdsv1.ServiceIntercept]) stepResult.Result { + ctx, obj := req.Context(), req.Object + check := rApi.NewRunningCheck(TrackInterceptedSvc, req) + + svc, err := rApi.Get(ctx, r.Client, fn.NN(obj.Namespace, obj.Name), &corev1.Service{}) + if err != nil { + return check.Failed(err) + } + + if !fn.IsOwner(obj, fn.AsOwner(svc, true)) { + obj.SetOwnerReferences(append(obj.GetOwnerReferences(), fn.AsOwner(svc, true))) + if err := r.Update(ctx, obj); err != nil { + return check.Failed(err) + } + return check.StillRunning(fmt.Errorf("waiting for reconcilation")).RequeueAfter(1 * time.Second) + } + + if !fn.MapEqual(obj.Status.Selector, svc.Spec.Selector) { + obj.Status.Selector = svc.Spec.Selector + if err := r.Status().Update(ctx, obj); err != nil { + return check.Failed(err) + } + return check.StillRunning(fmt.Errorf("waiting for reconcilation")).RequeueAfter(1 * time.Second) + } + + var podList corev1.PodList + if err := r.List(ctx, &podList, &client.ListOptions{ + LabelSelector: labels.SelectorFromValidatedSet(svc.Spec.Selector), + Namespace: obj.Namespace, + }); err != nil { + return check.Failed(err) + } + + for _, p := range podList.Items { + if cf := p.Labels[CreatedForLabel]; cf == "intercept" { + continue + } + + if err := r.Delete(ctx, &p); err != nil { + return check.Failed(err) + } + } + + return check.Completed() +} + +func (r *Reconciler) createSvcIntercept(req *rApi.Request[*crdsv1.ServiceIntercept]) stepResult.Result { + ctx, obj := req.Context(), req.Object + + check := rApi.NewRunningCheck(CreateIntercept, req) + + podname := obj.Name + "-intercept" + podns := obj.Namespace + pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: podname, Namespace: podns}} + + svciGenerationLabel := "kloudlite.io/svci-generation" + + if err := r.Get(ctx, client.ObjectKeyFromObject(pod), pod); err != nil { + if apiErrors.IsNotFound(err) { + portMappings := make(map[uint16]uint16, len(obj.Spec.PortMappings)) + for _, pm := range obj.Spec.PortMappings { + portMappings[pm.ContainerPort] = pm.ServicePort + } + + deviceHost := obj.Spec.ToAddr + + if obj.Spec.ToAddr == "" { + return check.Failed(fmt.Errorf("no address configured on service intercept, failed to intercept")).NoRequeue() + } + + svc, err := rApi.Get(ctx, r.Client, fn.NN(obj.Namespace, obj.Name), &corev1.Service{}) + if err != nil { + return check.Failed(err) + } + + b, err := templates.ParseBytes(r.svcInterceptTemplate, map[string]any{ + "name": podname, + "namespace": podns, + "labels": fn.MapMerge( + map[string]string{ + svciGenerationLabel: fmt.Sprintf("%d", obj.Generation), + CreatedForLabel: "intercept", + }, + svc.Spec.Selector, + ), + "owner-references": []metav1.OwnerReference{fn.AsOwner(obj, true)}, + "device-host": deviceHost, + "port-mappings": portMappings, + }) + if err != nil { + return check.Failed(err).NoRequeue() + } + + if _, err := r.yamlClient.ApplyYAML(ctx, b); err != nil { + return check.Failed(err).NoRequeue() + } + + return check.StillRunning(fmt.Errorf("waiting for intercept pod to start")) + } + } + + if pod.Labels[svciGenerationLabel] != fmt.Sprintf("%d", obj.Generation) { + if err := r.Delete(ctx, pod); err != nil { + return check.Failed(err) + } + return check.StillRunning(fmt.Errorf("waiting for previous generation pod to be deleted")) + } + + if pod.Status.Phase != corev1.PodRunning { + return check.StillRunning(fmt.Errorf("waiting for pod to start running")) + } + + return check.Completed() +} + +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, logger logging.Logger) error { + r.Client = mgr.GetClient() + r.Scheme = mgr.GetScheme() + r.Logger = logger.WithName(r.Name) + r.yamlClient = kubectl.NewYAMLClientOrDie(mgr.GetConfig(), kubectl.YAMLClientOpts{Logger: r.Logger}) + r.recorder = mgr.GetEventRecorderFor(r.GetName()) + + var err error + r.svcInterceptTemplate, err = templates.Read(templates.SvcIntercept) + if err != nil { + return err + } + + templateWebhook, err := templates.Read(templates.WebhookTemplate) + if err != nil { + return err + } + + webhookCertData, err := setupAdmissionWebhook(context.TODO(), SetupWebhookArgs{ + Client: r.Client, + YAMLClient: r.yamlClient, + Env: r.Env, + templateWebhook: templateWebhook, + }) + if err != nil { + return err + } + + if err := os.WriteFile("/tmp/tls.crt", webhookCertData["tls.crt"], 0o666); err != nil { + return err + } + if err := os.WriteFile("/tmp/tls.key", webhookCertData["tls.key"], 0o666); err != nil { + return err + } + + go webhook.Run(webhook.RunArgs{ + Addr: ":9443", + LogLevel: "info", + KubeRestConfig: mgr.GetConfig(), + CreatedForLabel: CreatedForLabel, + TLSCertFile: "/tmp/tls.crt", + TLSKeyFile: "/tmp/tls.key", + }) + + builder := ctrl.NewControllerManagedBy(mgr).For(&crdsv1.ServiceIntercept{}) + + builder.WithOptions(controller.Options{MaxConcurrentReconciles: r.Env.MaxConcurrentReconciles}) + builder.Owns(&corev1.Pod{}) + builder.WithEventFilter(rApi.ReconcileFilter()) + return builder.Complete(r) +} diff --git a/operators/service-intercept/internal/env/env.go b/operators/service-intercept/internal/env/env.go new file mode 100644 index 00000000..06990de3 --- /dev/null +++ b/operators/service-intercept/internal/env/env.go @@ -0,0 +1,33 @@ +package env + +import ( + "encoding/json" + "os" + + "github.com/codingconcepts/env" +) + +type Env struct { + MaxConcurrentReconciles int `env:"MAX_CONCURRENT_RECONCILES"` + KloudliteNamespace string `env:"KLOUDLITE_NAMESPACE" default:"kloudlite"` + ServiceInterceptWebhookServiceSelector map[string]string +} + +func GetEnvOrDie() *Env { + var ev Env + if err := env.Set(&ev); err != nil { + panic(err) + } + s, ok := os.LookupEnv("SERVICE_INTERCEPT_WEBHOOK_SERVICE_SELECTOR") + if ok { + if err := json.Unmarshal([]byte(s), &ev.ServiceInterceptWebhookServiceSelector); err != nil { + panic("invalid env-var 'SERVICE_INTERCEPT_WEBHOOK_SERVICE_SELECTOR', must be deserializable into a map[string]string") + } + } + if !ok { + ev.ServiceInterceptWebhookServiceSelector = map[string]string{"app": "kl-agent-operator"} + // panic("env-var 'SERVICE_INTERCEPT_WEBHOOK_SERVICE_SELECTOR' not provided") + } + + return &ev +} diff --git a/operators/service-intercept/internal/templates/embed.go b/operators/service-intercept/internal/templates/embed.go new file mode 100644 index 00000000..f00fc65a --- /dev/null +++ b/operators/service-intercept/internal/templates/embed.go @@ -0,0 +1,24 @@ +package templates + +import ( + "embed" + "path/filepath" + + "github.com/kloudlite/operator/pkg/templates" +) + +//go:embed * +var templatesDir embed.FS + +type templateFile string + +const ( + SvcIntercept templateFile = "./svc-intercept.yml.tpl" + WebhookTemplate templateFile = "./webhook.yml.tpl" +) + +func Read(t templateFile) ([]byte, error) { + return templatesDir.ReadFile(filepath.Join(string(t))) +} + +var ParseBytes = templates.ParseBytes diff --git a/operators/service-intercept/internal/templates/svc-intercept.yml.tpl b/operators/service-intercept/internal/templates/svc-intercept.yml.tpl new file mode 100644 index 00000000..f03380d6 --- /dev/null +++ b/operators/service-intercept/internal/templates/svc-intercept.yml.tpl @@ -0,0 +1,44 @@ +{{- $name := get . "name" }} +{{- $namespace := get . "namespace" }} +{{- $labels := get . "labels" | default dict }} +{{- $ownerReferences := get . "owner-references" | default list }} +{{- $deviceHost := get . "device-host" }} +{{- $portMappings := get . "port-mappings" | default dict }} + +apiVersion: v1 +kind: Pod +metadata: + name: {{$name}} + namespace: {{$namespace}} + labels: {{$labels | toYAML | nindent 4}} + ownerReferences: {{$ownerReferences | toYAML | nindent 4}} +spec: + containers: + - name: app-intercept + {{- /* image: alpine/socat */}} + image: ghcr.io/kloudlite/hub/socat:latest + command: + - sh + - -c + - |+ + {{- range $k, $v := $portMappings }} + (socat -dd tcp4-listen:{{$k}},fork,reuseaddr tcp4:{{$deviceHost}}:{{$v}} 2>&1 | grep -iE --line-buffered 'listening|exiting') & + pid="$pid $!" + {{- end }} + + trap "eval kill -9 $pid || exit 0" EXIT SIGINT SIGTERM + eval wait $pid + securityContext: + capabilities: + add: + - NET_BIND_SERVICE + - SETGID + drop: + - all + resources: + limits: + memory: "50Mi" + cpu: "50m" + requests: + memory: "50Mi" + cpu: "50m" diff --git a/operators/service-intercept/internal/templates/types.go b/operators/service-intercept/internal/templates/types.go new file mode 100644 index 00000000..3f64b1d5 --- /dev/null +++ b/operators/service-intercept/internal/templates/types.go @@ -0,0 +1,20 @@ +package templates + +import ( + crdsv1 "github.com/kloudlite/operator/apis/crds/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type HPATemplateVars struct { + Metadata metav1.ObjectMeta + *crdsv1.HPA +} + +type WebhookTemplateArgs struct { + CaBundle string + ServiceName string + ServiceNamespace string + ServiceHTTPSPort uint16 + ServiceSelector map[string]string + NamespaceSelector metav1.LabelSelector +} diff --git a/operators/service-intercept/internal/templates/webhook.yml.tpl b/operators/service-intercept/internal/templates/webhook.yml.tpl new file mode 100644 index 00000000..229022ed --- /dev/null +++ b/operators/service-intercept/internal/templates/webhook.yml.tpl @@ -0,0 +1,44 @@ +{{- with . }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: service-intercept +webhooks: + - name: {{.ServiceName}}.kloudlite.io + clientConfig: + service: + name: {{ .ServiceName | squote }} + namespace: {{.ServiceNamespace | squote}} + path: /mutate/pod + port: 443 + caBundle: {{ .CaBundle | b64enc | squote }} + + namespaceSelector: {{.NamespaceSelector | toJson }} + {{- /* matchExpressions: */}} + {{- /* - key: kloudlite.io/gateway.enabled */}} + {{- /* operator: In */}} + {{- /* values: ["true"] */}} + + rules: + - operations: ["CREATE", "UPDATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + admissionReviewVersions: ["v1"] + sideEffects: None + +--- + +apiVersion: v1 +kind: Service +metadata: + name: {{ .ServiceName }} + namespace: {{.ServiceNamespace}} +spec: + selector: {{.ServiceSelector | toJson }} + ports: + - name: webhook + port: 443 + protocol: TCP + targetPort: {{.ServiceHTTPSPort}} +{{- end }} diff --git a/operators/service-intercept/main.go b/operators/service-intercept/main.go new file mode 100644 index 00000000..4ac2b609 --- /dev/null +++ b/operators/service-intercept/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/kloudlite/operator/operator" + "github.com/kloudlite/operator/operators/service-intercept/controller" +) + +func main() { + mgr := operator.New("service-intercept") + controller.RegisterInto(mgr) + mgr.Start() +} diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 3aab49eb..19ab85fa 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -94,6 +94,7 @@ const ( EdgeNameKey string = "kloudlite.io/edge.name" EdgeRouterNameKey string = "kloudlite.io/edge-router.name" EnvironmentNameKey string = "kloudlite.io/environment.name" + EnvNameKey string = "kloudlite.io/env.name" TargetNamespaceKey string = "kloudlite.io/target-namespace" ImagePullSecretNameKey string = "kloudlite.io/image-pull-secret.name" CsiDriverNameKey string = "kloudlite.io/csi-driver.name" diff --git a/pkg/kubectl/without-kubectl.go b/pkg/kubectl/without-kubectl.go index d13051ff..f3b8eef4 100644 --- a/pkg/kubectl/without-kubectl.go +++ b/pkg/kubectl/without-kubectl.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "time" "github.com/kloudlite/operator/pkg/errors" @@ -46,7 +47,7 @@ type yamlClient struct { k8sClient *kubernetes.Clientset dynamicClient dynamic.Interface mapper meta.RESTMapper - logger logging.Logger + logger *slog.Logger } func (yc *yamlClient) Client() *kubernetes.Clientset { @@ -100,6 +101,8 @@ func (yc *yamlClient) ApplyYAML(ctx context.Context, yamls ...[]byte) ([]rApi.Re return yc.dynamicClient.Resource(mapping.Resource) }() + logger := yc.logger.With("gvk", gvk.String(), "resource", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())) + ann := obj.GetAnnotations() delete(ann, constants.LastAppliedKey) obj.SetAnnotations(ann) @@ -132,7 +135,8 @@ func (yc *yamlClient) ApplyYAML(ctx context.Context, yamls ...[]byte) ([]rApi.Re if err != nil { return resources, errors.NewEf(err, "resource: %s/%s", obj.GetNamespace(), obj.GetName()) } - yc.logger.Infof("created resource (gvk: %s) (%s/%s)", gvk.String(), obj.GetNamespace(), obj.GetName()) + + logger.Info("created resource") continue } @@ -140,7 +144,7 @@ func (yc *yamlClient) ApplyYAML(ctx context.Context, yamls ...[]byte) ([]rApi.Re prevLastApplied, ok := cobj.GetAnnotations()[constants.LastAppliedKey] if ok { if prevLastApplied == ann[constants.LastAppliedKey] { - yc.logger.Infof("No changes for resource (gvk: %s) (%s/%s)", gvk.String(), obj.GetNamespace(), obj.GetName()) + logger.Info("No changes for resource") continue } @@ -175,7 +179,7 @@ func (yc *yamlClient) ApplyYAML(ctx context.Context, yamls ...[]byte) ([]rApi.Re if _, err = resourceClient.Update(ctx, &obj, metav1.UpdateOptions{}); err != nil { return resources, errors.NewEf(err, "resource: %s/%s", obj.GetNamespace(), obj.GetName()) } - yc.logger.Infof("updated resource (gvk: %s) (%s/%s)", gvk.String(), obj.GetNamespace(), obj.GetName()) + logger.Info("Updated Resource") } return resources, nil } @@ -411,7 +415,10 @@ func (yc *yamlClient) RolloutRestart(ctx context.Context, kind Restartable, name } type YAMLClientOpts struct { + // Deprecated: use Slogger Logger logging.Logger + + Slogger *slog.Logger } func NewYAMLClient(config *rest.Config, opts YAMLClientOpts) (YAMLClient, error) { @@ -432,21 +439,22 @@ func NewYAMLClient(config *rest.Config, opts YAMLClientOpts) (YAMLClient, error) mapper := restmapper.NewDiscoveryRESTMapper(gr) - if opts.Logger == nil { - opts.Logger, err = logging.New(&logging.Options{ - Name: "k8s-yaml-client", - CallerTrace: true, - }) - if err != nil { - return nil, err - } + if opts.Slogger == nil { + opts.Slogger = slog.Default() + // opts.Logger, err = logging.New(&logging.Options{ + // Name: "k8s-yaml-client", + // CallerTrace: true, + // }) + // if err != nil { + // return nil, err + // } } return &yamlClient{ k8sClient: c, dynamicClient: dc, mapper: mapper, - logger: opts.Logger, + logger: opts.Slogger, }, nil } diff --git a/pkg/tls_utils/cert-gen.go b/pkg/tls_utils/cert-gen.go new file mode 100644 index 00000000..8b3dd050 --- /dev/null +++ b/pkg/tls_utils/cert-gen.go @@ -0,0 +1,138 @@ +package tls_utils + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" + + fn "github.com/kloudlite/operator/pkg/functions" +) + +type GenTLSCertArgs struct { + // DNSNames is SANs for which certs will be generated + DNSNames []string + + NotBefore *time.Time + NotAfter *time.Time + + CertificateLabel string +} + +func GenTLSCert(args GenTLSCertArgs) (caBundle []byte, tlsCert []byte, tlsKey []byte, err error) { + // Generate a private key for the CA + + if len(args.DNSNames) == 0 { + return nil, nil, nil, fmt.Errorf("at least 1 SAN must be provided") + } + + if args.NotBefore == nil { + args.NotBefore = fn.New(time.Now()) + } + + if args.NotAfter == nil { + args.NotAfter = fn.New(time.Now().Add(100 * 365 * 24 * time.Hour)) // 100 years + } + + if args.CertificateLabel == "" { + args.CertificateLabel = "My Certificate" + } + + caPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, nil, err + } + + // Create a template for the CA certificate + caTemplate := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Kloudlite CA"}, + }, + NotBefore: *args.NotBefore, + NotAfter: args.NotAfter.Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + } + + // Create the CA certificate + caCertBytes, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPriv.PublicKey, caPriv) + if err != nil { + return nil, nil, nil, err + } + + // Encode the CA certificate to PEM + caCertPEM := new(bytes.Buffer) + err = pem.Encode(caCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: caCertBytes}) + if err != nil { + // return nil, nil, nil, err + } + + // Encode the CA private key to PEM + caKeyPEM := new(bytes.Buffer) + caPrivBytes, err := x509.MarshalECPrivateKey(caPriv) + if err != nil { + return nil, nil, nil, err + } + err = pem.Encode(caKeyPEM, &pem.Block{Type: "EC PRIVATE KEY", Bytes: caPrivBytes}) + if err != nil { + return nil, nil, nil, err + } + + // Generate a private key for the server + serverPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, nil, err + } + + // Create a template for the server certificate + serverTemplate := x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{ + Organization: []string{args.CertificateLabel}, + }, + NotBefore: *args.NotBefore, + NotAfter: *args.NotAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: args.DNSNames, + } + + caCert, err := x509.ParseCertificate(caCertBytes) + if err != nil { + return nil, nil, nil, err + } + + // Create the server certificate + serverCertBytes, err := x509.CreateCertificate(rand.Reader, &serverTemplate, caCert, &serverPriv.PublicKey, caPriv) + if err != nil { + return nil, nil, nil, err + } + + // Encode the server certificate to PEM + serverCertPEM := new(bytes.Buffer) + err = pem.Encode(serverCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: serverCertBytes}) + if err != nil { + return nil, nil, nil, err + } + + // Encode the server private key to PEM + serverKeyPEM := new(bytes.Buffer) + serverPrivBytes, err := x509.MarshalECPrivateKey(serverPriv) + if err != nil { + return nil, nil, nil, err + } + err = pem.Encode(serverKeyPEM, &pem.Block{Type: "EC PRIVATE KEY", Bytes: serverPrivBytes}) + if err != nil { + return nil, nil, nil, err + } + + return caCertPEM.Bytes(), serverCertPEM.Bytes(), serverKeyPEM.Bytes(), nil +}